summaryrefslogtreecommitdiffstats
path: root/src/testdir
diff options
context:
space:
mode:
Diffstat (limited to 'src/testdir')
-rw-r--r--src/testdir/Make_all.mak555
-rw-r--r--src/testdir/Make_amiga.mak35
-rw-r--r--src/testdir/Make_dos.mak4
-rw-r--r--src/testdir/Make_ming.mak168
-rw-r--r--src/testdir/Make_mvc.mak162
-rw-r--r--src/testdir/Make_vms.mms108
-rw-r--r--src/testdir/Makefile176
-rw-r--r--src/testdir/README.txt121
-rw-r--r--src/testdir/amiga.vim6
-rw-r--r--src/testdir/check.vim286
-rw-r--r--src/testdir/color_ramp.vim85
-rw-r--r--src/testdir/dos.vim9
-rw-r--r--src/testdir/dotest.in3
-rw-r--r--src/testdir/dumps/Test_Xcursorline_1.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_10.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_11.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_12.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_13.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_14.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_15.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_16.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_17.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_18.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_19.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_2.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_20.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_21.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_22.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_23.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_24.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_3.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_4.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_5.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_6.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_7.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_8.dump20
-rw-r--r--src/testdir/dumps/Test_Xcursorline_9.dump20
-rw-r--r--src/testdir/dumps/Test_appendbufline_1.dump10
-rw-r--r--src/testdir/dumps/Test_autocmd_nested_switch.dump10
-rw-r--r--src/testdir/dumps/Test_balloon_eval_term_01.dump10
-rw-r--r--src/testdir/dumps/Test_balloon_eval_term_01a.dump10
-rw-r--r--src/testdir/dumps/Test_balloon_eval_term_02.dump10
-rw-r--r--src/testdir/dumps/Test_changing_cmdheight_1.dump8
-rw-r--r--src/testdir/dumps/Test_changing_cmdheight_2.dump8
-rw-r--r--src/testdir/dumps/Test_changing_cmdheight_3.dump8
-rw-r--r--src/testdir/dumps/Test_changing_cmdheight_4.dump8
-rw-r--r--src/testdir/dumps/Test_changing_cmdheight_5.dump8
-rw-r--r--src/testdir/dumps/Test_changing_cmdheight_6.dump8
-rw-r--r--src/testdir/dumps/Test_cmdheight_tabline_1.dump6
-rw-r--r--src/testdir/dumps/Test_cmdlineclear_tabenter.dump10
-rw-r--r--src/testdir/dumps/Test_cmdwin_interrupted.dump18
-rw-r--r--src/testdir/dumps/Test_cmdwin_no_terminal.dump12
-rw-r--r--src/testdir/dumps/Test_cmdwin_restore_1.dump12
-rw-r--r--src/testdir/dumps/Test_cmdwin_restore_2.dump12
-rw-r--r--src/testdir/dumps/Test_cmdwin_restore_3.dump18
-rw-r--r--src/testdir/dumps/Test_cmdwin_wrong_command_1.dump12
-rw-r--r--src/testdir/dumps/Test_cmdwin_wrong_command_2.dump12
-rw-r--r--src/testdir/dumps/Test_colorcolumn_1.dump10
-rw-r--r--src/testdir/dumps/Test_colorcolumn_2.dump10
-rw-r--r--src/testdir/dumps/Test_colorcolumn_3.dump10
-rw-r--r--src/testdir/dumps/Test_conceal_cuc_01.dump10
-rw-r--r--src/testdir/dumps/Test_conceal_cuc_02.dump10
-rw-r--r--src/testdir/dumps/Test_conceal_cul_01.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_cul_02.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_cul_03.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_linebreak_1.dump8
-rw-r--r--src/testdir/dumps/Test_conceal_resize_01.dump6
-rw-r--r--src/testdir/dumps/Test_conceal_resize_02.dump7
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_01.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_02.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_03.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_04.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_05.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_06c.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_06i.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_06n.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_06v.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_07c.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_07i.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_07in.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_07n.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_07v.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_08c.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_08i.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_08n.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_08v.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_09c.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_09i.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_09n.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_09v.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_10.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_11.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_12.dump20
-rw-r--r--src/testdir/dumps/Test_conceal_two_windows_13.dump20
-rw-r--r--src/testdir/dumps/Test_cursor_position_with_showbreak.dump6
-rw-r--r--src/testdir/dumps/Test_cursorcolumn_callback_1.dump8
-rw-r--r--src/testdir/dumps/Test_cursorcolumn_insert_on_tab_1.dump8
-rw-r--r--src/testdir/dumps/Test_cursorcolumn_insert_on_tab_2.dump8
-rw-r--r--src/testdir/dumps/Test_cursorcolumn_insert_on_tab_3.dump8
-rw-r--r--src/testdir/dumps/Test_cursorline_callback_1.dump8
-rw-r--r--src/testdir/dumps/Test_cursorline_redraw_1.dump20
-rw-r--r--src/testdir/dumps/Test_cursorline_redraw_2.dump20
-rw-r--r--src/testdir/dumps/Test_cursorline_screenline_1.dump8
-rw-r--r--src/testdir/dumps/Test_cursorline_screenline_2.dump8
-rw-r--r--src/testdir/dumps/Test_cursorline_with_visualmode_01.dump12
-rw-r--r--src/testdir/dumps/Test_cursorline_yank_01.dump8
-rw-r--r--src/testdir/dumps/Test_diff_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_03.dump20
-rw-r--r--src/testdir/dumps/Test_diff_04.dump20
-rw-r--r--src/testdir/dumps/Test_diff_05.dump20
-rw-r--r--src/testdir/dumps/Test_diff_06.0.dump20
-rw-r--r--src/testdir/dumps/Test_diff_06.1.dump20
-rw-r--r--src/testdir/dumps/Test_diff_06.2.dump20
-rw-r--r--src/testdir/dumps/Test_diff_06.dump20
-rw-r--r--src/testdir/dumps/Test_diff_07.dump20
-rw-r--r--src/testdir/dumps/Test_diff_08.dump20
-rw-r--r--src/testdir/dumps/Test_diff_09.dump20
-rw-r--r--src/testdir/dumps/Test_diff_10.dump20
-rw-r--r--src/testdir/dumps/Test_diff_11.dump20
-rw-r--r--src/testdir/dumps/Test_diff_12.dump20
-rw-r--r--src/testdir/dumps/Test_diff_13.dump20
-rw-r--r--src/testdir/dumps/Test_diff_14.dump20
-rw-r--r--src/testdir/dumps/Test_diff_15.dump20
-rw-r--r--src/testdir/dumps/Test_diff_16.dump20
-rw-r--r--src/testdir/dumps/Test_diff_17.dump20
-rw-r--r--src/testdir/dumps/Test_diff_18.dump20
-rw-r--r--src/testdir/dumps/Test_diff_19.dump20
-rw-r--r--src/testdir/dumps/Test_diff_20.dump20
-rw-r--r--src/testdir/dumps/Test_diff_bin_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_bin_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_bin_03.dump20
-rw-r--r--src/testdir/dumps/Test_diff_bin_04.dump20
-rw-r--r--src/testdir/dumps/Test_diff_cuc_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_cuc_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_cuc_03.dump20
-rw-r--r--src/testdir/dumps/Test_diff_cuc_04.dump20
-rw-r--r--src/testdir/dumps/Test_diff_of_diff_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_of_diff_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_rnu_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_rnu_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_rnu_03.dump20
-rw-r--r--src/testdir/dumps/Test_diff_scroll_1.dump12
-rw-r--r--src/testdir/dumps/Test_diff_scroll_2.dump12
-rw-r--r--src/testdir/dumps/Test_diff_scroll_change_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_scroll_change_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_scroll_change_03.dump20
-rw-r--r--src/testdir/dumps/Test_diff_syntax_1.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cul_bri_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cul_bri_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cul_bri_03.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cul_bri_04.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cursorline_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cursorline_02.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cursorline_03.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cursorline_number_01.dump20
-rw-r--r--src/testdir/dumps/Test_diff_with_cursorline_number_02.dump20
-rw-r--r--src/testdir/dumps/Test_display_fillchars_1.dump12
-rw-r--r--src/testdir/dumps/Test_display_fillchars_2.dump12
-rw-r--r--src/testdir/dumps/Test_display_lastline_1.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_2.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_3.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_4.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_5.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_6.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_euro_1.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_euro_2.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_euro_3.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_euro_4.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_euro_5.dump10
-rw-r--r--src/testdir/dumps/Test_display_lastline_euro_6.dump10
-rw-r--r--src/testdir/dumps/Test_display_scroll_at_topline.dump4
-rw-r--r--src/testdir/dumps/Test_display_scroll_update_visual.dump8
-rw-r--r--src/testdir/dumps/Test_display_unprintable_01.dump9
-rw-r--r--src/testdir/dumps/Test_display_unprintable_02.dump9
-rw-r--r--src/testdir/dumps/Test_display_visual_block_scroll.dump7
-rw-r--r--src/testdir/dumps/Test_echowin_eval.dump8
-rw-r--r--src/testdir/dumps/Test_echowin_showmode.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_1.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_2.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_3.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_4.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_5.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_6.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_7.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_8.dump8
-rw-r--r--src/testdir/dumps/Test_echowindow_9.dump8
-rw-r--r--src/testdir/dumps/Test_fileinfo_after_echo.dump6
-rw-r--r--src/testdir/dumps/Test_folds_with_rnu_01.dump20
-rw-r--r--src/testdir/dumps/Test_folds_with_rnu_02.dump20
-rw-r--r--src/testdir/dumps/Test_functions_echoraw.dump5
-rw-r--r--src/testdir/dumps/Test_hlsearch_1.dump6
-rw-r--r--src/testdir/dumps/Test_hlsearch_2.dump6
-rw-r--r--src/testdir/dumps/Test_hlsearch_block_visual_match.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_ctrlr_1.dump6
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_changed_1.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_1.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_2.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_3.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_4.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_5.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_single_line_1.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_single_line_2.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_single_line_2a.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_single_line_2b.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_cursearch_single_line_3.dump9
-rw-r--r--src/testdir/dumps/Test_hlsearch_visual_1.dump6
-rw-r--r--src/testdir/dumps/Test_hor_scroll_1.dump8
-rw-r--r--src/testdir/dumps/Test_hor_scroll_2.dump8
-rw-r--r--src/testdir/dumps/Test_hor_scroll_3.dump8
-rw-r--r--src/testdir/dumps/Test_hor_scroll_4.dump8
-rw-r--r--src/testdir/dumps/Test_hor_scroll_5.dump8
-rw-r--r--src/testdir/dumps/Test_incsearch_change_01.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_newline1.dump5
-rw-r--r--src/testdir/dumps/Test_incsearch_newline2.dump5
-rw-r--r--src/testdir/dumps/Test_incsearch_newline3.dump5
-rw-r--r--src/testdir/dumps/Test_incsearch_newline4.dump5
-rw-r--r--src/testdir/dumps/Test_incsearch_newline5.dump5
-rw-r--r--src/testdir/dumps/Test_incsearch_scrolling_01.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_search_01.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_search_02.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_sort_01.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_sort_02.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_sub_01.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_sub_02.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_01.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_02.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_03.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_04.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_05.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_06.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_07.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_08.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_09.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_10.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_11.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_12.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_13.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_14.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_substitute_15.dump4
-rw-r--r--src/testdir/dumps/Test_incsearch_vimgrep_01.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_vimgrep_02.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_vimgrep_03.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_vimgrep_04.dump9
-rw-r--r--src/testdir/dumps/Test_incsearch_vimgrep_05.dump9
-rw-r--r--src/testdir/dumps/Test_job_buffer_scroll_1.dump10
-rw-r--r--src/testdir/dumps/Test_keytyped_in_nested_func.dump6
-rw-r--r--src/testdir/dumps/Test_linebreak_reset_restore_1.dump8
-rw-r--r--src/testdir/dumps/Test_listchars_01.dump10
-rw-r--r--src/testdir/dumps/Test_listchars_02.dump10
-rw-r--r--src/testdir/dumps/Test_listchars_03.dump10
-rw-r--r--src/testdir/dumps/Test_listchars_04.dump10
-rw-r--r--src/testdir/dumps/Test_listchars_05.dump10
-rw-r--r--src/testdir/dumps/Test_listchars_06.dump10
-rw-r--r--src/testdir/dumps/Test_listchars_07.dump10
-rw-r--r--src/testdir/dumps/Test_long_file_name_1.dump8
-rw-r--r--src/testdir/dumps/Test_long_text_with_padding_1.dump8
-rw-r--r--src/testdir/dumps/Test_long_text_with_padding_2.dump8
-rw-r--r--src/testdir/dumps/Test_map_expr_1.dump10
-rw-r--r--src/testdir/dumps/Test_map_expr_2.dump10
-rw-r--r--src/testdir/dumps/Test_map_expr_3.dump10
-rw-r--r--src/testdir/dumps/Test_map_expr_4.dump10
-rw-r--r--src/testdir/dumps/Test_map_list_1.dump6
-rw-r--r--src/testdir/dumps/Test_match_linebreak.dump10
-rw-r--r--src/testdir/dumps/Test_match_tab_linebreak.dump10
-rw-r--r--src/testdir/dumps/Test_match_with_incsearch_1.dump6
-rw-r--r--src/testdir/dumps/Test_match_with_incsearch_2.dump6
-rw-r--r--src/testdir/dumps/Test_matchadd_1.dump12
-rw-r--r--src/testdir/dumps/Test_matchaddpos_1.dump14
-rw-r--r--src/testdir/dumps/Test_matchclear_1.dump12
-rw-r--r--src/testdir/dumps/Test_matchdelete_1.dump12
-rw-r--r--src/testdir/dumps/Test_matchparen_clear_highlight_1.dump5
-rw-r--r--src/testdir/dumps/Test_matchparen_clear_highlight_2.dump5
-rw-r--r--src/testdir/dumps/Test_misplaced_type.dump6
-rw-r--r--src/testdir/dumps/Test_mode_updated_1.dump5
-rw-r--r--src/testdir/dumps/Test_more_scrollback_1.dump10
-rw-r--r--src/testdir/dumps/Test_more_scrollback_2.dump10
-rw-r--r--src/testdir/dumps/Test_move_undo_1.dump10
-rw-r--r--src/testdir/dumps/Test_move_undo_2.dump10
-rw-r--r--src/testdir/dumps/Test_popup_and_previewwindow_01.dump20
-rw-r--r--src/testdir/dumps/Test_popup_command_01.dump20
-rw-r--r--src/testdir/dumps/Test_popup_command_02.dump20
-rw-r--r--src/testdir/dumps/Test_popup_command_03.dump20
-rw-r--r--src/testdir/dumps/Test_popup_command_04.dump20
-rw-r--r--src/testdir/dumps/Test_popup_command_05.dump20
-rw-r--r--src/testdir/dumps/Test_popup_position_01.dump8
-rw-r--r--src/testdir/dumps/Test_popup_position_02.dump8
-rw-r--r--src/testdir/dumps/Test_popup_position_03.dump8
-rw-r--r--src/testdir/dumps/Test_popup_position_04.dump10
-rw-r--r--src/testdir/dumps/Test_popup_prop_not_visible_01.dump10
-rw-r--r--src/testdir/dumps/Test_popup_prop_not_visible_01a.dump10
-rw-r--r--src/testdir/dumps/Test_popup_prop_not_visible_01b.dump10
-rw-r--r--src/testdir/dumps/Test_popup_prop_not_visible_02.dump10
-rw-r--r--src/testdir/dumps/Test_popup_prop_not_visible_03.dump10
-rw-r--r--src/testdir/dumps/Test_popup_settext_01.dump10
-rw-r--r--src/testdir/dumps/Test_popup_settext_02.dump10
-rw-r--r--src/testdir/dumps/Test_popup_settext_03.dump10
-rw-r--r--src/testdir/dumps/Test_popup_settext_04.dump10
-rw-r--r--src/testdir/dumps/Test_popup_settext_05.dump10
-rw-r--r--src/testdir/dumps/Test_popup_settext_06.dump10
-rw-r--r--src/testdir/dumps/Test_popup_settext_07.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_01.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_02.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_03.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_04.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_05.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_06.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_07.dump10
-rw-r--r--src/testdir/dumps/Test_popup_textprop_corn_1.dump12
-rw-r--r--src/testdir/dumps/Test_popup_textprop_corn_2.dump12
-rw-r--r--src/testdir/dumps/Test_popup_textprop_corn_3.dump12
-rw-r--r--src/testdir/dumps/Test_popup_textprop_corn_4.dump12
-rw-r--r--src/testdir/dumps/Test_popup_textprop_corn_5.dump12
-rw-r--r--src/testdir/dumps/Test_popup_textprop_corn_6.dump12
-rw-r--r--src/testdir/dumps/Test_popup_textprop_off_1.dump12
-rw-r--r--src/testdir/dumps/Test_popup_textprop_off_2.dump12
-rw-r--r--src/testdir/dumps/Test_popupwin_01.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_02.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_03.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_04.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_04a.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_05.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_06.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_07.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_08.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_10.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_11.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_20.dump15
-rw-r--r--src/testdir/dumps/Test_popupwin_21.dump15
-rw-r--r--src/testdir/dumps/Test_popupwin_22.dump12
-rw-r--r--src/testdir/dumps/Test_popupwin_23.dump12
-rw-r--r--src/testdir/dumps/Test_popupwin_24.dump12
-rw-r--r--src/testdir/dumps/Test_popupwin_atcursor_pos.dump12
-rw-r--r--src/testdir/dumps/Test_popupwin_behind.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_beval_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_beval_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_beval_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_close_01.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_close_02.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_close_03.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_close_04.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_close_05.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_corners.dump12
-rw-r--r--src/testdir/dumps/Test_popupwin_ctrl_c.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_4.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_5.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_6.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_7.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_cursorline_8.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_doublewidth_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_01.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_02.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_03.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_04.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_05.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_06.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_minwidth_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_minwidth_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_drag_minwidth_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_firstline_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_firstline_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_1.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_2.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_3.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_4.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_5.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_6.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_7.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_8.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_9.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_align_1.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_align_2.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_align_3.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_hidden_1.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_hidden_2.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_hidden_3.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_nb_1.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_infopopup_wide_1.dump8
-rw-r--r--src/testdir/dumps/Test_popupwin_longtitle_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_longtitle_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_longtitle_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_longtitle_4.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_mask_1.dump13
-rw-r--r--src/testdir/dumps/Test_popupwin_mask_2.dump13
-rw-r--r--src/testdir/dumps/Test_popupwin_mask_3.dump13
-rw-r--r--src/testdir/dumps/Test_popupwin_mask_4.dump13
-rw-r--r--src/testdir/dumps/Test_popupwin_mask_5.dump13
-rw-r--r--src/testdir/dumps/Test_popupwin_matches.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_01.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_02.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_03.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_04.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_filter_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_filter_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_filter_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_filter_4.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_filter_5.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_maxwidth_1.dump13
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_scroll_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_scroll_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_scroll_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_scroll_4.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_scroll_5.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_menu_scroll_6.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_multibytetitle.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_normal_cmd.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_nospace.dump12
-rw-r--r--src/testdir/dumps/Test_popupwin_notify_01.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_notify_02.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_nowrap.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_poptermscroll_1.dump15
-rw-r--r--src/testdir/dumps/Test_popupwin_poptermscroll_2.dump15
-rw-r--r--src/testdir/dumps/Test_popupwin_poptermscroll_3.dump15
-rw-r--r--src/testdir/dumps/Test_popupwin_poptermscroll_4.dump15
-rw-r--r--src/testdir/dumps/Test_popupwin_popupmenu_masking_1.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_popupmenu_masking_2.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_1.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_10.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_2.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_3.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_4.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_5.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_6.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_7.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_8.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_previewpopup_9.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_10.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_11.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_12.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_13.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_4.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_5.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_6.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_7.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_8.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_scroll_9.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_select_01.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_select_02.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_set_firstline_1.dump16
-rw-r--r--src/testdir/dumps/Test_popupwin_set_firstline_2.dump16
-rw-r--r--src/testdir/dumps/Test_popupwin_showbreak.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_sign_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_sign_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_term_01.dump16
-rw-r--r--src/testdir/dumps/Test_popupwin_term_02.dump16
-rw-r--r--src/testdir/dumps/Test_popupwin_term_03.dump16
-rw-r--r--src/testdir/dumps/Test_popupwin_term_04.dump16
-rw-r--r--src/testdir/dumps/Test_popupwin_three_errors_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_three_errors_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_title.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_toohigh_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_toohigh_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_toohigh_3.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_win_execute.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_win_execute_cursorline.dump14
-rw-r--r--src/testdir/dumps/Test_popupwin_wrap.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_wrap_1.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_wrap_2.dump10
-rw-r--r--src/testdir/dumps/Test_popupwin_wrong_name.dump10
-rw-r--r--src/testdir/dumps/Test_prop_above_below_empty_1.dump16
-rw-r--r--src/testdir/dumps/Test_prop_above_below_empty_2.dump16
-rw-r--r--src/testdir/dumps/Test_prop_above_below_empty_3.dump16
-rw-r--r--src/testdir/dumps/Test_prop_above_below_empty_4.dump16
-rw-r--r--src/testdir/dumps/Test_prop_above_below_empty_5.dump16
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_10.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_11.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_12.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_13.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_14.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_15.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_16.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_4.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_5.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_6.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_7.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_8.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_below_smoothscroll_9.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_empty_1.dump16
-rw-r--r--src/testdir/dumps/Test_prop_above_empty_2.dump16
-rw-r--r--src/testdir/dumps/Test_prop_above_number_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_above_number_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_after_linebreak.dump10
-rw-r--r--src/testdir/dumps/Test_prop_after_tab.dump10
-rw-r--r--src/testdir/dumps/Test_prop_at_same_pos.dump5
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_01.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_02.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_03.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_04.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_05.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_06.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_07.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_08.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_09.dump8
-rw-r--r--src/testdir/dumps/Test_prop_before_tab_10.dump8
-rw-r--r--src/testdir/dumps/Test_prop_below_after_empty_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_below_after_empty_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_below_after_empty_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_below_split_line_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_below_split_line_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_below_split_line_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_delete_updates_1.dump10
-rw-r--r--src/testdir/dumps/Test_prop_delete_updates_2.dump10
-rw-r--r--src/testdir/dumps/Test_prop_delete_updates_3.dump10
-rw-r--r--src/testdir/dumps/Test_prop_diff_mode_1.dump10
-rw-r--r--src/testdir/dumps/Test_prop_diff_mode_2.dump10
-rw-r--r--src/testdir/dumps/Test_prop_insert_list_mode_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_list_mode_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_list_mode_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_4.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_5.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_6.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_7.dump8
-rw-r--r--src/testdir/dumps/Test_prop_insert_start_incl_8.dump8
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_4.dump8
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_5.dump8
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_6.dump8
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_hi_1.dump6
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_hi_2.dump6
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_hi_3.dump6
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_hi_4.dump6
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_hi_5.dump6
-rw-r--r--src/testdir/dumps/Test_prop_inserts_text_hi_6.dump6
-rw-r--r--src/testdir/dumps/Test_prop_linebreak_1.dump10
-rw-r--r--src/testdir/dumps/Test_prop_linebreak_2.dump10
-rw-r--r--src/testdir/dumps/Test_prop_multibyte_below_1.dump10
-rw-r--r--src/testdir/dumps/Test_prop_negative_error_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_negative_error_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_right_align_twice_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_right_align_twice_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_right_align_twice_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_text_change_arg_1.dump5
-rw-r--r--src/testdir/dumps/Test_prop_text_change_arg_2.dump5
-rw-r--r--src/testdir/dumps/Test_prop_text_with_padding_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_text_with_padding_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_text_with_padding_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_text_with_padding_4.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_linebreak_1.dump6
-rw-r--r--src/testdir/dumps/Test_prop_with_linebreak_2.dump6
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_1.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_1a.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_1b.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_1c.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_2.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_3.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_4.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_5.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_6.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_7.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_8.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_above_9.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_below_trunc_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_below_trunc_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_below_trunc_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_join_split_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_join_split_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_join_split_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_join_split_4.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_join_split_5.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_joined_1.dump6
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_nowrap_1.dump12
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_nowrap_2.dump12
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_nowrap_3.dump12
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_trunc_1.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_trunc_2.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_trunc_3.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_trunc_4.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_trunc_5.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump9
-rw-r--r--src/testdir/dumps/Test_prop_with_text_below_after_match_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_below_cul_1.dump6
-rw-r--r--src/testdir/dumps/Test_prop_with_text_below_nowrap_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_below_nowrap_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_cursormoved_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_cursormoved_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_empty_line_1.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_empty_line_2.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_empty_line_3.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_empty_line_4.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_empty_line_5.dump8
-rw-r--r--src/testdir/dumps/Test_prop_with_text_override_1.dump6
-rw-r--r--src/testdir/dumps/Test_prop_with_text_override_2.dump6
-rw-r--r--src/testdir/dumps/Test_prop_with_wrap_1.dump6
-rw-r--r--src/testdir/dumps/Test_props_after_1.dump8
-rw-r--r--src/testdir/dumps/Test_props_after_2.dump8
-rw-r--r--src/testdir/dumps/Test_pum_preview_1.dump12
-rw-r--r--src/testdir/dumps/Test_pum_preview_2.dump12
-rw-r--r--src/testdir/dumps/Test_pum_preview_3.dump12
-rw-r--r--src/testdir/dumps/Test_pum_preview_4.dump12
-rw-r--r--src/testdir/dumps/Test_pum_rightleft_01.dump8
-rw-r--r--src/testdir/dumps/Test_pum_rightleft_02.dump7
-rw-r--r--src/testdir/dumps/Test_pum_scrollbar_01.dump7
-rw-r--r--src/testdir/dumps/Test_pum_scrollbar_02.dump7
-rw-r--r--src/testdir/dumps/Test_pum_stopped_by_timer.dump12
-rw-r--r--src/testdir/dumps/Test_pum_with_folds_two_tabs.dump10
-rw-r--r--src/testdir/dumps/Test_pum_with_preview_win.dump12
-rw-r--r--src/testdir/dumps/Test_quickfix_cwindow_1.dump12
-rw-r--r--src/testdir/dumps/Test_quickfix_cwindow_2.dump12
-rw-r--r--src/testdir/dumps/Test_quickfix_cwindow_3.dump12
-rw-r--r--src/testdir/dumps/Test_quickfix_cwindow_4.dump12
-rw-r--r--src/testdir/dumps/Test_quickfix_window_fails.dump13
-rw-r--r--src/testdir/dumps/Test_quit_long_message.dump10
-rw-r--r--src/testdir/dumps/Test_redraw_in_autocmd_1.dump8
-rw-r--r--src/testdir/dumps/Test_redraw_in_autocmd_2.dump8
-rw-r--r--src/testdir/dumps/Test_redrawstatus_in_autocmd_1.dump8
-rw-r--r--src/testdir/dumps/Test_redrawstatus_in_autocmd_2.dump8
-rw-r--r--src/testdir/dumps/Test_redrawstatus_in_autocmd_3.dump8
-rw-r--r--src/testdir/dumps/Test_redrawstatus_in_autocmd_4.dump8
-rw-r--r--src/testdir/dumps/Test_redrawstatus_in_autocmd_5.dump8
-rw-r--r--src/testdir/dumps/Test_relativenumber_callback_1.dump8
-rw-r--r--src/testdir/dumps/Test_relnr_colors_1.dump10
-rw-r--r--src/testdir/dumps/Test_relnr_colors_2.dump10
-rw-r--r--src/testdir/dumps/Test_relnr_colors_3.dump10
-rw-r--r--src/testdir/dumps/Test_relnr_colors_4.dump10
-rw-r--r--src/testdir/dumps/Test_scroll_no_region_1.dump10
-rw-r--r--src/testdir/dumps/Test_scroll_no_region_2.dump10
-rw-r--r--src/testdir/dumps/Test_scroll_no_region_3.dump10
-rw-r--r--src/testdir/dumps/Test_scroll_no_region_4.dump10
-rw-r--r--src/testdir/dumps/Test_scroll_no_region_5.dump10
-rw-r--r--src/testdir/dumps/Test_scroll_no_region_6.dump10
-rw-r--r--src/testdir/dumps/Test_scrollbar_on_wide_char.dump10
-rw-r--r--src/testdir/dumps/Test_searchstat_1.dump10
-rw-r--r--src/testdir/dumps/Test_searchstat_2.dump10
-rw-r--r--src/testdir/dumps/Test_searchstat_3.dump10
-rw-r--r--src/testdir/dumps/Test_searchstat_4.dump10
-rw-r--r--src/testdir/dumps/Test_searchstat_inc_1.dump10
-rw-r--r--src/testdir/dumps/Test_searchstat_inc_2.dump10
-rw-r--r--src/testdir/dumps/Test_searchstat_inc_3.dump10
-rw-r--r--src/testdir/dumps/Test_searchstatgd_1.dump10
-rw-r--r--src/testdir/dumps/Test_searchstatgd_2.dump10
-rw-r--r--src/testdir/dumps/Test_setcellwidths_dump_1.dump6
-rw-r--r--src/testdir/dumps/Test_setcellwidths_dump_2.dump6
-rw-r--r--src/testdir/dumps/Test_sign_cursor_1.dump6
-rw-r--r--src/testdir/dumps/Test_sign_cursor_2.dump6
-rw-r--r--src/testdir/dumps/Test_sign_cursor_3.dump6
-rw-r--r--src/testdir/dumps/Test_sign_cursor_4.dump6
-rw-r--r--src/testdir/dumps/Test_sign_cursor_5.dump6
-rw-r--r--src/testdir/dumps/Test_sign_cursor_6.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_diff_1.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_list_1.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_list_2.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_long_1.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_10.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_11.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_12.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_13.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_14.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_15.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_2.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_3.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_4.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_5.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_6.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_7.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_8.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_9.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_showbreak_1.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_long_showbreak_2.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_number_1.dump12
-rw-r--r--src/testdir/dumps/Test_smooth_number_2.dump12
-rw-r--r--src/testdir/dumps/Test_smooth_number_3.dump12
-rw-r--r--src/testdir/dumps/Test_smooth_number_4.dump12
-rw-r--r--src/testdir/dumps/Test_smooth_number_5.dump12
-rw-r--r--src/testdir/dumps/Test_smooth_number_6.dump12
-rw-r--r--src/testdir/dumps/Test_smooth_number_7.dump12
-rw-r--r--src/testdir/dumps/Test_smooth_one_long_1.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_one_long_2.dump6
-rw-r--r--src/testdir/dumps/Test_smooth_wrap_1.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_wrap_2.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_wrap_3.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_wrap_4.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_wrap_5.dump8
-rw-r--r--src/testdir/dumps/Test_smooth_wrap_6.dump8
-rw-r--r--src/testdir/dumps/Test_smoothscroll_1.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_2.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_3.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_4.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_5.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_6.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_7.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_8.dump12
-rw-r--r--src/testdir/dumps/Test_smoothscroll_zero_1.dump6
-rw-r--r--src/testdir/dumps/Test_smoothscroll_zero_2.dump6
-rw-r--r--src/testdir/dumps/Test_spell_1.dump8
-rw-r--r--src/testdir/dumps/Test_spell_2.dump8
-rw-r--r--src/testdir/dumps/Test_spell_3.dump8
-rw-r--r--src/testdir/dumps/Test_spell_4.dump8
-rw-r--r--src/testdir/dumps/Test_spell_5.dump8
-rw-r--r--src/testdir/dumps/Test_spell_compatible_1.dump8
-rw-r--r--src/testdir/dumps/Test_spell_compatible_2.dump8
-rw-r--r--src/testdir/dumps/Test_splitkeep_callback_1.dump8
-rw-r--r--src/testdir/dumps/Test_splitkeep_callback_2.dump8
-rw-r--r--src/testdir/dumps/Test_splitkeep_callback_3.dump8
-rw-r--r--src/testdir/dumps/Test_splitkeep_callback_4.dump8
-rw-r--r--src/testdir/dumps/Test_splitkeep_fold_1.dump10
-rw-r--r--src/testdir/dumps/Test_splitkeep_fold_2.dump10
-rw-r--r--src/testdir/dumps/Test_splitkeep_fold_3.dump10
-rw-r--r--src/testdir/dumps/Test_splitkeep_fold_4.dump10
-rw-r--r--src/testdir/dumps/Test_splitkeep_status_1.dump10
-rw-r--r--src/testdir/dumps/Test_start_with_tabs.dump20
-rw-r--r--src/testdir/dumps/Test_statusline_1.dump10
-rw-r--r--src/testdir/dumps/Test_statusline_hl.dump6
-rw-r--r--src/testdir/dumps/Test_statusline_mode_1.dump7
-rw-r--r--src/testdir/dumps/Test_statusline_mode_2.dump7
-rw-r--r--src/testdir/dumps/Test_statusline_showcmd_1.dump6
-rw-r--r--src/testdir/dumps/Test_statusline_showcmd_2.dump6
-rw-r--r--src/testdir/dumps/Test_statusline_showcmd_3.dump6
-rw-r--r--src/testdir/dumps/Test_statusline_showcmd_4.dump6
-rw-r--r--src/testdir/dumps/Test_statusline_showcmd_5.dump6
-rw-r--r--src/testdir/dumps/Test_sub_highlight_zer_match_1.dump8
-rw-r--r--src/testdir/dumps/Test_syntax_c_01.dump20
-rw-r--r--src/testdir/dumps/Test_tabline_showcmd_1.dump6
-rw-r--r--src/testdir/dumps/Test_tabline_showcmd_2.dump6
-rw-r--r--src/testdir/dumps/Test_tabline_showcmd_3.dump6
-rw-r--r--src/testdir/dumps/Test_tabline_showcmd_4.dump6
-rw-r--r--src/testdir/dumps/Test_tabline_showcmd_5.dump6
-rw-r--r--src/testdir/dumps/Test_tabpage_cmdheight.dump20
-rw-r--r--src/testdir/dumps/Test_tenc_euc_jp_01.dump10
-rw-r--r--src/testdir/dumps/Test_term_popup_bufline.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_all_ansi_colors.dump10
-rw-r--r--src/testdir/dumps/Test_terminal_color_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_MyTermCol_over_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_MyWinCol_over_group.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_gui_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_gui_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_gui_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_gui_transp_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_gui_transp_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_gui_transp_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_transp_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_transp_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_color_transp_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_combining.dump9
-rw-r--r--src/testdir/dumps/Test_terminal_dumpload.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_focus_1.dump6
-rw-r--r--src/testdir/dumps/Test_terminal_focus_2.dump6
-rw-r--r--src/testdir/dumps/Test_terminal_focus_3.dump6
-rw-r--r--src/testdir/dumps/Test_terminal_from_cmd.dump20
-rw-r--r--src/testdir/dumps/Test_terminal_normal_1.dump8
-rw-r--r--src/testdir/dumps/Test_terminal_normal_2.dump8
-rw-r--r--src/testdir/dumps/Test_terminal_normal_3.dump8
-rw-r--r--src/testdir/dumps/Test_terminal_popup_1.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_2.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_3.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_4.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_5.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_6.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_7.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_8.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_MyPopupHlCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_MyTermCol_over_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_MyWinCol_over_group.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_MyPopupHlCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_transp_MyPopupHlCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_transp_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_transp_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_gui_transp_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_m1.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_transp_MyPopupHlCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_transp_MyTermCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_transp_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_popup_transp_Terminal.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_scrollback_1.dump20
-rw-r--r--src/testdir/dumps/Test_terminal_scrollback_2.dump20
-rw-r--r--src/testdir/dumps/Test_terminal_scrollback_3.dump20
-rw-r--r--src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol.dump15
-rw-r--r--src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol2.dump15
-rw-r--r--src/testdir/dumps/Test_text_after_nowrap_1.dump8
-rw-r--r--src/testdir/dumps/Test_text_after_nowrap_2.dump8
-rw-r--r--src/testdir/dumps/Test_text_after_nowrap_3.dump8
-rw-r--r--src/testdir/dumps/Test_text_after_nowrap_4.dump8
-rw-r--r--src/testdir/dumps/Test_text_after_nowrap_5.dump8
-rw-r--r--src/testdir/dumps/Test_text_after_nowrap_list_1.dump6
-rw-r--r--src/testdir/dumps/Test_text_below_nowrap_1.dump8
-rw-r--r--src/testdir/dumps/Test_textprop_01.dump8
-rw-r--r--src/testdir/dumps/Test_textprop_hl_override_1.dump8
-rw-r--r--src/testdir/dumps/Test_textprop_hl_override_2.dump8
-rw-r--r--src/testdir/dumps/Test_textprop_nesting.dump8
-rw-r--r--src/testdir/dumps/Test_textprop_nowrap_01.dump6
-rw-r--r--src/testdir/dumps/Test_textprop_nowrap_02.dump6
-rw-r--r--src/testdir/dumps/Test_textprop_syn_1.dump6
-rw-r--r--src/testdir/dumps/Test_textprop_tab.dump6
-rw-r--r--src/testdir/dumps/Test_textprop_vis_01.dump12
-rw-r--r--src/testdir/dumps/Test_textprop_vis_02.dump12
-rw-r--r--src/testdir/dumps/Test_tselect_1.dump10
-rw-r--r--src/testdir/dumps/Test_undo_after_write_1.dump6
-rw-r--r--src/testdir/dumps/Test_undo_after_write_2.dump6
-rw-r--r--src/testdir/dumps/Test_undo_after_write_2.vim2
-rw-r--r--src/testdir/dumps/Test_verbose_option_1.dump12
-rw-r--r--src/testdir/dumps/Test_verbose_system_1.dump10
-rw-r--r--src/testdir/dumps/Test_verbose_system_1.vim5
-rw-r--r--src/testdir/dumps/Test_verbose_system_2.dump10
-rw-r--r--src/testdir/dumps/Test_verbose_system_2.vim5
-rw-r--r--src/testdir/dumps/Test_vim9_closure_fails.dump6
-rw-r--r--src/testdir/dumps/Test_vim9_no_redraw.dump6
-rw-r--r--src/testdir/dumps/Test_vim9_reject_declaration_1.dump6
-rw-r--r--src/testdir/dumps/Test_vim9_reject_declaration_2.dump6
-rw-r--r--src/testdir/dumps/Test_vim9_silent_echo.dump6
-rw-r--r--src/testdir/dumps/Test_virtual_text_in_popup_highlight_1.dump8
-rw-r--r--src/testdir/dumps/Test_visual_block_with_virtualedit.dump8
-rw-r--r--src/testdir/dumps/Test_visual_block_with_virtualedit2.dump8
-rw-r--r--src/testdir/dumps/Test_wildmenu_1.dump8
-rw-r--r--src/testdir/dumps/Test_wildmenu_2.dump8
-rw-r--r--src/testdir/dumps/Test_wildmenu_3.dump8
-rw-r--r--src/testdir/dumps/Test_wildmenu_4.dump8
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_01.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_02.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_03.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_04.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_05.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_06.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_07.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_08.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_09.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_10.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_11.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_12.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_13.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_14.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_15.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_16.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_17.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_18.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_19.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_20.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_21.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_22.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_23.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_24.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_25.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_26.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_27.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_28.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_29.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_30.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_31.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_32.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_33.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_34.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_35.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_36.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_37.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_38.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_39.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_40.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_41.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_42.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_43.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_44.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_45.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_46.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_47.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_48.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_49.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_50.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_clear_entries_1.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_pum_term_01.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_1.dump10
-rw-r--r--src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_2.dump10
-rw-r--r--src/testdir/dumps/Test_win_gotoid_1.dump15
-rw-r--r--src/testdir/dumps/Test_win_gotoid_2.dump15
-rw-r--r--src/testdir/dumps/Test_win_gotoid_3.dump15
-rw-r--r--src/testdir/dumps/Test_winbar_not_visible.dump10
-rw-r--r--src/testdir/dumps/Test_winbar_not_visible_custom_statusline.dump10
-rw-r--r--src/testdir/dumps/Test_wincolor_01.dump8
-rw-r--r--src/testdir/dumps/Test_wincolor_lcs.dump8
-rw-r--r--src/testdir/dumps/Test_winline_rnu.dump5
-rw-r--r--src/testdir/dumps/Test_winscrolled_not_when_defined_1.dump10
-rw-r--r--src/testdir/dumps/Test_winscrolled_not_when_defined_2.dump10
-rw-r--r--src/testdir/dumps/Test_winscrolled_once_only_1.dump10
-rw-r--r--src/testdir/gen_opt_test.vim241
-rw-r--r--src/testdir/gui_init.vim6
-rw-r--r--src/testdir/gui_preinit.vim7
-rw-r--r--src/testdir/keycode_check.json1
-rw-r--r--src/testdir/keycode_check.vim470
-rw-r--r--src/testdir/lsan-suppress.txt13
-rw-r--r--src/testdir/mouse.vim372
-rw-r--r--src/testdir/popupbounce.vim80
-rw-r--r--src/testdir/python2/module.py2
-rw-r--r--src/testdir/python3/module.py2
-rw-r--r--src/testdir/python_after/after.py2
-rw-r--r--src/testdir/python_before/before.py1
-rw-r--r--src/testdir/python_before/before_1.py1
-rw-r--r--src/testdir/python_before/before_2.py1
-rw-r--r--src/testdir/pythonx/failing.py1
-rw-r--r--src/testdir/pythonx/failing_import.py1
-rw-r--r--src/testdir/pythonx/module.py1
-rw-r--r--src/testdir/pythonx/modulex.py1
-rw-r--r--src/testdir/pythonx/topmodule/__init__.py1
-rw-r--r--src/testdir/pythonx/topmodule/submodule/__init__.py1
-rw-r--r--src/testdir/pythonx/topmodule/submodule/subsubmodule/__init__.py1
-rw-r--r--src/testdir/pythonx/topmodule/submodule/subsubmodule/subsubsubmodule.py1
-rw-r--r--src/testdir/pyxfile/py2_magic.py4
-rw-r--r--src/testdir/pyxfile/py2_shebang.py4
-rw-r--r--src/testdir/pyxfile/py3_magic.py4
-rw-r--r--src/testdir/pyxfile/py3_shebang.py4
-rw-r--r--src/testdir/pyxfile/pyx.py2
-rw-r--r--src/testdir/runtest.vim618
-rw-r--r--src/testdir/samples/crypt_sodium_invalid.txtbin0 -> 16504 bytes
-rw-r--r--src/testdir/samples/quickfix.txt4
-rw-r--r--src/testdir/samples/re.freeze.txt6
-rw-r--r--src/testdir/samples/test000bin0 -> 8 bytes
-rw-r--r--src/testdir/sautest/autoload/Test104.vim1
-rw-r--r--src/testdir/sautest/autoload/auto9.vim9
-rw-r--r--src/testdir/sautest/autoload/foo.vim15
-rw-r--r--src/testdir/sautest/autoload/footest.vim5
-rw-r--r--src/testdir/sautest/autoload/globone.vim1
-rw-r--r--src/testdir/sautest/autoload/globtwo.vim1
-rw-r--r--src/testdir/sautest/autoload/sourced.vim4
-rw-r--r--src/testdir/screendump.vim123
-rw-r--r--src/testdir/script_util.vim69
-rw-r--r--src/testdir/setup.vim39
-rw-r--r--src/testdir/setup_gui.vim31
-rw-r--r--src/testdir/shared.vim411
-rw-r--r--src/testdir/silent.wavbin0 -> 65580 bytes
-rw-r--r--src/testdir/summarize.vim62
-rw-r--r--src/testdir/term_util.vim196
-rw-r--r--src/testdir/test10.in21
-rw-r--r--src/testdir/test10.ok2
-rw-r--r--src/testdir/test20.in27
-rw-r--r--src/testdir/test20.ok10
-rw-r--r--src/testdir/test21.in13
-rw-r--r--src/testdir/test21.ok4
-rw-r--r--src/testdir/test22.in15
-rw-r--r--src/testdir/test22.ok2
-rw-r--r--src/testdir/test23.inbin0 -> 1301 bytes
-rw-r--r--src/testdir/test23.ok32
-rw-r--r--src/testdir/test24.inbin0 -> 364 bytes
-rw-r--r--src/testdir/test24.ok2
-rw-r--r--src/testdir/test25.in108
-rw-r--r--src/testdir/test25.ok99
-rw-r--r--src/testdir/test26.in24
-rw-r--r--src/testdir/test26.ok3
-rw-r--r--src/testdir/test27.inbin0 -> 2368 bytes
-rw-r--r--src/testdir/test27.okbin0 -> 409 bytes
-rw-r--r--src/testdir/test77a.com8
-rw-r--r--src/testdir/test77a.in35
-rw-r--r--src/testdir/test77a.ok1
-rw-r--r--src/testdir/test_alot.vim31
-rw-r--r--src/testdir/test_alot_latin.vim7
-rw-r--r--src/testdir/test_alot_utf8.vim14
-rw-r--r--src/testdir/test_arabic.vim595
-rw-r--r--src/testdir/test_arglist.vim747
-rw-r--r--src/testdir/test_assert.vim488
-rw-r--r--src/testdir/test_autochdir.vim121
-rw-r--r--src/testdir/test_autocmd.vim4270
-rw-r--r--src/testdir/test_autoload.vim30
-rw-r--r--src/testdir/test_backspace_opt.vim141
-rw-r--r--src/testdir/test_backup.vim88
-rw-r--r--src/testdir/test_balloon.vim67
-rw-r--r--src/testdir/test_balloon_gui.vim21
-rw-r--r--src/testdir/test_behave.vim31
-rw-r--r--src/testdir/test_bench_regexp.vim24
-rw-r--r--src/testdir/test_blob.vim835
-rw-r--r--src/testdir/test_blockedit.vim134
-rw-r--r--src/testdir/test_breakindent.vim1055
-rw-r--r--src/testdir/test_buffer.vim517
-rw-r--r--src/testdir/test_bufline.vim341
-rw-r--r--src/testdir/test_bufwintabinfo.vim197
-rw-r--r--src/testdir/test_cd.vim247
-rw-r--r--src/testdir/test_cdo.vim216
-rw-r--r--src/testdir/test_changedtick.vim97
-rw-r--r--src/testdir/test_changelist.vim105
-rw-r--r--src/testdir/test_channel.py288
-rw-r--r--src/testdir/test_channel.vim2696
-rw-r--r--src/testdir/test_channel_6.py15
-rw-r--r--src/testdir/test_channel_lsp.py333
-rw-r--r--src/testdir/test_channel_pipe.py76
-rw-r--r--src/testdir/test_channel_unix.py56
-rw-r--r--src/testdir/test_channel_write.py18
-rw-r--r--src/testdir/test_charsearch.vim98
-rw-r--r--src/testdir/test_charsearch_utf8.vim19
-rw-r--r--src/testdir/test_checkpath.vim116
-rw-r--r--src/testdir/test_cindent.vim5442
-rw-r--r--src/testdir/test_cjk_linebreak.vim101
-rw-r--r--src/testdir/test_clientserver.vim196
-rw-r--r--src/testdir/test_close_count.vim176
-rw-r--r--src/testdir/test_cmd_lists.vim70
-rw-r--r--src/testdir/test_cmdline.vim3447
-rw-r--r--src/testdir/test_cmdmods.vim45
-rw-r--r--src/testdir/test_cmdwin.vim428
-rw-r--r--src/testdir/test_codestyle.vim145
-rw-r--r--src/testdir/test_command_count.vim198
-rw-r--r--src/testdir/test_comments.vim277
-rw-r--r--src/testdir/test_comparators.vim13
-rw-r--r--src/testdir/test_compiler.vim89
-rw-r--r--src/testdir/test_conceal.vim359
-rw-r--r--src/testdir/test_const.vim335
-rw-r--r--src/testdir/test_cpoptions.vim915
-rw-r--r--src/testdir/test_crypt.vim288
-rw-r--r--src/testdir/test_cscope.vim337
-rw-r--r--src/testdir/test_cursor_func.vim493
-rw-r--r--src/testdir/test_cursorline.vim327
-rw-r--r--src/testdir/test_curswant.vim25
-rw-r--r--src/testdir/test_debugger.vim1531
-rw-r--r--src/testdir/test_delete.vim110
-rw-r--r--src/testdir/test_diffmode.vim1630
-rw-r--r--src/testdir/test_digraph.vim607
-rw-r--r--src/testdir/test_display.vim471
-rw-r--r--src/testdir/test_edit.vim2109
-rw-r--r--src/testdir/test_environ.vim89
-rw-r--r--src/testdir/test_erasebackword.vim22
-rw-r--r--src/testdir/test_escaped_glob.vim35
-rw-r--r--src/testdir/test_eval_stuff.vim705
-rw-r--r--src/testdir/test_ex_equal.vim34
-rw-r--r--src/testdir/test_ex_mode.vim290
-rw-r--r--src/testdir/test_ex_undo.vim21
-rw-r--r--src/testdir/test_ex_z.vim117
-rw-r--r--src/testdir/test_excmd.vim728
-rw-r--r--src/testdir/test_exec_while_if.vim43
-rw-r--r--src/testdir/test_execute_func.vim207
-rw-r--r--src/testdir/test_exists.vim337
-rw-r--r--src/testdir/test_exists_autocmd.vim28
-rw-r--r--src/testdir/test_exit.vim130
-rw-r--r--src/testdir/test_expand.vim224
-rw-r--r--src/testdir/test_expand_dllpath.vim36
-rw-r--r--src/testdir/test_expand_func.vim144
-rw-r--r--src/testdir/test_expr.vim1023
-rw-r--r--src/testdir/test_expr_utf8.vim44
-rw-r--r--src/testdir/test_file_perm.vim29
-rw-r--r--src/testdir/test_file_size.vim61
-rw-r--r--src/testdir/test_filechanged.vim264
-rw-r--r--src/testdir/test_fileformat.vim327
-rw-r--r--src/testdir/test_filetype.vim2023
-rw-r--r--src/testdir/test_filter_cmd.vim199
-rw-r--r--src/testdir/test_filter_map.vim234
-rw-r--r--src/testdir/test_find_complete.vim164
-rw-r--r--src/testdir/test_findfile.vim249
-rw-r--r--src/testdir/test_fixeol.vim118
-rw-r--r--src/testdir/test_flatten.vim109
-rw-r--r--src/testdir/test_float_func.vim389
-rw-r--r--src/testdir/test_fnameescape.vim27
-rw-r--r--src/testdir/test_fnamemodify.vim106
-rw-r--r--src/testdir/test_fold.vim1758
-rw-r--r--src/testdir/test_function_lists.vim107
-rw-r--r--src/testdir/test_functions.vim3078
-rw-r--r--src/testdir/test_ga.vim44
-rw-r--r--src/testdir/test_getcwd.vim268
-rw-r--r--src/testdir/test_getvar.vim163
-rw-r--r--src/testdir/test_gf.vim295
-rw-r--r--src/testdir/test_glob2regpat.vim35
-rw-r--r--src/testdir/test_global.vim150
-rw-r--r--src/testdir/test_gn.vim221
-rw-r--r--src/testdir/test_goto.vim441
-rw-r--r--src/testdir/test_gui.vim1684
-rw-r--r--src/testdir/test_gui_init.vim46
-rw-r--r--src/testdir/test_hardcopy.vim212
-rw-r--r--src/testdir/test_help.vim209
-rw-r--r--src/testdir/test_help_tagjump.vim318
-rw-r--r--src/testdir/test_hide.vim97
-rw-r--r--src/testdir/test_highlight.vim1301
-rw-r--r--src/testdir/test_history.vim252
-rw-r--r--src/testdir/test_hlsearch.vim94
-rw-r--r--src/testdir/test_iminsert.vim326
-rw-r--r--src/testdir/test_increment.vim896
-rw-r--r--src/testdir/test_increment_dbcs.vim28
-rw-r--r--src/testdir/test_indent.vim278
-rw-r--r--src/testdir/test_input.vim61
-rw-r--r--src/testdir/test_ins_complete.vim2224
-rw-r--r--src/testdir/test_ins_complete_no_halt.vim51
-rw-r--r--src/testdir/test_interrupt.vim32
-rw-r--r--src/testdir/test_job_fails.vim17
-rw-r--r--src/testdir/test_join.vim448
-rw-r--r--src/testdir/test_json.vim329
-rw-r--r--src/testdir/test_jumplist.vim101
-rw-r--r--src/testdir/test_lambda.vim384
-rw-r--r--src/testdir/test_langmap.vim89
-rw-r--r--src/testdir/test_largefile.vim31
-rw-r--r--src/testdir/test_let.vim616
-rw-r--r--src/testdir/test_lineending.vim22
-rw-r--r--src/testdir/test_lispindent.vim129
-rw-r--r--src/testdir/test_listchars.vim693
-rw-r--r--src/testdir/test_listdict.vim1529
-rw-r--r--src/testdir/test_listener.vim453
-rw-r--r--src/testdir/test_listlbr.vim334
-rw-r--r--src/testdir/test_listlbr_utf8.vim284
-rw-r--r--src/testdir/test_lua.vim1235
-rw-r--r--src/testdir/test_makeencoding.py67
-rw-r--r--src/testdir/test_makeencoding.vim120
-rw-r--r--src/testdir/test_man.vim149
-rw-r--r--src/testdir/test_map_functions.vim584
-rw-r--r--src/testdir/test_mapping.vim1817
-rw-r--r--src/testdir/test_marks.vim321
-rw-r--r--src/testdir/test_match.vim437
-rw-r--r--src/testdir/test_matchadd_conceal.vim426
-rw-r--r--src/testdir/test_matchadd_conceal_utf8.vim45
-rw-r--r--src/testdir/test_matchfuzzy.vim256
-rw-r--r--src/testdir/test_memory_usage.vim165
-rw-r--r--src/testdir/test_menu.vim597
-rw-r--r--src/testdir/test_messages.vim593
-rw-r--r--src/testdir/test_method.vim175
-rw-r--r--src/testdir/test_mksession.vim1265
-rw-r--r--src/testdir/test_mksession_utf8.vim105
-rw-r--r--src/testdir/test_modeless.vim407
-rw-r--r--src/testdir/test_modeline.vim361
-rw-r--r--src/testdir/test_move.vim70
-rw-r--r--src/testdir/test_mswin_event.vim1006
-rw-r--r--src/testdir/test_mzscheme.vim62
-rw-r--r--src/testdir/test_nested_function.vim70
-rw-r--r--src/testdir/test_netbeans.py210
-rw-r--r--src/testdir/test_netbeans.vim973
-rw-r--r--src/testdir/test_normal.vim3924
-rw-r--r--src/testdir/test_number.vim357
-rw-r--r--src/testdir/test_options.vim1673
-rw-r--r--src/testdir/test_packadd.vim446
-rw-r--r--src/testdir/test_partial.vim409
-rw-r--r--src/testdir/test_paste.vim338
-rw-r--r--src/testdir/test_perl.vim356
-rw-r--r--src/testdir/test_plus_arg_edit.vim45
-rw-r--r--src/testdir/test_popup.vim1253
-rw-r--r--src/testdir/test_popupwin.vim4183
-rw-r--r--src/testdir/test_popupwin_textprop.vim173
-rw-r--r--src/testdir/test_preview.vim63
-rw-r--r--src/testdir/test_profile.vim772
-rw-r--r--src/testdir/test_prompt_buffer.vim255
-rw-r--r--src/testdir/test_put.vim246
-rw-r--r--src/testdir/test_python2.vim3788
-rw-r--r--src/testdir/test_python3.vim4114
-rw-r--r--src/testdir/test_pyx2.vim103
-rw-r--r--src/testdir/test_pyx3.vim103
-rw-r--r--src/testdir/test_quickfix.vim6408
-rw-r--r--src/testdir/test_quotestar.vim158
-rw-r--r--src/testdir/test_random.vim56
-rw-r--r--src/testdir/test_recover.vim451
-rw-r--r--src/testdir/test_regex_char_classes.vim297
-rw-r--r--src/testdir/test_regexp_latin.vim1163
-rw-r--r--src/testdir/test_regexp_utf8.vim579
-rw-r--r--src/testdir/test_registers.vim890
-rw-r--r--src/testdir/test_reltime.vim31
-rw-r--r--src/testdir/test_rename.vim113
-rw-r--r--src/testdir/test_restricted.vim120
-rw-r--r--src/testdir/test_retab.vim117
-rw-r--r--src/testdir/test_ruby.vim437
-rw-r--r--src/testdir/test_scriptnames.vim96
-rw-r--r--src/testdir/test_scroll_opt.vim608
-rw-r--r--src/testdir/test_scrollbind.vim275
-rw-r--r--src/testdir/test_search.vim2174
-rw-r--r--src/testdir/test_search_stat.vim436
-rw-r--r--src/testdir/test_searchpos.vim30
-rw-r--r--src/testdir/test_selectmode.vim313
-rw-r--r--src/testdir/test_set.vim78
-rw-r--r--src/testdir/test_sha256.vim24
-rw-r--r--src/testdir/test_shell.vim298
-rw-r--r--src/testdir/test_shift.vim115
-rw-r--r--src/testdir/test_short_sleep.py11
-rw-r--r--src/testdir/test_shortpathname.vim99
-rw-r--r--src/testdir/test_signals.vim205
-rw-r--r--src/testdir/test_signs.vim2062
-rw-r--r--src/testdir/test_sleep.vim27
-rw-r--r--src/testdir/test_smartindent.vim159
-rw-r--r--src/testdir/test_sort.vim1561
-rw-r--r--src/testdir/test_sound.vim100
-rw-r--r--src/testdir/test_source.vim677
-rw-r--r--src/testdir/test_source_utf8.vim63
-rw-r--r--src/testdir/test_spell.vim1505
-rw-r--r--src/testdir/test_spell_utf8.vim831
-rw-r--r--src/testdir/test_spellfile.vim1156
-rw-r--r--src/testdir/test_startup.vim1355
-rw-r--r--src/testdir/test_startup_utf8.vim81
-rw-r--r--src/testdir/test_stat.vim224
-rw-r--r--src/testdir/test_statusline.vim613
-rw-r--r--src/testdir/test_substitute.vim1427
-rw-r--r--src/testdir/test_suspend.vim121
-rw-r--r--src/testdir/test_swap.vim576
-rw-r--r--src/testdir/test_syn_attr.vim834
-rw-r--r--src/testdir/test_syntax.vim981
-rw-r--r--src/testdir/test_system.vim146
-rw-r--r--src/testdir/test_tab.vim92
-rw-r--r--src/testdir/test_tabline.vim205
-rw-r--r--src/testdir/test_tabpage.vim890
-rw-r--r--src/testdir/test_tagcase.vim75
-rw-r--r--src/testdir/test_tagfunc.vim416
-rw-r--r--src/testdir/test_tagjump.vim1548
-rw-r--r--src/testdir/test_taglist.vim263
-rw-r--r--src/testdir/test_tcl.vim752
-rw-r--r--src/testdir/test_termcodes.vim2726
-rw-r--r--src/testdir/test_termencoding.vim38
-rw-r--r--src/testdir/test_terminal.vim2353
-rw-r--r--src/testdir/test_terminal2.vim570
-rw-r--r--src/testdir/test_terminal3.vim935
-rw-r--r--src/testdir/test_terminal_fail.vim22
-rw-r--r--src/testdir/test_textformat.vim1306
-rw-r--r--src/testdir/test_textobjects.vim644
-rw-r--r--src/testdir/test_textprop.vim3884
-rw-r--r--src/testdir/test_timers.vim544
-rw-r--r--src/testdir/test_true_false.vim154
-rw-r--r--src/testdir/test_trycatch.vim2351
-rw-r--r--src/testdir/test_undo.vim807
-rw-r--r--src/testdir/test_unlet.vim67
-rw-r--r--src/testdir/test_user_func.vim802
-rw-r--r--src/testdir/test_usercommands.vim938
-rw-r--r--src/testdir/test_utf8.vim331
-rw-r--r--src/testdir/test_utf8_comparisons.vim96
-rw-r--r--src/testdir/test_vartabs.vim458
-rw-r--r--src/testdir/test_version.vim26
-rw-r--r--src/testdir/test_vim9_assign.vim2825
-rw-r--r--src/testdir/test_vim9_builtin.vim4949
-rw-r--r--src/testdir/test_vim9_class.vim1752
-rw-r--r--src/testdir/test_vim9_cmd.vim2040
-rw-r--r--src/testdir/test_vim9_disassemble.vim3049
-rw-r--r--src/testdir/test_vim9_expr.vim4160
-rw-r--r--src/testdir/test_vim9_fails.vim62
-rw-r--r--src/testdir/test_vim9_func.vim4576
-rw-r--r--src/testdir/test_vim9_import.vim2923
-rw-r--r--src/testdir/test_vim9_script.vim4573
-rw-r--r--src/testdir/test_viminfo.vim1282
-rw-r--r--src/testdir/test_vimscript.vim7534
-rw-r--r--src/testdir/test_virtualedit.vim612
-rw-r--r--src/testdir/test_visual.vim1558
-rw-r--r--src/testdir/test_winbar.vim190
-rw-r--r--src/testdir/test_winbuf_close.vim228
-rw-r--r--src/testdir/test_window_cmd.vim1952
-rw-r--r--src/testdir/test_window_id.vim142
-rw-r--r--src/testdir/test_windows_home.vim122
-rw-r--r--src/testdir/test_wnext.vim103
-rw-r--r--src/testdir/test_wordcount.vim106
-rw-r--r--src/testdir/test_writefile.vim980
-rw-r--r--src/testdir/test_xxd.vim404
-rw-r--r--src/testdir/testluaplugin/lua/testluaplugin/hello.lua7
-rw-r--r--src/testdir/testluaplugin/lua/testluaplugin/init.lua5
-rw-r--r--src/testdir/thread_util.py90
-rw-r--r--src/testdir/unix.vim13
-rw-r--r--src/testdir/view_util.vim117
-rw-r--r--src/testdir/vim9.vim275
-rw-r--r--src/testdir/vms.vim6
1248 files changed, 211817 insertions, 0 deletions
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
new file mode 100644
index 0000000..f198217
--- /dev/null
+++ b/src/testdir/Make_all.mak
@@ -0,0 +1,555 @@
+#
+# Common Makefile, defines the list of tests to run.
+#
+
+# Options for protecting the tests against undesirable interaction with the
+# environment
+NO_PLUGINS = --noplugin --not-a-term
+NO_INITS = -U NONE $(NO_PLUGINS)
+
+# File to delete when testing starts
+CLEANUP_FILES = test.log messages starttime
+
+# Tests for tiny build.
+SCRIPTS_TINY = \
+ test10 \
+ test20 \
+ test21 \
+ test22 \
+ test23 \
+ test24 \
+ test25 \
+ test26 \
+ test27
+
+SCRIPTS_TINY_OUT = \
+ test10.out \
+ test20.out \
+ test21.out \
+ test22.out \
+ test23.out \
+ test24.out \
+ test25.out \
+ test26.out \
+ test27.out
+
+# Tests for Vim9 script.
+TEST_VIM9 = \
+ test_vim9_assign \
+ test_vim9_builtin \
+ test_vim9_class \
+ test_vim9_cmd \
+ test_vim9_disassemble \
+ test_vim9_expr \
+ test_vim9_fails \
+ test_vim9_func \
+ test_vim9_import \
+ test_vim9_script
+
+TEST_VIM9_RES = \
+ test_vim9_assign.res \
+ test_vim9_builtin.res \
+ test_vim9_class.res \
+ test_vim9_cmd.res \
+ test_vim9_disassemble.res \
+ test_vim9_expr.res \
+ test_vim9_fails.res \
+ test_vim9_func.res \
+ test_vim9_import.res \
+ test_vim9_script.res
+
+# Benchmark scripts.
+SCRIPTS_BENCH = test_bench_regexp.res
+
+# Individual tests, including the ones part of test_alot.
+# Please keep sorted up to test_alot.
+NEW_TESTS = \
+ test_arabic \
+ test_arglist \
+ test_assert \
+ test_autochdir \
+ test_autocmd \
+ test_autoload \
+ test_backspace_opt \
+ test_backup \
+ test_balloon \
+ test_balloon_gui \
+ test_behave \
+ test_blob \
+ test_blockedit \
+ test_breakindent \
+ test_buffer \
+ test_bufline \
+ test_bufwintabinfo \
+ test_cd \
+ test_cdo \
+ test_changedtick \
+ test_changelist \
+ test_channel \
+ test_charsearch \
+ test_charsearch_utf8 \
+ test_checkpath \
+ test_cindent \
+ test_cjk_linebreak \
+ test_clientserver \
+ test_close_count \
+ test_cmd_lists \
+ test_cmdline \
+ test_cmdmods \
+ test_cmdwin \
+ test_codestyle \
+ test_command_count \
+ test_comments \
+ test_comparators \
+ test_compiler \
+ test_conceal \
+ test_const \
+ test_cpoptions \
+ test_crypt \
+ test_cscope \
+ test_cursor_func \
+ test_cursorline \
+ test_curswant \
+ test_debugger \
+ test_delete \
+ test_diffmode \
+ test_digraph \
+ test_display \
+ test_edit \
+ test_environ \
+ test_erasebackword \
+ test_escaped_glob \
+ test_eval_stuff \
+ test_ex_equal \
+ test_ex_mode \
+ test_ex_undo \
+ test_ex_z \
+ test_excmd \
+ test_exec_while_if \
+ test_execute_func \
+ test_exists \
+ test_exists_autocmd \
+ test_exit \
+ test_expand \
+ test_expand_dllpath \
+ test_expand_func \
+ test_expr \
+ test_expr_utf8 \
+ test_file_perm \
+ test_file_size \
+ test_filechanged \
+ test_fileformat \
+ test_filetype \
+ test_filter_cmd \
+ test_filter_map \
+ test_find_complete \
+ test_findfile \
+ test_fixeol \
+ test_flatten \
+ test_float_func \
+ test_fnameescape \
+ test_fnamemodify \
+ test_fold \
+ test_functions \
+ test_function_lists \
+ test_ga \
+ test_getcwd \
+ test_getvar \
+ test_gf \
+ test_glob2regpat \
+ test_global \
+ test_gn \
+ test_goto \
+ test_gui \
+ test_gui_init \
+ test_hardcopy \
+ test_help \
+ test_help_tagjump \
+ test_hide \
+ test_highlight \
+ test_history \
+ test_hlsearch \
+ test_iminsert \
+ test_increment \
+ test_increment_dbcs \
+ test_indent \
+ test_input \
+ test_ins_complete \
+ test_ins_complete_no_halt \
+ test_interrupt \
+ test_job_fails \
+ test_join \
+ test_json \
+ test_jumplist \
+ test_lambda \
+ test_langmap \
+ test_largefile \
+ test_let \
+ test_lineending \
+ test_lispindent \
+ test_listchars \
+ test_listdict \
+ test_listener \
+ test_listlbr \
+ test_listlbr_utf8 \
+ test_lua \
+ test_makeencoding \
+ test_man \
+ test_map_functions \
+ test_mapping \
+ test_marks \
+ test_match \
+ test_matchadd_conceal \
+ test_matchadd_conceal_utf8 \
+ test_matchfuzzy \
+ test_memory_usage \
+ test_menu \
+ test_messages \
+ test_method \
+ test_mksession \
+ test_mksession_utf8 \
+ test_modeless \
+ test_modeline \
+ test_move \
+ test_mswin_event \
+ test_mzscheme \
+ test_nested_function \
+ test_netbeans \
+ test_normal \
+ test_number \
+ test_options \
+ test_packadd \
+ test_partial \
+ test_paste \
+ test_perl \
+ test_plus_arg_edit \
+ test_popup \
+ test_popupwin \
+ test_popupwin_textprop \
+ test_preview \
+ test_profile \
+ test_prompt_buffer \
+ test_put \
+ test_python2 \
+ test_python3 \
+ test_pyx2 \
+ test_pyx3 \
+ test_quickfix \
+ test_quotestar \
+ test_random \
+ test_recover \
+ test_regex_char_classes \
+ test_regexp_latin \
+ test_regexp_utf8 \
+ test_registers \
+ test_reltime \
+ test_rename \
+ test_restricted \
+ test_retab \
+ test_ruby \
+ test_scriptnames \
+ test_scroll_opt \
+ test_scrollbind \
+ test_search \
+ test_search_stat \
+ test_searchpos \
+ test_selectmode \
+ test_set \
+ test_sha256 \
+ test_shell \
+ test_shift \
+ test_shortpathname \
+ test_signals \
+ test_signs \
+ test_sleep \
+ test_smartindent \
+ test_sort \
+ test_sound \
+ test_source \
+ test_source_utf8 \
+ test_spell \
+ test_spell_utf8 \
+ test_spellfile \
+ test_startup \
+ test_startup_utf8 \
+ test_stat \
+ test_statusline \
+ test_substitute \
+ test_suspend \
+ test_swap \
+ test_syn_attr \
+ test_syntax \
+ test_system \
+ test_tab \
+ test_tabline \
+ test_tabpage \
+ test_tagcase \
+ test_tagfunc \
+ test_tagjump \
+ test_taglist \
+ test_tcl \
+ test_termcodes \
+ test_termencoding \
+ test_terminal \
+ test_terminal2 \
+ test_terminal3 \
+ test_terminal_fail \
+ test_textformat \
+ test_textobjects \
+ test_textprop \
+ test_timers \
+ test_true_false \
+ test_trycatch \
+ test_undo \
+ test_unlet \
+ test_user_func \
+ test_usercommands \
+ test_utf8 \
+ test_utf8_comparisons \
+ test_vartabs \
+ test_version \
+ $(TEST_VIM9) \
+ test_viminfo \
+ test_vimscript \
+ test_virtualedit \
+ test_visual \
+ test_winbar \
+ test_winbuf_close \
+ test_window_cmd \
+ test_window_id \
+ test_windows_home \
+ test_wnext \
+ test_wordcount \
+ test_writefile \
+ test_xxd \
+ test_alot_latin \
+ test_alot_utf8 \
+ test_alot
+
+# Test targets that use runtest.vim.
+# Keep test_alot*.res as the last one, sort the others.
+# test_largefile.res is omitted, it uses too much resources to run on CI.
+NEW_TESTS_RES = \
+ test_arabic.res \
+ test_arglist.res \
+ test_assert.res \
+ test_autochdir.res \
+ test_autocmd.res \
+ test_autoload.res \
+ test_backspace_opt.res \
+ test_balloon.res \
+ test_balloon_gui.res \
+ test_blob.res \
+ test_blockedit.res \
+ test_breakindent.res \
+ test_buffer.res \
+ test_bufline.res \
+ test_bufwintabinfo.res \
+ test_cd.res \
+ test_cdo.res \
+ test_changedtick.res \
+ test_changelist.res \
+ test_channel.res \
+ test_charsearch.res \
+ test_checkpath.res \
+ test_cindent.res \
+ test_cjk_linebreak.res \
+ test_clientserver.res \
+ test_close_count.res \
+ test_cmd_lists.res \
+ test_cmdline.res \
+ test_cmdmods.res \
+ test_cmdwin.res \
+ test_codestyle.res \
+ test_command_count.res \
+ test_comments.res \
+ test_comparators.res \
+ test_conceal.res \
+ test_const.res \
+ test_cpoptions.res \
+ test_crypt.res \
+ test_cscope.res \
+ test_cursor_func.res \
+ test_cursorline.res \
+ test_curswant.res \
+ test_debugger.res \
+ test_delete.res \
+ test_diffmode.res \
+ test_digraph.res \
+ test_display.res \
+ test_edit.res \
+ test_environ.res \
+ test_erasebackword.res \
+ test_escaped_glob.res \
+ test_eval_stuff.res \
+ test_excmd.res \
+ test_exec_while_if.res \
+ test_execute_func.res \
+ test_exists.res \
+ test_exists_autocmd.res \
+ test_exit.res \
+ test_expr.res \
+ test_file_size.res \
+ test_filechanged.res \
+ test_fileformat.res \
+ test_filetype.res \
+ test_filter_cmd.res \
+ test_filter_map.res \
+ test_find_complete.res \
+ test_findfile.res \
+ test_fixeol.res \
+ test_flatten.res \
+ test_float_func.res \
+ test_fnameescape.res \
+ test_fold.res \
+ test_functions.res \
+ test_function_lists.res \
+ test_getcwd.res \
+ test_getvar.res \
+ test_gf.res \
+ test_gn.res \
+ test_goto.res \
+ test_gui.res \
+ test_gui_init.res \
+ test_hardcopy.res \
+ test_help.res \
+ test_help_tagjump.res \
+ test_hide.res \
+ test_highlight.res \
+ test_history.res \
+ test_hlsearch.res \
+ test_iminsert.res \
+ test_increment.res \
+ test_increment_dbcs.res \
+ test_indent.res \
+ test_input.res \
+ test_ins_complete.res \
+ test_ins_complete_no_halt.res \
+ test_interrupt.res \
+ test_job_fails.res \
+ test_join.res \
+ test_json.res \
+ test_jumplist.res \
+ test_lambda.res \
+ test_langmap.res \
+ test_let.res \
+ test_lineending.res \
+ test_lispindent.res \
+ test_listchars.res \
+ test_listdict.res \
+ test_listener.res \
+ test_listlbr.res \
+ test_listlbr_utf8.res \
+ test_lua.res \
+ test_makeencoding.res \
+ test_man.res \
+ test_map_functions.res \
+ test_mapping.res \
+ test_marks.res \
+ test_match.res \
+ test_matchadd_conceal.res \
+ test_matchadd_conceal_utf8.res \
+ test_matchfuzzy.res \
+ test_memory_usage.res \
+ test_menu.res \
+ test_messages.res \
+ test_method.res \
+ test_mksession.res \
+ test_modeless.res \
+ test_modeline.res \
+ test_mswin_event.res \
+ test_mzscheme.res \
+ test_nested_function.res \
+ test_netbeans.res \
+ test_normal.res \
+ test_number.res \
+ test_options.res \
+ test_packadd.res \
+ test_partial.res \
+ test_paste.res \
+ test_perl.res \
+ test_plus_arg_edit.res \
+ test_popup.res \
+ test_popupwin.res \
+ test_popupwin_textprop.res \
+ test_preview.res \
+ test_profile.res \
+ test_prompt_buffer.res \
+ test_python2.res \
+ test_python3.res \
+ test_pyx2.res \
+ test_pyx3.res \
+ test_quickfix.res \
+ test_quotestar.res \
+ test_random.res \
+ test_recover.res \
+ test_regex_char_classes.res \
+ test_registers.res \
+ test_rename.res \
+ test_restricted.res \
+ test_retab.res \
+ test_ruby.res \
+ test_scriptnames.res \
+ test_scroll_opt.res \
+ test_scrollbind.res \
+ test_search.res \
+ test_search_stat.res \
+ test_selectmode.res \
+ test_shell.res \
+ test_shortpathname.res \
+ test_signals.res \
+ test_signs.res \
+ test_sleep.res \
+ test_smartindent.res \
+ test_sort.res \
+ test_sound.res \
+ test_source.res \
+ test_spell.res \
+ test_spell_utf8.res \
+ test_spellfile.res \
+ test_startup.res \
+ test_stat.res \
+ test_statusline.res \
+ test_substitute.res \
+ test_suspend.res \
+ test_swap.res \
+ test_syn_attr.res \
+ test_syntax.res \
+ test_system.res \
+ test_tab.res \
+ test_tabpage.res \
+ test_tagjump.res \
+ test_taglist.res \
+ test_tcl.res \
+ test_termcodes.res \
+ test_termencoding.res \
+ test_terminal.res \
+ test_terminal2.res \
+ test_terminal3.res \
+ test_terminal_fail.res \
+ test_textformat.res \
+ test_textobjects.res \
+ test_textprop.res \
+ test_timers.res \
+ test_true_false.res \
+ test_trycatch.res \
+ test_undo.res \
+ test_user_func.res \
+ test_usercommands.res \
+ test_vartabs.res \
+ $(TEST_VIM9_RES) \
+ test_viminfo.res \
+ test_vimscript.res \
+ test_virtualedit.res \
+ test_visual.res \
+ test_winbar.res \
+ test_winbuf_close.res \
+ test_window_cmd.res \
+ test_window_id.res \
+ test_windows_home.res \
+ test_wordcount.res \
+ test_writefile.res \
+ test_xxd.res \
+ test_alot_latin.res \
+ test_alot_utf8.res \
+ test_alot.res
diff --git a/src/testdir/Make_amiga.mak b/src/testdir/Make_amiga.mak
new file mode 100644
index 0000000..843ea1e
--- /dev/null
+++ b/src/testdir/Make_amiga.mak
@@ -0,0 +1,35 @@
+#
+# Makefile to run all tests for Vim, on Amiga
+#
+# Requires "rm", "csh" and "diff"!
+
+VIMPROG = /vim
+
+default: nongui
+
+include Make_all.mak
+
+SCRIPTS = $(SCRIPTS_TINY_OUT)
+
+.SUFFIXES: .in .out .res .vim
+
+nongui: /tmp $(SCRIPTS)
+ csh -c echo ALL DONE
+
+clean:
+ csh -c \rm -rf *.out Xdir1 Xfind XfakeHOME Xdotest test.ok viminfo
+
+.in.out:
+ copy $*.ok test.ok
+ $(VIMPROG) -u amiga.vim -U NONE --noplugin --not-a-term -s dotest.in $*.in
+ diff test.out $*.ok
+ rename test.out $*.out
+ -delete X#? ALL QUIET
+ -delete test.ok
+
+# Create a directory for temp files
+/tmp:
+ makedir /tmp
+
+# Manx requires all dependencies, but we stopped updating them.
+# Delete the .out file(s) to run test(s).
diff --git a/src/testdir/Make_dos.mak b/src/testdir/Make_dos.mak
new file mode 100644
index 0000000..7f9d6bf
--- /dev/null
+++ b/src/testdir/Make_dos.mak
@@ -0,0 +1,4 @@
+!message Make_dos.mak is deprecated. Use Make_mvc.mak instead.
+!message
+
+!include Make_mvc.mak
diff --git a/src/testdir/Make_ming.mak b/src/testdir/Make_ming.mak
new file mode 100644
index 0000000..2676111
--- /dev/null
+++ b/src/testdir/Make_ming.mak
@@ -0,0 +1,168 @@
+#
+# Makefile to run all tests for Vim, on Dos-like machines
+# with sh.exe or zsh.exe in the path or not.
+#
+# Author: Bill McCarthy
+#
+# Requires a set of Unix tools: echo, diff, etc.
+
+# Don't use unix-like shell.
+SHELL = cmd.exe
+
+DEL = del
+DELDIR = rd /s /q
+MV = move /y
+CP = copy /y
+CAT = type
+
+VIMPROG = ..\\vim
+
+default: nongui
+
+include Make_all.mak
+
+# Explicit dependencies.
+test_options.res test_alot.res: opt_test.vim
+
+TEST_OUTFILES = $(SCRIPTS_TINY_OUT)
+DOSTMP = dostmp
+# Keep $(DOSTMP)/*.in
+.PRECIOUS: $(patsubst %.out, $(DOSTMP)/%.in, $(TEST_OUTFILES))
+
+.SUFFIXES: .in .out .res .vim
+
+# Add --gui-dialog-file to avoid getting stuck in a dialog.
+COMMON_ARGS = $(NO_INITS) --gui-dialog-file guidialog
+
+nongui: nolog tinytests newtests report
+
+gui: nolog tinytests newtests report
+
+tiny: nolog tinytests report
+
+benchmark: $(SCRIPTS_BENCH)
+
+report:
+ @rem without the +eval feature test_result.log is a copy of test.log
+ @if exist test.log ( copy /y test.log test_result.log > nul ) \
+ else ( echo No failures reported > test_result.log )
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S summarize.vim messages
+ -if exist starttime del starttime
+ @echo.
+ @echo Test results:
+ @cmd /c type test_result.log
+ @if exist test.log ( echo TEST FAILURE & exit /b 1 ) \
+ else ( echo ALL DONE )
+
+
+# Execute an individual new style test, e.g.:
+# mingw32-make -f Make_ming.mak test_largefile
+$(NEW_TESTS):
+ -if exist $@.res del $@.res
+ -if exist test.log del test.log
+ -if exist messages del messages
+ -if exist starttime del starttime
+ @$(MAKE) -f Make_ming.mak $@.res VIMPROG=$(VIMPROG) --no-print-directory
+ @type messages
+ @if exist test.log exit 1
+
+
+# Delete files that may interfere with running tests. This includes some files
+# that may result from working on the tests, not only from running them.
+clean:
+ -@if exist *.out $(DEL) *.out
+ -@if exist *.failed $(DEL) *.failed
+ -@if exist *.res $(DEL) *.res
+ -@if exist $(DOSTMP) rd /s /q $(DOSTMP)
+ -@if exist test.in $(DEL) test.in
+ -@if exist test.ok $(DEL) test.ok
+ -@if exist Xdir1 $(DELDIR) Xdir1
+ -@if exist Xfind $(DELDIR) Xfind
+ -@if exist XfakeHOME $(DELDIR) XfakeHOME
+ -@if exist X* $(DEL) X*
+ -@for /d %%i in (X*) do @rd /s/q %%i
+ -@if exist viminfo $(DEL) viminfo
+ -@if exist test.log $(DEL) test.log
+ -@if exist test_result.log del test_result.log
+ -@if exist messages $(DEL) messages
+ -@if exist starttime $(DEL) starttime
+ -@if exist benchmark.out del benchmark.out
+ -@if exist opt_test.vim $(DEL) opt_test.vim
+ -@if exist guidialog $(DEL) guidialog
+ -@if exist guidialogfile $(DEL) guidialogfile
+
+nolog:
+ -@if exist test.log $(DEL) test.log
+ -@if exist test_result.log del test_result.log
+ -@if exist messages $(DEL) messages
+ -@if exist starttime $(DEL) starttime
+
+
+# Tiny tests. Works even without the +eval feature.
+tinytests: $(SCRIPTS_TINY_OUT)
+
+# Copy the input files to dostmp, changing the fileformat to dos.
+$(DOSTMP)/%.in : %.in
+ if not exist $(DOSTMP)\nul mkdir $(DOSTMP)
+ if exist $(DOSTMP)\$< $(DEL) $(DOSTMP)\$<
+ $(VIMPROG) -u dos.vim $(COMMON_ARGS) "+set ff=dos|f $@|wq" $<
+
+# For each input file dostmp/test99.in run the tests.
+# This moves test99.in to test99.in.bak temporarily.
+%.out : $(DOSTMP)/%.in
+ -@if exist test.out $(DEL) test.out
+ -@if exist $(DOSTMP)\$@ $(DEL) $(DOSTMP)\$@
+ $(MV) $(notdir $<) $(notdir $<).bak > NUL
+ $(CP) $(DOSTMP)\$(notdir $<) $(notdir $<) > NUL
+ $(CP) $(basename $@).ok test.ok > NUL
+ $(VIMPROG) -u dos.vim $(COMMON_ARGS) -s dotest.in $(notdir $<)
+ -@if exist test.out $(MV) test.out $(DOSTMP)\$@ > NUL
+ -@if exist $(notdir $<).bak $(MV) $(notdir $<).bak $(notdir $<) > NUL
+ -@if exist test.ok $(DEL) test.ok
+ -@if exist Xdir1 $(DELDIR) /s /q Xdir1
+ -@if exist Xfind $(DELDIR) Xfind
+ -@if exist XfakeHOME $(DELDIR) XfakeHOME
+ -@del X*
+ -@if exist viminfo del viminfo
+ $(VIMPROG) -u dos.vim $(COMMON_ARGS) "+set ff=unix|f test.out|wq" \
+ $(DOSTMP)\$@
+ @diff test.out $(basename $@).ok & if errorlevel 1 \
+ ( $(MV) test.out $(basename $@).failed > NUL \
+ & del $(DOSTMP)\$@ \
+ & echo $(basename $@) FAILED >> test.log ) \
+ else ( $(MV) test.out $(basename $@).out > NUL )
+
+
+# New style of tests uses Vim script with assert calls. These are easier
+# to write and a lot easier to read and debug.
+# Limitation: Only works with the +eval feature.
+
+newtests: newtestssilent
+ @if exist messages type messages
+
+newtestssilent: $(NEW_TESTS_RES)
+
+.vim.res:
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S runtest.vim $*.vim
+ @$(DEL) vimcmd
+
+test_gui.res: test_gui.vim
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S runtest.vim $<
+ @$(DEL) vimcmd
+
+test_gui_init.res: test_gui_init.vim
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u gui_preinit.vim -U gui_init.vim $(NO_PLUGINS) -S runtest.vim $<
+ @$(DEL) vimcmd
+
+opt_test.vim: ../optiondefs.h gen_opt_test.vim
+ $(VIMPROG) -u NONE -S gen_opt_test.vim --noplugin --not-a-term ../optiondefs.h
+
+test_bench_regexp.res: test_bench_regexp.vim
+ -$(DEL) benchmark.out
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S runtest.vim $*.vim
+ @$(DEL) vimcmd
+ $(CAT) benchmark.out
diff --git a/src/testdir/Make_mvc.mak b/src/testdir/Make_mvc.mak
new file mode 100644
index 0000000..318cd4a
--- /dev/null
+++ b/src/testdir/Make_mvc.mak
@@ -0,0 +1,162 @@
+#
+# Makefile to run all tests for Vim, on Dos-like machines.
+#
+# Requires a set of Unix tools: echo, diff, etc.
+
+# Testing may be done with a debug build
+!IF EXIST(..\\vimd.exe) && !EXIST(..\\vim.exe)
+VIMPROG = ..\\vimd
+!ELSE
+VIMPROG = ..\\vim
+!ENDIF
+
+
+default: nongui
+
+!include Make_all.mak
+
+# Explicit dependencies.
+test_options.res test_alot.res: opt_test.vim
+
+TEST_OUTFILES = $(SCRIPTS_TINY_OUT)
+DOSTMP = dostmp
+DOSTMP_OUTFILES = $(TEST_OUTFILES:test=dostmp\test)
+DOSTMP_INFILES = $(DOSTMP_OUTFILES:.out=.in)
+
+.SUFFIXES: .in .out .res .vim
+
+# Add --gui-dialog-file to avoid getting stuck in a dialog.
+COMMON_ARGS = $(NO_INITS) --gui-dialog-file guidialog
+
+nongui: nolog tinytests newtests report
+
+gui: nolog tinytests newtests report
+
+tiny: nolog tinytests report
+
+benchmark: $(SCRIPTS_BENCH)
+
+report:
+ @rem without the +eval feature test_result.log is a copy of test.log
+ @if exist test.log ( copy /y test.log test_result.log > nul ) \
+ else ( echo No failures reported > test_result.log )
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S summarize.vim messages
+ -if exist starttime del starttime
+ @echo.
+ @echo Test results:
+ @cmd /c type test_result.log
+ @if exist test.log ( echo TEST FAILURE & exit /b 1 ) \
+ else ( echo ALL DONE )
+
+
+# Execute an individual new style test, e.g.:
+# nmake -f Make_mvc.mak test_largefile
+$(NEW_TESTS):
+ -if exist $@.res del $@.res
+ -if exist test.log del test.log
+ -if exist messages del messages
+ -if exist starttime del starttime
+ @$(MAKE) -nologo -f Make_mvc.mak $@.res VIMPROG=$(VIMPROG)
+ @type messages
+ @if exist test.log exit 1
+
+
+# Delete files that may interfere with running tests. This includes some files
+# that may result from working on the tests, not only from running them.
+clean:
+ -if exist *.out del *.out
+ -if exist *.failed del *.failed
+ -if exist *.res del *.res
+ -if exist $(DOSTMP) rd /s /q $(DOSTMP)
+ -if exist test.in del test.in
+ -if exist test.ok del test.ok
+ -if exist Xdir1 rd /s /q Xdir1
+ -if exist Xfind rd /s /q Xfind
+ -if exist XfakeHOME rd /s /q XfakeHOME
+ -if exist X* del X*
+ -for /d %i in (X*) do @rd /s/q %i
+ -if exist viminfo del viminfo
+ -if exist test.log del test.log
+ -if exist test_result.log del test_result.log
+ -if exist messages del messages
+ -if exist starttime del starttime
+ -if exist benchmark.out del benchmark.out
+ -if exist opt_test.vim del opt_test.vim
+ -if exist guidialog del guidialog
+ -if exist guidialogfile del guidialogfile
+
+nolog:
+ -if exist test.log del test.log
+ -if exist test_result.log del test_result.log
+ -if exist messages del messages
+ -if exist starttime del starttime
+
+
+# Tiny tests. Works even without the +eval feature.
+tinytests: $(SCRIPTS_TINY_OUT)
+
+# Copy the input files to dostmp, changing the fileformat to dos.
+$(DOSTMP_INFILES): $(*B).in
+ if not exist $(DOSTMP)\NUL md $(DOSTMP)
+ if exist $@ del $@
+ $(VIMPROG) -u dos.vim $(COMMON_ARGS) "+set ff=dos|f $@|wq" $(*B).in
+
+# For each input file dostmp/test99.in run the tests.
+# This moves test99.in to test99.in.bak temporarily.
+$(TEST_OUTFILES): $(DOSTMP)\$(*B).in
+ -@if exist test.out DEL test.out
+ -@if exist $(DOSTMP)\$(*B).out DEL $(DOSTMP)\$(*B).out
+ move $(*B).in $(*B).in.bak > nul
+ copy $(DOSTMP)\$(*B).in $(*B).in > nul
+ copy $(*B).ok test.ok > nul
+ $(VIMPROG) -u dos.vim $(COMMON_ARGS) -s dotest.in $(*B).in
+ -@if exist test.out MOVE /y test.out $(DOSTMP)\$(*B).out > nul
+ -@if exist $(*B).in.bak move /y $(*B).in.bak $(*B).in > nul
+ -@if exist test.ok del test.ok
+ -@if exist Xdir1 rd /s /q Xdir1
+ -@if exist Xfind rd /s /q Xfind
+ -@if exist XfakeHOME rd /s /q XfakeHOME
+ -@del X*
+ -@if exist viminfo del viminfo
+ $(VIMPROG) -u dos.vim $(COMMON_ARGS) "+set ff=unix|f test.out|wq" \
+ $(DOSTMP)\$(*B).out
+ @diff test.out $*.ok & if errorlevel 1 \
+ ( move /y test.out $*.failed > nul \
+ & del $(DOSTMP)\$(*B).out \
+ & echo $* FAILED >> test.log ) \
+ else ( move /y test.out $*.out > nul )
+
+
+# New style of tests uses Vim script with assert calls. These are easier
+# to write and a lot easier to read and debug.
+# Limitation: Only works with the +eval feature.
+
+newtests: newtestssilent
+ @if exist messages type messages
+
+newtestssilent: $(NEW_TESTS_RES)
+
+.vim.res:
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S runtest.vim $*.vim
+ @del vimcmd
+
+test_gui.res: test_gui.vim
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S runtest.vim $*.vim
+ @del vimcmd
+
+test_gui_init.res: test_gui_init.vim
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u gui_preinit.vim -U gui_init.vim $(NO_PLUGINS) -S runtest.vim $*.vim
+ @del vimcmd
+
+opt_test.vim: ../optiondefs.h gen_opt_test.vim
+ $(VIMPROG) -u NONE -S gen_opt_test.vim --noplugin --not-a-term ../optiondefs.h
+
+test_bench_regexp.res: test_bench_regexp.vim
+ -if exist benchmark.out del benchmark.out
+ @echo $(VIMPROG) > vimcmd
+ $(VIMPROG) -u NONE $(COMMON_ARGS) -S runtest.vim $*.vim
+ @del vimcmd
+ @IF EXIST benchmark.out ( type benchmark.out )
diff --git a/src/testdir/Make_vms.mms b/src/testdir/Make_vms.mms
new file mode 100644
index 0000000..b6c6fab
--- /dev/null
+++ b/src/testdir/Make_vms.mms
@@ -0,0 +1,108 @@
+#
+# Makefile to run all tests for Vim on VMS
+#
+# Authors: Zoltan Arpadffy, <arpadffy@polarhome.com>
+# Sandor Kopanyi, <sandor.kopanyi@mailbox.hu>
+#
+# Last change: 2020 Aug 14
+#
+# This has been tested on VMS 6.2 to 8.3 on DEC Alpha, VAX and IA64.
+# Edit the lines in the Configuration section below to select.
+#
+# Execute with:
+# mms/descrip=Make_vms.mms
+# Cleanup with:
+# mms/descrip=Make_vms.mms clean
+#
+# Make files are MMK compatible.
+#
+# NOTE: You can run this script just in X/Window environment. It will
+# create a new terminals, therefore you have to set up your DISPLAY
+# logical. More info in VMS documentation or with: help set disp.
+#
+#######################################################################
+# Configuration section.
+#######################################################################
+
+# Uncomment if you want tests in GUI mode. Terminal mode is default.
+# WANT_GUI = YES
+
+#######################################################################
+# End of configuration section.
+#
+# Please, do not change anything below without programming experience.
+#######################################################################
+
+VIMPROG = <->vim.exe
+
+.SUFFIXES : .out .in
+
+SCRIPT = test20.out test21.out test22.out test23.out test24.out \
+ test25.out test26.out test27.out \
+ test77a.out
+
+.IFDEF WANT_GUI
+GUI_OPTION = -g
+.ENDIF
+
+.in.out :
+ -@ !clean up before doing the test
+ -@ if "''F$SEARCH("test.out.*")'" .NES. "" then delete/noconfirm/nolog test.out.*
+ -@ if "''F$SEARCH("$*.out.*")'" .NES. "" then delete/noconfirm/nolog $*.out.*
+ -@ ! define TMP if not set - some tests use it
+ -@ if "''F$TRNLNM("TMP")'" .EQS. "" then define/nolog TMP []
+ -@ write sys$output " "
+ -@ write sys$output "-----------------------------------------------"
+ -@ write sys$output " "$*" "
+ -@ write sys$output "-----------------------------------------------"
+ -@ !run the test
+ -@ create/term/wait/nodetach mcr $(VIMPROG) $(GUI_OPTION) -u vms.vim --noplugin -s dotest.in $*.in
+ -@ !analyse the result
+ -@ directory /size/date test.out
+ -@ if "''F$SEARCH("test.out.*")'" .NES. "" then rename/nolog test.out $*.out
+ -@ if "''F$SEARCH("$*.out.*")'" .NES. "" then differences /par $*.out $*.ok;
+ -@ !clean up after the test
+ -@ if "''F$SEARCH("Xdotest.*")'" .NES. "" then delete/noconfirm/nolog Xdotest.*.*
+ -@ if "''F$SEARCH("Xtest.*")'" .NES. "" then delete/noconfirm/nolog Xtest.*.*
+
+all : clean nolog $(START_WITH) $(SCRIPT) nolog
+ -@ write sys$output " "
+ -@ write sys$output "-----------------------------------------------"
+ -@ write sys$output " All done"
+ -@ write sys$output "-----------------------------------------------"
+ -@ deassign sys$output
+ -@ delete/noconfirm/nolog x*.*.*
+ -@ type test.log
+
+nolog :
+ -@ define sys$output test.log
+ -@ write sys$output "-----------------------------------------------"
+ -@ write sys$output " Standard VIM test cases"
+ -@ write sys$output "-----------------------------------------------"
+ -@ write sys$output " OpenVMS version: ''F$GETSYI("VERSION")'"
+ -@ write sys$output " Vim version:"
+ -@ mcr $(VIMPROG) --version
+ -@ write sys$output " Test date:"
+ -@ show time
+ -@ write sys$output "-----------------------------------------------"
+ -@ write sys$output " Test results:"
+ -@ write sys$output "-----------------------------------------------"
+ -@ write sys$output "MAKE_VMS.MMS options:"
+ -@ write sys$output " WANT_GUI = ""$(WANT_GUI)"" "
+ -@ write sys$output "Default vimrc file is VMS.VIM:"
+ -@ write sys$output "-----------------------------------------------"
+ -@ type VMS.VIM
+
+clean :
+ -@ if "''F$SEARCH("*.out")'" .NES. "" then delete/noconfirm/nolog *.out.*
+ -@ if "''F$SEARCH("test.log")'" .NES. "" then delete/noconfirm/nolog test.log.*
+ -@ if "''F$SEARCH("test.ok")'" .NES. "" then delete/noconfirm/nolog test.ok.*
+ -@ if "''F$SEARCH("Xdotest.*")'" .NES. "" then delete/noconfirm/nolog Xdotest.*.*
+ -@ if "''F$SEARCH("Xtest*.*")'" .NES. "" then delete/noconfirm/nolog Xtest*.*.*
+ -@ if "''F$SEARCH("XX*.*")'" .NES. "" then delete/noconfirm/nolog XX*.*.*
+ -@ if "''F$SEARCH("_un_*.*")'" .NES. "" then delete/noconfirm/nolog _un_*.*.*
+ -@ if "''F$SEARCH("*.*_sw*")'" .NES. "" then delete/noconfirm/nolog *.*_sw*.*
+ -@ if "''F$SEARCH("*.failed")'" .NES. "" then delete/noconfirm/nolog *.failed.*
+ -@ if "''F$SEARCH("*.rej")'" .NES. "" then delete/noconfirm/nolog *.rej.*
+ -@ if "''F$SEARCH("viminfo.*")'" .NES. "" then delete/noconfirm/nolog viminfo.*.*
+
diff --git a/src/testdir/Makefile b/src/testdir/Makefile
new file mode 100644
index 0000000..b69d935
--- /dev/null
+++ b/src/testdir/Makefile
@@ -0,0 +1,176 @@
+#
+# Makefile to run all tests for Vim
+#
+
+# Use console or GUI.
+VIMPROG = ../vim
+XXDPROG = ../xxd/xxd
+# VIMPROG = ../gvim
+
+SCRIPTSOURCE = ../../runtime
+
+# Comment out this line to see the verbose output of tests.
+#
+# Catches SwapExists to avoid hanging at the ATTENTION prompt.
+REDIR_TEST_TO_NULL = --cmd 'au SwapExists * let v:swapchoice = "e"' | LC_ALL=C LANG=C LANGUAGE=C awk '/Executing Test_/{match($$0, "([0-9][0-9]:[0-9][0-9] *)?Executing Test_[^\\)]*\\)"); print substr($$0, RSTART, RLENGTH) "\r"; fflush()}'
+
+# Uncomment this line to use valgrind for memory leaks and extra warnings.
+# The output goes into a file "valgrind.testN"
+# Vim should be compiled with EXITFREE to avoid false warnings.
+# This will make testing about 10 times as slow.
+# VALGRIND = valgrind --tool=memcheck --leak-check=yes --num-callers=35 --log-file=valgrind.$*
+
+# To execute one test, e.g. in gdb, use something like this:
+# run -f -u unix.vim --gui-dialog-file guidialog -U NONE --noplugin --not-a-term -S runtest.vim test_name.vim
+
+default: nongui
+
+# The list of tests is common to all systems.
+# This defines SCRIPTS_TINY_OUT, NEW_TESTS and NEW_TESTS_RES.
+include Make_all.mak
+
+# Explicit dependencies.
+test_options.res test_alot.res: opt_test.vim
+
+.SUFFIXES: .in .out .res .vim
+
+nongui: nolog tinytests newtests report
+
+gui: nolog tinytests newtests report
+
+tiny: nolog tinytests report
+
+benchmark: $(SCRIPTS_BENCH)
+
+report:
+ @# without the +eval feature test_result.log is a copy of test.log
+ @/bin/sh -c "if test -f test.log; \
+ then cp test.log test_result.log; \
+ else echo No failures reported > test_result.log; \
+ fi"
+ $(VIMPROG) -u NONE $(NO_INITS) -S summarize.vim messages $(REDIR_TEST_TO_NULL)
+ @rm -f starttime
+ @echo
+ @echo 'Test results:'
+ @cat test_result.log
+ @/bin/sh -c "if test -f test.log; \
+ then echo TEST FAILURE; exit 1; \
+ else echo ALL DONE; \
+ fi"
+
+$(SCRIPTS_TINY_OUT) $(NEW_TESTS_RES): $(VIMPROG)
+
+
+# Execute an individual new style test, e.g.:
+# make test_largefile
+$(NEW_TESTS):
+ rm -f $@.res $(CLEANUP_FILES)
+ @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res VIMPROG=$(VIMPROG) XXDPROG=$(XXDPROG) SCRIPTSOURCE=$(SCRIPTSOURCE)
+ @cat messages
+ @if test -f test.log; then \
+ exit 1; \
+ fi
+
+# Run only tests specific for Vim9 script
+test_vim9:
+ rm -f test_vim9_*.res $(CLEANUP_FILES)
+ @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $(TEST_VIM9_RES) VIMPROG=$(VIMPROG) XXDPROG=$(XXDPROG) SCRIPTSOURCE=$(SCRIPTSOURCE)
+ @cat messages
+ @rm -f starttime
+ @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile report VIMPROG=$(VIMPROG) XXDPROG=$(XXDPROG) SCRIPTSOURCE=$(SCRIPTSOURCE)
+ @if test -f test.log; then \
+ exit 1; \
+ fi
+
+RM_ON_RUN = test.out X* viminfo
+RM_ON_START = test.ok benchmark.out
+RUN_VIM = VIMRUNTIME=$(SCRIPTSOURCE) $(VALGRIND) $(VIMPROG) -f $(GUI_FLAG) -u unix.vim $(NO_INITS) -s dotest.in
+
+# Delete files that may interfere with running tests. This includes some files
+# that may result from working on the tests, not only from running them.
+clean:
+ -rm -rf *.out *.failed *.res *.rej *.orig XfakeHOME Xdir1 Xfind
+ -rm -f opt_test.vim test_result.log $(CLEANUP_FILES)
+ -rm -rf $(RM_ON_RUN) $(RM_ON_START)
+ -rm -f valgrind.*
+ -rm -f asan.*
+ -rm -f guidialog guidialogfile
+
+# Delete the files produced by benchmarking, so they can run again.
+benchmarkclean:
+ rm -f $(SCRIPTS_BENCH)
+
+nolog:
+ -rm -f test_result.log $(CLEANUP_FILES)
+
+
+# Tiny tests. Works even without the +eval feature.
+tinytests: $(SCRIPTS_TINY_OUT)
+
+.in.out:
+ -rm -rf $*.failed test.ok $(RM_ON_RUN)
+ cp $*.ok test.ok
+ @# Sleep a moment to avoid that the xterm title is messed up.
+ @# 200 msec is sufficient, but only modern sleep supports a fraction of
+ @# a second, fall back to a second if it fails.
+ @-/bin/sh -c "sleep .2 > /dev/null 2>&1 || sleep 1"
+ $(RUN_VIM) $*.in $(REDIR_TEST_TO_NULL)
+
+ @# Check if the test.out file matches test.ok.
+ @/bin/sh -c "if test -f test.out; then \
+ if diff test.out $*.ok; \
+ then mv -f test.out $*.out; \
+ else echo $* FAILED >>test.log; mv -f test.out $*.failed; \
+ fi \
+ else echo $* NO OUTPUT >>test.log; \
+ fi"
+ @/bin/sh -c "if test -f valgrind; then\
+ mv -f valgrind valgrind.$*; \
+ fi"
+ -rm -rf X* test.ok viminfo
+
+
+# New style of tests uses Vim script with assert calls. These are easier
+# to write and a lot easier to read and debug.
+# Limitation: Only works with the +eval feature.
+# Add --gui-dialog-file to avoid getting stuck in a dialog.
+RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE) $(VALGRIND) $(VIMPROG) -f $(GUI_FLAG) -u unix.vim --gui-dialog-file guidialog
+
+newtests: newtestssilent
+ @/bin/sh -c "if test -f messages; then cat messages; fi"
+
+newtestssilent: $(NEW_TESTS_RES)
+
+
+.vim.res:
+ @echo "$(VIMPROG)" > vimcmd
+ @echo "$(RUN_VIMTEST)" >> vimcmd
+ $(RUN_VIMTEST) $(NO_INITS) -S runtest.vim $*.vim $(REDIR_TEST_TO_NULL)
+ @rm vimcmd
+
+test_gui.res: test_gui.vim
+ @echo "$(VIMPROG)" > vimcmd
+ @echo "$(RUN_GVIMTEST)" >> vimcmd
+ $(RUN_VIMTEST) -u NONE $(NO_INITS) -S runtest.vim $<
+ @rm vimcmd
+
+test_gui_init.res: test_gui_init.vim
+ @echo "$(VIMPROG)" > vimcmd
+ @echo "$(RUN_GVIMTEST_WITH_GVIMRC)" >> vimcmd
+ $(RUN_VIMTEST) -u gui_preinit.vim -U gui_init.vim $(NO_PLUGINS) -S runtest.vim $<
+ @rm vimcmd
+
+opt_test.vim: ../optiondefs.h gen_opt_test.vim
+ $(VIMPROG) -u NONE -S gen_opt_test.vim --noplugin --not-a-term ../optiondefs.h
+
+test_xxd.res:
+ XXD=$(XXDPROG); export XXD; $(RUN_VIMTEST) $(NO_INITS) -S runtest.vim test_xxd.vim
+
+test_bench_regexp.res: test_bench_regexp.vim
+ -rm -rf benchmark.out $(RM_ON_RUN)
+ @# Sleep a moment to avoid that the xterm title is messed up.
+ @# 200 msec is sufficient, but only modern sleep supports a fraction of
+ @# a second, fall back to a second if it fails.
+ @-/bin/sh -c "sleep .2 > /dev/null 2>&1 || sleep 1"
+ $(RUN_VIMTEST) $(NO_INITS) -S runtest.vim $*.vim $(REDIR_TEST_TO_NULL)
+ @/bin/sh -c "if test -f benchmark.out; then cat benchmark.out; fi"
diff --git a/src/testdir/README.txt b/src/testdir/README.txt
new file mode 100644
index 0000000..f72bdbf
--- /dev/null
+++ b/src/testdir/README.txt
@@ -0,0 +1,121 @@
+This directory contains tests for various Vim features.
+For testing an indent script see runtime/indent/testdir/README.txt.
+
+If it makes sense, add a new test method to an already existing file. You may
+want to separate it from other tests with comment lines.
+
+TO ADD A NEW STYLE TEST:
+
+1) Create a test_<subject>.vim file.
+2) Add test_<subject>.res to NEW_TESTS_RES in Make_all.mak in alphabetical
+ order.
+3) Also add an entry "test_<subject>" to NEW_TESTS in Make_all.mak.
+4) Use make test_<subject> to run a single test.
+
+At 2), instead of running the test separately, it can be included in
+"test_alot". Do this for quick tests without side effects. The test runs a
+bit faster, because Vim doesn't have to be started, one Vim instance runs many
+tests.
+
+At 4), to run a test in GUI, add "GUI_FLAG=-g" to the make command.
+
+
+What you can use (see test_assert.vim for an example):
+
+- Call assert_equal(), assert_true(), assert_false(), etc.
+
+- Use assert_fails() to check for expected errors.
+
+- Use try/catch to avoid an exception aborts the test.
+
+- Use test_alloc_fail() to have memory allocation fail. This makes it possible
+ to check memory allocation failures are handled gracefully. You need to
+ change the source code to add an ID to the allocation. Add a new one to
+ alloc_id_T, before aid_last.
+
+- Use test_override() to make Vim behave differently, e.g. if char_avail()
+ must return FALSE for a while. E.g. to trigger the CursorMovedI autocommand
+ event. See test_cursor_func.vim for an example.
+
+- If the bug that is being tested isn't fixed yet, you can throw an exception
+ with "Skipped" so that it's clear this still needs work. E.g.: throw
+ "Skipped: Bug with <c-e> and popupmenu not fixed yet"
+
+- The following environment variables are recognized and can be set to
+ influence the behavior of the test suite (see runtest.vim for details)
+
+ - $TEST_MAY_FAIL=Test_channel_one - ignore those failing tests
+ - $TEST_FILTER=Test_channel - only run test that match this pattern
+ - $TEST_SKIP_PAT=Test_channel - skip tests that match this pattern
+ - $TEST_NO_RETRY=yes - do not try to re-run failing tests
+ You can also set them in Vim:
+ :let $TEST_MAY_FAIL = 'Test_channel_one'
+ :let $TEST_FILTER = '_set_mode'
+ :let $TEST_SKIP_PAT = 'Test_loop_forever'
+ :let $TEST_NO_RETRY = 'yes'
+ Use an empty string to revert, e.g.:
+ :let $TEST_FILTER = ''
+
+- See the start of runtest.vim for more help.
+
+
+TO ADD A SCREEN DUMP TEST:
+
+Mostly the same as writing a new style test. Additionally, see help on
+"terminal-dumptest". Put the reference dump in "dumps/Test_func_name.dump".
+
+
+OLD STYLE TESTS:
+
+There are a few tests that are used when Vim was built without the +eval
+feature. These cannot use the "assert" functions, therefore they consist of a
+.in file that contains Normal mode commands between STARTTEST and ENDTEST.
+They modify the file and the result gets written in the test.out file. This
+is then compared with the .ok file. If they are equal the test passed. If
+they differ the test failed.
+
+
+RUNNING THE TESTS:
+
+To run a single test from the src directory:
+
+ $ make test_<name>
+
+The below commands should be run from the src/testdir directory.
+
+To run a single test:
+
+ $ make test_<name>.res
+
+The file 'messages' contains the messages generated by the test script. If a
+test fails, then the test.log file contains the error messages. If all the
+tests are successful, then this file will be an empty file.
+
+- To run a single test function from a test script:
+
+ $ ../vim -u NONE -S runtest.vim <test_file>.vim <function_name>
+
+- To execute only specific test functions, add a second argument:
+
+ $ ../vim -u NONE -S runtest.vim test_channel.vim open_delay
+
+
+- To run all the tests:
+
+ $ make
+
+- To run the test on MS-Windows using the MSVC nmake:
+
+ > nmake -f Make_mvc.mak
+
+- To run the tests with GUI Vim:
+
+ $ make GUI_FLAG=-g
+
+ or
+
+ $ make VIMPROG=../gvim
+
+- To cleanup the temporary files after running the tests:
+
+ $ make clean
diff --git a/src/testdir/amiga.vim b/src/testdir/amiga.vim
new file mode 100644
index 0000000..79956d7
--- /dev/null
+++ b/src/testdir/amiga.vim
@@ -0,0 +1,6 @@
+" Settings for test script execution
+set shell=csh
+map! /tmp t:
+cmap !rm !Delete all
+
+source setup.vim
diff --git a/src/testdir/check.vim b/src/testdir/check.vim
new file mode 100644
index 0000000..82abb64
--- /dev/null
+++ b/src/testdir/check.vim
@@ -0,0 +1,286 @@
+source shared.vim
+source term_util.vim
+
+command -nargs=1 MissingFeature throw 'Skipped: ' .. <args> .. ' feature missing'
+
+" Command to check for the presence of a feature.
+command -nargs=1 CheckFeature call CheckFeature(<f-args>)
+func CheckFeature(name)
+ if !has(a:name, 1)
+ throw 'Checking for non-existent feature ' .. a:name
+ endif
+ if !has(a:name)
+ MissingFeature a:name
+ endif
+endfunc
+
+" Command to check for the absence of a feature.
+command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>)
+func CheckNotFeature(name)
+ if !has(a:name, 1)
+ throw 'Checking for non-existent feature ' .. a:name
+ endif
+ if has(a:name)
+ throw 'Skipped: ' .. a:name .. ' feature present'
+ endif
+endfunc
+
+" Command to check for the presence of a working option.
+command -nargs=1 CheckOption call CheckOption(<f-args>)
+func CheckOption(name)
+ if !exists('&' .. a:name)
+ throw 'Checking for non-existent option ' .. a:name
+ endif
+ if !exists('+' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' option not supported'
+ endif
+endfunc
+
+" Command to check for the presence of a built-in function.
+command -nargs=1 CheckFunction call CheckFunction(<f-args>)
+func CheckFunction(name)
+ if !exists('?' .. a:name)
+ throw 'Checking for non-existent function ' .. a:name
+ endif
+ if !exists('*' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' function missing'
+ endif
+endfunc
+
+" Command to check for the presence of an Ex command
+command -nargs=1 CheckCommand call CheckCommand(<f-args>)
+func CheckCommand(name)
+ if !exists(':' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' command not supported'
+ endif
+endfunc
+
+" Command to check for the presence of a shell command
+command -nargs=1 CheckExecutable call CheckExecutable(<f-args>)
+func CheckExecutable(name)
+ if !executable(a:name)
+ throw 'Skipped: ' .. a:name .. ' program not executable'
+ endif
+endfunc
+
+" Command to check for the presence of python. Argument should have been
+" obtained with PythonProg()
+func CheckPython(name)
+ if a:name == ''
+ throw 'Skipped: python command not available'
+ endif
+endfunc
+
+" Command to check for running on MS-Windows
+command CheckMSWindows call CheckMSWindows()
+func CheckMSWindows()
+ if !has('win32')
+ throw 'Skipped: only works on MS-Windows'
+ endif
+endfunc
+
+" Command to check for NOT running on MS-Windows
+command CheckNotMSWindows call CheckNotMSWindows()
+func CheckNotMSWindows()
+ if has('win32')
+ throw 'Skipped: does not work on MS-Windows'
+ endif
+endfunc
+
+" Command to check for running on Unix
+command CheckUnix call CheckUnix()
+func CheckUnix()
+ if !has('unix')
+ throw 'Skipped: only works on Unix'
+ endif
+endfunc
+
+" Command to check for running on Linux
+command CheckLinux call CheckLinux()
+func CheckLinux()
+ if !has('linux')
+ throw 'Skipped: only works on Linux'
+ endif
+endfunc
+
+" Command to check for not running on a BSD system.
+command CheckNotBSD call CheckNotBSD()
+func CheckNotBSD()
+ if has('bsd')
+ throw 'Skipped: does not work on BSD'
+ endif
+endfunc
+
+" Command to check for not running on a MacOS
+command CheckNotMac call CheckNotMac()
+func CheckNotMac()
+ if has('mac')
+ throw 'Skipped: does not work on MacOS'
+ endif
+endfunc
+
+" Command to check for not running on a MacOS M1 system.
+command CheckNotMacM1 call CheckNotMacM1()
+func CheckNotMacM1()
+ if has('mac') && system('uname -a') =~ '\<arm64\>'
+ throw 'Skipped: does not work on MacOS M1'
+ endif
+endfunc
+
+" Command to check that making screendumps is supported.
+" Caller must source screendump.vim
+command CheckScreendump call CheckScreendump()
+func CheckScreendump()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+endfunc
+
+" Command to check that we can Run Vim in a terminal window
+command CheckRunVimInTerminal call CheckRunVimInTerminal()
+func CheckRunVimInTerminal()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+endfunc
+
+" Command to check that we can run the GUI
+command CheckCanRunGui call CheckCanRunGui()
+func CheckCanRunGui()
+ if !has('gui') || ($DISPLAY == "" && !has('gui_running'))
+ throw 'Skipped: cannot start the GUI'
+ endif
+endfunc
+
+" Command to Check for an environment variable
+command -nargs=1 CheckEnv call CheckEnv(<f-args>)
+func CheckEnv(name)
+ if empty(eval('$' .. a:name))
+ throw 'Skipped: Environment variable ' .. a:name .. ' is not set'
+ endif
+endfunc
+
+" Command to check that we are using the GUI
+command CheckGui call CheckGui()
+func CheckGui()
+ if !has('gui_running')
+ throw 'Skipped: only works in the GUI'
+ endif
+endfunc
+
+" Command to check that not currently using the GUI
+command CheckNotGui call CheckNotGui()
+func CheckNotGui()
+ if has('gui_running')
+ throw 'Skipped: only works in the terminal'
+ endif
+endfunc
+
+" Command to check that test is not running as root
+command CheckNotRoot call CheckNotRoot()
+func CheckNotRoot()
+ if IsRoot()
+ throw 'Skipped: cannot run test as root'
+ endif
+endfunc
+
+" Command to check that the current language is English
+command CheckEnglish call CheckEnglish()
+func CheckEnglish()
+ if v:lang != "C" && v:lang !~ '^[Ee]n'
+ throw 'Skipped: only works in English language environment'
+ endif
+endfunc
+
+" Command to check that loopback device has IPv6 address
+command CheckIPv6 call CheckIPv6()
+func CheckIPv6()
+ if !has('ipv6')
+ throw 'Skipped: cannot use IPv6 networking'
+ endif
+ if !exists('s:ipv6_loopback')
+ let s:ipv6_loopback = s:CheckIPv6Loopback()
+ endif
+ if !s:ipv6_loopback
+ throw 'Skipped: no IPv6 address for loopback device'
+ endif
+endfunc
+
+func s:CheckIPv6Loopback()
+ if has('win32')
+ return system('netsh interface ipv6 show interface') =~? '\<Loopback\>'
+ elseif filereadable('/proc/net/if_inet6')
+ return (match(readfile('/proc/net/if_inet6'), '\slo$') >= 0)
+ elseif executable('ifconfig')
+ for dev in ['lo0', 'lo', 'loop']
+ " NOTE: On SunOS, need specify address family 'inet6' to get IPv6 info.
+ if system('ifconfig ' .. dev .. ' inet6 2>/dev/null') =~? '\<inet6\>'
+ \ || system('ifconfig ' .. dev .. ' 2>/dev/null') =~? '\<inet6\>'
+ return v:true
+ endif
+ endfor
+ else
+ " TODO: How to check it in other platforms?
+ endif
+ return v:false
+endfunc
+
+" Command to check for not running under ASAN
+command CheckNotAsan call CheckNotAsan()
+func CheckNotAsan()
+ if execute('version') =~# '-fsanitize=[a-z,]*\<address\>'
+ throw 'Skipped: does not work with ASAN'
+ endif
+endfunc
+
+" Command to check for not running under valgrind
+command CheckNotValgrind call CheckNotValgrind()
+func CheckNotValgrind()
+ if RunningWithValgrind()
+ throw 'Skipped: does not work well with valgrind'
+ endif
+endfunc
+
+" Command to check for X11 based GUI
+command CheckX11BasedGui call CheckX11BasedGui()
+func CheckX11BasedGui()
+ if !g:x11_based_gui
+ throw 'Skipped: requires X11 based GUI'
+ endif
+endfunc
+
+" Command to check that there are two clipboards
+command CheckTwoClipboards call CheckTwoClipboards()
+func CheckTwoClipboards()
+ " avoid changing the clipboard here, only X11 supports both
+ if !has('X11')
+ throw 'Skipped: requires two clipboards'
+ endif
+endfunc
+
+" Command to check for satisfying any of the conditions.
+" e.g. CheckAnyOf Feature:bsd Feature:sun Linux
+command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>)
+func CheckAnyOf(...)
+ let excp = []
+ for arg in a:000
+ try
+ exe 'Check' .. substitute(arg, ':', ' ', '')
+ return
+ catch /^Skipped:/
+ let excp += [substitute(v:exception, '^Skipped:\s*', '', '')]
+ endtry
+ endfor
+ throw 'Skipped: ' .. join(excp, '; ')
+endfunc
+
+" Command to check for satisfying all of the conditions.
+" e.g. CheckAllOf Unix Gui Option:ballooneval
+command -nargs=+ CheckAllOf call CheckAllOf(<f-args>)
+func CheckAllOf(...)
+ for arg in a:000
+ exe 'Check' .. substitute(arg, ':', ' ', '')
+ endfor
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/color_ramp.vim b/src/testdir/color_ramp.vim
new file mode 100644
index 0000000..8eed8f9
--- /dev/null
+++ b/src/testdir/color_ramp.vim
@@ -0,0 +1,85 @@
+" Script to generate a file that shows al 256 xterm colors
+
+new
+let lnum = 1
+
+" | in original color pair to see white background.
+let trail_bar = "\033[m|"
+
+" ANSI colors
+call setline(lnum, 'ANSI background')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 7)
+ let s .= "\033[4" . nr . "m "
+endfor
+for nr in range(8, 15)
+ let s .= "\033[10" . (nr - 8) . "m "
+endfor
+let s .= trail_bar
+
+call setline(lnum, s)
+let lnum += 1
+
+" ANSI text colors
+call setline(lnum, 'ANSI text')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 7)
+ let s .= "\033[0;3" . nr . "mxxxx"
+endfor
+for nr in range(8, 15)
+ let s .= "\033[0;9" . (nr - 8) . "mxxxx"
+endfor
+let s .= trail_bar
+
+call setline(lnum, s)
+let lnum += 1
+
+" ANSI with bold text
+call setline(lnum, 'ANSI bold text')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 7)
+ let s .= "\033[1;3" . nr . "mxxxx"
+endfor
+for nr in range(8, 15)
+ let s .= "\033[1;9" . (nr - 8) . "mxxxx"
+endfor
+let s .= trail_bar
+
+call setline(lnum, s)
+let lnum += 1
+
+" 6 x 6 x 6 color cube
+call setline(lnum, 'color cube')
+let lnum += 1
+
+for high in range(0, 5)
+ let s = ''
+ for low in range(0, 35)
+ let nr = low + high * 36
+ let s .= "\033[48;5;" . (nr + 16) . "m "
+ endfor
+ let s .= trail_bar
+ call setline(lnum + high, s)
+endfor
+let lnum += 6
+
+" 24 shades of grey
+call setline(lnum, 'grey ramp')
+let lnum += 1
+
+let s = ''
+for nr in range(0, 23)
+ let s .= "\033[48;5;" . (nr + 232) . "m "
+endfor
+let s .= trail_bar
+call setline(lnum, s)
+
+set binary
+write! <sfile>:h/color_ramp.txt
+quit
diff --git a/src/testdir/dos.vim b/src/testdir/dos.vim
new file mode 100644
index 0000000..3ea6ab2
--- /dev/null
+++ b/src/testdir/dos.vim
@@ -0,0 +1,9 @@
+" Settings for test script execution
+" Always use "COMMAND.COM", don't use the value of "$SHELL".
+set shell=c:\COMMAND.COM shellquote= shellxquote= shellcmdflag=/c shellredir=>
+" This is used only when the +eval feature is available.
+if executable("cmd.exe")
+ set shell=cmd.exe
+endif
+
+source setup.vim
diff --git a/src/testdir/dotest.in b/src/testdir/dotest.in
new file mode 100644
index 0000000..b2a0e1a
--- /dev/null
+++ b/src/testdir/dotest.in
@@ -0,0 +1,3 @@
+:set cp
+:map dotest /^STARTTEST j:set ff=unix cpo-=A :.,/ENDTEST/-1w! Xdotest :set ff& cpo+=A nj0:so! Xdotest dotest
+dotest
diff --git a/src/testdir/dumps/Test_Xcursorline_1.dump b/src/testdir/dumps/Test_Xcursorline_1.dump
new file mode 100644
index 0000000..e4176e1
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_1.dump
@@ -0,0 +1,20 @@
+| +0#af5f00255#ffffff0@1|1| >1+8#0000000&| |f|o@7| |a|r| |e|i|n|s|<+8#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f| |s|e|c|h|s| |s|i|e|b|e|n| |a|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&| |u|n| |z|e|h|n| |e|l|f| |z|w|ö|f|l| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n| @2|v| |i|e|r|z|e|h|n||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&| +0#0000000&@6|f|ü|n|f|z|e|h|n| @4||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1| @5|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_10.dump b/src/testdir/dumps/Test_Xcursorline_10.dump
new file mode 100644
index 0000000..152246a
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_10.dump
@@ -0,0 +1,20 @@
+| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+8#0000000&|-+8#0000e05&|u+8#0000000&|n|-+8#0000e05&|z+8#0000000&|e|h|n|-+8#0000e05&|e+8#0000000&>l|f|-+8#0000e05&|z+8#0000000&|w|ö|f|l|-+8#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z|e|h|n|^+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|I+0#0000e05&|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|7|1|-|7|6| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |l|i|s|t| |c|u|r|s|o|r|l|i|n|e|o|p|t|+|=|n|u|m|b|e|r| |l|i|s|t|c|h|a|r|s|=|s|p|a|c|e|:|-| @25
diff --git a/src/testdir/dumps/Test_Xcursorline_11.dump b/src/testdir/dumps/Test_Xcursorline_11.dump
new file mode 100644
index 0000000..5c0fb24
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_11.dump
@@ -0,0 +1,20 @@
+| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+8#0000000&|r|e|i|z|e|h|n|^+8#0000e05&|I|v+8#0000000&>-+8#0000e05&|i+8#0000000&|e|r|z|e|h|n|^+8#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|I+0#0000e05&|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|9|1|-|9|8| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |l|i|s|t| |c|u|r|s|o|r|l|i|n|e|o|p|t|+|=|n|u|m|b|e|r| |l|i|s|t|c|h|a|r|s|=|s|p|a|c|e|:|-| @25
diff --git a/src/testdir/dumps/Test_Xcursorline_12.dump b/src/testdir/dumps/Test_Xcursorline_12.dump
new file mode 100644
index 0000000..10fb120
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_12.dump
@@ -0,0 +1,20 @@
+| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z|e|h|n|^+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|I+8#0000e05&|f+8#0000000&|ü|n|f|z|e|h>n| @10||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1|0|8|-|1|2|1| @3|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |l|i|s|t| |c|u|r|s|o|r|l|i|n|e|o|p|t|+|=|n|u|m|b|e|r| |l|i|s|t|c|h|a|r|s|=|s|p|a|c|e|:|-| @25
diff --git a/src/testdir/dumps/Test_Xcursorline_13.dump b/src/testdir/dumps/Test_Xcursorline_13.dump
new file mode 100644
index 0000000..710cdc9
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_13.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@3| +8#4040ff13#ffffff0@1|1| >1+8#0000000&|-+8#0000e05&|f+8#0000000&|o@7|-+8#0000e05&|a+8#0000000&|r|-+8#0000e05&|e+8#0000000&|i||+1&&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|n+0#0000000&|s|<+0#0000e05&|2|0@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @2||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|@+0#4040ff13&@2||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1| @5|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_14.dump b/src/testdir/dumps/Test_Xcursorline_14.dump
new file mode 100644
index 0000000..fb8d56c
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_14.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@3| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i||+1&&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|n+8#0000000&|s|<+8#0000e05&|2|0@1|d|>|z+8#0000000&|w|e>i|-+8#0000e05&|d+8#0000000&|r|e||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @2||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|@+0#4040ff13&@2||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|2|6|-|3|0| @1|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_15.dump b/src/testdir/dumps/Test_Xcursorline_15.dump
new file mode 100644
index 0000000..1470bf7
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_15.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@3| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i||+1&&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|n+0#0000000&|s|<+0#0000e05&|2|0@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+8#0000000&|-+8#0000e05&|v+8#0000000&|i|e|r|-+8#0000e05&|f+8#0000000&|ü|n|f>-+8#0000e05&|s+8#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @2||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|@+0#4040ff13&@2||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|4|3|-|4|7| @1|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_16.dump b/src/testdir/dumps/Test_Xcursorline_16.dump
new file mode 100644
index 0000000..e2b41fe
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_16.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@3| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i||+1&&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|n+0#0000000&|s|<+0#0000e05&|2|0@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|s+8#0000000&|-+8#0000e05&|s+8#0000000&|i|e|b|e|n|-+8#0000e05&|a+8#0000000&|c>h|t|-+8#0000e05&|u+8#0000000&|n||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @2||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|@+0#4040ff13&@2||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|5|9|-|6|4| @1|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_17.dump b/src/testdir/dumps/Test_Xcursorline_17.dump
new file mode 100644
index 0000000..47344c7
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_17.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@3| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i||+1&&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|n+0#0000000&|s|<+0#0000e05&|2|0@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|-+8#0000e05&|z+8#0000000&|e|h|n|-+8#0000e05&|e+8#0000000&|l|f|-+8#0000e05&|z+8#0000000&>w|ö|f|l|-+8#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @2||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|@+0#4040ff13&@2||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|7|5|-|8|1| @1|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_18.dump b/src/testdir/dumps/Test_Xcursorline_18.dump
new file mode 100644
index 0000000..cea6538
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_18.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@3| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i||+1&&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|n+0#0000000&|s|<+0#0000e05&|2|0@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|d+8#0000000&|r|e|i|z|e|h|n|^+8#0000e05&|I|v+8#0000000&>-+8#0000e05&|i+8#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @2||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|b|e|n| @11||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|i+0#0000000&|n|s|-+0#0000e05&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+| +0#0000e05#a8a8a8255@3| +0#af5f00255#ffffff0@3|>+0#4040ff13&|e+0#0000000&|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|@+0#4040ff13&@2||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|9|1|-|9|8| @1|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_19.dump b/src/testdir/dumps/Test_Xcursorline_19.dump
new file mode 100644
index 0000000..5c5bb0d
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_19.dump
@@ -0,0 +1,20 @@
+>1+8&#ffffff0|-+8#0000e05&|f+8#0000000&|o@7|-+8#0000e05&|a+8#0000000&|r|-+8#0000e05&|e+8#0000000&|i|n|s|<+8#0000e05&|2|0@1|d|>||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+|>|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l||+1&&|~+0#4040ff13&| @47
+|>|f+0#0000000&|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+|2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+| +0#0000000&@24||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1| @5|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |n|o|n|u|m|b|e|r| @61
diff --git a/src/testdir/dumps/Test_Xcursorline_2.dump b/src/testdir/dumps/Test_Xcursorline_2.dump
new file mode 100644
index 0000000..ef2a07d
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_2.dump
@@ -0,0 +1,20 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&| |f|o@7| |a|r| |e|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+8#0000e05&@1|d|>|z+8#0000000&|w|e|i| |d|r>e|i| |v|i|e|r| |f||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f| |s|e|c|h|s| |s|i|e|b|e|n| |a|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&| |u|n| |z|e|h|n| |e|l|f| |z|w|ö|f|l| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n| @2|v| |i|e|r|z|e|h|n||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&| +0#0000000&@6|f|ü|n|f|z|e|h|n| @4||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|3|0|-|3|4| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_20.dump b/src/testdir/dumps/Test_Xcursorline_20.dump
new file mode 100644
index 0000000..7ffdf8f
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_20.dump
@@ -0,0 +1,20 @@
+|1+0&#ffffff0|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2|0@1|d|>||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+|>+0#4040ff13&|z+8#0000000&|w|e|i|-+8#0000e05&|d+8#0000000&|r|e|i|-+8#0000e05&|v+8#0000000&>i|e|r|-+8#0000e05&|f+8#0000000&|ü|n|f|-+8#0000e05&|s+8#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+|>|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l||+1&&|~+0#4040ff13&| @47
+|>|f+0#0000000&|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+|2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+| +0#0000000&@24||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|3|4|-|3|8| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |n|o|n|u|m|b|e|r| @61
diff --git a/src/testdir/dumps/Test_Xcursorline_21.dump b/src/testdir/dumps/Test_Xcursorline_21.dump
new file mode 100644
index 0000000..cb3b0b8
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_21.dump
@@ -0,0 +1,20 @@
+|1+0&#ffffff0|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2|0@1|d|>||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+|>|s+8#0000000&|-+8#0000e05&|s+8#0000000&|i|e|b|e|n|-+8#0000e05&|a+8#0000000&|c>h|t|-+8#0000e05&|u+8#0000000&|n|-+8#0000e05&|z+8#0000000&|e|h|n|-+8#0000e05&|e+8#0000000&|l||+1&&|~+0#4040ff13&| @47
+|>|f+0#0000000&|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+|2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+| +0#0000000&@24||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|5|9|-|6|3| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |n|o|n|u|m|b|e|r| @61
diff --git a/src/testdir/dumps/Test_Xcursorline_22.dump b/src/testdir/dumps/Test_Xcursorline_22.dump
new file mode 100644
index 0000000..0370465
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_22.dump
@@ -0,0 +1,20 @@
+|1+0&#ffffff0|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2|0@1|d|>||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+|>|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l||+1&&|~+0#4040ff13&| @47
+|>|f+8#0000000&|-+8#0000e05&|z+8#0000000&|w|ö|f|l|-+8#0000e05&|d+8#0000000&|r|e>i|z|e|h|n|^+8#0000e05&|I|v+8#0000000&|-+8#0000e05&|i+8#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+|2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+| +0#0000000&@24||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|8|4|-|8@1| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |n|o|n|u|m|b|e|r| @61
diff --git a/src/testdir/dumps/Test_Xcursorline_23.dump b/src/testdir/dumps/Test_Xcursorline_23.dump
new file mode 100644
index 0000000..ed021eb
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_23.dump
@@ -0,0 +1,20 @@
+|1+0&#ffffff0|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2|0@1|d|>||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+|>|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l||+1&&|~+0#4040ff13&| @47
+|>|f+0#0000000&|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+|>|e+8#0000000&|h|n|^+8#0000e05&|I|f+8#0000000&|ü|n|f|z|e>h|n| @10||+1&&|~+0#4040ff13&| @47
+|2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+| +0#0000000&@24||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1|0|7|-|1@1|9| @3|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |n|o|n|u|m|b|e|r| @61
diff --git a/src/testdir/dumps/Test_Xcursorline_24.dump b/src/testdir/dumps/Test_Xcursorline_24.dump
new file mode 100644
index 0000000..f10121d
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_24.dump
@@ -0,0 +1,20 @@
+|1+0&#ffffff0|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2|0@1|d|>||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h||+1&&|~+0#4040ff13&| @47
+|>|s+0#0000000&|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h|t|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l||+1&&|~+0#4040ff13&| @47
+|>|f+0#0000000&|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|h|n|^+0#0000e05&|I|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+|2+8#0000000&|-+8#0000e05&|f+8#0000000&|o@7|-+8#0000e05&>b+8#0000000&|a|r|-+8#0000e05&|e+8#0000000&|i|n|s|-+8#0000e05&|z+8#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+|4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&|z+0#0000000&|w|e|i||+1&&|~+0#4040ff13&| @47
+|>|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i||+1&&|~+0#4040ff13&| @47
+|>|e+0#0000000&|b|e|n| @19||+1&&|~+0#4040ff13&| @47
+| +0#0000000&@24||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |2|,|1|3| @4|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |n|o|n|u|m|b|e|r| @61
diff --git a/src/testdir/dumps/Test_Xcursorline_3.dump b/src/testdir/dumps/Test_Xcursorline_3.dump
new file mode 100644
index 0000000..69a85bd
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_3.dump
@@ -0,0 +1,20 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&| |f|o@7| |a|r| |e|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+8#0000000&|n|f| |s|e|c|h|s| |s>i|e|b|e|n| |a|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&| |u|n| |z|e|h|n| |e|l|f| |z|w|ö|f|l| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n| @2|v| |i|e|r|z|e|h|n||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&| +0#0000000&@6|f|ü|n|f|z|e|h|n| @4||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|5|1|-|5@1| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_4.dump b/src/testdir/dumps/Test_Xcursorline_4.dump
new file mode 100644
index 0000000..253312b
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_4.dump
@@ -0,0 +1,20 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&| |f|o@7| |a|r| |e|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f| |s|e|c|h|s| |s|i|e|b|e|n| |a|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+8#0000000&| |u|n| |z|e|h|n| |e>l|f| |z|w|ö|f|l| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n| @2|v| |i|e|r|z|e|h|n||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&| +0#0000000&@6|f|ü|n|f|z|e|h|n| @4||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|7|1|-|7|6| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_5.dump b/src/testdir/dumps/Test_Xcursorline_5.dump
new file mode 100644
index 0000000..f86e708
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_5.dump
@@ -0,0 +1,20 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&| |f|o@7| |a|r| |e|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f| |s|e|c|h|s| |s|i|e|b|e|n| |a|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&| |u|n| |z|e|h|n| |e|l|f| |z|w|ö|f|l| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+8#0000000&|r|e|i|z|e|h|n| @2>v| |i|e|r|z|e|h|n||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&| +0#0000000&@6|f|ü|n|f|z|e|h|n| @4||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|9|0|-|9|7| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_6.dump b/src/testdir/dumps/Test_Xcursorline_6.dump
new file mode 100644
index 0000000..1266b09
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_6.dump
@@ -0,0 +1,20 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&| |f|o@7| |a|r| |e|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f| |s|e|c|h|s| |s|i|e|b|e|n| |a|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&| |u|n| |z|e|h|n| |e|l|f| |z|w|ö|f|l| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n| @2|v| |i|e|r|z|e|h|n||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&| +8#0000000&@6|f|ü|n|f>z|e|h|n| @4||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&| |f|o@7| |b|a|r| |e|i|n|s| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i| |d|r|e|i| |v|i|e|r| |f|ü|n|f| ||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s| |s|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1|0|5|-|1@1|8| @3|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_Xcursorline_7.dump b/src/testdir/dumps/Test_Xcursorline_7.dump
new file mode 100644
index 0000000..5e4369b
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_7.dump
@@ -0,0 +1,20 @@
+| +8#4040ff13#ffffff0@1|1| >1+8#0000000&|-+8#0000e05&|f+8#0000000&|o@7|-+8#0000e05&|a+8#0000000&|r|-+8#0000e05&|e+8#0000000&|i|n|s|<+8#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z|e|h|n|^+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|I+0#0000e05&|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1| @5|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |l|i|s|t| |c|u|r|s|o|r|l|i|n|e|o|p|t|+|=|n|u|m|b|e|r| |l|i|s|t|c|h|a|r|s|=|s|p|a|c|e|:|-| @25
diff --git a/src/testdir/dumps/Test_Xcursorline_8.dump b/src/testdir/dumps/Test_Xcursorline_8.dump
new file mode 100644
index 0000000..8fd8dba
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_8.dump
@@ -0,0 +1,20 @@
+| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+8#0000e05&@1|d|>|z+8#0000000&|w|e|i|-+8#0000e05&|d+8#0000000&|r>e|i|-+8#0000e05&|v+8#0000000&|i|e|r|-+8#0000e05&|f+8#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+0#0000000&|n|f|-+0#0000e05&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n|-+0#0000e05&|a+0#0000000&|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z|e|h|n|^+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|I+0#0000e05&|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|3|0|-|3|4| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |l|i|s|t| |c|u|r|s|o|r|l|i|n|e|o|p|t|+|=|n|u|m|b|e|r| |l|i|s|t|c|h|a|r|s|=|s|p|a|c|e|:|-| @25
diff --git a/src/testdir/dumps/Test_Xcursorline_9.dump b/src/testdir/dumps/Test_Xcursorline_9.dump
new file mode 100644
index 0000000..9fe2d55
--- /dev/null
+++ b/src/testdir/dumps/Test_Xcursorline_9.dump
@@ -0,0 +1,20 @@
+| +8#4040ff13#ffffff0@1|1| |1+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|a+0#0000000&|r|-+0#0000e05&|e+0#0000000&|i|n|s|<+0#0000e05&|2||+1#0000000&| +0#af5f00255&@1|5| | +8#0000000&@44
+| +0#af5f00255&@3|>+0#4040ff13&|0+0#0000e05&@1|d|>|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|ü+8#0000000&|n|f|-+8#0000e05&|s+8#0000000&|e|c|h|s|-+8#0000e05&|s+8#0000000&>i|e|b|e|n|-+8#0000e05&|a+8#0000000&|c|h||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|t+0#0000000&|-+0#0000e05&|u+0#0000000&|n|-+0#0000e05&|z+0#0000000&|e|h|n|-+0#0000e05&|e+0#0000000&|l|f|-+0#0000e05&|z+0#0000000&|w|ö|f|l|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|d+0#0000000&|r|e|i|z|e|h|n|^+0#0000e05&|I|v+0#0000000&|-+0#0000e05&|i+0#0000000&|e|r|z|e|h|n|^+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|I+0#0000e05&|f+0#0000000&|ü|n|f|z|e|h|n| @10||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|2| |2+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|3| |3+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|4| |4+0#0000000&|-+0#0000e05&|f+0#0000000&|o@7|-+0#0000e05&|b+0#0000000&|a|r|-+0#0000e05&|e+0#0000000&|i|n|s|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|z+0#0000000&|w|e|i|-+0#0000e05&|d+0#0000000&|r|e|i|-+0#0000e05&|v+0#0000000&|i|e|r|-+0#0000e05&|f+0#0000000&|ü|n|f|-+0#0000e05&||+1#0000000&|~+0#4040ff13&| @47
+| +0#af5f00255&@3|>+0#4040ff13&|s+0#0000000&|e|c|h|s|-+0#0000e05&|s+0#0000000&|i|e|b|e|n| @7||+1&&|~+0#4040ff13&| @47
+| +0#af5f00255&@1|5| | +0#0000000&@20||+1&&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|~| @23||+1#0000000&|~+0#4040ff13&| @47
+|<+3#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|5|1|-|5@1| @1|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @17|5|,|0|-|1| @9|B|o|t
+|:+0&&|s|e|t| |l|i|s|t| |c|u|r|s|o|r|l|i|n|e|o|p|t|+|=|n|u|m|b|e|r| |l|i|s|t|c|h|a|r|s|=|s|p|a|c|e|:|-| @25
diff --git a/src/testdir/dumps/Test_appendbufline_1.dump b/src/testdir/dumps/Test_appendbufline_1.dump
new file mode 100644
index 0000000..470256d
--- /dev/null
+++ b/src/testdir/dumps/Test_appendbufline_1.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|H|e|l@1|o| |V|i|m| |w|o|r|l|d|.@2| @56
+|~+0#4040ff13&| @73
+|~| @73
+|f+1#0000000&|o@1| |[|+|]| @49|1|,|0|-|1| @9|A|l@1
+> +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|-+0&&@1|N|o| |l|i|n|e|s| |i|n| |b|u|f@1|e|r|-@1| @52
diff --git a/src/testdir/dumps/Test_autocmd_nested_switch.dump b/src/testdir/dumps/Test_autocmd_nested_switch.dump
new file mode 100644
index 0000000..499653e
--- /dev/null
+++ b/src/testdir/dumps/Test_autocmd_nested_switch.dump
@@ -0,0 +1,10 @@
+>(+0&#ffffff0|)| @72
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|X+3#0000000&|a|u|t|o|f|i|l|e| @47|1|,|1| @11|A|l@1
+|(+0&&|)| @72
+|~+0#4040ff13&| @73
+|~| @73
+|X+1#0000000&|a|u|t|o|f|i|l|e| @47|1|,|1| @11|A|l@1
+|"+0&&|X|a|u|t|o|f|i|l|e|"| |1|L|,| |3|B| @56
diff --git a/src/testdir/dumps/Test_balloon_eval_term_01.dump b/src/testdir/dumps/Test_balloon_eval_term_01.dump
new file mode 100644
index 0000000..be98212
--- /dev/null
+++ b/src/testdir/dumps/Test_balloon_eval_term_01.dump
@@ -0,0 +1,10 @@
+|o+0&#ffffff0|n>e| |o|n|e| |o|n|e| @38
+|t|w|o| |t|X|o| |t|w|o| @38
+|t|h|r|e| +0#0000001#ffd7ff255@17| +0#0000000#ffffff0@27
+|~+0#4040ff13&| @2| +0#0000001#ffd7ff255|l|i|n|e| |2| |c|o|l|u|m|n| |6|:| | +0#4040ff13#ffffff0@27
+|~| @2| +0#0000001#ffd7ff255|t|X|o|<| @12| +0#4040ff13#ffffff0@27
+|~| @2| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@27
+|~| @48
+|~| @48
+|~| @48
+|h+0#0000000&|o|l|d| |f|i|r|e|d| @39
diff --git a/src/testdir/dumps/Test_balloon_eval_term_01a.dump b/src/testdir/dumps/Test_balloon_eval_term_01a.dump
new file mode 100644
index 0000000..be98212
--- /dev/null
+++ b/src/testdir/dumps/Test_balloon_eval_term_01a.dump
@@ -0,0 +1,10 @@
+|o+0&#ffffff0|n>e| |o|n|e| |o|n|e| @38
+|t|w|o| |t|X|o| |t|w|o| @38
+|t|h|r|e| +0#0000001#ffd7ff255@17| +0#0000000#ffffff0@27
+|~+0#4040ff13&| @2| +0#0000001#ffd7ff255|l|i|n|e| |2| |c|o|l|u|m|n| |6|:| | +0#4040ff13#ffffff0@27
+|~| @2| +0#0000001#ffd7ff255|t|X|o|<| @12| +0#4040ff13#ffffff0@27
+|~| @2| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@27
+|~| @48
+|~| @48
+|~| @48
+|h+0#0000000&|o|l|d| |f|i|r|e|d| @39
diff --git a/src/testdir/dumps/Test_balloon_eval_term_02.dump b/src/testdir/dumps/Test_balloon_eval_term_02.dump
new file mode 100644
index 0000000..51c5e32
--- /dev/null
+++ b/src/testdir/dumps/Test_balloon_eval_term_02.dump
@@ -0,0 +1,10 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| @38
+|t|w|o| |t|X|o| |t|w|o| @38
+|t|h|r|e|e+0&#e0e0e08| |t|h>r+0&#ffffff0|e@1| |t|h|r|e@1| @32
+|~+0#4040ff13&| @2| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@27
+|~| @2| +0#0000001#ffd7ff255|l|i|n|e| |3| |c|o|l|u|m|n| |5|:| | +0#4040ff13#ffffff0@27
+|~| @2| +0#0000001#ffd7ff255|e| |t|h|r|<| @10| +0#4040ff13#ffffff0@27
+|~| @2| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@27
+|~| @48
+|~| @48
+|-+2#0000000&@1| |V|I|S|U|A|L| |-@1| +0&&@9|5| @8|3|,|9| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_changing_cmdheight_1.dump b/src/testdir/dumps/Test_changing_cmdheight_1.dump
new file mode 100644
index 0000000..db6d411
--- /dev/null
+++ b/src/testdir/dumps/Test_changing_cmdheight_1.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
+@75
+@75
+|:|r|e|s|i|z|e| |-|3| @64
diff --git a/src/testdir/dumps/Test_changing_cmdheight_2.dump b/src/testdir/dumps/Test_changing_cmdheight_2.dump
new file mode 100644
index 0000000..76d9440
--- /dev/null
+++ b/src/testdir/dumps/Test_changing_cmdheight_2.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|:+0&&|s|e|t| |c|m|d|h|e|i|g|h|t|+|=|3| @57
+@75
+@75
+@75
diff --git a/src/testdir/dumps/Test_changing_cmdheight_3.dump b/src/testdir/dumps/Test_changing_cmdheight_3.dump
new file mode 100644
index 0000000..d652cc6
--- /dev/null
+++ b/src/testdir/dumps/Test_changing_cmdheight_3.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
+@75
+@75
+@75
+@75
diff --git a/src/testdir/dumps/Test_changing_cmdheight_4.dump b/src/testdir/dumps/Test_changing_cmdheight_4.dump
new file mode 100644
index 0000000..c4d6fc9
--- /dev/null
+++ b/src/testdir/dumps/Test_changing_cmdheight_4.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
+@75
+@75
diff --git a/src/testdir/dumps/Test_changing_cmdheight_5.dump b/src/testdir/dumps/Test_changing_cmdheight_5.dump
new file mode 100644
index 0000000..fb2a521
--- /dev/null
+++ b/src/testdir/dumps/Test_changing_cmdheight_5.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_changing_cmdheight_6.dump b/src/testdir/dumps/Test_changing_cmdheight_6.dump
new file mode 100644
index 0000000..fb2a521
--- /dev/null
+++ b/src/testdir/dumps/Test_changing_cmdheight_6.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_cmdheight_tabline_1.dump b/src/testdir/dumps/Test_cmdheight_tabline_1.dump
new file mode 100644
index 0000000..7260309
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdheight_tabline_1.dump
@@ -0,0 +1,6 @@
+| +2&#ffffff0|[|N|o| |N|a|m|e|]| | +1&&@63
+> +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_cmdlineclear_tabenter.dump b/src/testdir/dumps/Test_cmdlineclear_tabenter.dump
new file mode 100644
index 0000000..27dcca5
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdlineclear_tabenter.dump
@@ -0,0 +1,10 @@
+| +8#0000001#e0e0e08|+| |[|N|o| |N|a|m|e|]| | +2#0000000#ffffff0|[|N|o| |N|a|m|e|]| | +1&&@49|X+8#0000001#e0e0e08
+> +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@74
+@75
+@57|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_cmdwin_interrupted.dump b/src/testdir/dumps/Test_cmdwin_interrupted.dump
new file mode 100644
index 0000000..325bc54
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdwin_interrupted.dump
@@ -0,0 +1,18 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|d+0#0000000&|o|n|e| @52|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_cmdwin_no_terminal.dump b/src/testdir/dumps/Test_cmdwin_no_terminal.dump
new file mode 100644
index 0000000..97d8147
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdwin_no_terminal.dump
@@ -0,0 +1,12 @@
+| +0&#ffffff0@74
+|[+1&&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|:+0#4040ff13&|s+0#af5f00255&|e|t| +0#0000000&|c+0#e000e06&|m|d|h|e|i|g|h|t|=+0#0000000&|2| @58
+|:+0#4040ff13&> +0#0000000&@73
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|C|o|m@1|a|n|d| |L|i|n|e|]| @42|2|,|0|-|1| @9|A|l@1
+|E+0#ffffff16#e000002|1@1|8@1|:| |C|a|n@1|o|t| |o|p|e|n| |a| |t|e|r|m|i|n|a|l| |f|r|o|m| |t|h|e| |c|o|m@1|a|n|d| |l|i|n|e| |w|i|n|d|o|w| +0#0000000#ffffff0@16
+@75
diff --git a/src/testdir/dumps/Test_cmdwin_restore_1.dump b/src/testdir/dumps/Test_cmdwin_restore_1.dump
new file mode 100644
index 0000000..8ac0295
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdwin_restore_1.dump
@@ -0,0 +1,12 @@
+|0+0&#ffffff0| @73
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+|0+0&&| @73
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+|:+0#4040ff13&> +0#0000000&@73
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|C|o|m@1|a|n|d| |L|i|n|e|]| @42|1|,|0|-|1| @9|A|l@1
+|:+0&&| @73
diff --git a/src/testdir/dumps/Test_cmdwin_restore_2.dump b/src/testdir/dumps/Test_cmdwin_restore_2.dump
new file mode 100644
index 0000000..ce0b2b5
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdwin_restore_2.dump
@@ -0,0 +1,12 @@
+>0+0&#ffffff0| @73
+|1| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+|0+0&&| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+| +0&&@74
diff --git a/src/testdir/dumps/Test_cmdwin_restore_3.dump b/src/testdir/dumps/Test_cmdwin_restore_3.dump
new file mode 100644
index 0000000..1cc0f3f
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdwin_restore_3.dump
@@ -0,0 +1,18 @@
+>0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|1|0| @72
+|1@1| @72
+|1|2| @72
+|1|3| @72
+|1|4| @72
+|1|5| @72
+|1|6| @72
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_cmdwin_wrong_command_1.dump b/src/testdir/dumps/Test_cmdwin_wrong_command_1.dump
new file mode 100644
index 0000000..95a686b
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdwin_wrong_command_1.dump
@@ -0,0 +1,12 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|:+0#4040ff13&|l+0#af5f00255&>s| +0#0000000&@71
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|C|o|m@1|a|n|d| |L|i|n|e|]| @42|1|,|2| @11|A|l@1
+|E+0#ffffff16#e000002|1@1|:| |I|n|v|a|l|i|d| |i|n| |c|o|m@1|a|n|d|-|l|i|n|e| |w|i|n|d|o|w|;| |:|q|<|C|R|>| |c|l|o|s|e|s| |t|h|e| |w|i|n|d|o|w| +0#0000000#ffffff0@13
diff --git a/src/testdir/dumps/Test_cmdwin_wrong_command_2.dump b/src/testdir/dumps/Test_cmdwin_wrong_command_2.dump
new file mode 100644
index 0000000..3ef02cd
--- /dev/null
+++ b/src/testdir/dumps/Test_cmdwin_wrong_command_2.dump
@@ -0,0 +1,12 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|l|s> @71
diff --git a/src/testdir/dumps/Test_colorcolumn_1.dump b/src/testdir/dumps/Test_colorcolumn_1.dump
new file mode 100644
index 0000000..cbaf27d
--- /dev/null
+++ b/src/testdir/dumps/Test_colorcolumn_1.dump
@@ -0,0 +1,10 @@
+| +8#af5f00255#ffffff0@1|1| |1+0#0000000&@1|1+0&#ffd7d7255|1+0&#ffffff0@4|1+0&#ffd7d7255|1+0&#ffffff0| @60
+| +0#af5f00255&@1|2| |2+0#0000000&@1|2+0&#ffd7d7255|2+0&#ffffff0@4|2+0&#ffd7d7255|2+0&#ffffff0@1| @59
+| +0#af5f00255&@1|3| |3+0#0000000&@1|3+0&#ffd7d7255|3+0&#ffffff0@4|3+0&#ffd7d7255|3+0&#ffffff0| @60
+|~+0#4040ff13&| @73
+|X+1#0000000&| @55|1|,|1| @11|A|l@1
+| +8#af5f00255&@1|1| >1+0#0000000&@1|1+0&#ffd7d7255|1+0&#ffffff0@4|1+0&#ffd7d7255|1+0&#ffffff0| @60
+| +0#af5f00255&@1|2| |2+0#0000000&@1|2+0&#ffd7d7255|2+0&#ffffff0@4|2+0&#ffd7d7255|2+0&#ffffff0@1| @59
+| +0#af5f00255&@1|3| |3+0#0000000&@1|3+0&#ffd7d7255|3+0&#ffffff0@4|3+0&#ffd7d7255|3+0&#ffffff0| @60
+|X+3&&| @55|1|,|1| @11|A|l@1
+|:+0&&| @73
diff --git a/src/testdir/dumps/Test_colorcolumn_2.dump b/src/testdir/dumps/Test_colorcolumn_2.dump
new file mode 100644
index 0000000..44e0e7a
--- /dev/null
+++ b/src/testdir/dumps/Test_colorcolumn_2.dump
@@ -0,0 +1,10 @@
+>T+0&#ffffff0|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|e|d| |o|v|e|r| |t|h|e| @3| +0&#ffd7d7255
+@1| +0&#ffffff0|l+0&#ffd7d7255|a+0&#ffffff0|z|y| |d|o|g|s| @28
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_colorcolumn_3.dump b/src/testdir/dumps/Test_colorcolumn_3.dump
new file mode 100644
index 0000000..6cb0c24
--- /dev/null
+++ b/src/testdir/dumps/Test_colorcolumn_3.dump
@@ -0,0 +1,10 @@
+>T+0&#ffffff0|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|e|d| |o|v|e|r| |t|h|e| |l|a|z|y+0&#ffd7d7255
+|++0#4040ff13&|++0&#ffffff0|++0&#ffd7d7255|>+0&#ffffff0| | +0#0000000&|d|o|g|s| @29
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_conceal_cuc_01.dump b/src/testdir/dumps/Test_conceal_cuc_01.dump
new file mode 100644
index 0000000..bf42819
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_cuc_01.dump
@@ -0,0 +1,10 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| @1|o|n|e| |o|n|e| |o|n|e| |o+0&#e0e0e08|n+0&#ffffff0|e| @11
+| +0#4040ff13&|>@2| |o+0#0000000&|n|e| |o+0&#ffd7d7255|n+0&#ffffff0|e| |o|n|e| |o|n|e| @19
+|t|w|o| |t|w|o| |t|w|o| |t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| |t|w|o| |t|w|o| @2
+|t|h|r|e@1| @1|t|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| |t+0&#e0e0e08|h+0&#ffffff0|r|e@1| @9
+| +0#4040ff13&|>@2| |t+0#0000000&|h|r|e|e+0&#ffd7d7255| +0&#ffffff0|t|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @11
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|/+0#0000000&|h|e|r|e| @16|2|,|2|6| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_conceal_cuc_02.dump b/src/testdir/dumps/Test_conceal_cuc_02.dump
new file mode 100644
index 0000000..1b6662e
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_cuc_02.dump
@@ -0,0 +1,10 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| @1|o|n|e| |o|n|e| |o|n|e| |o|n|e| @11
+| +0#4040ff13&|>@2| |o+0#0000000&|n|e| |o+0&#ffd7d7255|n+0&#ffffff0|e| |o|n|e| |o|n|e| @19
+|t|w|o| |t|w|o| |t|w|o| |t|w|o| |||h|i|d@1|e|n||| |h|e|r|e| |t|w|o| |t|w>o| @2
+|t|h|r|e@1| @1|t|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @9
+| +0#4040ff13&|>@2| |t+0#0000000&|h|r|e|e+0&#ffd7d7255| +0&#ffffff0|t|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @11
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|/+0#0000000&|h|e|r|e| @16|2|,|3|7| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_conceal_cul_01.dump b/src/testdir/dumps/Test_conceal_cul_01.dump
new file mode 100644
index 0000000..6d698d1
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_cul_01.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| @71
+|t|w|o| @71
+>t+8&&|h|r|e@1| @69
+|f+0&&|o|u|r| @70
+|f|i|v|e| @70
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1| @11|A|l@1
+| +0&&@74
+|t+8&&|h|i|s| |i|s| |a| |t|e|s|t| @60
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_conceal_cul_02.dump b/src/testdir/dumps/Test_conceal_cul_02.dump
new file mode 100644
index 0000000..46b296c
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_cul_02.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| @71
+|t|w|o| @71
+|t+8&&|h|r|e@1| @69
+|f+0&&|o|u|r| @70
+|f|i|v|e| @70
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1| @11|A|l@1
+| +0&&@74
+|t+8&&|h|i|s| |i|s| |a| |t|e|s>t| @60
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|w|i|n|c|m|d| |w| @65
diff --git a/src/testdir/dumps/Test_conceal_cul_03.dump b/src/testdir/dumps/Test_conceal_cul_03.dump
new file mode 100644
index 0000000..275f35c
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_cul_03.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| @71
+|t|w|o| @71
+|t+8&&|h|r|e@1| @69
+|f+0&&|o|u|r| @70
+|f|i|v|e| @70
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1| @11|A|l@1
+> +8&&@74
+|t+0&&|h|i|s| |i|s| |a| |t|e|s|t| @60
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|0|-|1| @9|A|l@1
+|:+0&&|w|i|n|c|m|d| |w| @65
diff --git a/src/testdir/dumps/Test_conceal_linebreak_1.dump b/src/testdir/dumps/Test_conceal_linebreak_1.dump
new file mode 100644
index 0000000..8c93df0
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_linebreak_1.dump
@@ -0,0 +1,8 @@
+>x+0&#ffffff0@74
+| @74
+|a@63| @10
+|++0#4040ff13&| |b+0#0000000&@66| @5
+|++0#4040ff13&| |c+0#0000000&@5| @66
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_conceal_resize_01.dump b/src/testdir/dumps/Test_conceal_resize_01.dump
new file mode 100644
index 0000000..8bb9c38
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_resize_01.dump
@@ -0,0 +1,6 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e|,| |t|h|e| >b|a|c|k|t|i|c|k|s| |s|h|o|u|l|d| |b|e| |c|o|n|c|e|a|l|e|d| @16
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|4|0| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_conceal_resize_02.dump b/src/testdir/dumps/Test_conceal_resize_02.dump
new file mode 100644
index 0000000..876a8ec
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_resize_02.dump
@@ -0,0 +1,7 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e|,| |t|h|e| >b|a|c|k|t|i|c|k|s| |s|h|o|u|l|d| |b|e| |c|o|n|c|e|a|l|e|d| @16
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|4|0| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_conceal_two_windows_01.dump b/src/testdir/dumps/Test_conceal_two_windows_01.dump
new file mode 100644
index 0000000..0a70677
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_01.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1>h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|/+0&&|h|e|r|e| @69
diff --git a/src/testdir/dumps/Test_conceal_two_windows_02.dump b/src/testdir/dumps/Test_conceal_two_windows_02.dump
new file mode 100644
index 0000000..367149f
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_02.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o>n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|/+0&&|h|e|r|e| @69
diff --git a/src/testdir/dumps/Test_conceal_two_windows_03.dump b/src/testdir/dumps/Test_conceal_two_windows_03.dump
new file mode 100644
index 0000000..27242eb
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_03.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|s|e|t| |c|o|n|c|e|a|l|c|u|r|s|o|r|=| @55
diff --git a/src/testdir/dumps/Test_conceal_two_windows_04.dump b/src/testdir/dumps/Test_conceal_two_windows_04.dump
new file mode 100644
index 0000000..ff52320
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_04.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| |||h|i|d@1|e|n>|| |t|h|r|e@1| @54
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|s|e|t| |c|o|n|c|e|a|l|c|u|r|s|o|r|=| @55
diff --git a/src/testdir/dumps/Test_conceal_two_windows_05.dump b/src/testdir/dumps/Test_conceal_two_windows_05.dump
new file mode 100644
index 0000000..68904d0
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_05.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|s|e|t| |c|o|n|c|e|a|l|c|u|r|s|o|r|=| @55
diff --git a/src/testdir/dumps/Test_conceal_two_windows_06c.dump b/src/testdir/dumps/Test_conceal_two_windows_06c.dump
new file mode 100644
index 0000000..b3869f9
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_06c.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| |h|e+1&&|r+0&&|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|/+0&&|e> @72
diff --git a/src/testdir/dumps/Test_conceal_two_windows_06i.dump b/src/testdir/dumps/Test_conceal_two_windows_06i.dump
new file mode 100644
index 0000000..c2325b9
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_06i.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| |h>e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|-+2&&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/src/testdir/dumps/Test_conceal_two_windows_06n.dump b/src/testdir/dumps/Test_conceal_two_windows_06n.dump
new file mode 100644
index 0000000..a147249
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_06n.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1>h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|s|e|t| |c|o|n|c|e|a|l|c|u|r|s|o|r|=|n| @54
diff --git a/src/testdir/dumps/Test_conceal_two_windows_06v.dump b/src/testdir/dumps/Test_conceal_two_windows_06v.dump
new file mode 100644
index 0000000..7456625
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_06v.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@51|1| @9
diff --git a/src/testdir/dumps/Test_conceal_two_windows_07c.dump b/src/testdir/dumps/Test_conceal_two_windows_07c.dump
new file mode 100644
index 0000000..b3869f9
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_07c.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| |h|e+1&&|r+0&&|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|/+0&&|e> @72
diff --git a/src/testdir/dumps/Test_conceal_two_windows_07i.dump b/src/testdir/dumps/Test_conceal_two_windows_07i.dump
new file mode 100644
index 0000000..f1cd232
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_07i.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h>e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|-+2&&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/src/testdir/dumps/Test_conceal_two_windows_07in.dump b/src/testdir/dumps/Test_conceal_two_windows_07in.dump
new file mode 100644
index 0000000..67c8cdb
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_07in.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_conceal_two_windows_07n.dump b/src/testdir/dumps/Test_conceal_two_windows_07n.dump
new file mode 100644
index 0000000..9ed71a6
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_07n.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|s|e|t| |c|o|n|c|e|a|l|c|u|r|s|o|r|=|i| @54
diff --git a/src/testdir/dumps/Test_conceal_two_windows_07v.dump b/src/testdir/dumps/Test_conceal_two_windows_07v.dump
new file mode 100644
index 0000000..7456625
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_07v.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@51|1| @9
diff --git a/src/testdir/dumps/Test_conceal_two_windows_08c.dump b/src/testdir/dumps/Test_conceal_two_windows_08c.dump
new file mode 100644
index 0000000..2d12a9c
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_08c.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e+1&&|r+0&&|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|/+0&&|e> @72
diff --git a/src/testdir/dumps/Test_conceal_two_windows_08i.dump b/src/testdir/dumps/Test_conceal_two_windows_08i.dump
new file mode 100644
index 0000000..c2325b9
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_08i.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| |h>e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|-+2&&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/src/testdir/dumps/Test_conceal_two_windows_08n.dump b/src/testdir/dumps/Test_conceal_two_windows_08n.dump
new file mode 100644
index 0000000..13e8141
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_08n.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|s|e|t| |c|o|n|c|e|a|l|c|u|r|s|o|r|=|c| @54
diff --git a/src/testdir/dumps/Test_conceal_two_windows_08v.dump b/src/testdir/dumps/Test_conceal_two_windows_08v.dump
new file mode 100644
index 0000000..7456625
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_08v.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@51|1| @9
diff --git a/src/testdir/dumps/Test_conceal_two_windows_09c.dump b/src/testdir/dumps/Test_conceal_two_windows_09c.dump
new file mode 100644
index 0000000..b3869f9
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_09c.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| |h|e+1&&|r+0&&|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|/+0&&|e> @72
diff --git a/src/testdir/dumps/Test_conceal_two_windows_09i.dump b/src/testdir/dumps/Test_conceal_two_windows_09i.dump
new file mode 100644
index 0000000..c2325b9
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_09i.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| |h>e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|-+2&&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/src/testdir/dumps/Test_conceal_two_windows_09n.dump b/src/testdir/dumps/Test_conceal_two_windows_09n.dump
new file mode 100644
index 0000000..e00c814
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_09n.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| >h|e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|:+0&&|s|e|t| |c|o|n|c|e|a|l|c|u|r|s|o|r|=|v| @54
diff --git a/src/testdir/dumps/Test_conceal_two_windows_09v.dump b/src/testdir/dumps/Test_conceal_two_windows_09v.dump
new file mode 100644
index 0000000..8d6cb4b
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_09v.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1>h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|4| @10|A|l@1
+|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@51|1| @9
diff --git a/src/testdir/dumps/Test_conceal_two_windows_10.dump b/src/testdir/dumps/Test_conceal_two_windows_10.dump
new file mode 100644
index 0000000..c2325b9
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_10.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| |||h|i|d@1|e|n||| |h>e|r|e| @57
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1|5| @10|A|l@1
+|-+2&&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/src/testdir/dumps/Test_conceal_two_windows_11.dump b/src/testdir/dumps/Test_conceal_two_windows_11.dump
new file mode 100644
index 0000000..42a3fd5
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_11.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| |||h|i|d@1|e|n||> |t|h|r|e@1| @54
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|5| @10|A|l@1
+|-+2&&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/src/testdir/dumps/Test_conceal_two_windows_12.dump b/src/testdir/dumps/Test_conceal_two_windows_12.dump
new file mode 100644
index 0000000..63861a1
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_12.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| |||h|i|d@1|e|n>|| |t|h|r|e@1| @54
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_conceal_two_windows_13.dump b/src/testdir/dumps/Test_conceal_two_windows_13.dump
new file mode 100644
index 0000000..2f93e31
--- /dev/null
+++ b/src/testdir/dumps/Test_conceal_two_windows_13.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+|S|e|c|o|n|d| |w|i|n|d|o|w| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1|4| @10|A|l@1
+|o+0&&|n|e| |o|n|e| |o|n|e| |o|n|e| |o|n|e| @55
+|t|w|o| @1|h|e|r|e| @65
+|t|h|r|e@1| @1|t|h|r|e@1| @62
+> @74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|4|,|1| @11|A|l@1
+|-+2&&@1| |I|N|S|E|R|T| |-@1| +0&&@62
diff --git a/src/testdir/dumps/Test_cursor_position_with_showbreak.dump b/src/testdir/dumps/Test_cursor_position_with_showbreak.dump
new file mode 100644
index 0000000..92b79cc
--- /dev/null
+++ b/src/testdir/dumps/Test_cursor_position_with_showbreak.dump
@@ -0,0 +1,6 @@
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@71|X
+> +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|e|c|o|n|d| |l|i|n|e| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|1|,|7|4| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorcolumn_callback_1.dump b/src/testdir/dumps/Test_cursorcolumn_callback_1.dump
new file mode 100644
index 0000000..7b1b85b
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorcolumn_callback_1.dump
@@ -0,0 +1,8 @@
+>a+0&#ffffff0@4| @69
+|b+0&#e0e0e08|b+0&#ffffff0@3| @69
+|c+0&#e0e0e08|c+0&#ffffff0@3| @69
+|d+0&#e0e0e08|d+0&#ffffff0@3| @69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|4|,|5| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_1.dump b/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_1.dump
new file mode 100644
index 0000000..7a691e8
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_1.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8+0&#e0e0e08|9+0&#ffffff0| @65
+|a| @5> |b| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|2|,|2|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_2.dump b/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_2.dump
new file mode 100644
index 0000000..29d6b19
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_2.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2+0&#e0e0e08|3+0&#ffffff0|4|5|6|7|8|9| @65
+|a> @6|b| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|2|,|2| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_3.dump b/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_3.dump
new file mode 100644
index 0000000..9d0cbed
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorcolumn_insert_on_tab_3.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8+0&#e0e0e08|9+0&#ffffff0| @65
+|a| @5> |b| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |(|i|n|s|e|r|t|)| |-@1| +0&&@42|2|,|2|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorline_callback_1.dump b/src/testdir/dumps/Test_cursorline_callback_1.dump
new file mode 100644
index 0000000..c4f32ea
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorline_callback_1.dump
@@ -0,0 +1,8 @@
+|a+0&#ffffff0@4| @69
+>b+8&&@4| @69
+|c+0&&@4| @69
+|d@4| @69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|4|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorline_redraw_1.dump b/src/testdir/dumps/Test_cursorline_redraw_1.dump
new file mode 100644
index 0000000..71fdc89
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorline_redraw_1.dump
@@ -0,0 +1,20 @@
+| +0&#ffffff0@23|o|n|e| |b|y| |o|n|e| |t|o| |a|v
+|o|i|d| |p|r|o|b|l|e|m|s|.| @26
+@24|A|l|s|o| |s|e@1| |||:|s|e|t|-|a
+|r|g|s||| |a|b|o|v|e|.| @28
+@40
+|T|h|e| |{|o|p|t|i|o|n|}| |a|r|g|u|m|e|n|t|s| |t|o| |"|:|s|e|t|"| |m|a|y| |b|e|
+|r|e|p|e|a|t|e|d|.| @1|F|o|r| |e|x|a|m|p|l|e|:| |>| @14
+@8|:|s|e|t| |a|i| |n|o|s|i| |s|w|=|3| |t|s|=|3| @9
+|I|f| |y|o|u| |m|a|k|e| |a|n| |e|r@1|o|r| |i|n| |o|n|e| |o|f| |t|h|e| |a|r|g|u|m
+|e|n|t|s|,| |a|n| |e|r@1|o|r| |m|e|s@1|a|g|e| |w|i|l@1| |b|e| |g|i|v|e|n| @3
+|a|n|d| |t|h|e| |f|o|l@1|o|w|i|n|g| |a|r|g|u|m|e|n|t|s| |w|i|l@1| |b|e| |i|g|n|o
+|r|e|d|.| @35
+@40
+@40
+@16|*|:|s|e|t|-|v|e|r|b|o|s|e|*| @9
+>W+8&&|h|e|n| |'|v|e|r|b|o|s|e|'| |i|s| |n|o|n|-|z|e|r|o|,| |d|i|s|p|l|a|y|i|n|g| |a
+|n| |o|p|t|i|o|n| |v|a|l|u|e| |w|i|l@1| |a|l|s|o| |t|e|l@1| |w|h|e|r|e| |i|t| @1
+|w+0&&|a|s| |l|a|s|t| |s|e|t|.| @1|E|x|a|m|p|l|e|:| |>| @14
+@8|:|v|e|r|b|o|s|e| |s|e|t| |s|h|i|f|t|w|i|d|t|h| |c|i|n|d|e|n|t|?
+|<| |2@1|L|,| |9|4|2|B| @10|1|2|,|1| @9|2|0|%|
diff --git a/src/testdir/dumps/Test_cursorline_redraw_2.dump b/src/testdir/dumps/Test_cursorline_redraw_2.dump
new file mode 100644
index 0000000..323bef8
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorline_redraw_2.dump
@@ -0,0 +1,20 @@
+| +0&#ffffff0@39
+|T|h|e| |{|o|p|t|i|o|n|}| |a|r|g|u|m|e|n|t|s| |t|o| |"|:|s|e|t|"| |m|a|y| |b|e|
+|r|e|p|e|a|t|e|d|.| @1|F|o|r| |e|x|a|m|p|l|e|:| |>| @14
+| +8&&@7>:|s|e|t| |a|i| |n|o|s|i| |s|w|=|3| |t|s|=|3| @9
+|I+0&&|f| |y|o|u| |m|a|k|e| |a|n| |e|r@1|o|r| |i|n| |o|n|e| |o|f| |t|h|e| |a|r|g|u|m
+|e|n|t|s|,| |a|n| |e|r@1|o|r| |m|e|s@1|a|g|e| |w|i|l@1| |b|e| |g|i|v|e|n| @3
+|a|n|d| |t|h|e| |f|o|l@1|o|w|i|n|g| |a|r|g|u|m|e|n|t|s| |w|i|l@1| |b|e| |i|g|n|o
+|r|e|d|.| @35
+@40
+@40
+@16|*|:|s|e|t|-|v|e|r|b|o|s|e|*| @9
+|W|h|e|n| |'|v|e|r|b|o|s|e|'| |i|s| |n|o|n|-|z|e|r|o|,| |d|i|s|p|l|a|y|i|n|g| |a
+|n| |o|p|t|i|o|n| |v|a|l|u|e| |w|i|l@1| |a|l|s|o| |t|e|l@1| |w|h|e|r|e| |i|t| @1
+|w|a|s| |l|a|s|t| |s|e|t|.| @1|E|x|a|m|p|l|e|:| |>| @14
+@8|:|v|e|r|b|o|s|e| |s|e|t| |s|h|i|f|t|w|i|d|t|h| |c|i|n|d|e|n|t|?
+|<| @1|s|h|i|f|t|w|i|d|t|h|=|4| |~| @22
+@10|L|a|s|t| |s|e|t| |f|r|o|m| |m|o|d|e|l|i|n|e| |l|i|n|e| |1|
+|~| @38
+@2|c|i|n|d|e|n|t| |~| @28
+@22|7|,|2|-|9| @8|4@1|%|
diff --git a/src/testdir/dumps/Test_cursorline_screenline_1.dump b/src/testdir/dumps/Test_cursorline_screenline_1.dump
new file mode 100644
index 0000000..d28794b
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorline_screenline_1.dump
@@ -0,0 +1,8 @@
+|x+0&#ffffff0|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z
+| +8&&|x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| > @29
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|1|,|1|2|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorline_screenline_2.dump b/src/testdir/dumps/Test_cursorline_screenline_2.dump
new file mode 100644
index 0000000..3c44da1
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorline_screenline_2.dump
@@ -0,0 +1,8 @@
+>x+8&#ffffff0|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z
+| +0&&|x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| |x|y|z| @30
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_cursorline_with_visualmode_01.dump b/src/testdir/dumps/Test_cursorline_with_visualmode_01.dump
new file mode 100644
index 0000000..b6e20b7
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorline_with_visualmode_01.dump
@@ -0,0 +1,12 @@
+|a+0&#e0e0e08|b|c| | +0&#ffffff0@70
+|a+0&#e0e0e08|b|c| | +0&#ffffff0@70
+|a+0&#e0e0e08|b|c| | +0&#ffffff0@70
+|a+0&#e0e0e08|b|c| | +0&#ffffff0@70
+|a+0&#e0e0e08|b|c| | +0&#ffffff0@70
+>a|b+0&#e0e0e08|c| | +0&#ffffff0@70
+|a|b|c| @71
+|a|b|c| @71
+|a|b|c| @71
+|a|b|c| @71
+|a|b|c| @71
+|-+2&&@1| |V|I|S|U|A|L| |L|I|N|E| |-@1| +0&&@29|1|2| @7|1|2|,|1| @9|1|5|%|
diff --git a/src/testdir/dumps/Test_cursorline_yank_01.dump b/src/testdir/dumps/Test_cursorline_yank_01.dump
new file mode 100644
index 0000000..7b749b1
--- /dev/null
+++ b/src/testdir/dumps/Test_cursorline_yank_01.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|3| | +0#0000000&@70
+| +0#af5f00255&@1|2| |1+0#0000000&| @69
+| +0#af5f00255&@1|1| |2+0#0000000&| @69
+| +8#af5f00255&@1|0| >3+8#0000000&| @69
+| +0#af5f00255&@1|1| | +0#0000000&@70
+|~+0#4040ff13&| @73
+|~| @73
+|4+0#0000000&| |l|i|n|e|s| |y|a|n|k|e|d| @42|4|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_diff_01.dump b/src/testdir/dumps/Test_diff_01.dump
new file mode 100644
index 0000000..125c391
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|0+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_02.dump b/src/testdir/dumps/Test_diff_02.dump
new file mode 100644
index 0000000..a2a1852
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_02.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|0+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_03.dump b/src/testdir/dumps/Test_diff_03.dump
new file mode 100644
index 0000000..5832eec
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_03.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19
+| @1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_04.dump b/src/testdir/dumps/Test_diff_04.dump
new file mode 100644
index 0000000..84fa6f7
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_04.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19
+| @1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+|~+0&#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_05.dump b/src/testdir/dumps/Test_diff_05.dump
new file mode 100644
index 0000000..7bffa1a
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_05.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|4+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+|~+0&#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_06.0.dump b/src/testdir/dumps/Test_diff_06.0.dump
new file mode 100644
index 0000000..458295b
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_06.0.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| >+|-@1| @1|3| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|3| |l|i|n|e|s|:| |1|-@19
+| @1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |6|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |6|-@19
+| @1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|c|o|n|t|e|x|t|:|0| @51
diff --git a/src/testdir/dumps/Test_diff_06.1.dump b/src/testdir/dumps/Test_diff_06.1.dump
new file mode 100644
index 0000000..8f2d4fb
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_06.1.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| >+|-@1| @1|3| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|3| |l|i|n|e|s|:| |1|-@19
+| @1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |6|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |6|-@19
+| @1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|c|o|n|t|e|x|t|:|1| @51
diff --git a/src/testdir/dumps/Test_diff_06.2.dump b/src/testdir/dumps/Test_diff_06.2.dump
new file mode 100644
index 0000000..c772270
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_06.2.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| >+|-@1| @1|2| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|2| |l|i|n|e|s|:| |1|-@19
+| @1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+|++0#0000e05#a8a8a8255| |+|-@1| @1|2| |l|i|n|e|s|:| |7|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|2| |l|i|n|e|s|:| |7|-@19
+| @1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&@1|s|e|t| |d|i|f@1|o|p|t|+|=|c|o|n|t|e|x|t|:|2| @50
diff --git a/src/testdir/dumps/Test_diff_06.dump b/src/testdir/dumps/Test_diff_06.dump
new file mode 100644
index 0000000..a5e6348
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_06.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_07.dump b/src/testdir/dumps/Test_diff_07.dump
new file mode 100644
index 0000000..c8f2aa6
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_07.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16||+1&&| +0#0000e05#a8a8a8255@1|#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|/+2#0000000#ff404010@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| +0&#ffd7ff255@13||+1&#ffffff0| +0#0000e05#a8a8a8255@1|i+2#0000000#ff404010|n|t| |f|i|b|(|i|n|t| |n|)| +0&#ffd7ff255@20
+| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|n+2&#ff404010|t| |i|;| +0&#ffd7ff255@24||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|f+2&#ff404010|(|n| |>| |2|)| +0&#ffd7ff255@21
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@7|p+2&#ff404010|r|i|n|t|f|(|"|Y|o|u|r| |a|n|s|w|e|r| |i|s|:| |"|)+0&#ffd7ff255|;||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@7|r+2&#ff404010|e|t|u|r|n| |f|i|b|(|n|-|1|)| |+| |f|i|b|(|n|-|2|)+0&#ffd7ff255|;
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|p|r|i|n|t|f|(|"|%|d|\|n|"|,| |f|o@1|)|;| @6||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|}| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|}| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|r|e|t|u|r|n| |1|;| @21
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|i+2#0000000#ff404010|n|t| |f|a|c|t|(|i|n|t| |n|)| +0&#ffd7ff255@19||+1&#ffffff0| +0#0000e05#a8a8a8255@1|/+2#0000000#ff404010@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| +0&#ffd7ff255@13
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|f+2&#ff404010|(|n| |>| |1|)| +0&#ffd7ff255@21||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|n+2&#ff404010|t| |i|;| +0&#ffd7ff255@24
+|X+3&#ffffff0|d|i|f|i|l|e|1| @10|1|,|1| @11|T|o|p| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|T|o|p
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|i|n|t|e|r|n|a|l| @52
diff --git a/src/testdir/dumps/Test_diff_08.dump b/src/testdir/dumps/Test_diff_08.dump
new file mode 100644
index 0000000..a8392df
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_08.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16||+1&&| +0#0000e05#a8a8a8255@1|#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|i|b|(|i|n|t| |n|)| @20
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|{+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|i|f|(|n| |>| |2|)| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|{| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|r|e|t|u|r|n| |f|i|b|(|n|-|1|)| |+| |f|i|b|(|n|-|2|)|;
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|}| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|r|e|t|u|r|n| |1|;| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|}+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13||+1&&| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|p|r|i|n|t|f|(|"|Y|o|u|r| |a|n|s|w|e|r| |i|s|:| |"|)|;||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+|X+3#0000000#ffffff0|d|i|f|i|l|e|1| @10|1|,|1| @11|T|o|p| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|T|o|p
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|a|l|g|o|r|i|t|h|m|:|p|a|t|i|e|n|c|e| @42
diff --git a/src/testdir/dumps/Test_diff_09.dump b/src/testdir/dumps/Test_diff_09.dump
new file mode 100644
index 0000000..700d30c
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_09.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16||+1&&| +0#0000e05#a8a8a8255@1|#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|i|b|(|i|n|t| |n|)| @20
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|{+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|i|f|(|n| |>| |2|)| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|{| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|r|e|t|u|r|n| |f|i|b|(|n|-|1|)| |+| |f|i|b|(|n|-|2|)|;
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|}| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|r|e|t|u|r|n| |1|;| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|}+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13||+1&&| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|p|r|i|n|t|f|(|"|Y|o|u|r| |a|n|s|w|e|r| |i|s|:| |"|)|;||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+|X+3#0000000#ffffff0|d|i|f|i|l|e|1| @10|1|,|1| @11|T|o|p| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|T|o|p
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|a|l|g|o|r|i|t|h|m|:|h|i|s|t|o|g|r|a|m| @41
diff --git a/src/testdir/dumps/Test_diff_10.dump b/src/testdir/dumps/Test_diff_10.dump
new file mode 100644
index 0000000..8bc91fb
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_10.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1> +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@5|v|.|p|r|e|p|a|r|e| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|e|n|d| @27
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|0|-|1| @9|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|0|-|1| @9|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|i|n|t|e|r|n|a|l| @52
diff --git a/src/testdir/dumps/Test_diff_11.dump b/src/testdir/dumps/Test_diff_11.dump
new file mode 100644
index 0000000..216c66b
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_11.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1> +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@5|v|.|p|r|e|p|a|r|e| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|e|n|d| @27
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|0|-|1| @9|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|0|-|1| @9|A|l@1
+|:+0&&| @73
diff --git a/src/testdir/dumps/Test_diff_12.dump b/src/testdir/dumps/Test_diff_12.dump
new file mode 100644
index 0000000..dd29b9f
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_12.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| |+|-@1| |1|0| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| |1|0| |l|i|n|e|s|:| |1|-@19
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_13.dump b/src/testdir/dumps/Test_diff_13.dump
new file mode 100644
index 0000000..b93fba7
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_13.dump
@@ -0,0 +1,20 @@
+|-+0#0000e05#a8a8a8255| | +0#0000000#ffffff0@34||+1&&|-+0#0000e05#a8a8a8255| | +0#0000000#ffffff0@34
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|0|,|0|-|1| @9|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|0|,|0|-|1| @9|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_14.dump b/src/testdir/dumps/Test_diff_14.dump
new file mode 100644
index 0000000..c241898
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_14.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|A+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|d| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|D|e+2&#ff404010| +0&#ffd7ff255@31
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_15.dump b/src/testdir/dumps/Test_diff_15.dump
new file mode 100644
index 0000000..6a65c2d
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_15.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|i|f| |(|0|)| @25
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|{| @30
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @4
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|r|e|t|u|r|n| |0|;| @22||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|r|e|t|u|r|n| |0|;| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|}| @30
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|&|v|i|m| |d|i|f@1|o|p|t|+|=|f|i|l@1|e|r| |d|i|f@1|o|p|t|+|=|i|w|h|i|t|e| @26
diff --git a/src/testdir/dumps/Test_diff_16.dump b/src/testdir/dumps/Test_diff_16.dump
new file mode 100644
index 0000000..5121bc9
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_16.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|i|f| |(|0|)| @25
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|{| @30
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @4
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|r|e|t|u|r|n| |0|;| @22||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|r|e|t|u|r|n| |0|;| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|}| @30
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|i|n|t|e|r|n|a|l| @52
diff --git a/src/testdir/dumps/Test_diff_17.dump b/src/testdir/dumps/Test_diff_17.dump
new file mode 100644
index 0000000..f1cd41f
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_17.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0|d| @32||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0|d| @32
+| +0#0000e05#a8a8a8255@1|e+0#0000000#ffffff0|f| @32||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|x+2#0000000#ff404010@2| +0&#ffd7ff255@31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|e+0#0000000#ffffff0|f| @32
+|~+0#4040ff13&| @35||+1#0000000&| +0#0000e05#a8a8a8255@1|y+2#0000000#ff404010@2| +0&#ffd7ff255@31
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_18.dump b/src/testdir/dumps/Test_diff_18.dump
new file mode 100644
index 0000000..5fd231c
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_18.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0|d| @32
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0|d| @32||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|e+0#0000000#ffffff0|f| @32||+1&&| +0#0000e05#a8a8a8255@1|e+0#0000000#ffffff0|f| @32
+| +0#0000e05#a8a8a8255@1|x+2#0000000#ff404010@2| +0&#ffd7ff255@31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|y+2#0000000#ff404010@2| +0&#ffd7ff255@31
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_19.dump b/src/testdir/dumps/Test_diff_19.dump
new file mode 100644
index 0000000..82543df
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_19.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|d| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255| +2&#ff404010|d+0&#ffd7ff255| @31
+| +0#0000e05#a8a8a8255@1|e+0#0000000#ffd7ff255|f| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010|e+0&#ffd7ff255|f| @31
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffd7ff255@1| | +2&#ff404010|x+0&#ffd7ff255@1| @28||+1&#ffffff0| +0#0000e05#a8a8a8255@1|x+0#0000000#ffd7ff255@1| |x@1| @29
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0|a|r| @31||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0|a|r| @31
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_20.dump b/src/testdir/dumps/Test_diff_20.dump
new file mode 100644
index 0000000..1180de6
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_20.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0|d| @32||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0| |d| @31
+| +0#0000e05#a8a8a8255@1|e+0#0000000#ffffff0|f| @32||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0|e|f| @31
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@1| @1|x@1| @28||+1&&| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@1| |x@1| @29
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0|a|r| @31||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0|a|r| @31
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|X+3#0000000&|d|i|f|i|l|e|1| @10|1|,|1| @11|A|l@1| |X+1&&|d|i|f|i|l|e|2| @10|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_bin_01.dump b/src/testdir/dumps/Test_diff_bin_01.dump
new file mode 100644
index 0000000..a7d4cce
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_bin_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>A+2#0000000#ff404010| +0&#ffd7ff255@33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|a+2#0000000#ff404010| +0&#ffd7ff255@33
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|^+2#0000e05#ff404010|@| +0#0000000#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|E+2#0000000#ff404010| +0&#ffd7ff255@33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|e+2#0000000#ff404010| +0&#ffd7ff255@33
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_bin_02.dump b/src/testdir/dumps/Test_diff_bin_02.dump
new file mode 100644
index 0000000..64ee1ac
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_bin_02.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>A+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|^+2#0000e05#ff404010|@| +0#0000000#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|E+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|e+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_bin_03.dump b/src/testdir/dumps/Test_diff_bin_03.dump
new file mode 100644
index 0000000..a7d4cce
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_bin_03.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>A+2#0000000#ff404010| +0&#ffd7ff255@33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|a+2#0000000#ff404010| +0&#ffd7ff255@33
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|^+2#0000e05#ff404010|@| +0#0000000#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|E+2#0000000#ff404010| +0&#ffd7ff255@33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|e+2#0000000#ff404010| +0&#ffd7ff255@33
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_bin_04.dump b/src/testdir/dumps/Test_diff_bin_04.dump
new file mode 100644
index 0000000..64ee1ac
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_bin_04.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>A+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|^+2#0000e05#ff404010|@| +0#0000000#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|d+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|E+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|e+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|g+0#0000000#ffffff0| @33
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_cuc_01.dump b/src/testdir/dumps/Test_diff_cuc_01.dump
new file mode 100644
index 0000000..19c42f4
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_cuc_01.dump
@@ -0,0 +1,20 @@
+>a+0&#ffffff0@1| @34||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0@1| @32
+|-+0#4040ff13#afffff255@36||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255@1| @32
+|c+0&#e0e0e08|c+0&#ffffff0| @34||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0@1| @32
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_cuc_02.dump b/src/testdir/dumps/Test_diff_cuc_02.dump
new file mode 100644
index 0000000..80a296c
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_cuc_02.dump
@@ -0,0 +1,20 @@
+|a+0&#ffffff0>a| @34||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0@1| @32
+|-+0#4040ff13#afffff255@36||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255@1| @32
+|c+0&#ffffff0|c+0&#e0e0e08| +0&#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0@1| @32
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|2| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|2| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_cuc_03.dump b/src/testdir/dumps/Test_diff_cuc_03.dump
new file mode 100644
index 0000000..19a3c1f
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_cuc_03.dump
@@ -0,0 +1,20 @@
+|a+0&#e0e0e08|a+0&#ffffff0| @34||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0@1| @32
+|-+0#4040ff13#afffff255@36||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255@1| @32
+>c+0&#ffffff0@1| @34||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0@1| @32
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|2|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|3|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_cuc_04.dump b/src/testdir/dumps/Test_diff_cuc_04.dump
new file mode 100644
index 0000000..ba29893
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_cuc_04.dump
@@ -0,0 +1,20 @@
+|a+0&#ffffff0|a+0&#e0e0e08| +0&#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0@1| @32
+|-+0#4040ff13#afffff255@36||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255@1| @32
+|c+0&#ffffff0>c| @34||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0@1| @32
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|2|,|2| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|3|,|2| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_of_diff_01.dump b/src/testdir/dumps/Test_diff_of_diff_01.dump
new file mode 100644
index 0000000..2213bf3
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_of_diff_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| >a+0#0000000&@1| @28||+1&&| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0@1| @32
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| |b+0#0000000&@1| @28||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0@1| @32
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |c+0#0000000&@1| @28||+1&&| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0@1| @32
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|-+0#4040ff13#afffff255@30||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|@+0#0000000#5fd7ff255@1| |-|3|,|2| |+|5|,|7| |@@1| @19
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|-+0#4040ff13#afffff255@30||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|d+0#0000000#5fd7ff255@1| @32
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|-+0#4040ff13#afffff255@30||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|e+0#0000000#5fd7ff255@1| @32
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|-+0#4040ff13#afffff255@30||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|f+0#0000000#5fd7ff255@1| @32
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_of_diff_02.dump b/src/testdir/dumps/Test_diff_of_diff_02.dump
new file mode 100644
index 0000000..86c06f5
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_of_diff_02.dump
@@ -0,0 +1,20 @@
+| +0&#ffffff0@28|a>a| +0#af5f00255&|1| @1| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0@1| @32
+@29|b@1| +0#af5f00255&|2| @1| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0@1| @32
+@29|c@1| +0#af5f00255&|3| @1| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffffff0@1| @32
+|-+0#4040ff13#afffff255@30| +0#af5f00255#ffffff0@3| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|@+0#0000000#5fd7ff255@1| |-|3|,|2| |+|5|,|7| |@@1| @19
+|-+0#4040ff13#afffff255@30| +0#af5f00255#ffffff0@3| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|d+0#0000000#5fd7ff255@1| @32
+|-+0#4040ff13#afffff255@30| +0#af5f00255#ffffff0@3| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|e+0#0000000#5fd7ff255@1| @32
+|-+0#4040ff13#afffff255@30| +0#af5f00255#ffffff0@3| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|f+0#0000000#5fd7ff255@1| @32
+| +0#4040ff13#ffffff0@35|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+@36|~||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |r|i|g|h|t|l|e|f|t| @60
diff --git a/src/testdir/dumps/Test_diff_rnu_01.dump b/src/testdir/dumps/Test_diff_rnu_01.dump
new file mode 100644
index 0000000..dc7c0f9
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_rnu_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&|1+0#af5f00255&| @2>a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|1| |a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|2| |a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|x+2#0000000#ff404010| +0&#ffd7ff255@33||+1&#ffffff0| +0#af5f00255&@1|3| |y+2#0000000#ff404010| +0&#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|x+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#af5f00255&@3|-+0#4040ff13#afffff255@32
+| +0#0000e05#a8a8a8255@1|x+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#af5f00255&@3|-+0#4040ff13#afffff255@32
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|4| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|5| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|6| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|7| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|8| |b+0#0000000&| @31
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_rnu_02.dump b/src/testdir/dumps/Test_diff_rnu_02.dump
new file mode 100644
index 0000000..32ffc40
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_rnu_02.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|1| |a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&|2+0#af5f00255&| @2>a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|1| |a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|x+2#0000000#ff404010| +0&#ffd7ff255@33||+1&#ffffff0| +0#af5f00255&@1|2| |y+2#0000000#ff404010| +0&#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|x+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#af5f00255&@3|-+0#4040ff13#afffff255@32
+| +0#0000e05#a8a8a8255@1|x+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#af5f00255&@3|-+0#4040ff13#afffff255@32
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|3| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|4| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|5| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|6| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|7| |b+0#0000000&| @31
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|2|,|1| @11|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|2|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_rnu_03.dump b/src/testdir/dumps/Test_diff_rnu_03.dump
new file mode 100644
index 0000000..8c8b938
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_rnu_03.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|2| |a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|1| |a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&|3+0#af5f00255&| @2>a+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|x+2#0000000#ff404010| +0&#ffd7ff255@33||+1&#ffffff0| +0#af5f00255&@1|1| |y+2#0000000#ff404010| +0&#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|x+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#af5f00255&@3|-+0#4040ff13#afffff255@32
+| +0#0000e05#a8a8a8255@1|x+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#af5f00255&@3|-+0#4040ff13#afffff255@32
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|2| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|3| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|4| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|5| |b+0#0000000&| @31
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#af5f00255&@1|6| |b+0#0000000&| @31
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|3|,|1| @11|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|3|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_scroll_1.dump b/src/testdir/dumps/Test_diff_scroll_1.dump
new file mode 100644
index 0000000..ee53c35
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_scroll_1.dump
@@ -0,0 +1,12 @@
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |1| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |1| @28
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |2| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |2| @28
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |3| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |3| @28
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |4| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |4| @28
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1> +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|L+0#0000000#5fd7ff255|o|r|e|m| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|p|s|u|m| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|d+0#0000000#5fd7ff255|o|l|o|r| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|s+0#0000000#5fd7ff255|i|t| @31
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|a+0#0000000#5fd7ff255|m|e|t|,| @29
+|X+1&#ffffff0|l|e|f|t| @13|5|,|0|-|1| @9|T|o|p| |X+3&&|r|i|g|h|t| @12|5|,|0|-|1| @9|T|o|p
+|"+0&&|X|r|i|g|h|t|"| |3@1|L|,| |2|6|3|B| @56
diff --git a/src/testdir/dumps/Test_diff_scroll_2.dump b/src/testdir/dumps/Test_diff_scroll_2.dump
new file mode 100644
index 0000000..4cac386
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_scroll_2.dump
@@ -0,0 +1,12 @@
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |1| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |1| @28
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |2| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |2| @28
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |3| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |3| @28
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |4| @28||+1&&| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |4| @28
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1>L+0#0000000#5fd7ff255|o|r|e|m| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|p|s|u|m| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|d+0#0000000#5fd7ff255|o|l|o|r| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|s+0#0000000#5fd7ff255|i|t| @31
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|a+0#0000000#5fd7ff255|m|e|t|,| @29
+|X+1&#ffffff0|l|e|f|t| @13|6|,|1| @11|T|o|p| |X+3&&|r|i|g|h|t| @12|6|,|1| @11|T|o|p
+|"+0&&|X|r|i|g|h|t|"| |3@1|L|,| |2|6|3|B| @56
diff --git a/src/testdir/dumps/Test_diff_scroll_change_01.dump b/src/testdir/dumps/Test_diff_scroll_change_01.dump
new file mode 100644
index 0000000..886fe60
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_scroll_change_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|6+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|7+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|8+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0@1| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0@1| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|2| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|2| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|3| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|3| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|4| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|4| @32
+|-+0#0000e05#a8a8a8255| |1+0#0000000#ffffff0>5| @32||+1&&|-+0#0000e05#a8a8a8255| |1+0#0000000#ffffff0|5| @32
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|7|,|2| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|5|,|2| @10|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_scroll_change_02.dump b/src/testdir/dumps/Test_diff_scroll_change_02.dump
new file mode 100644
index 0000000..d699ded
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_scroll_change_02.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|6+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|7+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|8+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0@1| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0@1| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|2| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|2| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|3| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|3| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|4| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|4| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffd7ff255|5>x+2&#ff404010| +0&#ffd7ff255@31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#ffd7ff255|5| @32
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|7|,|3| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|5|,|3| @10|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_scroll_change_03.dump b/src/testdir/dumps/Test_diff_scroll_change_03.dump
new file mode 100644
index 0000000..6d0c860
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_scroll_change_03.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|6+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|7+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|8+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0@1| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0@1| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|2| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|2| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|3| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|3| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|4| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|4| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffd7ff255|5|x+2&#ff404010| +0&#ffd7ff255@31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#ffd7ff255|5>y+2&#ff404010| +0&#ffd7ff255@31
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|7|,|3| @11|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|5|,|3| @10|B|o|t
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_syntax_1.dump b/src/testdir/dumps/Test_diff_syntax_1.dump
new file mode 100644
index 0000000..c96793a
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_syntax_1.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>v+0#00e0003#ffd7ff255|o|i|d| +0#0000000&|d|o|S+2&#ff404010|o|m|e|t+0&#ffd7ff255|h|i|n|g|(|)| |{| @52
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|i+0#00e0003&|n|t| +0#0000000&|x| |=| |0+0#e000002&|;+0#0000000&| @59
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@2|c+0#00e0003&|h|a|r| +0#0000000&|*|s| |=| |"+0#e000002&|t+2&#ff404010|h|e|r|e|"+0&#ffd7ff255|;+0#0000000&| @51
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|r+0#af5f00255&|e|t|u|r|n| +0#0000000&|5+0#e000002&|;+0#0000000&| @60
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @71
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|X+3#0000000&|p|r|o|g|r|a|m|2|.|c| @45|1|,|1| @11|A|l@1
+| +0#0000e05#a8a8a8255@1|v+0#00e0003#ffd7ff255|o|i|d| +0#0000000&|d|o|N+2&#ff404010|o|t+0&#ffd7ff255|h|i|n|g|(|)| |{| @54
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|i+0#00e0003&|n|t| +0#0000000&|x| |=| |0+0#e000002&|;+0#0000000&| @59
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@2|c+0#00e0003&|h|a|r| +0#0000000&|*|s| |=| |"+0#e000002&|h+2&#ff404010|e|l@1|o|"+0&#ffd7ff255|;+0#0000000&| @51
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|r+0#af5f00255&|e|t|u|r|n| +0#0000000&|5+0#e000002&|;+0#0000000&| @60
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @71
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|X+1#0000000&|p|r|o|g|r|a|m|1|.|c| @45|1|,|1| @11|A|l@1
+|"+0&&|X|p|r|o|g|r|a|m|2|.|c|"| |5|L|,| |7|2|B| @53
diff --git a/src/testdir/dumps/Test_diff_with_cul_bri_01.dump b/src/testdir/dumps/Test_diff_with_cul_bri_01.dump
new file mode 100644
index 0000000..8151088
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cul_bri_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@1|b+10&&|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1> +8#ffffff16#ff404010@1|f+10&&|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@21
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b+10#ffffff16&|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o+10#ffffff16&|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b+10#ffffff16&|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1| +8&&@2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o+10#ffffff16&@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b|a|z+2&#ff404010|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b|a|r+2&#ff404010|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z| +0&#ffd7ff255@2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r||+1&#ffffff0|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|<+1#0000000&|o| |N|a|m|e|]| |[|+|]| |1|,|1| @5|A|l@1| |<+3&&| |N|a|m|e|]| |[|+|]| |1|,|1| @5|A|l@1| |[+1&&|N|o| |N|a|m|e|]| @2|0|,|0|-|1| @3|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cul_bri_02.dump b/src/testdir/dumps/Test_diff_with_cul_bri_02.dump
new file mode 100644
index 0000000..62126f6
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cul_bri_02.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b+2&#ff404010|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f+2&#ff404010|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@21
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1| +0&#ffd7ff255@2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1> +8#ffffff16#ff404010@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f+8#ffffff16#ff404010|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o+8#ffffff16#ff404010|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f+8#ffffff16#ff404010|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o+8#ffffff16#ff404010@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b|a|z+2&#ff404010|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b|a|r+2&#ff404010|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z| +0&#ffd7ff255@2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r||+1&#ffffff0|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|<+1#0000000&|o| |N|a|m|e|]| |[|+|]| |2|,|1| @5|A|l@1| |<+3&&| |N|a|m|e|]| |[|+|]| |2|,|1| @5|A|l@1| |[+1&&|N|o| |N|a|m|e|]| @2|0|,|0|-|1| @3|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cul_bri_03.dump b/src/testdir/dumps/Test_diff_with_cul_bri_03.dump
new file mode 100644
index 0000000..f32be73
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cul_bri_03.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b+2&#ff404010|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f+2&#ff404010|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@21
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1| +0&#ffd7ff255@2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1> +8#ffffff16#ff404010@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f+8#ffffff16#ff404010|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o+8#ffffff16#ff404010|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f+8#ffffff16#ff404010|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o+8#ffffff16#ff404010@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b|a|z+2&#ff404010|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b|a|r+2&#ff404010|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z| +0&#ffd7ff255@2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r||+1&#ffffff0|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|<+1#0000000&|o| |N|a|m|e|]| |[|+|]| |3|,|1| @5|A|l@1| |<+3&&| |N|a|m|e|]| |[|+|]| |3|,|1| @5|A|l@1| |[+1&&|N|o| |N|a|m|e|]| @2|0|,|0|-|1| @3|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cul_bri_04.dump b/src/testdir/dumps/Test_diff_with_cul_bri_04.dump
new file mode 100644
index 0000000..9489f9a
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cul_bri_04.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|b+2&#ff404010|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f+2&#ff404010|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@21
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1|b|e@1| +0&#ffd7ff255@2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1| @2||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@1|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1|f|o@1||+1&#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +8#ffffff16#ff404010@1|b|a|z+10&&|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1> +8#ffffff16#ff404010@1|b|a|r+10&&|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b+10#ffffff16&|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|r+10#ffffff16&|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b||+1#0000000#ffffff0|~+0#4040ff13&| @22
+| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|b+10#ffffff16&|a|z|b|a|z|b|a|z|b|a|z|b|a|z|b|a|z| +8&&@2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +2#0000000#ff404010@1|a+10#ffffff16&|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r|b|a|r||+1#0000000#ffffff0|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|~| @23||+1#0000000&|~+0#4040ff13&| @22||+1#0000000&|~+0#4040ff13&| @22
+|<+1#0000000&|o| |N|a|m|e|]| |[|+|]| |4|,|1| @5|A|l@1| |<+3&&| |N|a|m|e|]| |[|+|]| |4|,|1| @5|A|l@1| |[+1&&|N|o| |N|a|m|e|]| @2|0|,|0|-|1| @3|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cursorline_01.dump b/src/testdir/dumps/Test_diff_with_cursorline_01.dump
new file mode 100644
index 0000000..d43c9ca
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cursorline_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|b+8#ffffff16#ff404010|e@1| @31||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31||+1&&| +0#0000e05#a8a8a8255@1>f+8#ffffff16#ff404010|o@1| @31
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31
+| +0#0000e05#a8a8a8255@1|b+2#0000000#ff404010|a|z| +0&#ffd7ff255@31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|f+2#0000000#ff404010|o@1| +0&#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255|a|r| @31
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cursorline_02.dump b/src/testdir/dumps/Test_diff_with_cursorline_02.dump
new file mode 100644
index 0000000..477e367
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cursorline_02.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255|e@1| @31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31
+| +0#0000e05#a8a8a8255@1|f+8#ffffff16#ff404010|o@1| @31||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1>f+8#ffffff16#ff404010|o@1| @31
+| +0#0000e05#a8a8a8255@1|b+2#0000000#ff404010|a|z| +0&#ffd7ff255@31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|f+2#0000000#ff404010|o@1| +0&#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255|a|r| @31
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|3|,|1| @11|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|2|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cursorline_03.dump b/src/testdir/dumps/Test_diff_with_cursorline_03.dump
new file mode 100644
index 0000000..efef1c1
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cursorline_03.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255|e@1| @31||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31||+1&&| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @31
+| +0#0000e05#a8a8a8255@1|b+10#ffffff16#ff404010|a|z| +8&&@31||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1>f+10#ffffff16#ff404010|o@1| +8&&@31
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|b+0#0000000#5fd7ff255|a|r| @31
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|4|,|1| @11|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|3|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cursorline_number_01.dump b/src/testdir/dumps/Test_diff_with_cursorline_number_01.dump
new file mode 100644
index 0000000..8289210
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cursorline_number_01.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|-+0#4040ff13#afffff255@30||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |b+0#0000000#5fd7ff255|a|z| @27
+| +0#0000e05#a8a8a8255@1| +8#0000001#ffffff16@1|1| >f+8#ffffff16#ff404010|o@1| @27||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +8#0000001#ffffff16@1|2| |f+8#ffffff16#ff404010|o@1| @27
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| |f+0#0000000&|o@1| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |f+0#0000000&|o@1| @27
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |b+0#0000000&|a|r| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|4| |b+0#0000000&|a|r| @27
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|2|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_diff_with_cursorline_number_02.dump b/src/testdir/dumps/Test_diff_with_cursorline_number_02.dump
new file mode 100644
index 0000000..028441c
--- /dev/null
+++ b/src/testdir/dumps/Test_diff_with_cursorline_number_02.dump
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|-+0#4040ff13#afffff255@30||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |b+0#0000000#5fd7ff255|a|z| @27
+| +0#0000e05#a8a8a8255@1| +8#0000001#ffffff16@1|1| >f+0#0000000#ffffff0|o@1| @27||+1&&| +0#0000e05#a8a8a8255@1| +8#0000001#ffffff16@1|2| |f+8#ffffff16#ff404010|o@1| @27
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| |f+0#0000000&|o@1| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |f+0#0000000&|o@1| @27
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |b+0#0000000&|a|r| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|4| |b+0#0000000&|a|r| @27
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|2|,|1| @11|A|l@1
+|:+0&&|s|e|t| |c|u|r|s|o|r|l|i|n|e|o|p|t|=|n|u|m|b|e|r| @49
diff --git a/src/testdir/dumps/Test_display_fillchars_1.dump b/src/testdir/dumps/Test_display_fillchars_1.dump
new file mode 100644
index 0000000..b0b1642
--- /dev/null
+++ b/src/testdir/dumps/Test_display_fillchars_1.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|i|n|d|o|w| |2| @28|++1&&|w+0&&|i|n|d|o|w| |1| @28
+|w|i|n|d|o|w| |2| @28|++1&&|w+0&&|i|n|d|o|w| |1| @28
+|w|i|n|d|o|w| |2| @28|++1&&|w+0&&|i|n|d|o|w| |1| @28
+|y+0#4040ff13&| @35|++1#0000000&|x+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]|b@5|1|,|1|b@11|A|l@1|b|[|N|o| |N|a|m|e|]| |[|+|]|a@5|1|,|1|a@11|A|l@1
+>w+0&&|i|n|d|o|w| |4| @28|>+1&&|w+0&&|i|n|d|o|w| |3| @28
+|w|i|n|d|o|w| |4| @28|>+1&&|w+0&&|i|n|d|o|w| |3| @28
+|w|i|n|d|o|w| |4| @28|>+1&&|w+0&&|i|n|d|o|w| |3| @28
+|o+0#4040ff13&| @35|>+1#0000000&|z+0#4040ff13&| @35
+|o| @35|>+1#0000000&|z+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]|4@5|1|,|1|4@11|A|l@1|4|[+1&&|N|o| |N|a|m|e|]| |[|+|]|c@5|1|,|1|c@11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_display_fillchars_2.dump b/src/testdir/dumps/Test_display_fillchars_2.dump
new file mode 100644
index 0000000..b983fac
--- /dev/null
+++ b/src/testdir/dumps/Test_display_fillchars_2.dump
@@ -0,0 +1,12 @@
+>w+0&#ffffff0|i|n|d|o|w| |2| @28|++1&&|w+0&&|i|n|d|o|w| |1| @28
+|w|i|n|d|o|w| |2| @28|++1&&|w+0&&|i|n|d|o|w| |1| @28
+|w|i|n|d|o|w| |2| @28|++1&&|w+0&&|i|n|d|o|w| |1| @28
+|y+0#4040ff13&| @35|++1#0000000&|x+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]|2@5|1|,|1|2@11|A|l@1|2|[+1&&|N|o| |N|a|m|e|]| |[|+|]|a@5|1|,|1|a@11|A|l@1
+|w+0&&|i|n|d|o|w| |4| @28|>+1&&|w+0&&|i|n|d|o|w| |3| @28
+|w|i|n|d|o|w| |4| @28|>+1&&|w+0&&|i|n|d|o|w| |3| @28
+|w|i|n|d|o|w| |4| @28|>+1&&|w+0&&|i|n|d|o|w| |3| @28
+|o+0#4040ff13&| @35|>+1#0000000&|z+0#4040ff13&| @35
+|o| @35|>+1#0000000&|z+0#4040ff13&| @35
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]|d@5|1|,|1|d@11|A|l@1|d|[|N|o| |N|a|m|e|]| |[|+|]|c@5|1|,|1|c@11|A|l@1
+|:+0&&|w|i|n|c|m|d| |k| @65
diff --git a/src/testdir/dumps/Test_display_lastline_1.dump b/src/testdir/dumps/Test_display_lastline_1.dump
new file mode 100644
index 0000000..d074773
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_1.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0||+1&&|a+0&&@2| @69
+|a||+1&&|b+0&&@72
+|a||+1&&|b+0&&@72
+@1||+1&&|b+0&&@53| @18
+|b||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|@||+1#0000000&|~+0#4040ff13&| @71
+|<+3#0000000&| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_display_lastline_2.dump b/src/testdir/dumps/Test_display_lastline_2.dump
new file mode 100644
index 0000000..065e5d8
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_2.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0||+1&&|a+0&&@2| @69
+|a||+1&&|b+0&&@72
+|a||+1&&|b+0&&@72
+@1||+1&&|b+0&&@53| @18
+|b||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|@||+1#0000000&|~+0#4040ff13&| @71
+|<+3#0000000&| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|s|p|l|a|y|=|l|a|s|t|l|i|n|e| @53
diff --git a/src/testdir/dumps/Test_display_lastline_3.dump b/src/testdir/dumps/Test_display_lastline_3.dump
new file mode 100644
index 0000000..38673ab
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_3.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@2| @69||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@53| @18||+1&&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|@+0#4040ff13&
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1| |<+1&&
+|:+0&&|1|0@1|w|i|n|c|m|d| |>| @62
diff --git a/src/testdir/dumps/Test_display_lastline_4.dump b/src/testdir/dumps/Test_display_lastline_4.dump
new file mode 100644
index 0000000..2978631
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_4.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@2| @69||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@53| @18||+1&&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|@+0#4040ff13&
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1| |<+1&&
+|:+0&&|s|e|t| |d|i|s|p|l|a|y|=|t|r|u|n|c|a|t|e| @53
diff --git a/src/testdir/dumps/Test_display_lastline_5.dump b/src/testdir/dumps/Test_display_lastline_5.dump
new file mode 100644
index 0000000..7567da1
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_5.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@2| @71
+|b@74
+|@+0#4040ff13&@2| @71
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+|a+0&&@2| @71
+|b@74
+@75
+@50| @24
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+|:+0&&|3|s|p|l|i|t| @67
diff --git a/src/testdir/dumps/Test_display_lastline_6.dump b/src/testdir/dumps/Test_display_lastline_6.dump
new file mode 100644
index 0000000..e5ede7f
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_6.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@1||+1&&|a+0&&@2| @68
+|a| ||+1&&|b+0&&@71
+@2||+1&&|b+0&&@71
+@2||+1&&|b+0&&@55| @15
+|b@1||+1&&|~+0#4040ff13&| @70
+|b+0#0000000&@1||+1&&|~+0#4040ff13&| @70
+|b+0#0000000&@1||+1&&|~+0#4040ff13&| @70
+|@@1||+1#0000000&|~+0#4040ff13&| @70
+|<+3#0000000&|1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @40|1|,|1| @11|A|l@1
+|:+0&&|2|v|s|p|l|i|t| @66
diff --git a/src/testdir/dumps/Test_display_lastline_euro_1.dump b/src/testdir/dumps/Test_display_lastline_euro_1.dump
new file mode 100644
index 0000000..2223d87
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_euro_1.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0||+1&&|a+0&&@2| @69
+|a||+1&&|b+0&&@72
+|a||+1&&|b+0&&@72
+@1||+1&&|b+0&&@53| @18
+|b||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|€||+1#0000000&|~+0#4040ff13&| @71
+|<+3#0000000&| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_display_lastline_euro_2.dump b/src/testdir/dumps/Test_display_lastline_euro_2.dump
new file mode 100644
index 0000000..e8da202
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_euro_2.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0||+1&&|a+0&&@2| @69
+|a||+1&&|b+0&&@72
+|a||+1&&|b+0&&@72
+@1||+1&&|b+0&&@53| @18
+|b||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|b+0#0000000&||+1&&|~+0#4040ff13&| @71
+|€||+1#0000000&|~+0#4040ff13&| @71
+|<+3#0000000&| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|s|p|l|a|y|=|l|a|s|t|l|i|n|e| @53
diff --git a/src/testdir/dumps/Test_display_lastline_euro_3.dump b/src/testdir/dumps/Test_display_lastline_euro_3.dump
new file mode 100644
index 0000000..db414e7
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_euro_3.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@2| @69||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@53| @18||+1&&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|€+0#4040ff13&
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1| |<+1&&
+|:+0&&|1|0@1|w|i|n|c|m|d| |>| @62
diff --git a/src/testdir/dumps/Test_display_lastline_euro_4.dump b/src/testdir/dumps/Test_display_lastline_euro_4.dump
new file mode 100644
index 0000000..492438c
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_euro_4.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@2| @69||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@72||+1&&|a+0&&
+|b@53| @18||+1&&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|b+0&&
+|~+0#4040ff13&| @71||+1#0000000&|€+0#4040ff13&
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @41|1|,|1| @11|A|l@1| |<+1&&
+|:+0&&|s|e|t| |d|i|s|p|l|a|y|=|t|r|u|n|c|a|t|e| @53
diff --git a/src/testdir/dumps/Test_display_lastline_euro_5.dump b/src/testdir/dumps/Test_display_lastline_euro_5.dump
new file mode 100644
index 0000000..75fc732
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_euro_5.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@2| @71
+|b@74
+|€+0#4040ff13&@2| @71
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+|a+0&&@2| @71
+|b@74
+@75
+@50| @24
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+|:+0&&|3|s|p|l|i|t| @67
diff --git a/src/testdir/dumps/Test_display_lastline_euro_6.dump b/src/testdir/dumps/Test_display_lastline_euro_6.dump
new file mode 100644
index 0000000..80e2cbb
--- /dev/null
+++ b/src/testdir/dumps/Test_display_lastline_euro_6.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@1||+1&&|a+0&&@2| @68
+|a| ||+1&&|b+0&&@71
+@2||+1&&|b+0&&@71
+@2||+1&&|b+0&&@55| @15
+|b@1||+1&&|~+0#4040ff13&| @70
+|b+0#0000000&@1||+1&&|~+0#4040ff13&| @70
+|b+0#0000000&@1||+1&&|~+0#4040ff13&| @70
+|€@1||+1#0000000&|~+0#4040ff13&| @70
+|<+3#0000000&|1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @40|1|,|1| @11|A|l@1
+|:+0&&|2|v|s|p|l|i|t| @66
diff --git a/src/testdir/dumps/Test_display_scroll_at_topline.dump b/src/testdir/dumps/Test_display_scroll_at_topline.dump
new file mode 100644
index 0000000..9c843c0
--- /dev/null
+++ b/src/testdir/dumps/Test_display_scroll_at_topline.dump
@@ -0,0 +1,4 @@
+> +0&#ffffff0@19
+|a@19
+@1| @18
+|~+0#4040ff13&| @18
diff --git a/src/testdir/dumps/Test_display_scroll_update_visual.dump b/src/testdir/dumps/Test_display_scroll_update_visual.dump
new file mode 100644
index 0000000..c174390
--- /dev/null
+++ b/src/testdir/dumps/Test_display_scroll_update_visual.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1>f+0#0000000#ffffff0|o+0&#e0e0e08@1| | +0&#ffffff0@53
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @54
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @54
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @54
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @54
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @54
+| +0#0000e05#a8a8a8255@1|f+0#0000000#ffffff0|o@1| @54
+|-+2&&@1| |V|I|S|U|A|L| |L|I|N|E| |-@1| +0&&@14|2| @8|2|,|1| @10|3@1|%|
diff --git a/src/testdir/dumps/Test_display_unprintable_01.dump b/src/testdir/dumps/Test_display_unprintable_01.dump
new file mode 100644
index 0000000..552b402
--- /dev/null
+++ b/src/testdir/dumps/Test_display_unprintable_01.dump
@@ -0,0 +1,9 @@
+>m+0&#ffffff0|a|c| @46
+|^+0#0000e05&|J|t+0#0000000&|w|o|^+0#0000e05&|J| +0#0000000&@42
+|~+0#4040ff13&| @48
+|X+3#0000000&|m|a|c|.|t|x|t| @23|1|,|1| @11|A|l@1
+|u+0&&|n|i|x|^+0#0000e05&|M| +0#0000000&@43
+|t|w|o| @46
+|~+0#4040ff13&| @48
+|X+1#0000000&|u|n|i|x|.|t|x|t| @22|1|,|1| @11|A|l@1
+|"+0&&|X|m|a|c|.|t|x|t|"| |[|n|o|e|o|l|]|[|m|a|c|]| |2|L|,| |9|B| @19
diff --git a/src/testdir/dumps/Test_display_unprintable_02.dump b/src/testdir/dumps/Test_display_unprintable_02.dump
new file mode 100644
index 0000000..6338465
--- /dev/null
+++ b/src/testdir/dumps/Test_display_unprintable_02.dump
@@ -0,0 +1,9 @@
+|m+0&#ffffff0|a|c| @46
+|^+0#0000e05&|J|t+0#0000000&|w|o|^+0#0000e05&|J| +0#0000000&@42
+|~+0#4040ff13&| @48
+|X+1#0000000&|m|a|c|.|t|x|t| @23|1|,|1| @11|A|l@1
+>u+0&&|n|i|x|^+0#0000e05&|M| +0#0000000&@43
+|t|w|o| @46
+|~+0#4040ff13&| @48
+|X+3#0000000&|u|n|i|x|.|t|x|t| @22|1|,|1| @11|A|l@1
+| +0&&@49
diff --git a/src/testdir/dumps/Test_display_visual_block_scroll.dump b/src/testdir/dumps/Test_display_visual_block_scroll.dump
new file mode 100644
index 0000000..afb52fb
--- /dev/null
+++ b/src/testdir/dumps/Test_display_visual_block_scroll.dump
@@ -0,0 +1,7 @@
+|{+0&#e0e0e08| | +0&#ffffff0@72
+|}+0&#e0e0e08| | +0&#ffffff0@72
+|{+0&#e0e0e08| | +0&#ffffff0@72
+|f+0&#e0e0e08| | +0&#ffffff0@72
+>g| +0&#e0e0e08| +0&#ffffff0@72
+|}| @73
+|-+2&&@1| |V|I|S|U|A|L| |L|I|N|E| |-@1| +0&&@29|7| @8|1@1|,|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_echowin_eval.dump b/src/testdir/dumps/Test_echowin_eval.dump
new file mode 100644
index 0000000..80d5fc7
--- /dev/null
+++ b/src/testdir/dumps/Test_echowin_eval.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|â•+0#e000002&@74
+|t|e|s|t| @70
+|1+0#0000000&|2|3| @53|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_echowin_showmode.dump b/src/testdir/dumps/Test_echowin_showmode.dump
new file mode 100644
index 0000000..24eb4d0
--- /dev/null
+++ b/src/testdir/dumps/Test_echowin_showmode.dump
@@ -0,0 +1,8 @@
+>o+0&#ffffff0|n+0&#e0e0e08|e| | +0&#ffffff0@70
+|t|w|o| @71
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|â•+0#e000002&@74
+|e|c|h|o| |w|i|n|d|o|w| @63
+|-+2#0000000&@1| |V|I|S|U|A|L| |L|I|N|E| |-@1| +0&&@29|1| @8|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_1.dump b/src/testdir/dumps/Test_echowindow_1.dump
new file mode 100644
index 0000000..8c3db07
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_1.dump
@@ -0,0 +1,8 @@
+>s+0&#ffffff0|o|m|e| |t|e|x|t| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|â•+0#e000002&@74
+|f|i|r|s|t| |l|i|n|e| @64
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_2.dump b/src/testdir/dumps/Test_echowindow_2.dump
new file mode 100644
index 0000000..58b2f3f
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_2.dump
@@ -0,0 +1,8 @@
+>s+0&#ffffff0|o|m|e| |t|e|x|t| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|â•+0#e000002&@74
+|f|i|r|s|t| |l|i|n|e| @64
+|s|e|c|o|n|d| |l|i|n|e| @63
+|:+0#0000000&|c|a|l@1| |S|h|o|w|M|e|s@1|a|g|e|(|'|s|e|c|o|n|d| |l|i|n|e|'|)| @24|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_3.dump b/src/testdir/dumps/Test_echowindow_3.dump
new file mode 100644
index 0000000..5d84236
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_3.dump
@@ -0,0 +1,8 @@
+>s+0&#ffffff0|o|m|e| |t|e|x|t| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_4.dump b/src/testdir/dumps/Test_echowindow_4.dump
new file mode 100644
index 0000000..c8bd387
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_4.dump
@@ -0,0 +1,8 @@
+>â•+0#e000002#ffffff0@74
+|l|i|n|e| |1|4| @67
+|l|i|n|e| |1|5| @67
+|l|i|n|e| |1|6| @67
+|l|i|n|e| |1|7| @67
+|l|i|n|e| |1|8| @67
+|l|i|n|e| |1|9| @67
+|:+0#0000000&|c|a|l@1| |M|a|n|y|M|e|s@1|a|g|e|s|(|)| @36|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_5.dump b/src/testdir/dumps/Test_echowindow_5.dump
new file mode 100644
index 0000000..a87640c
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_5.dump
@@ -0,0 +1,8 @@
+>s+0&#ffffff0|o|m|e| |t|e|x|t| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|â•+0#e000002&@74
+|m|e|s@1|a|g|e| @67
+|o+0#0000000&|n|e| @71
+|t|w|o| @53|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_6.dump b/src/testdir/dumps/Test_echowindow_6.dump
new file mode 100644
index 0000000..3bf6303
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_6.dump
@@ -0,0 +1,8 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|o+0#0000000&|n|e| @71
+|t|w|o| @71
+|t|h|r|e@1| @69
+|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@35
diff --git a/src/testdir/dumps/Test_echowindow_7.dump b/src/testdir/dumps/Test_echowindow_7.dump
new file mode 100644
index 0000000..774495f
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_7.dump
@@ -0,0 +1,8 @@
+>s+0&#ffffff0|o|m|e| |t|e|x|t| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|â•+0#e000002&@74
+|l|a|t|e|r| |m|e|s@1|a|g|e| @61
+| +0#0000000&@74
+@57|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_8.dump b/src/testdir/dumps/Test_echowindow_8.dump
new file mode 100644
index 0000000..cb5b50d
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_8.dump
@@ -0,0 +1,8 @@
+| +8#0000001#e0e0e08|+| |[|N|o| |N|a|m|e|]| | +2#0000000#ffffff0|[|N|o| |N|a|m|e|]| | +1&&@49|X+8#0000001#e0e0e08
+> +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|â•+0#e000002&@74
+|l|a|t|e|r| |m|e|s@1|a|g|e| @61
+|m|o|r|e| @70
+|:+0#0000000&|7|e|c|h|o|w|i|n| |'|m|o|r|e|'| @58
+@57|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_echowindow_9.dump b/src/testdir/dumps/Test_echowindow_9.dump
new file mode 100644
index 0000000..2efdef0
--- /dev/null
+++ b/src/testdir/dumps/Test_echowindow_9.dump
@@ -0,0 +1,8 @@
+| +8#0000001#e0e0e08|+| |[|N|o| |N|a|m|e|]| | +2#0000000#ffffff0|[|N|o| |N|a|m|e|]| | +1&&@49|X+8#0000001#e0e0e08
+> +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |H|i|d|e|W|i|n|(|)| @59
+@57|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_fileinfo_after_echo.dump b/src/testdir/dumps/Test_fileinfo_after_echo.dump
new file mode 100644
index 0000000..787dced
--- /dev/null
+++ b/src/testdir/dumps/Test_fileinfo_after_echo.dump
@@ -0,0 +1,6 @@
+>h+0&#ffffff0|i| @72
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|'+0#0000000&|b|'| |w|r|i|t@1|e|n| @45|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_folds_with_rnu_01.dump b/src/testdir/dumps/Test_folds_with_rnu_01.dump
new file mode 100644
index 0000000..5e6724e
--- /dev/null
+++ b/src/testdir/dumps/Test_folds_with_rnu_01.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| @2|0| >+|-@1| @1|2| |l|i|n|e|s|:| |-@54
+|+| @2|1| |+|-@1| @1|2| |l|i|n|e|s|:| |-@54
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_folds_with_rnu_02.dump b/src/testdir/dumps/Test_folds_with_rnu_02.dump
new file mode 100644
index 0000000..d4a2646
--- /dev/null
+++ b/src/testdir/dumps/Test_folds_with_rnu_02.dump
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| @2|1| |+|-@1| @1|2| |l|i|n|e|s|:| |-@54
+|+| @2|0| >+|-@1| @1|2| |l|i|n|e|s|:| |-@54
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_functions_echoraw.dump b/src/testdir/dumps/Test_functions_echoraw.dump
new file mode 100644
index 0000000..f27cd03
--- /dev/null
+++ b/src/testdir/dumps/Test_functions_echoraw.dump
@@ -0,0 +1,5 @@
+>x+0&#ffffff0|e|l@1|o| @34
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_1.dump b/src/testdir/dumps/Test_hlsearch_1.dump
new file mode 100644
index 0000000..abcd91c
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_1.dump
@@ -0,0 +1,6 @@
+|x+0&#ffff4012@2| | +0&#ffffff0@45
+>x+8&#ffff4012@2| | +8&#ffffff0@45
+|x+0&#ffff4012@2| | +0&#ffffff0@45
+|~+0#4040ff13&| @48
+|~| @48
+| +0#0000000&@31|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_2.dump b/src/testdir/dumps/Test_hlsearch_2.dump
new file mode 100644
index 0000000..0cb13c3
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_2.dump
@@ -0,0 +1,6 @@
+|x+0&#ffff4012@2| | +0&#ffffff0@45
+|x+0&#ffff4012@2| | +0&#ffffff0@45
+>x+8&#ffff4012@2| | +8&#ffffff0@45
+|~+0#4040ff13&| @48
+|~| @48
+|/+0#0000000&|\|_|.|*| @26|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_block_visual_match.dump b/src/testdir/dumps/Test_hlsearch_block_visual_match.dump
new file mode 100644
index 0000000..0b45678
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_block_visual_match.dump
@@ -0,0 +1,9 @@
+|a+0&#ffff4012@1| | +0&#ffffff0@56
+>b+0&#ffff4012@3| | +0&#ffffff0@54
+|c+0&#ffff4012@5| | +0&#ffffff0@52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+|/+0#0000000&|\|%|V| @37|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_ctrlr_1.dump b/src/testdir/dumps/Test_hlsearch_ctrlr_1.dump
new file mode 100644
index 0000000..e0a0af3
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_ctrlr_1.dump
@@ -0,0 +1,6 @@
+| +0&#ffffff0@59
+|t+1&&|e|x|t| +0&&@55
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|/+0#0000000&|t|e|x|t> @54
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_changed_1.dump b/src/testdir/dumps/Test_hlsearch_cursearch_changed_1.dump
new file mode 100644
index 0000000..607e079
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_changed_1.dump
@@ -0,0 +1,9 @@
+|-+0&#ffffff0@2| @56
+|a+0&#ffff4012|b|c|d|e+0&#ffffff0|f|g| @52
+>a+0&#4040ff13|b|c|d|e+0&#ffffff0|f|g| @52
+|h|i|j|k|l| @54
+|-@2| @56
+|a+0&#ffff4012|b|c|d|e+0&#ffffff0|f|g| @52
+|h|i|j|k|l| @54
+|~+0#4040ff13&| @58
+| +0#0000000&@41|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_1.dump b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_1.dump
new file mode 100644
index 0000000..950ffef
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_1.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| @56
+>f+0&#4040ff13|o@1| | +0&#ffffff0@55
+|b+0&#4040ff13|a|r| +0&#ffffff0@56
+|b|a|z| @56
+|f+0&#ffff4012|o@1| | +0&#ffffff0@55
+|b+0&#ffff4012|a|r| +0&#ffffff0@56
+|~+0#4040ff13&| @58
+|~| @58
+|/+0#0000000&|f|o@1|\|n|b|a|r| @32|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_2.dump b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_2.dump
new file mode 100644
index 0000000..7b091cf
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_2.dump
@@ -0,0 +1,9 @@
+|-+0&#ffffff0@2| @56
+|a|b|c|d>e+0&#4040ff13|f|g| | +0&#ffffff0@51
+|h+0&#4040ff13|i|j|k+0&#ffffff0|l| @54
+|-@2| @56
+|a|b|c|d|e+0&#ffff4012|f|g| | +0&#ffffff0@51
+|h+0&#ffff4012|i|j|k+0&#ffffff0|l| @54
+|~+0#4040ff13&| @58
+|~| @58
+|/+0#0000000&|e|f|g|\|n|h|i|j| @32|2|,|5| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_3.dump b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_3.dump
new file mode 100644
index 0000000..ef03675
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_3.dump
@@ -0,0 +1,9 @@
+|-+0&#ffffff0@2| @56
+|a|b|c>d|e+0&#ffff4012|f|g| | +0&#ffffff0@51
+|h+0&#ffff4012|i|j|k+0&#ffffff0|l| @54
+|-@2| @56
+|a|b|c|d|e+0&#ffff4012|f|g| | +0&#ffffff0@51
+|h+0&#ffff4012|i|j|k+0&#ffffff0|l| @54
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|2|,|4| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_4.dump b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_4.dump
new file mode 100644
index 0000000..2235295
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_4.dump
@@ -0,0 +1,9 @@
+|-+0&#ffffff0@2| @56
+|a|b|c|d|e+0&#ffff4012|f|g| | +0&#ffffff0@51
+|h+0&#ffff4012|i|j>k+0&#ffffff0|l| @54
+|-@2| @56
+|a|b|c|d|e+0&#ffff4012|f|g| | +0&#ffffff0@51
+|h+0&#ffff4012|i|j|k+0&#ffffff0|l| @54
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|3|,|4| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_5.dump b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_5.dump
new file mode 100644
index 0000000..2294566
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_multiple_line_5.dump
@@ -0,0 +1,9 @@
+|-+0&#ffffff0@2| @56
+|a|b|c|d|e+0&#4040ff13|f|g| | +0&#ffffff0@51
+|h+0&#4040ff13|i>j|k+0&#ffffff0|l| @54
+|-@2| @56
+|a|b|c|d|e+0&#ffff4012|f|g| | +0&#ffffff0@51
+|h+0&#ffff4012|i|j|k+0&#ffffff0|l| @54
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|3|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_single_line_1.dump b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_1.dump
new file mode 100644
index 0000000..ab72665
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_1.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| @56
+>f+0&#4040ff13|o@1| +0&#ffffff0@56
+|b|a|r| @56
+|b|a|z| @56
+|f+0&#ffff4012|o@1| +0&#ffffff0|t|h|e| |f+0&#ffff4012|o@1| +0&#ffffff0|a|n|d| |f+0&#ffff4012|o@1| +0&#ffffff0@40
+|b|a|r| @56
+|~+0#4040ff13&| @58
+|~| @58
+|/+0#0000000&|f|o@1| @37|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2.dump b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2.dump
new file mode 100644
index 0000000..dd4aa89
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| @56
+|f+0&#ffff4012|o@1| +0&#ffffff0@56
+|b|a|r| @56
+|b|a|z| @56
+>f+0&#4040ff13|o@1| +0&#ffffff0|t|h|e| |f+0&#ffff4012|o@1| +0&#ffffff0|a|n|d| |f+0&#ffff4012|o@1| +0&#ffffff0@40
+|b|a|r| @56
+|~+0#4040ff13&| @58
+|~| @58
+|/+0#0000000&|f|o@1| @37|5|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2a.dump b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2a.dump
new file mode 100644
index 0000000..2cc3c17
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2a.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| @56
+|f+0&#ffff4012|o@1| +0&#ffffff0@56
+|b|a|r| @56
+|b|a|z| @56
+|f+0&#ffff4012|o@1| +0&#ffffff0|t|h|e| >f+0&#4040ff13|o@1| +0&#ffffff0|a|n|d| |f+0&#ffff4012|o@1| +0&#ffffff0@40
+|b|a|r| @56
+|~+0#4040ff13&| @58
+|~| @58
+|/+0#0000000&|f|o@1| @37|5|,|9| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2b.dump b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2b.dump
new file mode 100644
index 0000000..0b20bd5
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_2b.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| @56
+|f+0&#ffff4012|o@1| +0&#ffffff0@56
+|b|a|r| @56
+|b|a|z| @56
+|f+0&#ffff4012|o@1| +0&#ffffff0|t|h|e| |f+0&#ffff4012|o@1| +0&#ffffff0|a|n|d| >f+0&#4040ff13|o@1| +0&#ffffff0@40
+|b|a|r| @56
+|~+0#4040ff13&| @58
+|~| @58
+|/+0#0000000&|f|o@1| @37|5|,|1|7| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_cursearch_single_line_3.dump b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_3.dump
new file mode 100644
index 0000000..b0c38c2
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_cursearch_single_line_3.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| @56
+>f+0&#4040ff13|o@1| +0&#ffffff0@56
+|b|a|r| @56
+|b|a|z| @56
+|f+0&#ffff4012|o@1| +0&#ffffff0@56
+|b|a|r| @56
+|~+0#4040ff13&| @58
+|~| @58
+|?+0#0000000&|f|o@1| @37|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hlsearch_visual_1.dump b/src/testdir/dumps/Test_hlsearch_visual_1.dump
new file mode 100644
index 0000000..99150db
--- /dev/null
+++ b/src/testdir/dumps/Test_hlsearch_visual_1.dump
@@ -0,0 +1,6 @@
+|x+0&#ffffff0@2| |y+2&#ffff4012|y+2&#e0e0e08@1| +0&&|z@2| | +0&#ffffff0@27
+|x+0&#e0e0e08@2| |y+2&&@2| +0&&|z@2| | +0&#ffffff0@27
+|x+0&#e0e0e08@2| |y+2&&>y+2&#ffff4012@1| +0&#ffffff0|z@2| @28
+|~+0#4040ff13&| @38
+|~| @38
+|-+2#0000000&@1| |V|I|S|U|A|L| |-@1|3+0&&| @8|3|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_hor_scroll_1.dump b/src/testdir/dumps/Test_hor_scroll_1.dump
new file mode 100644
index 0000000..615afb8
--- /dev/null
+++ b/src/testdir/dumps/Test_hor_scroll_1.dump
@@ -0,0 +1,8 @@
+|a+0&#ffffff0| |b@1| |c@1| |d@1| |e@1| |f@1| |g@1| ||+1&&|a+0&&@1| |b@1| |c@1| |d@1| |e@1| |f@1| |g@1> |h@1| |i@1| |j@1| |k@1| |l@1| |m@1| |n@1| |o@1| |p@1| |q@1| |r@1|
+@19| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+@19| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+@19| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+@19| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @52
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|2|1| @2|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @22|1|,|2|1| @10|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_hor_scroll_2.dump b/src/testdir/dumps/Test_hor_scroll_2.dump
new file mode 100644
index 0000000..dfe2e98
--- /dev/null
+++ b/src/testdir/dumps/Test_hor_scroll_2.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0|h@1| |i@1| |j@1| |k@1| |l@1| |m@1| |n||+1&&|a+0&&@1| |b@1| |c@1| |d@1| |e@1| |f@1| |g@1| |h@1| |i@1| |j@1| >k@1| |l@1| |m@1| |n@1| |o@1| |p@1| |q@1| |r@1|
+@10| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+@10| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+@10| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+@10| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @52
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|3|1| @2|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @22|1|,|3|1| @10|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_hor_scroll_3.dump b/src/testdir/dumps/Test_hor_scroll_3.dump
new file mode 100644
index 0000000..db9fd6f
--- /dev/null
+++ b/src/testdir/dumps/Test_hor_scroll_3.dump
@@ -0,0 +1,8 @@
+|a+8&#ffffff0| |b@1| |c@1| |d@1| |e@1| |f@1| |g@1| ||+1&&|a+8&&@1| |b@1| |c@1| |d@1| |e@1| |f@1| |g@1> |h@1| |i@1| |j@1| |k@1| |l@1| |m@1| |n@1| |o@1| |p@1| |q@1| |r@1|
+| +0&&@18| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+@19| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+@19| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+@19| +0&#e0e0e08||+1&#ffffff0| +0&&@19| +0&#e0e0e08| +0&#ffffff0@32
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @52
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|2|1| @2|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @22|1|,|2|1| @10|A|l@1
+|:+0&&|w|i|n|d|o| |:|s|e|t| |c|u|r|s|o|r|l|i|n|e| @52
diff --git a/src/testdir/dumps/Test_hor_scroll_4.dump b/src/testdir/dumps/Test_hor_scroll_4.dump
new file mode 100644
index 0000000..8182160
--- /dev/null
+++ b/src/testdir/dumps/Test_hor_scroll_4.dump
@@ -0,0 +1,8 @@
+| +8&#ffffff0|h@1| |i@1| |j@1| |k@1| |l@1| |m@1| |n||+1&&|a+8&&@1| |b@1| |c@1| |d@1| |e@1| |f@1| |g@1| |h@1| |i@1| |j@1| >k@1| |l@1| |m@1| |n@1| |o@1| |p@1| |q@1| |r@1|
+| +0&&@9| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+@10| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+@10| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+@10| +0&#e0e0e08| +0&#ffffff0@8||+1&&| +0&&@29| +0&#e0e0e08| +0&#ffffff0@22
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @52
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|3|1| @2|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @22|1|,|3|1| @10|A|l@1
+|:+0&&|w|i|n|d|o| |:|s|e|t| |c|u|r|s|o|r|l|i|n|e| @52
diff --git a/src/testdir/dumps/Test_hor_scroll_5.dump b/src/testdir/dumps/Test_hor_scroll_5.dump
new file mode 100644
index 0000000..f2986f3
--- /dev/null
+++ b/src/testdir/dumps/Test_hor_scroll_5.dump
@@ -0,0 +1,8 @@
+|k+0&#ffffff0@1| |l@1| |m@1| |n@1| |o@1| |p@1| |q@1||+1&&|a+0&&@1| |b@1| |c@1| |d@1| |e@1| |f@1| |g@1| |h@1| |i@1| |j@1| |k@1| |l@1| |m@1| |n>n| |o@1| |p@1| |q@1| |r@1|
+@20||+1&&| +0&&@53
+@20||+1&&| +0&&@53
+@20||+1&&| +0&&@53
+@20||+1&&| +0&&@53
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @52
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|4|1| @2|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @22|1|,|4|1| @10|A|l@1
+|:+0&&|w|i|n|d|o| |:|s|e|t| |n|o|c|u|r|s|o|r|l|i|n|e| |n|o|c|u|r|s|o|r|c|o|l|u|m|n| @35
diff --git a/src/testdir/dumps/Test_incsearch_change_01.dump b/src/testdir/dumps/Test_incsearch_change_01.dump
new file mode 100644
index 0000000..1c84faf
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_change_01.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| @66
+|x| @68
+|t|h|r|e@1| @64
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|%|s|/|X> @64
diff --git a/src/testdir/dumps/Test_incsearch_newline1.dump b/src/testdir/dumps/Test_incsearch_newline1.dump
new file mode 100644
index 0000000..9e9241a
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_newline1.dump
@@ -0,0 +1,5 @@
+|t+1&#ffffff0|e|s|t| +0&&@5
+|x@2| @6
+|~+0#4040ff13&| @8
+|~| @8
+|/+0#0000000&|t|e|s|t> @4
diff --git a/src/testdir/dumps/Test_incsearch_newline2.dump b/src/testdir/dumps/Test_incsearch_newline2.dump
new file mode 100644
index 0000000..677343d
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_newline2.dump
@@ -0,0 +1,5 @@
+|t+1&#ffffff0|e|s|t| | +0&&@4
+|x@2| @6
+|~+0#4040ff13&| @8
+|~| @8
+|/+0#0000000&|t|e|s|t|\|n> @2
diff --git a/src/testdir/dumps/Test_incsearch_newline3.dump b/src/testdir/dumps/Test_incsearch_newline3.dump
new file mode 100644
index 0000000..d80cc74
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_newline3.dump
@@ -0,0 +1,5 @@
+|t+1&#ffffff0|e|s|t| | +0&&@4
+|x+1&&|x+0&&@1| @6
+|~+0#4040ff13&| @8
+|~| @8
+|/+0#0000000&|t|e|s|t|\|n|x> @1
diff --git a/src/testdir/dumps/Test_incsearch_newline4.dump b/src/testdir/dumps/Test_incsearch_newline4.dump
new file mode 100644
index 0000000..50d88b3
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_newline4.dump
@@ -0,0 +1,5 @@
+|t+1&#ffffff0|e|s|t| | +0&&@4
+|x+1&&@1|x+0&&| @6
+|~+0#4040ff13&| @8
+|~| @8
+|/+0#0000000&|t|e|s|t|\|n|x@1>
diff --git a/src/testdir/dumps/Test_incsearch_newline5.dump b/src/testdir/dumps/Test_incsearch_newline5.dump
new file mode 100644
index 0000000..6568348
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_newline5.dump
@@ -0,0 +1,5 @@
+|o+0#00e0003#ffffff0|r| |t|y|p|e| |c|o
+|m| @7|m
+|a|n|d| |t|o| |c|o|n
+|t| @7|i
+|n|u|e> +0#0000000&@6
diff --git a/src/testdir/dumps/Test_incsearch_scrolling_01.dump b/src/testdir/dumps/Test_incsearch_scrolling_01.dump
new file mode 100644
index 0000000..c133d5f
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_scrolling_01.dump
@@ -0,0 +1,9 @@
+|.+0&#ffffff0@69
+@50| @19
+|.@69
+@50| @19
+@70
+|t+1&&|a|r|g|e+0&&|t| @63
+|@+0#4040ff13&@2| @66
+|/+0#0000000&|t|a|r|g> @64
+@70
diff --git a/src/testdir/dumps/Test_incsearch_search_01.dump b/src/testdir/dumps/Test_incsearch_search_01.dump
new file mode 100644
index 0000000..324abfe
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_search_01.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|o|o+0&#ffffff0| |1| @64
+|f+0&#ffff4012|o|o+0&#ffffff0| |2| @64
+|f+0&#ffff4012|o|o+0&#ffffff0| |3| @64
+|f+1&&|o|o+0&&| |4| @64
+|f+0&#ffff4012|o|o+0&#ffffff0| |5| @64
+|f+0&#ffff4012|o|o+0&#ffffff0| |6| @64
+|f+0&#ffff4012|o|o+0&#ffffff0| |7| @64
+|f+0&#ffff4012|o|o+0&#ffffff0| |8| @64
+|/|f|o> @66
diff --git a/src/testdir/dumps/Test_incsearch_search_02.dump b/src/testdir/dumps/Test_incsearch_search_02.dump
new file mode 100644
index 0000000..6c1b743
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_search_02.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |1| @64
+|f|o@1| |2| @64
+|f|o+1&&|o+0&&| |3| @64
+|f|o@1| |4| @64
+|f|o@1| |5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|/|\|v> @66
diff --git a/src/testdir/dumps/Test_incsearch_sort_01.dump b/src/testdir/dumps/Test_incsearch_sort_01.dump
new file mode 100644
index 0000000..6c003af
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_sort_01.dump
@@ -0,0 +1,9 @@
+|a+0&#ffffff0|n|o|t|h|e|r| |o+1&&|n|e+0&&| |2| @56
+|t|h|a|t| |o+0&#ffff4012|n|e+0&#ffffff0| |3| @59
+|t|h|e| |o+0&#ffff4012|n|e+0&#ffffff0| |1| @60
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|s|o|r|t| |n|i| |u| |/|o|n> @55
diff --git a/src/testdir/dumps/Test_incsearch_sort_02.dump b/src/testdir/dumps/Test_incsearch_sort_02.dump
new file mode 100644
index 0000000..ee752e1
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_sort_02.dump
@@ -0,0 +1,9 @@
+|a+0&#ffffff0|n|o|t|h|e|r| |o+1&&|n|e+0&&| |2| @56
+|t|h|a|t| |o+0&#ffff4012|n|e+0&#ffffff0| |3| @59
+|t|h|e| |o+0&#ffff4012|n|e+0&#ffffff0| |1| @60
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|s|o|r|t|!| |/|o|n> @59
diff --git a/src/testdir/dumps/Test_incsearch_sub_01.dump b/src/testdir/dumps/Test_incsearch_sub_01.dump
new file mode 100644
index 0000000..5924fbd
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_sub_01.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |1| @64
+|f|o@1| |2| @64
+|f|o@1| |3| @64
+|f|o@1| |4| @64
+|a|b|c|||d|e|f| @62
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|:+0#0000000&|%|s|/|\|v|a|b|c||> @59
diff --git a/src/testdir/dumps/Test_incsearch_sub_02.dump b/src/testdir/dumps/Test_incsearch_sub_02.dump
new file mode 100644
index 0000000..c6b58cb
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_sub_02.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |1| @64
+|f|o@1| |2| @64
+|f|o@1| |3| @64
+|f|o@1| |4| @64
+|a|b|c|||d|e|f| @62
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|:+0#0000000&|1|,|5|s|/|\|v||> @60
diff --git a/src/testdir/dumps/Test_incsearch_substitute_01.dump b/src/testdir/dumps/Test_incsearch_substitute_01.dump
new file mode 100644
index 0000000..63886f5
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_01.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |1| @64
+|f|o@1| |2| @64
+|f+1&&|o@1| +0&&|3| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|4| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|:|.|,|.|+|2|s|/|f|o@1> @58
diff --git a/src/testdir/dumps/Test_incsearch_substitute_02.dump b/src/testdir/dumps/Test_incsearch_substitute_02.dump
new file mode 100644
index 0000000..fd1f912
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_02.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |1| @64
+|f|o@1| |2| @64
+|f|o@1| |3| @64
+|f+1&&|o@1| +0&&|4| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|5| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|:|.|,|.|+|2|s|/@1> @60
diff --git a/src/testdir/dumps/Test_incsearch_substitute_03.dump b/src/testdir/dumps/Test_incsearch_substitute_03.dump
new file mode 100644
index 0000000..5d7afa0
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_03.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|o@1| +0&#ffffff0|1| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|2| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|3| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|4| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|5| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|6| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|7| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|8| @64
+|:|.|,|.|+|2|s|/> @61
diff --git a/src/testdir/dumps/Test_incsearch_substitute_04.dump b/src/testdir/dumps/Test_incsearch_substitute_04.dump
new file mode 100644
index 0000000..bae6c7b
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_04.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |1| @64
+|f+1&&|o@1| +0&&|2| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|3| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|4| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|:|5|,|2|s|/|f|o@1> @60
diff --git a/src/testdir/dumps/Test_incsearch_substitute_05.dump b/src/testdir/dumps/Test_incsearch_substitute_05.dump
new file mode 100644
index 0000000..6ec8ea3
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_05.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |1| @64
+|f+1&&|o|o+0&&| |2| @64
+|f+0&#ffff4012|o|o+0&#ffffff0| |3| @64
+|f|o@1| |4| @64
+|f|o@1| |5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|:|2|,|3|s|u|b| @1|/|f|o> @57
diff --git a/src/testdir/dumps/Test_incsearch_substitute_06.dump b/src/testdir/dumps/Test_incsearch_substitute_06.dump
new file mode 100644
index 0000000..8a66620
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_06.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |3| @64
+|f+1&&|o@1| +0&&|4| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|:|a|b|o|v|e| |b|e|l|o|w| |b|r|o|w|s|e| |b|o|t|r| |c|o|n|f|i|r|m| |k|e@1|p|m|a|r| |k|e@1|p|a|l|t| |k|e@1|p@1|a|t| |k|e@1|p|j|u|m| |f|i|l|t|e
+|r| |x@2| |h|i|d|e| |l|o|c|k|m| |l|e|f|t|a|b|o|v|e| |n|o|a|u| |n|o|s|w|a|p| |r|i|g|h|t|b|e|l| |s|a|n|d|b|o|x| |s|i|l|e|n|t| |s|i|l|e|n|t|!
+| |$|t|a|b| |t|o|p| |u|n|s|i|l| |v|e|r|t| |v|e|r|b|o|s|e| |4|,|5|s|/|f|o|.> @32
diff --git a/src/testdir/dumps/Test_incsearch_substitute_07.dump b/src/testdir/dumps/Test_incsearch_substitute_07.dump
new file mode 100644
index 0000000..7b4dc6e
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_07.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |4| @64
+|f|o@1| |5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|f|o@1| |9| @64
+|f|o@1| |1|0| @63
+|b+9&&|a|r| +8&&|1@1| @63
+|:+0&&|9|,|1@1|s|/|b|a|r> @59
diff --git a/src/testdir/dumps/Test_incsearch_substitute_08.dump b/src/testdir/dumps/Test_incsearch_substitute_08.dump
new file mode 100644
index 0000000..d87e507
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_08.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |4| @64
+|f|o@1| |5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|f+8&&|o@1| |9| @64
+|f+0&&|o@1| |1|0| @63
+|b|a|r| |1@1| @63
+|:|9|,|1|0|s|/|b|a|r> @59
diff --git a/src/testdir/dumps/Test_incsearch_substitute_09.dump b/src/testdir/dumps/Test_incsearch_substitute_09.dump
new file mode 100644
index 0000000..633e7d8
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_09.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |3| @64
+|f+8&&|o@1| |4| @64
+|f+0&&|o@1| |5| @64
+|f|o@1| |6| @64
+|f|o@1| |7| @64
+|f|o@1| |8| @64
+|f|o@1| |9| @64
+|f|o@1| |1|0| @63
+|:|6|,|7|s|/|\|v> @61
diff --git a/src/testdir/dumps/Test_incsearch_substitute_10.dump b/src/testdir/dumps/Test_incsearch_substitute_10.dump
new file mode 100644
index 0000000..f98e046
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_10.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|o@1| +0&#ffffff0|1| @64
+>f+0&#ffff4012|o@1| +0&#ffffff0|2| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|3| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|4| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|5| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|6| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|7| @64
+|f+0&#ffff4012|o@1| +0&#ffffff0|8| @64
+@52|2|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_incsearch_substitute_11.dump b/src/testdir/dumps/Test_incsearch_substitute_11.dump
new file mode 100644
index 0000000..cc401be
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_11.dump
@@ -0,0 +1,9 @@
+|f+1&#ffffff0|o+0&#ffff4012@1| |1| +0&#ffffff0@64
+|f+0&#ffff4012|o@1| |2| +0&#ffffff0@64
+|f+0&#ffff4012|o@1| |3| +0&#ffffff0@64
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @38|1|,|1| @11|T|o|p
+|f+0&#ffff4012|o@1| |2| +0&#ffffff0@64
+|f+0&#ffff4012|o@1| |3| +0&#ffffff0@64
+|f+0&#ffff4012|o@1| |4| +0&#ffffff0@64
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @38|2|,|1| @11|1|2|%
+|:+0&&|%|s|/|.> @64
diff --git a/src/testdir/dumps/Test_incsearch_substitute_12.dump b/src/testdir/dumps/Test_incsearch_substitute_12.dump
new file mode 100644
index 0000000..3170e54
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_12.dump
@@ -0,0 +1,9 @@
+|f+0&#ffffff0|o@1| |2| @64
+|f|o@1| |3| @64
+|f|o@1| |4| @64
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @38|2|,|1| @11|1|2|%
+|f+0&&|o@1| |2| @64
+|f|o@1| |3| @64
+|f|o@1| |4| @64
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @38|2|,|1| @11|1|2|%
+|:+0&&|%|s|/> @65
diff --git a/src/testdir/dumps/Test_incsearch_substitute_13.dump b/src/testdir/dumps/Test_incsearch_substitute_13.dump
new file mode 100644
index 0000000..32feb55
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_13.dump
@@ -0,0 +1,9 @@
+>f+0&#ffffff0|o@1| |2| @64
+|f|o@1| |3| @64
+|f|o@1| |4| @64
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @38|2|,|1| @11|1|2|%
+|f+0&&|o@1| |2| @64
+|f|o@1| |3| @64
+|f|o@1| |4| @64
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @38|2|,|1| @11|1|2|%
+| +0&&@69
diff --git a/src/testdir/dumps/Test_incsearch_substitute_14.dump b/src/testdir/dumps/Test_incsearch_substitute_14.dump
new file mode 100644
index 0000000..c319856
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_14.dump
@@ -0,0 +1,9 @@
+|a+1&#ffffff0|s+0&&|d|f|a+0&#ffff4012|s+0&#ffffff0|d|f| @61
+|~+0#4040ff13&| @68
+|~| @68
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @38|1|,|1| @11|A|l@1
+|b+0&&|s|d|f|b|s|d|f| @61
+|~+0#4040ff13&| @68
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @38|1|,|1| @11|A|l@1
+|:+0&&|'|<|,|'|>|s|/|a|/|b|/|g> @56
+@70
diff --git a/src/testdir/dumps/Test_incsearch_substitute_15.dump b/src/testdir/dumps/Test_incsearch_substitute_15.dump
new file mode 100644
index 0000000..41dd358
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_substitute_15.dump
@@ -0,0 +1,4 @@
+|h+0&#ffffff0|e+1&&|l@1|o|/|t|h|e|r+0&&|e| @8
+|~+0#4040ff13&| @18
+|~| @18
+|:+0#0000000&|%|s|;|e|l@1|o|/|t|h|e> @7
diff --git a/src/testdir/dumps/Test_incsearch_vimgrep_01.dump b/src/testdir/dumps/Test_incsearch_vimgrep_01.dump
new file mode 100644
index 0000000..955d030
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_vimgrep_01.dump
@@ -0,0 +1,9 @@
+|a+0&#ffffff0|n|o|t|h|e|r| |o+1&&|n|e+0&&| |2| @56
+|t|h|a|t| |o+0&#ffff4012|n|e+0&#ffffff0| |3| @59
+|t|h|e| |o+0&#ffff4012|n|e+0&#ffffff0| |1| @60
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|v|i|m|g|r|e|p| |o|n> @58
diff --git a/src/testdir/dumps/Test_incsearch_vimgrep_02.dump b/src/testdir/dumps/Test_incsearch_vimgrep_02.dump
new file mode 100644
index 0000000..a5d94ea
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_vimgrep_02.dump
@@ -0,0 +1,9 @@
+|a+0&#ffffff0|n|o|t|h|e|r| |o+1&&|n|e+0&&| |2| @56
+|t|h|a|t| |o+0&#ffff4012|n|e+0&#ffffff0| |3| @59
+|t|h|e| |o+0&#ffff4012|n|e+0&#ffffff0| |1| @60
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|v|i|m|g| |/|o|n|/| |*|.|t|x|t> @53
diff --git a/src/testdir/dumps/Test_incsearch_vimgrep_03.dump b/src/testdir/dumps/Test_incsearch_vimgrep_03.dump
new file mode 100644
index 0000000..038ceb9
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_vimgrep_03.dump
@@ -0,0 +1,9 @@
+|a+0&#ffffff0|n|o|t|h|e|r| |o+1&&|n|e+0&&| |2| @56
+|t|h|a|t| |o+0&#ffff4012|n|e+0&#ffffff0| |3| @59
+|t|h|e| |o+0&#ffff4012|n|e+0&#ffffff0| |1| @60
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|v|i|m|g|r|e|p|a|d@1| |"|\|<|o|n> @52
diff --git a/src/testdir/dumps/Test_incsearch_vimgrep_04.dump b/src/testdir/dumps/Test_incsearch_vimgrep_04.dump
new file mode 100644
index 0000000..92dd78d
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_vimgrep_04.dump
@@ -0,0 +1,9 @@
+|a+0&#ffffff0|n|o|t|h|e|r| |o|n|e| |2| @56
+|t+1&&|h|a|t+0&&| |o|n|e| |3| @59
+|t|h|e| |o|n|e| |1| @60
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|l|v| |"|t|h|a> @61
diff --git a/src/testdir/dumps/Test_incsearch_vimgrep_05.dump b/src/testdir/dumps/Test_incsearch_vimgrep_05.dump
new file mode 100644
index 0000000..e11ff57
--- /dev/null
+++ b/src/testdir/dumps/Test_incsearch_vimgrep_05.dump
@@ -0,0 +1,9 @@
+|a+0&#ffffff0|n|o|t+1&&|h|e|r+0&&| |o|n|e| |2| @56
+|t|h|a|t| |o|n|e| |3| @59
+|t+0&#ffff4012|h|e| +0&#ffffff0|o|n|e| |1| @60
+|~+0#4040ff13&| @68
+|~| @68
+|~| @68
+|~| @68
+|~| @68
+|:+0#0000000&|l|v|i|m|g|r|e|p|a| |"|t|h|e|"| |*@1|/|*|.|t|x|t> @44
diff --git a/src/testdir/dumps/Test_job_buffer_scroll_1.dump b/src/testdir/dumps/Test_job_buffer_scroll_1.dump
new file mode 100644
index 0000000..11abe60
--- /dev/null
+++ b/src/testdir/dumps/Test_job_buffer_scroll_1.dump
@@ -0,0 +1,10 @@
+|s+0&#ffffff0|o|m|e|t|e|x|t| @66
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|X+1#0000000&|s|c|r|o|l@1|b|u|f@1|e|r| |[|+|]| @39|1|,|1| @11|A|l@1
+> +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|-+0&&@1|N|o| |l|i|n|e|s| |i|n| |b|u|f@1|e|r|-@1| @52
diff --git a/src/testdir/dumps/Test_keytyped_in_nested_func.dump b/src/testdir/dumps/Test_keytyped_in_nested_func.dump
new file mode 100644
index 0000000..46bebea
--- /dev/null
+++ b/src/testdir/dumps/Test_keytyped_in_nested_func.dump
@@ -0,0 +1,6 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|"> @72
diff --git a/src/testdir/dumps/Test_linebreak_reset_restore_1.dump b/src/testdir/dumps/Test_linebreak_reset_restore_1.dump
new file mode 100644
index 0000000..5c6db4f
--- /dev/null
+++ b/src/testdir/dumps/Test_linebreak_reset_restore_1.dump
@@ -0,0 +1,8 @@
+|a+0&#ffffff0@64| @9
+|b@9| > @63
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|4|9|0|:| |N|o| |f|o|l|d| |f|o|u|n|d| +0#0000000#ffffff0@37|1|,|7|8|-|8|7| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_listchars_01.dump b/src/testdir/dumps/Test_listchars_01.dump
new file mode 100644
index 0000000..72fff58
--- /dev/null
+++ b/src/testdir/dumps/Test_listchars_01.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @13||+1&&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0|>+0#4040ff13&||+1#0000000&| +0#0000e05#a8a8a8255@2>a+0#0000000#ffffff0@2| @26
+| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@16||+1&&| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@1||+1&&| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@29
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @15||+1&&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| ||+1&&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @28
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @10||+1&&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0|>+0#4040ff13&||+1#0000000&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @23
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|~| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|~| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|~| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|1| @3|A|l@1| |<|]| |1|,| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @3|1|,|1| @9|A|l@1
+| +0&&@59
diff --git a/src/testdir/dumps/Test_listchars_02.dump b/src/testdir/dumps/Test_listchars_02.dump
new file mode 100644
index 0000000..b7be52e
--- /dev/null
+++ b/src/testdir/dumps/Test_listchars_02.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @13||+1&&| +0#0000e05#a8a8a8255@2|>+0#4040ff13#ffffff0||+1#0000000&| +0#0000e05#a8a8a8255@2>a+0#0000000#ffffff0@2| @27
+| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@16||+1&&| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0||+1&&| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@30
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @15||+1&&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0||+1&&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @29
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @10||+1&&| +0#0000e05#a8a8a8255@2|>+0#4040ff13#ffffff0||+1#0000000&| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @24
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @2||+1#0000000&|~+0#4040ff13&| @32
+|~| @18||+1#0000000&|~+0#4040ff13&| @2||+1#0000000&|~+0#4040ff13&| @32
+|~| @18||+1#0000000&|~+0#4040ff13&| @2||+1#0000000&|~+0#4040ff13&| @32
+|~| @18||+1#0000000&|~+0#4040ff13&| @2||+1#0000000&|~+0#4040ff13&| @32
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|1| @3|A|l@1| |<| |1|,| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @3|1|,|1| @10|A|l@1
+| +0&&@59
diff --git a/src/testdir/dumps/Test_listchars_03.dump b/src/testdir/dumps/Test_listchars_03.dump
new file mode 100644
index 0000000..13aca6d
--- /dev/null
+++ b/src/testdir/dumps/Test_listchars_03.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @13||+1&&| +0#0000e05#a8a8a8255@2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2>a+0#0000000#ffffff0@2| @28
+| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@16||+1&&| +0#0000e05#a8a8a8255@2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@31
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @15||+1&&| +0#0000e05#a8a8a8255@2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @30
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @10||+1&&| +0#0000e05#a8a8a8255@2||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @25
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @1||+1#0000000&|~+0#4040ff13&| @33
+|~| @18||+1#0000000&|~+0#4040ff13&| @1||+1#0000000&|~+0#4040ff13&| @33
+|~| @18||+1#0000000&|~+0#4040ff13&| @1||+1#0000000&|~+0#4040ff13&| @33
+|~| @18||+1#0000000&|~+0#4040ff13&| @1||+1#0000000&|~+0#4040ff13&| @33
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|1| @3|A|l@1| |<| |1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @4|1|,|1| @10|A|l@1
+| +0&&@59
diff --git a/src/testdir/dumps/Test_listchars_04.dump b/src/testdir/dumps/Test_listchars_04.dump
new file mode 100644
index 0000000..e62c5af
--- /dev/null
+++ b/src/testdir/dumps/Test_listchars_04.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @13||+1&&| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2>a+0#0000000#ffffff0@2| @29
+| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@16||+1&&| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@32
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @15||+1&&| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @31
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @10||+1&&| +0#0000e05#a8a8a8255@1||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @26
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| ||+1#0000000&|~+0#4040ff13&| @34
+|~| @18||+1#0000000&|~+0#4040ff13&| ||+1#0000000&|~+0#4040ff13&| @34
+|~| @18||+1#0000000&|~+0#4040ff13&| ||+1#0000000&|~+0#4040ff13&| @34
+|~| @18||+1#0000000&|~+0#4040ff13&| ||+1#0000000&|~+0#4040ff13&| @34
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|1| @3|A|l@1| |<|1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @4|1|,|1| @11|A|l@1
+| +0&&@59
diff --git a/src/testdir/dumps/Test_listchars_05.dump b/src/testdir/dumps/Test_listchars_05.dump
new file mode 100644
index 0000000..89b759f
--- /dev/null
+++ b/src/testdir/dumps/Test_listchars_05.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @13||+1&&| +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2>a+0#0000000#ffffff0@2| @30
+| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@16||+1&&| +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@33
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @15||+1&&| +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @32
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @10||+1&&| +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @27
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|~| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|~| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|~| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|1| @3|A|l@1| |<| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+| +0&&@59
diff --git a/src/testdir/dumps/Test_listchars_06.dump b/src/testdir/dumps/Test_listchars_06.dump
new file mode 100644
index 0000000..9f4b5d3
--- /dev/null
+++ b/src/testdir/dumps/Test_listchars_06.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @13||+1&&| +0#0000e05#a8a8a8255@4||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @26
+| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@16||+1&&| +0#0000e05#a8a8a8255@4||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@29
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @15||+1&&| +0#0000e05#a8a8a8255@4||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @28
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @10||+1&&| +0#0000e05#a8a8a8255@3> ||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @23
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|~| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|~| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|~| @18||+1#0000000&|~+0#4040ff13&| @3||+1#0000000&|~+0#4040ff13&| @31
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|1| @3|A|l@1| |<+3&&|]| |1|,| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @3|1|,|1| @9|A|l@1
+|:+0&&|s|e|t| |n|o|w|r|a|p| |f|o|l|d|c|o|l|u|m|n|=|4| @35
diff --git a/src/testdir/dumps/Test_listchars_07.dump b/src/testdir/dumps/Test_listchars_07.dump
new file mode 100644
index 0000000..ad81656
--- /dev/null
+++ b/src/testdir/dumps/Test_listchars_07.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @13||+1&&| +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@2| @30
+| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@16||+1&&| +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2| +0#0000000#ffffff0@33
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @15||+1&&| +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0| @32
+| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @10||+1&&> +0#0000e05#a8a8a8255||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@2|a+0#0000000#ffffff0@5| @27
+|~+0#4040ff13&| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|~| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|~| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|~| @18||+1#0000000&|~+0#4040ff13&||+1#0000000&|~+0#4040ff13&| @35
+|<+1#0000000&|a|m|e|]| |[|+|]| |1|,|1| @3|A|l@1| |<+3&&| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |n|o|w|r|a|p| |f|o|l|d|c|o|l|u|m|n|=|4| @35
diff --git a/src/testdir/dumps/Test_long_file_name_1.dump b/src/testdir/dumps/Test_long_file_name_1.dump
new file mode 100644
index 0000000..1dcafc3
--- /dev/null
+++ b/src/testdir/dumps/Test_long_file_name_1.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|<+0#0000000&|x@64|"| |0|L|,| |0|B|
diff --git a/src/testdir/dumps/Test_long_text_with_padding_1.dump b/src/testdir/dumps/Test_long_text_with_padding_1.dump
new file mode 100644
index 0000000..18ca00c
--- /dev/null
+++ b/src/testdir/dumps/Test_long_text_with_padding_1.dump
@@ -0,0 +1,8 @@
+|f+0&#ffffff0|i|r|s|t| |l|i|n|e| @49
+@3|a+0&#ffd7ff255|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|…
+| +0&#ffffff0@29|m+0&#ffd7ff255|o|r|e| |m|o|r|e| |m|o|r|e| |m|o|r|e| |m|o|r|e| |m|o|r|e|…
+|s+0&#ffffff0|e|c|o|n|d| >l|i|n|e| @48
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_long_text_with_padding_2.dump b/src/testdir/dumps/Test_long_text_with_padding_2.dump
new file mode 100644
index 0000000..981613f
--- /dev/null
+++ b/src/testdir/dumps/Test_long_text_with_padding_2.dump
@@ -0,0 +1,8 @@
+|f+0&#ffffff0|i|r|s|t| |l|i|n|e|$+0#4040ff13&| +0#0000000&@48
+@3|a+0&#ffd7ff255|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|t|e|r| |a|f|…
+| +0&#ffffff0@29|m+0&#ffd7ff255|o|r|e| |m|o|r|e| |m|o|r|e| |m|o|r|e| |m|o|r|e| |m|o|r|e|…
+|s+0&#ffffff0|e|c|o|n|d| >l|i|n|e|$+0#4040ff13&| +0#0000000&@47
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|:+0#0000000&|s|e|t| |l|i|s|t| @32|2|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_map_expr_1.dump b/src/testdir/dumps/Test_map_expr_1.dump
new file mode 100644
index 0000000..15b772e
--- /dev/null
+++ b/src/testdir/dumps/Test_map_expr_1.dump
@@ -0,0 +1,10 @@
+|o+0&#ffffff0|n|e| @71
+>t|w|o| @71
+|t|h|r|e@1| @69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+0#ffffff16#e000002|o|n|]| @70
+| +0#0000000#ffffff0@74
diff --git a/src/testdir/dumps/Test_map_expr_2.dump b/src/testdir/dumps/Test_map_expr_2.dump
new file mode 100644
index 0000000..270a233
--- /dev/null
+++ b/src/testdir/dumps/Test_map_expr_2.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_map_expr_3.dump b/src/testdir/dumps/Test_map_expr_3.dump
new file mode 100644
index 0000000..6404e8c
--- /dev/null
+++ b/src/testdir/dumps/Test_map_expr_3.dump
@@ -0,0 +1,10 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|a|b|c| @70
+|E+0#ffffff16#e000002|r@1|o|r| |d|e|t|e|c|t|e|d| |w|h|i|l|e| |p|r|o|c|e|s@1|i|n|g| |f|u|n|c|t|i|o|n| |F|u|n|c|[|1|]|.@1|f|u|n|c|t|i|o|n| |F|u|n|c|:| +0#0000000#ffffff0@10
+|l+0#af5f00255&|i|n|e| @3|1|:| +0#0000000&@64
+|E+0#ffffff16#e000002|6|0|5|:| |E|x|c|e|p|t|i|o|n| |n|o|t| |c|a|u|g|h|t|:| |t|e|s|t| +0#0000000#ffffff0@42
+|:|a|b|c> @70
diff --git a/src/testdir/dumps/Test_map_expr_4.dump b/src/testdir/dumps/Test_map_expr_4.dump
new file mode 100644
index 0000000..270a233
--- /dev/null
+++ b/src/testdir/dumps/Test_map_expr_4.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_map_list_1.dump b/src/testdir/dumps/Test_map_list_1.dump
new file mode 100644
index 0000000..64bdcf9
--- /dev/null
+++ b/src/testdir/dumps/Test_map_list_1.dump
@@ -0,0 +1,6 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|n+0#0000000&| @1|a| @12|b| @38|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_match_linebreak.dump b/src/testdir/dumps/Test_match_linebreak.dump
new file mode 100644
index 0000000..33be5b3
--- /dev/null
+++ b/src/testdir/dumps/Test_match_linebreak.dump
@@ -0,0 +1,10 @@
+>x+0&#ffffff0@49|]+0#ffffff16#e000002| +0#0000000#ffffff0@23
+|x@69| @4
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_match_tab_linebreak.dump b/src/testdir/dumps/Test_match_tab_linebreak.dump
new file mode 100644
index 0000000..9a525d1
--- /dev/null
+++ b/src/testdir/dumps/Test_match_tab_linebreak.dump
@@ -0,0 +1,10 @@
+| +0#ffffff16#e000002@6> |i+0#0000000#ffffff0|x| @64
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_match_with_incsearch_1.dump b/src/testdir/dumps/Test_match_with_incsearch_1.dump
new file mode 100644
index 0000000..8a9d30c
--- /dev/null
+++ b/src/testdir/dumps/Test_match_with_incsearch_1.dump
@@ -0,0 +1,6 @@
+>0+0&#ffffff0| @73
+|1| @73
+|2+0#ffffff16#e000002| +0#0000000#ffffff0@73
+|3| @73
+|4| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_match_with_incsearch_2.dump b/src/testdir/dumps/Test_match_with_incsearch_2.dump
new file mode 100644
index 0000000..9e9868b
--- /dev/null
+++ b/src/testdir/dumps/Test_match_with_incsearch_2.dump
@@ -0,0 +1,6 @@
+|0+1&#ffffff0| +0&&@73
+|1| @73
+|2+0#ffffff16#e000002| +0#0000000#ffffff0@73
+|3| @73
+|4| @73
+|:|s|/|0> @70
diff --git a/src/testdir/dumps/Test_matchadd_1.dump b/src/testdir/dumps/Test_matchadd_1.dump
new file mode 100644
index 0000000..d07507b
--- /dev/null
+++ b/src/testdir/dumps/Test_matchadd_1.dump
@@ -0,0 +1,12 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|H+0&#ffff4012|e|l@1|o| +0&#ffffff0|V|i|m| |w+0#ffffff16#ff404010|o|r|l|d| +0#0000000#ffffff0@59
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+|:+0&&| @73
diff --git a/src/testdir/dumps/Test_matchaddpos_1.dump b/src/testdir/dumps/Test_matchaddpos_1.dump
new file mode 100644
index 0000000..b282b24
--- /dev/null
+++ b/src/testdir/dumps/Test_matchaddpos_1.dump
@@ -0,0 +1,14 @@
+>1+0&#ffff4012|2+0&#ffffff0|3|4|5|6|7|8|9|0|1|2|3| @61
+|1|2+0&#ffff4012|3+0&#ffffff0|4|5|6|7|8|9|0|1|2|3| @61
+|1|2|3+0&#ffff4012|4+0&#ffffff0|5|6|7|8|9|0|1|2|3| @61
+|1|2|3|4+0&#ffff4012|5+0&#ffffff0|6|7|8|9|0|1|2|3| @61
+|1|2|3|4|5+0&#ffff4012|6+0&#ffffff0|7|8|9|0|1|2|3| @61
+|1|2|3|4|5|6+0&#ffff4012|7+0&#ffffff0|8|9|0|1|2|3| @61
+|1|2|3|4|5|6|7+0&#ffff4012|8+0&#ffffff0|9|0|1|2|3| @61
+|1|2|3|4|5|6|7|8+0&#ffff4012|9+0&#ffffff0|0|1|2|3| @61
+|1|2|3|4|5|6|7|8|9+0&#ffff4012|0+0&#ffffff0|1|2|3| @61
+|1|2|3|4|5|6|7|8|9|0+0&#ffff4012|1+0&#ffffff0|2|3| @61
+|1|2|3|4|5|6|7|8|9|0|1+0&#ffff4012|2+0&#ffffff0|3| @61
+|1|2|3|4|5|6|7|8|9|0|1|2+0&#ffff4012|3+0&#ffffff0| @61
+|1|2|3|4|5|6|7|8|9|0|1|2|3| @61
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_matchclear_1.dump b/src/testdir/dumps/Test_matchclear_1.dump
new file mode 100644
index 0000000..164bfd4
--- /dev/null
+++ b/src/testdir/dumps/Test_matchclear_1.dump
@@ -0,0 +1,12 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|H+0&&|e|l@1|o| |V|i|m| |w|o|r|l|d| @59
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+|:+0&&|c|a|l@1| |c|l|e|a|r|m|a|t|c|h|e|s|(|w|i|n|i|d|)| @49
diff --git a/src/testdir/dumps/Test_matchdelete_1.dump b/src/testdir/dumps/Test_matchdelete_1.dump
new file mode 100644
index 0000000..06b16b5
--- /dev/null
+++ b/src/testdir/dumps/Test_matchdelete_1.dump
@@ -0,0 +1,12 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|H+0&&|e|l@1|o| |V|i|m| |w|o|r|l|d| @59
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+|:+0&&|c|a|l@1| |m|a|t|c|h|d|e|l|e|t|e|(|m|i|d|,| |w|i|n|i|d|)| @45
diff --git a/src/testdir/dumps/Test_matchparen_clear_highlight_1.dump b/src/testdir/dumps/Test_matchparen_clear_highlight_1.dump
new file mode 100644
index 0000000..1a96275
--- /dev/null
+++ b/src/testdir/dumps/Test_matchparen_clear_highlight_1.dump
@@ -0,0 +1,5 @@
+>(+0&#40ffff15|)| +0&#ffffff0@72
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_matchparen_clear_highlight_2.dump b/src/testdir/dumps/Test_matchparen_clear_highlight_2.dump
new file mode 100644
index 0000000..c3f93fa
--- /dev/null
+++ b/src/testdir/dumps/Test_matchparen_clear_highlight_2.dump
@@ -0,0 +1,5 @@
+>a+0&#ffffff0@1| @72
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_misplaced_type.dump b/src/testdir/dumps/Test_misplaced_type.dump
new file mode 100644
index 0000000..afb82a0
--- /dev/null
+++ b/src/testdir/dumps/Test_misplaced_type.dump
@@ -0,0 +1,6 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|1|2|7|2|:| |U|s|i|n|g| |t|y|p|e| |n|o|t| |i|n| |a| |s|c|r|i|p|t| |c|o|n|t|e|x|t|:| |:| |s|t|r|i|n|g| +0#0000000#ffffff0@23
+|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@35
diff --git a/src/testdir/dumps/Test_mode_updated_1.dump b/src/testdir/dumps/Test_mode_updated_1.dump
new file mode 100644
index 0000000..6bbded4
--- /dev/null
+++ b/src/testdir/dumps/Test_mode_updated_1.dump
@@ -0,0 +1,5 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_more_scrollback_1.dump b/src/testdir/dumps/Test_more_scrollback_1.dump
new file mode 100644
index 0000000..619f5a7
--- /dev/null
+++ b/src/testdir/dumps/Test_more_scrollback_1.dump
@@ -0,0 +1,10 @@
+|0+0#ffffff16#0000001| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|-+0#00e0003&@1| |M|o|r|e| |-@1> +0#ffffff16&@64
diff --git a/src/testdir/dumps/Test_more_scrollback_2.dump b/src/testdir/dumps/Test_more_scrollback_2.dump
new file mode 100644
index 0000000..619f5a7
--- /dev/null
+++ b/src/testdir/dumps/Test_more_scrollback_2.dump
@@ -0,0 +1,10 @@
+|0+0#ffffff16#0000001| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|-+0#00e0003&@1| |M|o|r|e| |-@1> +0#ffffff16&@64
diff --git a/src/testdir/dumps/Test_move_undo_1.dump b/src/testdir/dumps/Test_move_undo_1.dump
new file mode 100644
index 0000000..71e3120
--- /dev/null
+++ b/src/testdir/dumps/Test_move_undo_1.dump
@@ -0,0 +1,10 @@
+|S+0&#ffffff0|e|c|o|n|d| @53
+>F|i|r|s|t| @54
+|T|h|i|r|d| @54
+|F|o|u|r|t|h| @53
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|m|o|v|e| |+|1| @33|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_move_undo_2.dump b/src/testdir/dumps/Test_move_undo_2.dump
new file mode 100644
index 0000000..ff0bc1a
--- /dev/null
+++ b/src/testdir/dumps/Test_move_undo_2.dump
@@ -0,0 +1,10 @@
+>F+0&#ffffff0|i|r|s|t| @54
+|S|e|c|o|n|d| @53
+|T|h|i|r|d| @54
+|F|o|u|r|t|h| @53
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_and_previewwindow_01.dump b/src/testdir/dumps/Test_popup_and_previewwindow_01.dump
new file mode 100644
index 0000000..b235c7d
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_and_previewwindow_01.dump
@@ -0,0 +1,20 @@
+|a+0&#ffffff0|b|0| @71
+|a|b|1| @71
+|a|b|2| @71
+|a|b|3| @71
+|a|b|4| @71
+|a|b|5| @71
+|a|b|6| @71
+|a|b|7| @71
+|a|b|8| @71
+|a+0#0000001#e0e0e08|b|0| @11| +0#0000000#0000001|e+1&#ffffff0|w|]|[|+|]| @34|1|,|1| @11|T|o|p
+|a+0#0000001#ffd7ff255|b|1| @11| +0#0000000#0000001| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255|b|2| @11| +0#0000000#0000001| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255|b|3| @11| +0#0000000#0000001| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255|b|4| @11| +0#0000000#a8a8a8255| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255|b|5| @11| +0#0000000#a8a8a8255| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255|b|6| @11| +0#0000000#a8a8a8255| +0&#ffffff0@58
+|a|b|0> @71
+|~+0#4040ff13&| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1@1|,|1| @10|B|o|t
+|-+2&&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |1|0| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popup_command_01.dump b/src/testdir/dumps/Test_popup_command_01.dump
new file mode 100644
index 0000000..8d0cd9c
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_command_01.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| @51
+|a|n|d| |o|n|e| |t|w|o| >X|t|h|r|e@1| |f|o|u|r| |f|i|v|e| @46
+|o|n|e| |m|o|r|e| |t|w| +0#0000001#ffd7ff255|U|n|d|o| @12| +0#0000000#ffffff0@45
+|~+0#4040ff13&| @9| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|P|a|s|t|e| @11| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |W|o|r|d| @5| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |S|e|n|t|e|n|c|e| @1| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |P|a|r|a|g|r|a|p|h| | +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |L|i|n|e| @5| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |B|l|o|c|k| @4| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |A|l@1| @6| +0#4040ff13#ffffff0@45
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|p|o|p|u|p| |P|o|p|U|p| @62
diff --git a/src/testdir/dumps/Test_popup_command_02.dump b/src/testdir/dumps/Test_popup_command_02.dump
new file mode 100644
index 0000000..e33ea4d
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_command_02.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| @51
+|a|n|d| |o|n|e| |t|w|o| >X|t|h|r|e@1| |f|o|u|r| |f|i|v|e| @46
+|o|n|e| |m|o|r|e| |t|w| +0#0000001#ffd7ff255|U|n|d|o| @12| +0#0000000#ffffff0@45
+|~+0#4040ff13&| @9| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#e0e0e08|P|a|s|t|e| @11| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |W|o|r|d| @5| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |S|e|n|t|e|n|c|e| @1| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |P|a|r|a|g|r|a|p|h| | +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |L|i|n|e| @5| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |B|l|o|c|k| @4| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |A|l@1| @6| +0#4040ff13#ffffff0@45
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|p|o|p|u|p| |P|o|p|U|p| @62
diff --git a/src/testdir/dumps/Test_popup_command_03.dump b/src/testdir/dumps/Test_popup_command_03.dump
new file mode 100644
index 0000000..fa2ac70
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_command_03.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| @51
+|a|n|d| |o|n|e| |t|w|o| |X+0&#e0e0e08|t|h|r|e@1> +0&#ffffff0|f|o|u|r| |f|i|v|e| @46
+|o|n|e| |m|o|r|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| @46
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |V|I|S|U|A|L| |-@1| +0&&@34|7| @8|2|,|1|9| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_command_04.dump b/src/testdir/dumps/Test_popup_command_04.dump
new file mode 100644
index 0000000..c14576c
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_command_04.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| @51
+|a|n|d| |o|n|e| |t|w|o| |X|t|h|r|e@1| |f|o|u|r| |f|i|v|e| @46
+|o|n|e| |m|o|r|e| |t|w| +0#0000001#ffd7ff255|U|n|d|o| @12| +0#0000000#ffffff0@45
+|~+0#4040ff13&| @9| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|P|a|s|t|e| @11| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |W|o|r|d| @5| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |S|e|n|t|e|n|c|e| @1| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |P|a|r|a|g|r|a|p|h| | +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |L|i|n|e| @5| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |B|l|o|c|k| @4| +0#4040ff13#ffffff0@45
+|~| @9| +0#0000001#ffd7ff255|S|e|l|e|c|t| |A|l@1| @6| +0#4040ff13#ffffff0@45
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|c+0#0000000&|h|a|n|g|e|d> @67
diff --git a/src/testdir/dumps/Test_popup_command_05.dump b/src/testdir/dumps/Test_popup_command_05.dump
new file mode 100644
index 0000000..de1c95e
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_command_05.dump
@@ -0,0 +1,20 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| @51
+|a|n|d| |o|n|e| |t|w|o| >X|t|h|r|e@1| |f|o|u|r| |f|i|v|e| @46
+|o|n|e| |m|o|r|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| @46
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|p+0#0000000&|a|s|t|e|d| @50|2|,|1|3| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_position_01.dump b/src/testdir/dumps/Test_popup_position_01.dump
new file mode 100644
index 0000000..43900fb
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_position_01.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @5||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @5
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @5||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @5
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5||+1&&| +0&&@11|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|6|7|8|9|_|a> @30||+1&&|6+0&&|7|8|9|_|a| @30
+|~+0#4040ff13&| @9| +0#0000001#e0e0e08|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| | +0#4040ff13#ffffff0@30
+|~| @9| +0#0000001#ffd7ff255|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| | +0#4040ff13#ffffff0@30
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
diff --git a/src/testdir/dumps/Test_popup_position_02.dump b/src/testdir/dumps/Test_popup_position_02.dump
new file mode 100644
index 0000000..c3613c3
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_position_02.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @5||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @5
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @5||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @5
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5||+1&&| +0&&@11|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|6|7|8|9|_|a| @30||+1&&|6+0&&|7|8|9|_|a> @30
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @9| +0#0000001#e0e0e08|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @9| +0#0000001#ffd7ff255|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
diff --git a/src/testdir/dumps/Test_popup_position_03.dump b/src/testdir/dumps/Test_popup_position_03.dump
new file mode 100644
index 0000000..650cb7f
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_position_03.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @5||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @5
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @5||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @5
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5||+1&&| +0&&@11|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|6|7|8|9|_|a| @30||+1&&|6+0&&|7|8|9|_|a> @30
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @4| +0#0000001#e0e0e08|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @4| +0#0000001#ffd7ff255|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
diff --git a/src/testdir/dumps/Test_popup_position_04.dump b/src/testdir/dumps/Test_popup_position_04.dump
new file mode 100644
index 0000000..1793b23
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_position_04.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7
+|8|9|_|a| @32||+1&&|8+0&&|9|_|a| @32
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7||+1&&|1+0&&|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7
+|8|9|_|b| @32||+1&&|8+0&&|9|_|b| @32
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5||+1&&| +0&&@11|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @20||+1&&|6+0&&|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a> @20
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @9| +0#0000001#e0e0e08|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @9| +0#0000001#ffd7ff255|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
diff --git a/src/testdir/dumps/Test_popup_prop_not_visible_01.dump b/src/testdir/dumps/Test_popup_prop_not_visible_01.dump
new file mode 100644
index 0000000..dbcfda1
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_prop_not_visible_01.dump
@@ -0,0 +1,10 @@
+| +2&#ffffff0|2+2#e000e06&|++2#0000000&| |[|N|o| |N|a|m|e|]| | +1&&@60
+| +0&&@42||+1&&> +0&&@30
+|~+0#4040ff13&| @41||+1#0000000&|s+0&&|o|m|e| |t|e|x|t|a+0#ffffff16#e000002|t@1|a|c|h|e|d| |t|o| |"|s|o|m|e|"| +0#0000000#ffffff0@3
+|~+0#4040ff13&| @41||+1#0000000&| +0&&@30
+|~+0#4040ff13&| @41||+1#0000000&|o+0&&|t|h|e|r| |t|e|x|t|a+0#ffffff16#e000002|t@1|a|c|h|e|d| |t|o| |"|o|t|h|e|r|"| +0#0000000#ffffff0@1
+|~+0#4040ff13&| @41||+1#0000000&|~+0#4040ff13&| @29
+|~| @41||+1#0000000&|~+0#4040ff13&| @29
+|~| @41||+1#0000000&|~+0#4040ff13&| @29
+|[+1#0000000&|N|o| |N|a|m|e|]| @15|0|,|0|-|1| @9|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @2|1|,|0|-|1| @6|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_popup_prop_not_visible_01a.dump b/src/testdir/dumps/Test_popup_prop_not_visible_01a.dump
new file mode 100644
index 0000000..0b97cb8
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_prop_not_visible_01a.dump
@@ -0,0 +1,10 @@
+| +2&#ffffff0|2+2#e000e06&|++2#0000000&| |[|N|o| |N|a|m|e|]| | +1&&@60
+| +0&&@42||+1&&> +0&&@30
+|~+0#4040ff13&| @41||+1#0000000&|s+0&&|o|m|e| |t|e|x|t| @21
+|~+0#4040ff13&| @41||+1#0000000&| +0&&@30
+|~+0#4040ff13&| @41||+1#0000000&|o+0&&|t|h|e|r| |t|e|x|t|a+0#ffffff16#e000002|t@1|a|c|h|e|d| |t|o| |"|o|t|h|e|r|"| +0#0000000#ffffff0@1
+|~+0#4040ff13&| @41||+1#0000000&|~+0#4040ff13&| @29
+|~| @41||+1#0000000&|~+0#4040ff13&| @29
+|~| @41||+1#0000000&|~+0#4040ff13&| @29
+|[+1#0000000&|N|o| |N|a|m|e|]| @15|0|,|0|-|1| @9|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @2|1|,|0|-|1| @6|A|l@1
+|:+0&&|c|a|l@1| |p|o|p|u|p|_|h|i|d|e|(|g|:|s|o|m|e|_|i|d|)| @47
diff --git a/src/testdir/dumps/Test_popup_prop_not_visible_01b.dump b/src/testdir/dumps/Test_popup_prop_not_visible_01b.dump
new file mode 100644
index 0000000..e8a5c2e
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_prop_not_visible_01b.dump
@@ -0,0 +1,10 @@
+| +2&#ffffff0|2+2#e000e06&|++2#0000000&| |[|N|o| |N|a|m|e|]| | +1&&@60
+| +0&&@42||+1&&> +0&&@30
+|~+0#4040ff13&| @41||+1#0000000&|s+0&&|o|m|e| |t|e|x|t|a+0#ffffff16#e000002|t@1|a|c|h|e|d| |t|o| |"|s|o|m|e|"| +0#0000000#ffffff0@3
+|~+0#4040ff13&| @41||+1#0000000&| +0&&@30
+|~+0#4040ff13&| @41||+1#0000000&|o+0&&|t|h|e|r| |t|e|x|t|a+0#ffffff16#e000002|t@1|a|c|h|e|d| |t|o| |"|o|t|h|e|r|"| +0#0000000#ffffff0@1
+|~+0#4040ff13&| @41||+1#0000000&|~+0#4040ff13&| @29
+|~| @41||+1#0000000&|~+0#4040ff13&| @29
+|~| @41||+1#0000000&|~+0#4040ff13&| @29
+|[+1#0000000&|N|o| |N|a|m|e|]| @15|0|,|0|-|1| @9|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @2|1|,|0|-|1| @6|A|l@1
+|:+0&&|c|a|l@1| |p|o|p|u|p|_|s|h|o|w|(|g|:|s|o|m|e|_|i|d|)| @47
diff --git a/src/testdir/dumps/Test_popup_prop_not_visible_02.dump b/src/testdir/dumps/Test_popup_prop_not_visible_02.dump
new file mode 100644
index 0000000..1d1a553
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_prop_not_visible_02.dump
@@ -0,0 +1,10 @@
+| +2&#ffffff0|2+2#e000e06&|++2#0000000&| |[|N|o| |N|a|m|e|]| | +1&&@60
+| +0&&@56||+1&&> +0&&@16
+|~+0#4040ff13&| @55||+1#0000000&|s+0&&|o|m|e| |t|e|x|t|a+0#ffffff16#e000002|t@1|a|c|h|e|d
+|~+0#4040ff13#ffffff0| @55||+1#0000000&| +0&&@16
+|~+0#4040ff13&| @54|a+0#ffffff16#e000002|t@1|a|c|h|e|d| |t|o| |"|o|t|h|e|r|"
+|~+0#4040ff13#ffffff0| @55||+1#0000000&|~+0#4040ff13&| @15
+|~| @55||+1#0000000&|~+0#4040ff13&| @15
+|~| @55||+1#0000000&|~+0#4040ff13&| @15
+|[+1#0000000&|N|o| |N|a|m|e|]| @29|0|,|0|-|1| @9|A|l@1| |<+3&&|m|e|]| |[|+|]| |1|,|0|-|1| @2
+|:+0&&|v|e|r|t| |r|e|s|i|z|e| |-|1|4| @58
diff --git a/src/testdir/dumps/Test_popup_prop_not_visible_03.dump b/src/testdir/dumps/Test_popup_prop_not_visible_03.dump
new file mode 100644
index 0000000..caaa881
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_prop_not_visible_03.dump
@@ -0,0 +1,10 @@
+| +2&#ffffff0|2+2#e000e06&|++2#0000000&| |[|N|o| |N|a|m|e|]| | +1&&@60
+| +0&&@64||+1&&> +0&&@8
+|~+0#4040ff13&| @63||+1#0000000&|s+0&&|o|m|e| |t|e|x|t
+|~+0#4040ff13&| @63||+1#0000000&| +0&&@8
+|~+0#4040ff13&| @63||+1#0000000&|o+0&&|t|h|e|r| |t|e|x
+|~+0#4040ff13&| @63||+1#0000000&|~+0#4040ff13&| @7
+|~| @63||+1#0000000&|~+0#4040ff13&| @7
+|~| @63||+1#0000000&|~+0#4040ff13&| @7
+|[+1#0000000&|N|o| |N|a|m|e|]| @37|0|,|0|-|1| @9|A|l@1| |<+3&&|[|+|]| |1|,|0|-
+|:+0&&|v|e|r|t| |r|e|s|i|z|e| |-|8| @59
diff --git a/src/testdir/dumps/Test_popup_settext_01.dump b/src/testdir/dumps/Test_popup_settext_01.dump
new file mode 100644
index 0000000..768c1bd
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_01.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @28|t+0#0000001#ffd7ff255|h|i|s| |i|s| |a| |t|e|x|t| +0#4040ff13#ffffff0@30
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_settext_02.dump b/src/testdir/dumps/Test_popup_settext_02.dump
new file mode 100644
index 0000000..565e97f
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_02.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @35| +0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |'@1|)| @30|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_settext_03.dump b/src/testdir/dumps/Test_popup_settext_03.dump
new file mode 100644
index 0000000..b2c0504
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_03.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @35|a+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @35|b+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @35|c+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |[|'|a|'|,|'|b|'|,|'|c|'|]|)| @19|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_settext_04.dump b/src/testdir/dumps/Test_popup_settext_04.dump
new file mode 100644
index 0000000..8e20916
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_04.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @35|a+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |[|'|a|'|]|)| @27|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_settext_05.dump b/src/testdir/dumps/Test_popup_settext_05.dump
new file mode 100644
index 0000000..1ae670e
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_05.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @35| +0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |[|]|)| @30|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_settext_06.dump b/src/testdir/dumps/Test_popup_settext_06.dump
new file mode 100644
index 0000000..99c32c8
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_06.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @33|a+0#0000001#ffd7ff255@3| +0#4040ff13#ffffff0@35
+|~| @33|b+0#0000001#ffd7ff255@3| +0#4040ff13#ffffff0@35
+|~| @33|c+0#0000001#ffd7ff255@3| +0#4040ff13#ffffff0@35
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_settext_07.dump b/src/testdir/dumps/Test_popup_settext_07.dump
new file mode 100644
index 0000000..2362aa7
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_07.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @35| +0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|1|0|2|4|:| |U|s|i|n|g| |a| |N|u|m|b|e|r| |a|s| |a| |S|t|r|i|n|g| +0#0000000#ffffff0@23|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_textprop_01.dump b/src/testdir/dumps/Test_popup_textprop_01.dump
new file mode 100644
index 0000000..e85ea7e
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_01.dump
@@ -0,0 +1,10 @@
+|4+0&#ffffff0|6| @72
+|4|7| @14|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@45
+|4|8| @14|â•‘+0#0000001#ffd7ff255| |t|h|e| |t|e|x|t| |â•‘| +0#0000000#ffffff0@45
+|4|9| @14|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@45
+>s|o|m|e| |t|e|x|t| |t+0&#5fd7ff255|o| |w|o|r|k| +0&#ffffff0|w|i|t|h| @52
+|5|1| @72
+|5|2| @72
+|5|3| @72
+|5|4| @72
+@57|5|0|,|1| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_02.dump b/src/testdir/dumps/Test_popup_textprop_02.dump
new file mode 100644
index 0000000..58fdc28
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_02.dump
@@ -0,0 +1,10 @@
+>s+0&#ffffff0|o|m|e| |t|e|x|t| |t+0&#5fd7ff255|o| |w|o|r|k| +0&#ffffff0|w|i|t|h| @52
+|5|1| @14|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@45
+|5|2| @14|â•‘+0#0000001#ffd7ff255| |t|h|e| |t|e|x|t| |â•‘| +0#0000000#ffffff0@45
+|5|3| @14|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@45
+|5|4| @72
+|5@1| @72
+|5|6| @72
+|5|7| @72
+|5|8| @72
+@57|5|0|,|1| @9|5|3|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_03.dump b/src/testdir/dumps/Test_popup_textprop_03.dump
new file mode 100644
index 0000000..efc6853
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_03.dump
@@ -0,0 +1,10 @@
+|4+0&#ffffff0|6| @72
+|4|7| @17|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@42
+|4|8| @17|â•‘+0#0000001#ffd7ff255| |t|h|e| |t|e|x|t| |â•‘| +0#0000000#ffffff0@42
+|4|9| @17|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@42
+|a|w>e|s|o|m|e| |t|e|x|t| |t+0&#5fd7ff255|o| |w|o|r|k| +0&#ffffff0|w|i|t|h| @49
+|5|1| @72
+|5|2| @72
+|5|3| @72
+|5|4| @72
+@57|5|0|,|3| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_04.dump b/src/testdir/dumps/Test_popup_textprop_04.dump
new file mode 100644
index 0000000..b8179c6
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_04.dump
@@ -0,0 +1,10 @@
+|4+0&#ffffff0|6| @72
+|4|7| @9|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@50
+|4|8| @9|â•‘+0#0000001#ffd7ff255| |t|h|e| |t|e|x|t| |â•‘| +0#0000000#ffffff0@50
+|4|9| @9|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@50
+>t|e|x|t| |t+0&#5fd7ff255|o| |w|o|r|k| +0&#ffffff0|w|i|t|h| @57
+|5|1| @72
+|5|2| @72
+|5|3| @72
+|5|4| @72
+@57|5|0|,|1| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_05.dump b/src/testdir/dumps/Test_popup_textprop_05.dump
new file mode 100644
index 0000000..78ae142
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_05.dump
@@ -0,0 +1,10 @@
+|4+0&#ffffff0|6| @72
+|4|7| @72
+|4|8| @9|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@50
+|4|9| @9|â•‘+0#0000001#ffd7ff255| |t|h|e| |t|e|x|t| |â•‘| +0#0000000#ffffff0@50
+|i|n|s|e|r|t|e>d| @3|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@50
+|t|e|x|t| |t+0&#5fd7ff255|o| |w|o|r|k| +0&#ffffff0|w|i|t|h| @57
+|5|1| @72
+|5|2| @72
+|5|3| @72
+@57|5|0|,|8| @9|4|8|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_06.dump b/src/testdir/dumps/Test_popup_textprop_06.dump
new file mode 100644
index 0000000..2327998
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_06.dump
@@ -0,0 +1,10 @@
+|4+0&#ffffff0|6| @9|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@50
+|4|7| @9|â•‘+0#0000001#ffd7ff255| |t|h|e| |t|e|x|t| |â•‘| +0#0000000#ffffff0@50
+|4|8| @9|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@50
+>t|e|x|t| |t+0&#5fd7ff255|o| |w|o|r|k| +0&#ffffff0|w|i|t|h| @57
+|5|1| @72
+|5|2| @72
+|5|3| @72
+|5|4| @72
+|5@1| @72
+@57|4|9|,|1| @9|5|0|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_07.dump b/src/testdir/dumps/Test_popup_textprop_07.dump
new file mode 100644
index 0000000..2950ddc
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_07.dump
@@ -0,0 +1,10 @@
+>5+0&#ffffff0|1| @72
+|5|2| @72
+|5|3| @72
+|5|4| @72
+|5@1| @72
+|5|6| @72
+|5|7| @72
+|5|8| @72
+|5|9| @72
+@57|5|0|,|1| @9|5|4|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_corn_1.dump b/src/testdir/dumps/Test_popup_textprop_corn_1.dump
new file mode 100644
index 0000000..ac18c99
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_corn_1.dump
@@ -0,0 +1,12 @@
+|4+0&#ffffff0|5| @72
+|4|6| @72
+|4|7| @3|â•”+0#0000001#ffd7ff255|â•@13|â•—| +0#0000000#ffffff0@52
+|4|8| @3|â•‘+0#0000001#ffd7ff255| |b|o|t@1|o|m| |r|i|g|h|t| |â•‘| +0#0000000#ffffff0@52
+|4|9| @3|â•š+0#0000001#ffd7ff255|â•@13|â•| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|b|o|t@1|o|m| |l|e|f|t| | +0#0000000#ffffff0@33
+>n|o|w| |w|o|r|k|i|n|g| |w|i|t|h| |s|o|m|e| |l+0&#5fd7ff255|o|n|g|e|r| +0&#ffffff0|t|e|x|t| |h|e|r|e| @36
+|5|1| @8| +0#0000001#ffd7ff255|t|o|p| |r|i|g|h|t| | +0#0000000#ffffff0@5|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@34
+|5|2| @25|â•‘+0#0000001#ffd7ff255| |t|o|p| |l|e|f|t| |â•‘| +0#0000000#ffffff0@34
+|5|3| @25|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@34
+|5|4| @72
+|5@1| @72
+@57|5|0|,|1| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_corn_2.dump b/src/testdir/dumps/Test_popup_textprop_corn_2.dump
new file mode 100644
index 0000000..b7be5dd
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_corn_2.dump
@@ -0,0 +1,12 @@
+|4+0&#ffffff0|5| @72
+|4|6| @72
+|4|7|â•”+0#0000001#ffd7ff255|â•@13|â•—| +0#0000000#ffffff0@56
+|4|8|â•‘+0#0000001#ffd7ff255| |b|o|t@1|o|m| |r|i|g|h|t| |â•‘| +0#0000000#ffffff0@56
+|4|9|â•š+0#0000001#ffd7ff255|â•@13|â•| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|b|o|t@1|o|m| |l|e|f|t| | +0#0000000#ffffff0@37
+>w|o|r|k|i|n|g| |w|i|t|h| |s|o|m|e| |l+0&#5fd7ff255|o|n|g|e|r| +0&#ffffff0|t|e|x|t| |h|e|r|e| @40
+|5|1| @4| +0#0000001#ffd7ff255|t|o|p| |r|i|g|h|t| | +0#0000000#ffffff0@5|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@38
+|5|2| @21|â•‘+0#0000001#ffd7ff255| |t|o|p| |l|e|f|t| |â•‘| +0#0000000#ffffff0@38
+|5|3| @21|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@38
+|5|4| @72
+|5@1| @72
+@57|5|0|,|1| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_corn_3.dump b/src/testdir/dumps/Test_popup_textprop_corn_3.dump
new file mode 100644
index 0000000..bd77c07
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_corn_3.dump
@@ -0,0 +1,12 @@
+|4+0&#ffffff0|5| @72
+|4|6| @72
+|e|x|t|r>a| @69
+|4|7|â•”+0#0000001#ffd7ff255|â•@13|â•—| +0#0000000#ffffff0@56
+|4|8|â•‘+0#0000001#ffd7ff255| |b|o|t@1|o|m| |r|i|g|h|t| |â•‘| +0#0000000#ffffff0@56
+|4|9|â•š+0#0000001#ffd7ff255|â•@13|â•| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|b|o|t@1|o|m| |l|e|f|t| | +0#0000000#ffffff0@37
+|w|o|r|k|i|n|g| |w|i|t|h| |s|o|m|e| |l+0&#5fd7ff255|o|n|g|e|r| +0&#ffffff0|t|e|x|t| |h|e|r|e| @40
+|5|1| @4| +0#0000001#ffd7ff255|t|o|p| |r|i|g|h|t| | +0#0000000#ffffff0@5|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@38
+|5|2| @21|â•‘+0#0000001#ffd7ff255| |t|o|p| |l|e|f|t| |â•‘| +0#0000000#ffffff0@38
+|5|3| @21|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@38
+|5|4| @72
+@57|4|7|,|5| @9|4|8|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_corn_4.dump b/src/testdir/dumps/Test_popup_textprop_corn_4.dump
new file mode 100644
index 0000000..6db9eee
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_corn_4.dump
@@ -0,0 +1,12 @@
+|4+0&#ffffff0|5| @72
+>4|6| @72
+|4|7|â•”+0#0000001#ffd7ff255|â•@13|â•—| +0#0000000#ffffff0@56
+|4|8|â•‘+0#0000001#ffd7ff255| |b|o|t@1|o|m| |r|i|g|h|t| |â•‘| +0#0000000#ffffff0@56
+|4|9|â•š+0#0000001#ffd7ff255|â•@13|â•| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|b|o|t@1|o|m| |l|e|f|t| | +0#0000000#ffffff0@37
+|w|o|r|k|i|n|g| |w|i|t|h| |s|o|m|e| |l+0&#5fd7ff255|o|n|g|e|r| +0&#ffffff0|t|e|x|t| |h|e|r|e| @40
+|5|1| @4| +0#0000001#ffd7ff255|t|o|p| |r|i|g|h|t| | +0#0000000#ffffff0@5|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@38
+|5|2| @21|â•‘+0#0000001#ffd7ff255| |t|o|p| |l|e|f|t| |â•‘| +0#0000000#ffffff0@38
+|5|3| @21|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@38
+|5|4| @72
+|5@1| @72
+|:| @55|4|6|,|1| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_corn_5.dump b/src/testdir/dumps/Test_popup_textprop_corn_5.dump
new file mode 100644
index 0000000..59bae78
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_corn_5.dump
@@ -0,0 +1,12 @@
+> +0&#ffffff0@36||+1&&|4+0&&|5| @34
+|~+0#4040ff13&| @35||+1#0000000&|4+0&&|6| @34
+|~+0#4040ff13&| @35||+1#0000000&|4+0&&|7|â•”+0#0000001#ffd7ff255|â•@13|â•—| +0#0000000#ffffff0@18
+|~+0#4040ff13&| @35||+1#0000000&|4+0&&|8|â•‘+0#0000001#ffd7ff255| |b|o|t@1|o|m| |r|i|g|h|t| |â•‘| +0#0000000#ffffff0@18
+|~+0#4040ff13&| @35||+1#0000000&|4+0&&|9|â•š+0#0000001#ffd7ff255|â•@13|â•| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|b|o|t@1|o|m| |l|e|f|t|
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|w+0&&|o|r|k|i|n|g| |w|i|t|h| |s|o|m|e| |l+0&#5fd7ff255|o|n|g|e|r| +0&#ffffff0|t|e|x|t| |h|e|r|e| @2
+|~+0#4040ff13&| @35||+1#0000000&|5+0&&|1| @4| +0#0000001#ffd7ff255|t|o|p| |r|i|g|h|t| | +0#0000000#ffffff0@5|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0
+|~+0#4040ff13&| @35||+1#0000000&|5+0&&|2| @21|â•‘+0#0000001#ffd7ff255| |t|o|p| |l|e|f|t| |â•‘| +0#0000000#ffffff0
+|~+0#4040ff13&| @35||+1#0000000&|5+0&&|3| @21|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0
+|~+0#4040ff13&| @35||+1#0000000&|5+0&&|4| @34
+|f+3&&|o@1| @15|0|,|0|-|1| @9|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|4|6|,|1| @10|4|8|%
+|"+0&&|f|o@1|"| |[|N|e|w|]| @63
diff --git a/src/testdir/dumps/Test_popup_textprop_corn_6.dump b/src/testdir/dumps/Test_popup_textprop_corn_6.dump
new file mode 100644
index 0000000..7e6426c
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_corn_6.dump
@@ -0,0 +1,12 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|o|n|l|y|!| @50|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popup_textprop_off_1.dump b/src/testdir/dumps/Test_popup_textprop_off_1.dump
new file mode 100644
index 0000000..33f2978
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_off_1.dump
@@ -0,0 +1,12 @@
+|4+0&#ffffff0|5| @72
+|4|6| @1|â•”+0#0000001#ffd7ff255|â•@13|â•—| +0#0000000#ffffff0@54
+|4|7| @1|â•‘+0#0000001#ffd7ff255| |b|o|t@1|o|m| |r|i|g|h|t| |â•‘| +0#0000000#ffffff0@54
+|4|8| @1|â•š+0#0000001#ffd7ff255|â•@13|â•| +0#0000000#ffffff0@9| +0#0000001#ffd7ff255|b|o|t@1|o|m| |l|e|f|t| | +0#0000000#ffffff0@31
+|4|9| @72
+>n|o|w| |w|o|r|k|i|n|g| |w|i|t|h| |s|o|m|e| |l+0&#5fd7ff255|o|n|g|e|r| +0&#ffffff0|t|e|x|t| |h|e|r|e| @36
+|5|1| @72
+|5|2| @6| +0#0000001#ffd7ff255|t|o|p| |r|i|g|h|t| | +0#0000000#ffffff0@9|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@32
+|5|3| @27|â•‘+0#0000001#ffd7ff255| |t|o|p| |l|e|f|t| |â•‘| +0#0000000#ffffff0@32
+|5|4| @27|â•š+0#0000001#ffd7ff255|â•@9|â•| +0#0000000#ffffff0@32
+|5@1| @72
+@57|5|0|,|1| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popup_textprop_off_2.dump b/src/testdir/dumps/Test_popup_textprop_off_2.dump
new file mode 100644
index 0000000..1896c64
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_textprop_off_2.dump
@@ -0,0 +1,12 @@
+|4+0&#ffffff0|5| @72
+|4|6| @72
+|4|7| @72
+|4|8| @72
+|4|9| @72
+>n|o|w| |w|o|r|k|i|n|g| |w|i|t|h| |s|o|m|e| |l|o|n|g|e|r| |t|e|x|t| |h|e|r|e| @36
+|5|1| @72
+|5|2| @72
+|5|3| @72
+|5|4| @72
+|5@1| @72
+|:|c|a|l@1| |p|r|o|p|_|c|l|e|a|r|(|5|0|)| @36|5|0|,|1| @9|4|9|%|
diff --git a/src/testdir/dumps/Test_popupwin_01.dump b/src/testdir/dumps/Test_popupwin_01.dump
new file mode 100644
index 0000000..2a444ca
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_01.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @8|h+0&#5fd7ff255|e|l@1|o| |t|h|e|r|e| @8|r+0&#afffff255| |o|n|e| @8| +0&#ffffff0@30
+|4| @22|a+0&#afffff255|n|o|t|h|e|r| |t|w|o| @8| +0&#ffffff0@30
+|5| @22|a+0&#afffff255|n|o|t|h|e|r| |t|h|r|e@1| @6| +0&#ffffff0@30
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_02.dump b/src/testdir/dumps/Test_popupwin_02.dump
new file mode 100644
index 0000000..9f658e5
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_02.dump
@@ -0,0 +1,10 @@
+| +8#0000001#e0e0e08|+| |[|N|o| |N|a|m|e|]| | +2#0000000#ffffff0|[|N|o| |N|a|m|e|]| | +1&&@49|X+8#0000001#e0e0e08
+> +0#0000000#ffffff0@74
+|~+0&#e0e0e08| @73
+|~| @6|o+0#0000001#ffd7ff255|t|h|e|r| |t|a|b| @10| +0#0000000#e0e0e08@46
+|~| @6|a+0#0000001#ffd7ff255| |c+0#ff404010&|o|m@1|e|n|t| +0#0000001&|l|i|n|e| @5| +0#0000000#e0e0e08@46
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0&#ffffff0@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_03.dump b/src/testdir/dumps/Test_popupwin_03.dump
new file mode 100644
index 0000000..d842654
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_03.dump
@@ -0,0 +1,10 @@
+| +2&#ffffff0|+| |[|N|o| |N|a|m|e|]| | +8#0000001#e0e0e08|[|N|o| |N|a|m|e|]| | +1#0000000#ffffff0@49|X+8#0000001#e0e0e08
+>1+0#0000000#ffffff0| @73
+|2| @8|h+0&#5fd7ff255|e|l@1|o| |t|h|e|r|e| @8|r+0&#afffff255| |o|n|e| @8| +0&#ffffff0@30
+|3| @22|a+0&#afffff255|n|o|t|h|e|r| |t|w|o| @8| +0&#ffffff0@30
+|4| @22|a+0&#afffff255|n|o|t|h|e|r| |t|h|r|e@1| @6| +0&#ffffff0@30
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_04.dump b/src/testdir/dumps/Test_popupwin_04.dump
new file mode 100644
index 0000000..d83beda
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_04.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0&#e0e0e08| @73
+|~| @73
+|~| @6|o+0#0000001#ffd7ff255|t|h|e|r| |t|a|b| @10| +0#0000000#e0e0e08@46
+|~| @6|a+0#0000001#ffd7ff255| |c+0#ff404010&|o|m@1|e|n|t| +0#0000001&|l|i|n|e| @5| +0#0000000#e0e0e08@46
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0&#ffffff0|q|u|i|t|!| @50|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_04a.dump b/src/testdir/dumps/Test_popupwin_04a.dump
new file mode 100644
index 0000000..4df0dd3
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_04a.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@11
+|~+0&#e0e0e08| @10
+|~| @10
+|~| @6|o+0#0000001#ffd7ff255|t|h|e
+|~+0#0000000#e0e0e08| @6|r+0#0000001#ffd7ff255| |t|a
+|~+0#0000000#e0e0e08| @6|b+0#0000001#ffd7ff255| @2
+|~+0#0000000#e0e0e08| @6|a+0#0000001#ffd7ff255| |c+0#ff404010&|o
+|~+0#0000000#e0e0e08| @6|m+0#ff404010#ffd7ff255@1|e|n
+|~+0#0000000#e0e0e08| @6|t+0#ff404010#ffd7ff255| +0#0000001&|l|i
+| +0#0000000#ffffff0@5|0|,|n+0#0000001#ffd7ff255|e| @1
diff --git a/src/testdir/dumps/Test_popupwin_05.dump b/src/testdir/dumps/Test_popupwin_05.dump
new file mode 100644
index 0000000..7e23a09
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_05.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0&#e0e0e08| @73
+|~| @73
+|~| @6|o+0#0000001#ffd7ff255|t|h|e|r| |t|a|b| @5| +0#0000000#e0e0e08@51
+|~| @6|a+0#0000001#ffd7ff255| |c+0#ff404010&|o|m@1|e|n|t| +0#0000001&|l|i|n|e| | +0#0000000#e0e0e08@51
+|~| @6| +0#4040ff13#ffd7ff255@14| +0#0000000#e0e0e08@51
+|~| @73
+|~| @73
+|~| @73
+|:+0&#ffffff0|r|e|d|r|a|w| @49|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_06.dump b/src/testdir/dumps/Test_popupwin_06.dump
new file mode 100644
index 0000000..6d89503
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_06.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0&#e0e0e08| @73
+|~| @73
+|~| @6|o+0#0000001#ffd7ff255|t|h|e|r| |t|a|b| @15| +0#0000000#e0e0e08@41
+|~| @6|a+0#0000001#ffd7ff255| |c+0#ff404010&|o|m@1|e|n|t| +0#0000001&|l|i|n|e| @10| +0#0000000#e0e0e08@41
+|~| @6|t+0#0000001#ffd7ff255|h|i|s| |l|i|n|e| |w|i|l@1| |n|o|t| |f|i|t| |h|e| +0#0000000#e0e0e08@41
+|~| @6|r+0#0000001#ffd7ff255|e| @22| +0#0000000#e0e0e08@41
+|~| @73
+|~| @73
+|:+0&#ffffff0|r|e|d|r|a|w| @49|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_07.dump b/src/testdir/dumps/Test_popupwin_07.dump
new file mode 100644
index 0000000..07f1fc4
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_07.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0&#e0e0e08| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @52|o+0#0000001#ffd7ff255|t|h|e|r| |t|a|b| @11
+|~+0#0000000#e0e0e08| @52|a+0#0000001#ffd7ff255| |c+0#ff404010&|o|m@1|e|n|t| +0#0000001&|l|i|n|e| @6
+|:+0#0000000#ffffff0| @52|t+0#0000001#ffd7ff255|h|i|s| |l|i|n|e| |w|i|l@1| |n|o|t| |f|i
+| +0#0000000#ffffff0@53|t+0#0000001#ffd7ff255| |h|e|r|e| @14
diff --git a/src/testdir/dumps/Test_popupwin_08.dump b/src/testdir/dumps/Test_popupwin_08.dump
new file mode 100644
index 0000000..814c979
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_08.dump
@@ -0,0 +1,10 @@
+>x+0&#ffffff0@2| @71
+|~+0&#e0e0e08| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0&#ffffff0@74
+@57|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_10.dump b/src/testdir/dumps/Test_popupwin_10.dump
new file mode 100644
index 0000000..2d80e0a
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_10.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @22|#+0#e000e06#5fd7ff255|i|n|c|l|u|d|e| |<+0#e000002&|s|t|d|i|o|.|h|>| +0#0000000#ffffff0@32
+|4| @22|i+0#00e0003#5fd7ff255|n|t| +0#0000000&|m|a|i|n|(|v+0#00e0003&|o|i|d|)+0#0000000&| @3| +0&#ffffff0@32
+|5| @22|{+0&#5fd7ff255| @16| +0&#ffffff0@32
+|6| @22| +0&#5fd7ff255@3|p|r|i|n|t|f|(|1+0#e000002&|2|3|)+0#0000000&|;| @1| +0&#ffffff0@32
+|7| @22|}+0&#5fd7ff255| @16| +0&#ffffff0@32
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_11.dump b/src/testdir/dumps/Test_popupwin_11.dump
new file mode 100644
index 0000000..f67a314
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_11.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @18|#+0#e000e06#e0e0e08|i|n|c|l|u|d|e| |<+0#e000002&|s|t|d|i|o|.|h|>| +0#0000000&@1| +0&#ffffff0@34
+|4| @18|i+0#00e0003#e0e0e08|n|t| +0#0000000&|m|a|i|n|(|v+0#00e0003&|o|i|d|)+0#0000000&| @5| +0&#ffffff0@34
+|5| @18|{+0&#e0e0e08| @18| +0&#ffffff0@34
+|6| @18| +0&#e0e0e08@7|p|r|i|n|t|f|(|5+0#e000002&|6|7|)+0#0000000&|;| +0&#ffffff0@34
+|7| @18|}+0&#e0e0e08| @18| +0&#ffffff0@34
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_20.dump b/src/testdir/dumps/Test_popupwin_20.dump
new file mode 100644
index 0000000..692708b
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_20.dump
@@ -0,0 +1,15 @@
+>1+0&#ffffff0| @73
+|2| |++0#0000001#ffd7ff255|-@11|+| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@4|+|-@11|+| @15|X+0#0000001#ffd7ff255| +0#0000000#ffffff0@1
+|3| ||+0#0000001#ffd7ff255|h|e|l@1|o| |b|o|r|d|e|r||| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|h|e|l@1|o| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@4||| @11||| @16|X+0#0000001#ffd7ff255| +0#0000000#ffffff0
+|4| |++0#0000001#ffd7ff255|-@11|+| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@4||| |h|e|l@1|o| |b|o|t|h| ||| @17|X+0#0000001#ffd7ff255
+|5+0#0000000#ffffff0| @40||| @11||| @17|X+0#0000001#ffd7ff255
+|6+0#0000000#ffffff0| |++0#0000001#ffd7ff255|-@8| +0#0000000#ffffff0@9| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@5|+|-@11|+| @18
+|7| ||+0#0000001#ffd7ff255|b|o|r|d|e|r| |T|L| +0#0000000#ffffff0@9| +0#0000001#ffd7ff255@3|p|a|d@1|i|n|g|s| @1| +0#0000000#ffffff0@38
+|8| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@13||+0#0000001#ffd7ff255| @2|w|r|a|p@1|e|d| |l|o|n|g|e|r| |t|e| @2||
+|9+0#0000000#ffffff0| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@13||+0#0000001#ffd7ff255| @2|x|t| @17||
+|1+0#0000000#ffffff0|0| @19| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@38
+|1@1| @46||+0#0000001#ffd7ff255| @2|r|i|g|h|t| |a|l|i|g|n|e|d| |t|e|x|t| @2||
+|1+0#0000000#ffffff0|2| @72
+|1|3| @72
+|1|4| @72
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_21.dump b/src/testdir/dumps/Test_popupwin_21.dump
new file mode 100644
index 0000000..6398549
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_21.dump
@@ -0,0 +1,15 @@
+>1+0&#ffffff0| @73
+|2| |â•”+0#0000001#ffd7ff255|â•@11|â•—| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@4|â•”|â•@11|â•—| @15|X+0#0000001#ffd7ff255| +0#0000000#ffffff0@1
+|3| |â•‘+0#0000001#ffd7ff255|h|e|l@1|o| |b|o|r|d|e|r|â•‘| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|h|e|l@1|o| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@4|â•‘| @11|â•‘| @16|X+0#0000001#ffd7ff255| +0#0000000#ffffff0
+|4| |â•š+0#0000001#ffd7ff255|â•@11|â•| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@4|â•‘| |h|e|l@1|o| |b|o|t|h| |â•‘| @17|X+0#0000001#ffd7ff255
+|5+0#0000000#ffffff0| @40|â•‘| @11|â•‘| @17|X+0#0000001#ffd7ff255
+|6+0#0000000#ffffff0| |â•”+0#0000001#ffd7ff255|â•@8| +0#0000000#ffffff0@9| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@5|â•š|â•@11|â•| @18
+|7| |â•‘+0#0000001#ffd7ff255|b|o|r|d|e|r| |T|L| +0#0000000#ffffff0@9| +0#0000001#ffd7ff255@3|p|a|d@1|i|n|g|s| @1| +0#0000000#ffffff0@38
+|8| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@13|â•‘+0#0000001#ffd7ff255| @2|w|r|a|p@1|e|d| |l|o|n|g|e|r| |t|e| @2|â•‘
+|9+0#0000000#ffffff0| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@13|â•‘+0#0000001#ffd7ff255| @2|x|t| @17|â•‘
+|1+0#0000000#ffffff0|0| @19| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@38
+|1@1| @46|â•‘+0#0000001#ffd7ff255| @2|r|i|g|h|t| |a|l|i|g|n|e|d| |t|e|x|t| @2|â•‘
+|1+0#0000000#ffffff0|2| @72
+|1|3| @72
+|1|4| @72
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_22.dump b/src/testdir/dumps/Test_popupwin_22.dump
new file mode 100644
index 0000000..7e38906
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_22.dump
@@ -0,0 +1,12 @@
+>1+0&#ffffff0| @73
+|2| |â•”+0&#5fd7ff255|â•@11|â•—| +0&#ffffff0@5|â•”+0&#dadada255|â•@11|â•—+0&#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|3| |â•‘+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#5fd7ff255| +0&#ffffff0@5|â•‘+0&#a8a8a8255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|4| |â•š+0&#5fd7ff255|â•@11|â•| +0&#ffffff0@5|â•‘+0&#a8a8a8255|a+0#0000001#ffd7ff255|n|d| |m|o|r|e| @3|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|l+0#0000001#ffd7ff255|i|n|e|s| |o|n|l|y| @1|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |c|o|r|n|e|r|s|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|5| @20|â•š+0&#585858255|â•@11|â•| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|6| |4+0&#5fd7ff255|0@11|5| +0&#ffffff0@58
+|7| |3+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|1+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255@13| +0&#ffffff0@38
+|8| |3+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |n|u|m|b|e|r|s|1+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r| +0#0000000#5fd7ff255| +0&#ffffff0@38
+|9| |7+0&#5fd7ff255|2@11|6| +0&#ffffff0@5| +0&#5fd7ff255|j+0#0000001#ffd7ff255|u|s|t| |b|l|a|n|k|s| | +0#0000000#5fd7ff255| +0&#ffffff0@38
+|1|0| @19| +0&#5fd7ff255@13| +0&#ffffff0@38
+|1@1| @72
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_23.dump b/src/testdir/dumps/Test_popupwin_23.dump
new file mode 100644
index 0000000..737d8f1
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_23.dump
@@ -0,0 +1,12 @@
+>1+0&#ffffff0| @73
+|2| |â•”+0&#5fd7ff255|â•@11|â•—| +0&#ffffff0@5|â•”+0&#dadada255|â•@11|â•—+0&#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|3| |â•‘+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#5fd7ff255| +0&#ffffff0@5|â•‘+0&#a8a8a8255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|4| |â•š+0&#5fd7ff255|â•@11|â•| +0&#ffffff0@5|â•‘+0&#a8a8a8255|a+0#0000001#ffd7ff255|n|d| |m|o|r|e| @3|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|l+0#0000001#ffd7ff255|i|n|e|s| |o|n|l|y| @1|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |c|o|r|n|e|r|s|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|5| @20|â•š+0&#585858255|â•@11|â•| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|6| |e+0&#5fd7ff255|a@11|f| +0&#ffffff0@58
+|7| |d+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|b+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255@13| +0&#ffffff0@38
+|8| |d+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |n|u|m|b|e|r|s|b+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r| +0#0000000#5fd7ff255| +0&#ffffff0@38
+|9| |h+0&#5fd7ff255|c@11|g| +0&#ffffff0@5| +0&#5fd7ff255|j+0#0000001#ffd7ff255|u|s|t| |b|l|a|n|k|s| | +0#0000000#5fd7ff255| +0&#ffffff0@38
+|1|0| @19| +0&#5fd7ff255@13| +0&#ffffff0@38
+|1@1| @72
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_24.dump b/src/testdir/dumps/Test_popupwin_24.dump
new file mode 100644
index 0000000..f5386c9
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_24.dump
@@ -0,0 +1,12 @@
+>1+0&#ffffff0| @73
+|2| |â•”+0&#5fd7ff255|â•@11|â•—| +0&#ffffff0@5|â•”+0&#dadada255|â•@11|â•—+0&#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|3| |â•‘+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#5fd7ff255| +0&#ffffff0@5|â•‘+0&#a8a8a8255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|4| |â•š+0&#5fd7ff255|â•@11|â•| +0&#ffffff0@5|â•‘+0&#a8a8a8255|a+0#0000001#ffd7ff255|n|d| |m|o|r|e| @3|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|l+0#0000001#ffd7ff255|i|n|e|s| |o|n|l|y| @1|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |c|o|r|n|e|r|s|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|5| @20|â•š+0&#585858255|â•@11|â•| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|6| |e+0&#5fd7ff255|a@11|f| +0&#ffffff0@58
+|7| |d+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|b+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255@13| +0&#ffffff0@38
+|8| |d+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |n|u|m|b|e|r|s|b+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r| +0#0000000#5fd7ff255| +0&#ffffff0@5|┌+0#0000001#ffd7ff255|─@4|â”| +0#0000000#ffffff0@25
+|9| |h+0&#5fd7ff255|c@11|g| +0&#ffffff0@5| +0&#5fd7ff255|j+0#0000001#ffd7ff255|u|s|t| |b|l|a|n|k|s| | +0#0000000#5fd7ff255| +0&#ffffff0@5|│+0#0000001#ffd7ff255|h|e|l@1|o|│| +0#0000000#ffffff0@25
+|1|0| @19| +0&#5fd7ff255@13| +0&#ffffff0@5|└+0#0000001#ffd7ff255|─@4|┘| +0#0000000#ffffff0@25
+|1@1| @72
+|:|c|a|l@1| |M|u|l|t|i|B|y|t|e|(|)| @39|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_atcursor_pos.dump b/src/testdir/dumps/Test_popupwin_atcursor_pos.dump
new file mode 100644
index 0000000..3a4f0c2
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_atcursor_pos.dump
@@ -0,0 +1,12 @@
+|-+0&#ffffff0@59| @14
+|-@59| @14
+|-@25|%|-@16|@|-@14| @14
+|-@25|f+0#0000001#ffd7ff255|i|R|S|t| |-+0#0000000#ffffff0@6|F+0#0000001#ffd7ff255|i|r|s|t| |-+0#0000000#ffffff0@14| @14
+|-@25|s+0#0000001#ffd7ff255|e|C|O|n|d|-+0#0000000#ffffff0@6|S+0#0000001#ffd7ff255|e|c|o|n|D|-+0#0000000#ffffff0@14| @14
+|-@59| @14
+|-@1|f+0#0000001#ffd7ff255|i|r|s|t| |-+0#0000000#ffffff0@6|F+0#0000001#ffd7ff255|I|r|s|T| |-+0#0000000#ffffff0@38| @14
+|-@1|s+0#0000001#ffd7ff255|e|c|o|n|d|-+0#0000000#ffffff0@6|S+0#0000001#ffd7ff255|E|c|o|N|D|-+0#0000000#ffffff0@6|m+0#0000001#ffd7ff255|a|r|k|-+0#0000000#ffffff0@27| @14
+|-@1|#|-@16|&|-@4| @1>X|-@21| @23
+|-@59| @14
+|-@59| @14
+@57|9|,|3|8| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_behind.dump b/src/testdir/dumps/Test_popupwin_behind.dump
new file mode 100644
index 0000000..cec0a52
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_behind.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@36||+1&&> +0&&@36
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @34|l+0#0000001#ffd7ff255|i|n|e|1| +0#4040ff13#ffffff0@33
+|[+1#0000000&|N|o| |N|a|m|e|]| @9|0|,|0|-|1| @9|A|l|l+0#0000001#ffd7ff255|i|n|e|2| +3#0000000#ffffff0|N|a|m|e|]| @9|0|,|0|-|1| @9|A|l@1
+| +0&&@35|l+0#0000001#ffd7ff255|i|n|e|3| +0#0000000#ffffff0@33
+|~+0#4040ff13&| @34|l+0#0000001#ffd7ff255|i|n|e|4| +0#4040ff13#ffffff0@33
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_popupwin_beval_1.dump b/src/testdir/dumps/Test_popupwin_beval_1.dump
new file mode 100644
index 0000000..410ac5c
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_beval_1.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+>2| @73
+|3| @73
+|4| @12|t+0#0000001#ffd7ff255|e|x|t| +0#0000000#ffffff0@56
+|h|e|r|e| |i|s| |s|o|m|e| |t|e|x|t| |t|o| |h|o|v|e|r| |o|v|e|r| @43
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|:|c|a|l@1| |H|o|v|e|r|(|)| @43|2|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_beval_2.dump b/src/testdir/dumps/Test_popupwin_beval_2.dump
new file mode 100644
index 0000000..34b222d
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_beval_2.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+>2| @73
+|3| @73
+|4| @12|t+0#0000001#ffd7ff255|e|x|t| +0#0000000#ffffff0@56
+|h|e|r|e| |i|s| |s|o|m|e| |t|e|x|t| |t|o| |h|o|v|e|r| |o|v|e|r| @43
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|:|c|a|l@1| |M|o|v|e|O|n|t|o|P|o|p|u|p|(|)| @35|2|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_beval_3.dump b/src/testdir/dumps/Test_popupwin_beval_3.dump
new file mode 100644
index 0000000..2e8e419
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_beval_3.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+>2| @73
+|3| @73
+|4| @73
+|h|e|r|e| |i|s| |s|o|m|e| |t|e|x|t| |t|o| |h|o|v|e|r| |o|v|e|r| @43
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|:|c|a|l@1| |M|o|v|e|A|w|a|y|(|)| @40|2|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_close_01.dump b/src/testdir/dumps/Test_popupwin_close_01.dump
new file mode 100644
index 0000000..e11ae83
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_close_01.dump
@@ -0,0 +1,10 @@
+>â•”+0#0000001#ffd7ff255|â•@5|X| +0#0000000#ffffff0@66
+|â•‘+0#0000001#ffd7ff255|f|o@1|b|a|r|â•‘| +0#0000000#ffffff0@66
+|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#0000000#ffffff0@5|n+0#0000001#ffd7ff255|o|t|i|f|i|c|a|t|i|o|n| +0#0000000#ffffff0@48
+|4| @73
+|5| |n+0#0000001#ffd7ff255|o| |b|o|r|d|e|r| |h|e|r|X| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@12|X| +0#0000000#ffffff0@38
+|6| @20| +0#0000001#ffd7ff255|o|n|l|y| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@38
+|7| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@38
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_close_02.dump b/src/testdir/dumps/Test_popupwin_close_02.dump
new file mode 100644
index 0000000..75342ef
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_close_02.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @12|n+0#0000001#ffd7ff255|o|t|i|f|i|c|a|t|i|o|n| +0#0000000#ffffff0@48
+|4| @73
+|5| |n+0#0000001#ffd7ff255|o| |b|o|r|d|e|r| |h|e|r|X| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@12|X| +0#0000000#ffffff0@38
+|6| @20| +0#0000001#ffd7ff255|o|n|l|y| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@38
+|7| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@38
+|8| @73
+|9| @73
+|:|c|a|l@1| |C|l|o|s|e|W|i|t|h|X|(|)| @38|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_close_03.dump b/src/testdir/dumps/Test_popupwin_close_03.dump
new file mode 100644
index 0000000..82438c8
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_close_03.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @73
+|5| |n+0#0000001#ffd7ff255|o| |b|o|r|d|e|r| |h|e|r|X| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@12|X| +0#0000000#ffffff0@38
+|6| @20| +0#0000001#ffd7ff255|o|n|l|y| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@38
+|7| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@38
+|8| @73
+|9| @73
+|P|o|p|u|p| |c|l|o|s|e|d| |w|i|t|h| |-|2| @36|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_close_04.dump b/src/testdir/dumps/Test_popupwin_close_04.dump
new file mode 100644
index 0000000..91d7f09
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_close_04.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @37|â•”+0#0000001#ffd7ff255|â•@5|X| +0#0000000#ffffff0@27
+|2| @37|â•‘+0#0000001#ffd7ff255|b|a|r|f|o@1|â•‘| +0#0000000#ffffff0@27
+|3| @37|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#0000000#ffffff0@27
+|4| @73
+|5| |n+0#0000001#ffd7ff255|o| |b|o|r|d|e|r| |h|e|r|X| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@12|X| +0#0000000#ffffff0@38
+|6| @20| +0#0000001#ffd7ff255|o|n|l|y| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@38
+|7| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@38
+|8| @73
+|9| @73
+|:|c|a|l@1| |C|r|e|a|t|e|W|i|t|h|M|e|n|u|F|i|l|t|e|r|(|)| @28|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_close_05.dump b/src/testdir/dumps/Test_popupwin_close_05.dump
new file mode 100644
index 0000000..75ce052
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_close_05.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @73
+|5| |n+0#0000001#ffd7ff255|o| |b|o|r|d|e|r| |h|e|r|X| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@12|X| +0#0000000#ffffff0@38
+|6| @20| +0#0000001#ffd7ff255|o|n|l|y| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@38
+|7| @20| +0#0000001#ffd7ff255@13| +0#0000000#ffffff0@38
+|8| @73
+|9| @73
+|:|c|a|l@1| |C|r|e|a|t|e|W|i|t|h|M|e|n|u|F|i|l|t|e|r|(|)| @28|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_corners.dump b/src/testdir/dumps/Test_popupwin_corners.dump
new file mode 100644
index 0000000..82188c0
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_corners.dump
@@ -0,0 +1,12 @@
+|-+0&#ffffff0@49>*|-@8| @14
+|-@1|#|-@19|@|-@25|â•”+0#0000001#ffd7ff255|â•@4|â•—|-+0#0000000#ffffff0@2| @14
+|-@1|â•”+0#0000001#ffd7ff255|â•@7|â•—|-+0#0000000#ffffff0@1|â•”+0#0000001#ffd7ff255|â•@7|â•—|-+0#0000000#ffffff0@1|â•”+0#0000001#ffd7ff255|â•@7|â•—|-+0#0000000#ffffff0@1|â•”+0#0000001#ffd7ff255|â•@7|â•—|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @4|â•‘|-+0#0000000#ffffff0@2| @14
+|-@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |o|n|e| |â•‘|-+0#0000000#ffffff0@2| @14
+|-@1|â•‘+0#0000001#ffd7ff255| |f|i|r|s|t| @1|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |F|i|r|s|t| @1|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |f|i|R|S|t| @1|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |F|I|r|s|T| @1|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |t|w|o| |â•‘|-+0#0000000#ffffff0@2| @14
+|-@1|â•‘+0#0000001#ffd7ff255| |s|e|c|o|n|d| |â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |S|e|c|o|n|D| |â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |s|e|C|O|n|d| |â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| |S|E|c|o|N|D| |â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @4|â•‘|-+0#0000000#ffffff0@2| @14
+|-@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255| @7|â•‘|-+0#0000000#ffffff0@1|â•š+0#0000001#ffd7ff255|â•@4|â•|-+0#0000000#ffffff0@2| @14
+|-@1|â•š+0#0000001#ffd7ff255|â•@7|â•|-+0#0000000#ffffff0@1|â•š+0#0000001#ffd7ff255|â•@7|â•|-+0#0000000#ffffff0@1|â•š+0#0000001#ffd7ff255|â•@7|â•|-+0#0000000#ffffff0@1|â•š+0#0000001#ffd7ff255|â•@7|â•|-+0#0000000#ffffff0@11| @14
+|-@25|%|-@19|&|-@11| @14
+|-@59| @14
+|-@59| @14
+@57|1|,|5|1| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_ctrl_c.dump b/src/testdir/dumps/Test_popupwin_ctrl_c.dump
new file mode 100644
index 0000000..d6636ae
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_ctrl_c.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@36||+1&&| +0&&@36
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|~| @35||+1#0000000&|~+0#4040ff13&| @35
+|[+3#0000000&|N|o| |N|a|m|e|]| @9|0|,|0|-|1| @9|A|l@1| |[+1&&|N|o| |N|a|m|e|]| @9|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_1.dump b/src/testdir/dumps/Test_popupwin_cursorline_1.dump
new file mode 100644
index 0000000..d73b149
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_1.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @34|1+0#0000001#ffd7ff255@2| +0#4040ff13#ffffff0@35
+|~| @34|2+0#0000001#ffd7ff255@2| +0#4040ff13#ffffff0@35
+|~| @34|3+0#0000001#ffd7ff255@2| +0#4040ff13#ffffff0@35
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_2.dump b/src/testdir/dumps/Test_popupwin_cursorline_2.dump
new file mode 100644
index 0000000..6c00f96
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_2.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @34|1+0#0000001#e0e0e08@2| +0#4040ff13#ffffff0@35
+|~| @34|2+0#0000001#ffd7ff255@2| +0#4040ff13#ffffff0@35
+|~| @34|3+0#0000001#ffd7ff255@2| +0#4040ff13#ffffff0@35
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_3.dump b/src/testdir/dumps/Test_popupwin_cursorline_3.dump
new file mode 100644
index 0000000..5866f4f
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_3.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |1@2| | +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |2@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_4.dump b/src/testdir/dumps/Test_popupwin_cursorline_4.dump
new file mode 100644
index 0000000..6139eb7
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_4.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |2@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |3@2| | +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_5.dump b/src/testdir/dumps/Test_popupwin_cursorline_5.dump
new file mode 100644
index 0000000..bd9b2d4
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_5.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |1+0&#e0e0e08@2| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |2@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_6.dump b/src/testdir/dumps/Test_popupwin_cursorline_6.dump
new file mode 100644
index 0000000..d814edd
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_6.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |2@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |3+0&#e0e0e08@2| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_7.dump b/src/testdir/dumps/Test_popupwin_cursorline_7.dump
new file mode 100644
index 0000000..15eb0be
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_7.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @34|1+0#0000001#ffd7ff255@2| +0#4040ff13#ffffff0@35
+|~| @34|2+0#0000001#e0e0e08@2| +0#4040ff13#ffffff0@35
+|~| @34|3+0#0000001#ffd7ff255@2| +0#4040ff13#ffffff0@35
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_cursorline_8.dump b/src/testdir/dumps/Test_popupwin_cursorline_8.dump
new file mode 100644
index 0000000..51a009a
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_cursorline_8.dump
@@ -0,0 +1,10 @@
+>o+0&#ffffff0|n|e| @71
+|t|w|o| @71
+|t|h|r|e@1| @69
+|~+0#4040ff13&| @33|o+0#0000001#ffd7ff255|n|e| @1| +0#4040ff13#ffffff0@34
+|~| @33|t+0#0000001#e0e0e08|w|o| @1| +0#4040ff13#ffffff0@34
+|~| @33|t+0#0000001#ffd7ff255|h|r|e@1| +0#4040ff13#ffffff0@34
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_doublewidth_1.dump b/src/testdir/dumps/Test_popupwin_doublewidth_1.dump
new file mode 100644
index 0000000..d59466d
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_doublewidth_1.dump
@@ -0,0 +1,10 @@
+>x+0&#ffffff0| |你*0#0000001#ffd7ff255|好|,|世|界| +&|-| |>+0#4040ff13&| +0#0000000#ffffff0|好*&|世|界|你|好| +&@47
+|你*&|你*0#0000001#ffd7ff255|好|,|世|界|x+&@3|好*0#0000000#ffffff0|世|界|你|好| +&@48
+|x| |x+0#0000001#ffd7ff255| @12| +0#0000000#ffffff0|好*&|世|界|你|好| +&@47
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_drag_01.dump b/src/testdir/dumps/Test_popupwin_drag_01.dump
new file mode 100644
index 0000000..afed63e
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_01.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @35||+1&&|1+0&&| @35
+|2| @35||+1&&|2+0&&| @35
+|3| @35||+1&&|3+0&&| @35
+|4| @35||+1&&|4+0&&| @35
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|1+0&&| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#0000000#ffffff0@33
+|2| @31|â•‘+0#0000001#ffd7ff255|1@3| @1|â•‘| +0#0000000#ffffff0@33
+|3| @31|â•‘+0#0000001#ffd7ff255|2@5|â•‘| +0#0000000#ffffff0@33
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @10|â•‘+0#0000001#ffd7ff255|3@4| |â•‘| +1#0000000#ffffff0|N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+| +0&&@32|â•š+0#0000001#ffd7ff255|â•@5|⇲| +0#0000000#ffffff0@33
diff --git a/src/testdir/dumps/Test_popupwin_drag_02.dump b/src/testdir/dumps/Test_popupwin_drag_02.dump
new file mode 100644
index 0000000..a6a560d
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_02.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @35||+1&&|1+0&&| @35
+|2| @11|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#0000000#ffffff0@15||+1&&|2+0&&| @35
+|3| @11|â•‘+0#0000001#ffd7ff255|1@3| @1|â•‘| +0#0000000#ffffff0@15||+1&&|3+0&&| @35
+|4| @11|â•‘+0#0000001#ffd7ff255|2@5|â•‘| +0#0000000#ffffff0@15||+1&&|4+0&&| @35
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]|â•‘+0#0000001#ffd7ff255|3@4| |â•‘|1+3#0000000#ffffff0| @11|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|1+0&&| @11|â•š+0#0000001#ffd7ff255|â•@5|⇲| +0#0000000#ffffff0@15||+1&&|1+0&&| @35
+|2| @35||+1&&|2+0&&| @35
+|3| @35||+1&&|3+0&&| @35
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p| |[|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+| +0&&@74
diff --git a/src/testdir/dumps/Test_popupwin_drag_03.dump b/src/testdir/dumps/Test_popupwin_drag_03.dump
new file mode 100644
index 0000000..497a6e3
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_03.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @35||+1&&|1+0&&| @35
+|2| @11|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@11||+1&&|2+0&&| @35
+|3| @11|â•‘+0#0000001#ffd7ff255|1@3| @5|â•‘| +0#0000000#ffffff0@11||+1&&|3+0&&| @35
+|4| @11|â•‘+0#0000001#ffd7ff255|2@5| @3|â•‘| +0#0000000#ffffff0@11||+1&&|4+0&&| @35
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]|â•‘+0#0000001#ffd7ff255|3@4| @4|â•‘| +3#0000000#ffffff0@8|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|1+0&&| @11|â•‘+0#0000001#ffd7ff255| +0#4040ff13&@9|â•‘+0#0000001&| +0#0000000#ffffff0@11||+1&&|1+0&&| @35
+|2| @11|â•š+0#0000001#ffd7ff255|â•@9|⇲| +0#0000000#ffffff0@11||+1&&|2+0&&| @35
+|3| @35||+1&&|3+0&&| @35
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p| |[|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|:+0&&|c|a|l@1| |R|e|s|i|z|e|(|)| @60
diff --git a/src/testdir/dumps/Test_popupwin_drag_04.dump b/src/testdir/dumps/Test_popupwin_drag_04.dump
new file mode 100644
index 0000000..931e13c
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_04.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @35||+1&&|1+0&&| @35
+|2| @15|â•”+0#0000001#ffd7ff255|â•@9|â•—| +0#0000000#ffffff0@7||+1&&|2+0&&| @35
+|3| @15|â•‘+0#0000001#ffd7ff255|1@3| @5|â•‘| +0#0000000#ffffff0@7||+1&&|3+0&&| @35
+|4| @15|â•‘+0#0000001#ffd7ff255|2@5| @3|â•‘| +0#0000000#ffffff0@7||+1&&|4+0&&| @35
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @3|â•‘+0#0000001#ffd7ff255|3@4| @4|â•‘| +3#0000000#ffffff0@4|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|1+0&&| @15|â•‘+0#0000001#ffd7ff255| +0#4040ff13&@9|â•‘+0#0000001&| +0#0000000#ffffff0@7||+1&&|1+0&&| @35
+|2| @15|â•š+0#0000001#ffd7ff255|â•@9|⇲| +0#0000000#ffffff0@7||+1&&|2+0&&| @35
+|3| @35||+1&&|3+0&&| @35
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p| |[|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|:+0&&|c|a|l@1| |C|l|i|c|k|A|n|d|D|r|a|g|(|)| @54
diff --git a/src/testdir/dumps/Test_popupwin_drag_05.dump b/src/testdir/dumps/Test_popupwin_drag_05.dump
new file mode 100644
index 0000000..3ca5513
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_05.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @35||+1&&|1+0&&| @35
+|2| @35||+1&&|2+0&&| @35
+|3| @2|h+0#0000001#ffd7ff255|e|l@1|o| +0#0000000#ffffff0@27||+1&&|3+0&&| @35
+|4| @35||+1&&|4+0&&| @35
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|1+0&&| @35||+1&&|1+0&&| @35
+|2| @35||+1&&|2+0&&| @35
+|3| @35||+1&&|3+0&&| @35
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p| |[|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|:+0&&|c|a|l@1| |D|r|a|g|A|l@1|S|t|a|r|t|(|)| @54
diff --git a/src/testdir/dumps/Test_popupwin_drag_06.dump b/src/testdir/dumps/Test_popupwin_drag_06.dump
new file mode 100644
index 0000000..702f051
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_06.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @35||+1&&|1+0&&| @35
+|2| @35||+1&&|2+0&&| @35
+|3| @35||+1&&|3+0&&| @35
+|4| @35||+1&&|4+0&&| @35
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|h+0#0000001#ffd7ff255|e|l@1|o|o+1#0000000#ffffff0| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|1+0&&| @35||+1&&|1+0&&| @35
+|2| @35||+1&&|2+0&&| @35
+|3| @35||+1&&|3+0&&| @35
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p| |[|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|T|o|p
+|:+0&&|c|a|l@1| |D|r|a|g|A|l@1|D|r|a|g|(|)| @55
diff --git a/src/testdir/dumps/Test_popupwin_drag_minwidth_1.dump b/src/testdir/dumps/Test_popupwin_drag_minwidth_1.dump
new file mode 100644
index 0000000..fbeec7a
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_minwidth_1.dump
@@ -0,0 +1,10 @@
+>â•”+0#0000001#ffd7ff255|â•@73
+|â•‘|0| @72
+|â•‘|1| @72
+|â•‘|2| @72
+|â•‘|3| @72
+|â•‘|4| @72
+|â•‘|5| @72
+|â•‘|6| @72
+|â•‘|7| @72
+|â•‘|8| @72
diff --git a/src/testdir/dumps/Test_popupwin_drag_minwidth_2.dump b/src/testdir/dumps/Test_popupwin_drag_minwidth_2.dump
new file mode 100644
index 0000000..b4edcfe
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_minwidth_2.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @26|â•”+0#0000001#ffd7ff255|â•@44|â•—
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|0| @42| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|1| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|2| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|3| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+| +0#0000000#ffffff0@27|â•š+0#0000001#ffd7ff255|â•@44|â•
diff --git a/src/testdir/dumps/Test_popupwin_drag_minwidth_3.dump b/src/testdir/dumps/Test_popupwin_drag_minwidth_3.dump
new file mode 100644
index 0000000..e429d38
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_drag_minwidth_3.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @26|â•”+0#0000001#ffd7ff255|â•@44|â•—
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|0| @42| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|1| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|2| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|3| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|4| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|~+0#4040ff13#ffffff0| @26|â•‘+0#0000001#ffd7ff255|5| @42| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+| +0#0000000#ffffff0@27|â•š+0#0000001#ffd7ff255|â•@44|â•
diff --git a/src/testdir/dumps/Test_popupwin_firstline_1.dump b/src/testdir/dumps/Test_popupwin_firstline_1.dump
new file mode 100644
index 0000000..b3b0349
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_firstline_1.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @32|3+0#0000001#ffd7ff255@4| | +0#0000000#a8a8a8255| +0&#ffffff0@33
+|5| @32|4+0#0000001#ffd7ff255@1| @3| +0#0000000#0000001| +0&#ffffff0@33
+|6| @32|5+0#0000001#ffd7ff255| @4| +0#0000000#0000001| +0&#ffffff0@33
+|7| @32|6+0#0000001#ffd7ff255@5| +0#0000000#a8a8a8255| +0&#ffffff0@33
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_firstline_2.dump b/src/testdir/dumps/Test_popupwin_firstline_2.dump
new file mode 100644
index 0000000..6b95fb5
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_firstline_2.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @27|6+0#0000001#ffd7ff255@5| @9| +0#0000000#a8a8a8255| +0&#ffffff0@28
+|5| @27|7+0#0000001#ffd7ff255@4| @10| +0#0000000#a8a8a8255| +0&#ffffff0@28
+|6| @27|8+0#0000001#ffd7ff255@2| @12| +0#0000000#0000001| +0&#ffffff0@28
+|7| @27|9+0#0000001#ffd7ff255@15| +0#0000000#0000001| +0&#ffffff0@28
+|8| @73
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_1.dump b/src/testdir/dumps/Test_popupwin_infopopup_1.dump
new file mode 100644
index 0000000..36bb2ee
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_1.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|a|w|o|r|d> @15|â•”+0&#ffff4012|â•@15|X| +0&#ffffff0@9
+|~+0#4040ff13&| @23| +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| |â•‘+0#0000000#ffff4012| |w|o|r|d|s| |a|r|e| |c|o@1|l| |â•‘| +0#4040ff13#ffffff0@9
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| |â•š+0#0000000#ffff4012|â•@15|⇲| +0#4040ff13#ffffff0@9
+|~| @23| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_2.dump b/src/testdir/dumps/Test_popupwin_infopopup_2.dump
new file mode 100644
index 0000000..34c46e9
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_2.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|a|n|o|t|h|e|r|w|o|r|d> @37
+|~+0#4040ff13&| @23| +0#0000001#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| |â•”+0#0000000#ffff4012|â•@25|X
+|~+0#4040ff13#ffffff0| @23| +0#0000001#e0e0e08|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| |â•‘+0#0000000#ffff4012| |o|t|h|e|r| |w|o|r|d|s| |a|r|e| @9|â•‘
+|~+0#4040ff13#ffffff0| @23| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| |â•‘+0#0000000#ffff4012| |c|o@1|l|e|r| |t|h|a|n| |t|h|i|s| |a|n|d| |s|o|m| |â•‘
+|~+0#4040ff13#ffffff0| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| |â•‘+0#0000000#ffff4012| |e| |m|o|r|e| |t|e|x|t| @13|â•‘
+|~+0#4040ff13#ffffff0| @45|â•‘+0#0000000#ffff4012| |t|o| |m|a|k|e| |w|r|a|p| @12|â•‘
+|~+0#4040ff13#ffffff0| @45|â•š+0#0000000#ffff4012|â•@25|⇲
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_3.dump b/src/testdir/dumps/Test_popupwin_infopopup_3.dump
new file mode 100644
index 0000000..fe5e7b1
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_3.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|n|o|i|n|f|o> @42
+|~+0#4040ff13&| @23| +0#0000001#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| |â•”+0#0000000#ffff4012|â•@11|X| +0#4040ff13#ffffff0@13
+|~| @23| +0#0000001#e0e0e08|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| |â•‘+0#0000000#ffff4012| |l|e|t|s| @5| +0&#0000001|â•‘+0&#ffff4012| +0#4040ff13#ffffff0@13
+|~| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| |â•‘+0#0000000#ffff4012| |s|h|o|w| @5| +0&#0000001|â•‘+0&#ffff4012| +0#4040ff13#ffffff0@13
+|~| @45|â•‘+0#0000000#ffff4012| |a| @8| +0&#0000001|â•‘+0&#ffff4012| +0#4040ff13#ffffff0@13
+|~| @45|â•‘+0#0000000#ffff4012| |s|c|r|o|l@1|b|a|r| | +0&#a8a8a8255|â•‘+0&#ffff4012| +0#4040ff13#ffffff0@13
+|~| @45|â•š+0#0000000#ffff4012|â•@11|⇲| +0#4040ff13#ffffff0@13
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |3| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_4.dump b/src/testdir/dumps/Test_popupwin_infopopup_4.dump
new file mode 100644
index 0000000..3a15a9f
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_4.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| > @39
+|~+0#4040ff13&| @23| +0#0000001#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |B+0#e000002&|a|c|k| |a|t| |o|r|i|g|i|n|a|l| +0#0000000&@22
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_5.dump b/src/testdir/dumps/Test_popupwin_infopopup_5.dump
new file mode 100644
index 0000000..fbcebee
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_5.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|s|t| |t|e|x|t| |n|o|i|n|f|o> @23
+|~+0#4040ff13&| @42| +0#0000001#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@8
+|~| @28|â•”+0#0000000#ffff4012|â•@11|X| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@8
+|~| @28|â•‘+0#0000000#ffff4012| |l|e|t|s| @5| +0&#0000001|â•‘+0&#ffff4012| +0#0000001#e0e0e08|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@8
+|~| @28|â•‘+0#0000000#ffff4012| |s|h|o|w| @5| +0&#0000001|â•‘+0&#ffff4012| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@8
+|~| @28|â•‘+0#0000000#ffff4012| |a| @8| +0&#0000001|â•‘+0&#ffff4012| +0#4040ff13#ffffff0@30
+|~| @28|â•‘+0#0000000#ffff4012| |s|c|r|o|l@1|b|a|r| | +0&#a8a8a8255|â•‘+0&#ffff4012| +0#4040ff13#ffffff0@30
+|~| @28|â•š+0#0000000#ffff4012|â•@11|⇲| +0#4040ff13#ffffff0@30
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |3| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_6.dump b/src/testdir/dumps/Test_popupwin_infopopup_6.dump
new file mode 100644
index 0000000..18ed96b
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_6.dump
@@ -0,0 +1,14 @@
+|a+0&#ffffff0|w|o|r|d> @17|â•”+0&#ffff4012|â•@15|X| +0&#ffffff0@33
+|w+0#0000001#e0e0e08|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001|â•‘+0&#ffff4012| |w|o|r|d|s| |a|r|e| |c|o@1|l| |â•‘| +0#4040ff13#ffffff0@33
+|a+0#0000001#ffd7ff255|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001|â•š+0&#ffff4012|â•@15|⇲| +0#4040ff13#ffffff0@33
+|n+0#0000001#ffd7ff255|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@51
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_7.dump b/src/testdir/dumps/Test_popupwin_infopopup_7.dump
new file mode 100644
index 0000000..3890d12
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_7.dump
@@ -0,0 +1,14 @@
+|a+0&#ffffff0|w|o|r|d| @69
+|t|e|s|t| |t|e|x|t| |a|w|o|r|d> @17|â•”+0&#ffff4012|â•@15|X| +0&#ffffff0@23
+|~+0#4040ff13&| @7| +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001|â•‘+0&#ffff4012| |w|o|r|d|s| |a|r|e| |c|o@1|l| |â•‘| +0#4040ff13#ffffff0@23
+|~| @7| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001|â•š+0&#ffff4012|â•@15|⇲| +0#4040ff13#ffffff0@23
+|~| @7| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@41
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_8.dump b/src/testdir/dumps/Test_popupwin_infopopup_8.dump
new file mode 100644
index 0000000..6838bcd
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_8.dump
@@ -0,0 +1,14 @@
+|a+0&#ffffff0|w|o|r|d| @69
+|t|e|s|t| |t|a|w|o|r|d> @63
+|~+0#4040ff13&| @3| +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001| +0#0000001#e0e0e08|w|o|r|d|s| |a|r|e| |c|o@1|l| | +0#4040ff13#ffffff0@29
+|~| @3| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001| +0#4040ff13#ffffff0@45
+|~| @3| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@45
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_9.dump b/src/testdir/dumps/Test_popupwin_infopopup_9.dump
new file mode 100644
index 0000000..4a5fb3b
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_9.dump
@@ -0,0 +1,14 @@
+|a+0&#ffffff0|w|o|r|d| @69
+|t|e|s|a|w|o|r|d> @66
+|~+0#4040ff13&| | +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001| +0#0000001#e0e0e08|w|o|r|d|s| |a|r|e| @1| +0#4040ff13#ffffff0@36
+|~| | +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#0000001| +0#0000001#e0e0e08|c|o@1|l| @6| +0#4040ff13#ffffff0@36
+|~| | +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| @1| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@48
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_align_1.dump b/src/testdir/dumps/Test_popupwin_infopopup_align_1.dump
new file mode 100644
index 0000000..20c9790
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_align_1.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t@1|h|a|t|w|o|r|d> @40
+|~+0#4040ff13&| @23| +0#0000001#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0&#e0e0e08|t|h|a|t| |w|o|r|d| |i|s| |c|o@1|l| | +0#4040ff13#ffffff0@8
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#e0e0e08|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |4| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_align_2.dump b/src/testdir/dumps/Test_popupwin_infopopup_align_2.dump
new file mode 100644
index 0000000..6b4a4e0
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_align_2.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t@1|h|a|t|w|o|r|d|t|e|s|t| |t|e|x|t| |t|e|s|t| |a|n|o|t|h|e|r|w|o|r|d> @14
+|~+0#4040ff13&| @9| +0#0000001#e0e0e08|o|t|h|e|r| |w|o|r|d|s| |a|r|e| @20| +0&#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@4
+|~| @9| +0#0000001#e0e0e08|c|o@1|l|e|r| |t|h|a|n| |t|h|i|s| |a|n|d| |s|o|m|e| |m|o|r|e| |t|e|x|t| @1|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@4
+|~| @9| +0#0000001#e0e0e08|t|o| |m|a|k|e| |w|r|a|p| @23| +0&#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@4
+|~| @46| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@4
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_align_3.dump b/src/testdir/dumps/Test_popupwin_infopopup_align_3.dump
new file mode 100644
index 0000000..2599094
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_align_3.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t@1|h|a|t|w|o|r|d|t|e|s|t| |t|e|x|t| |t|e|s|t| |a|n|o|t|h|e|r|w|o|r|d| @14
+|x| @73
+|x| @73
+|x| @73
+|x| @73
+|x| @73
+|x| @73
+|x| @7| +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#ffffff0@43
+|x| @7| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#ffffff0@43
+|x| @7| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#ffffff0@43
+|x| @7| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#ffff4012|w|o|r|d|s| |a|r|e| |c|o@1|l| | +0&#ffffff0@27
+|t|e|s|t| |t|e|x|t| |a|w|o|r|d> @59
+|~+0#4040ff13&| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_hidden_1.dump b/src/testdir/dumps/Test_popupwin_infopopup_hidden_1.dump
new file mode 100644
index 0000000..f56ebc4
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_hidden_1.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|a|w|o|r|d> @43
+|~+0#4040ff13&| @23| +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_hidden_2.dump b/src/testdir/dumps/Test_popupwin_infopopup_hidden_2.dump
new file mode 100644
index 0000000..d834e57
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_hidden_2.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|a|n|o|t|h|e|r|w|o|r|d> @37
+|~+0#4040ff13&| @23| +0#0000001#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0&#e0e0e08|i|m@1|e|d|i|a|t|e| |i|n|f|o| |3| | +0#4040ff13#ffffff0@9
+|~| @23| +0#0000001#e0e0e08|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_hidden_3.dump b/src/testdir/dumps/Test_popupwin_infopopup_hidden_3.dump
new file mode 100644
index 0000000..410c393
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_hidden_3.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|n|o|i|n|f|o> @42
+|~+0#4040ff13&| @23| +0#0000001#ffd7ff255|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0&#e0e0e08|a|s|y|n|c| |i|n|f|o| |4| | +0#4040ff13#ffffff0@13
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#e0e0e08|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |3| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_nb_1.dump b/src/testdir/dumps/Test_popupwin_infopopup_nb_1.dump
new file mode 100644
index 0000000..41208ee
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_nb_1.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|a|w|o|r|d> @43
+|~+0#4040ff13&| @23| +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| @1|w|o|r|d|s| |a|r|e| |c|o@1|l| | +0#4040ff13#ffffff0@11
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|n|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @23| +0#0000001#ffd7ff255|t|h|a|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_infopopup_wide_1.dump b/src/testdir/dumps/Test_popupwin_infopopup_wide_1.dump
new file mode 100644
index 0000000..f7b583b
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_infopopup_wide_1.dump
@@ -0,0 +1,8 @@
+|s+0&#ffffff0|c|r|a|p> @69
+|s+0#0000001#e0e0e08|c|r|a|p| @5|s|o|m|e| |l|o|n|g| |t|e|x|t| |t|o| |m|a|k|e| |s|u|r|e| |t|h|e| |m|e|n|u| |t|a|k|e|s| |u|p| |a|l@1| |o|f| |t|h|e| |w|i|d|t|h| |o
+|s+0&#ffd7ff255|c|a|p@1|i|e|r| @2|s|o|m|e| |l|o|n|g| |t|e|x|t| |t|o| |m|a|k|e| |s|u|r|e| |t|h|e| |m|e|n|u| |t|a|k|e|s| |u|p| |a|l@1| |o|f| |t|h|e| |w|i|d|t|h| |o
+|s|c|r|a|p@1|i|e|r|2| |s|o|m|e| |l|o|n|g| |t|e|x|t| |t|o| |m|a|k|e| |s|u|r|e| |t|h|e| |m|e|n|u| |t|a|k|e|s| |u|p| |a|l@1| |o|f| |t|h|e| |w|i|d|t|h| |o
+|4+0#0000000#ffffff0| @73
+|5| @73
+|6| @73
+|-+2&&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/dumps/Test_popupwin_longtitle_1.dump b/src/testdir/dumps/Test_popupwin_longtitle_1.dump
new file mode 100644
index 0000000..92af8b2
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_longtitle_1.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @25| +0#0000001#ffd7ff255|a| |v|e|r|y| |.@2|g| |t|o| |f|i|t| | +0#0000000#ffffff0@27
+|5| @25|o+0#0000001#ffd7ff255|n|e| @16| +0#0000000#ffffff0@27
+|6| @25|t+0#0000001#ffd7ff255|w|o| @16| +0#0000000#ffffff0@27
+|7| @25|a+0#0000001#ffd7ff255|n|o|t|h|e|r| @12| +0#0000000#ffffff0@27
+|8| @73
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_longtitle_2.dump b/src/testdir/dumps/Test_popupwin_longtitle_2.dump
new file mode 100644
index 0000000..079fbc3
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_longtitle_2.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @24|â•”+0#0000001#ffd7ff255|a| |v|e|r|y| |l|.@2|n|g| |t|o| |f|i|t|â•—| +0#0000000#ffffff0@26
+|4| @24|â•‘+0#0000001#ffd7ff255|o|n|e| @16|â•‘| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#0000001#ffd7ff255|t|w|o| @16|â•‘| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#0000001#ffd7ff255|a|n|o|t|h|e|r| @12|â•‘| +0#0000000#ffffff0@26
+|7| @24|â•š+0#0000001#ffd7ff255|â•@19|â•| +0#0000000#ffffff0@26
+|8| @73
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_longtitle_3.dump b/src/testdir/dumps/Test_popupwin_longtitle_3.dump
new file mode 100644
index 0000000..b1e67c2
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_longtitle_3.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @27| +0#0000001#ffd7ff255|T|i|t|l|e| @9| +0#0000000#ffffff0@29
+|4| @27| +0#0000001#ffd7ff255@15| +0#0000000#ffffff0@29
+|5| @27| +0#0000001#ffd7ff255@1|a@2| @10| +0#0000000#ffffff0@29
+|6| @27| +0#0000001#ffd7ff255@1|b@2| @10| +0#0000000#ffffff0@29
+|7| @27| +0#0000001#ffd7ff255@15| +0#0000000#ffffff0@29
+|8| @27| +0#0000001#ffd7ff255@15| +0#0000000#ffffff0@29
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_longtitle_4.dump b/src/testdir/dumps/Test_popupwin_longtitle_4.dump
new file mode 100644
index 0000000..02ae4cc
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_longtitle_4.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @26|â•”+0#0000001#ffd7ff255|T|i|t|l|e|â•@10|â•—| +0#0000000#ffffff0@28
+|3| @26|â•‘+0#0000001#ffd7ff255| @15|â•‘| +0#0000000#ffffff0@28
+|4| @26|â•‘+0#0000001#ffd7ff255| @15|â•‘| +0#0000000#ffffff0@28
+|5| @26|â•‘+0#0000001#ffd7ff255| @1|a@2| @10|â•‘| +0#0000000#ffffff0@28
+|6| @26|â•‘+0#0000001#ffd7ff255| @1|b@2| @10|â•‘| +0#0000000#ffffff0@28
+|7| @26|â•‘+0#0000001#ffd7ff255| @15|â•‘| +0#0000000#ffffff0@28
+|8| @26|â•‘+0#0000001#ffd7ff255| @15|â•‘| +0#0000000#ffffff0@28
+|9| @26|â•š+0#0000001#ffd7ff255|â•@15|â•| +0#0000000#ffffff0@28
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_mask_1.dump b/src/testdir/dumps/Test_popupwin_mask_1.dump
new file mode 100644
index 0000000..2fa0d7c
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_mask_1.dump
@@ -0,0 +1,13 @@
+>1+0&#ffffff0|2|3|4|5|6|7|8|9|1| +0&#e0e0e08@12|1+0&#ffffff0|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9| +0&#e0e0e08|s|o|m|e| |1+0&#ffffff0|3|1|t+0&#e0e0e08| @3|1+0&#ffffff0|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9| +0&#e0e0e08|0+0&#ffffff0|1@1|t+0&#e0e0e08|h|1+0&#ffffff0|3|x+0#0000001#ffd7ff255|l+0#0000000#e0e0e08|i|n|e| |x+0#0000001#ffd7ff255@2|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9| +0&#e0e0e08@8|y+0#0000001#ffd7ff255@7|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|â•+0#0000001#ffd7ff255@13|X|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|â•‘+0#0000001#ffd7ff255| @4|1+0#0000000#ffffff0|3|1| +0#0000001#ffd7ff255@6|â•‘|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|â•‘+0#0000001#ffd7ff255| |j|u|s|t|1+0#0000000#ffffff0|3|1|e+0#0000001#ffd7ff255| |l|i|n|e| |â•‘|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|â•‘+0#0000001#ffd7ff255| @10|1+0#0000000#ffffff0|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|â•š+0#0000001#ffd7ff255|â•|1+0#0000000#ffffff0@2|â•+0#0000001#ffd7ff255@4|1+0#0000000#ffffff0|5|1|6|â•+0#0000001#ffd7ff255@1|â•|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+| @56|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_mask_2.dump b/src/testdir/dumps/Test_popupwin_mask_2.dump
new file mode 100644
index 0000000..173d40a
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_mask_2.dump
@@ -0,0 +1,13 @@
+>1+0&#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0| +0&#e0e0e08@12|7+0&#ffffff0|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1| +0&#e0e0e08|s|o|m|e| |3+0&#ffffff0|x+0#0000001#ffd7ff255@1|t+0#0000000#e0e0e08| @3|x+0#0000001#ffd7ff255@1|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1| +0&#e0e0e08|1+0&#ffffff0@2|t+0&#e0e0e08|h|3+0&#ffffff0|y+0#0000001#ffd7ff255@1|l+0#0000000#e0e0e08|i|n|e| |y+0#0000001#ffd7ff255@1|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1| +0&#e0e0e08@8|1+0&#ffffff0|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@1|â•+0#0000001#ffd7ff255@13|X|9+0#0000000#ffffff0|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|â•‘+0#0000001#ffd7ff255| @4|1+0#0000000#ffffff0|4|1| +0#0000001#ffd7ff255@6|â•‘|9+0#0000000#ffffff0|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|â•‘+0#0000001#ffd7ff255| |j|u|s|t|1+0#0000000#ffffff0|4|1|e+0#0000001#ffd7ff255| |l|i|n|e| |â•‘|9+0#0000000#ffffff0|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|â•‘+0#0000001#ffd7ff255| @10|1+0#0000000#ffffff0|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|â•š+0#0000001#ffd7ff255|â•|1+0#0000000#ffffff0|2|1|â•+0#0000001#ffd7ff255@4|1+0#0000000#ffffff0|6|1|7|â•+0#0000001#ffd7ff255@1|â•|9+0#0000000#ffffff0|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_mask_3.dump b/src/testdir/dumps/Test_popupwin_mask_3.dump
new file mode 100644
index 0000000..40681fb
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_mask_3.dump
@@ -0,0 +1,13 @@
+>1+0&#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7| +0&#e0e0e08@9
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|x+0#0000001#ffd7ff255@8|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3| +0&#e0e0e08|s|o|m|e| |0+0&#ffffff0|4|1|t+0&#e0e0e08|
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|y+0#0000001#ffd7ff255@8|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3| +0&#e0e0e08|3+0&#ffffff0|8|3|t+0&#e0e0e08|h|0+0&#ffffff0|4|1|l+0&#e0e0e08|i
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3| +0&#e0e0e08@8|4+0&#ffffff0|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|â•+0#0000001#ffd7ff255@10
+|1+0#0000000#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|â•‘+0#0000001#ffd7ff255| @4|9+0#0000000#ffffff0|4|0| +0#0000001#ffd7ff255@3
+|1+0#0000000#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|â•‘+0#0000001#ffd7ff255| |j|u|s|t|9+0#0000000#ffffff0|4|0|e+0#0000001#ffd7ff255| |l|i
+|1+0#0000000#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|â•‘+0#0000001#ffd7ff255| @10|2+0#0000000#ffffff0
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|â•š+0#0000001#ffd7ff255|â•|7+0#0000000#ffffff0|3|8|â•+0#0000001#ffd7ff255@4|1+0#0000000#ffffff0|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_mask_4.dump b/src/testdir/dumps/Test_popupwin_mask_4.dump
new file mode 100644
index 0000000..0cf44d2
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_mask_4.dump
@@ -0,0 +1,13 @@
+>1+0&#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+| +0&#e0e0e08@11|1+0&#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|o+0&#e0e0e08|m|e| |5+0&#ffffff0|6|7|t+0&#e0e0e08| @3|1+0&#ffffff0@1|2|1|3|x+0#0000001#ffd7ff255@8|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|t+0&#e0e0e08|h|5+0&#ffffff0|6|7|l+0&#e0e0e08|i|n|e| |1+0&#ffffff0@1|2|1|3|y+0#0000001#ffd7ff255@8|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+| +0&#e0e0e08@6|8+0&#ffffff0|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|â•+0#0000001#ffd7ff255@10|X|1+0#0000000#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+| +0#0000001#ffd7ff255|2+0#0000000#ffffff0|3|4| +0#0000001#ffd7ff255@6|â•‘|1+0#0000000#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|t+0#0000001#ffd7ff255|2+0#0000000#ffffff0|3|4|e+0#0000001#ffd7ff255| |l|i|n|e| |â•‘|1+0#0000000#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+| +0#0000001#ffd7ff255@6|8+0#0000000#ffffff0|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|â•+0#0000001#ffd7ff255@4|6+0#0000000#ffffff0|7|8|9|â•+0#0000001#ffd7ff255@1|â•|1+0#0000000#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_mask_5.dump b/src/testdir/dumps/Test_popupwin_mask_5.dump
new file mode 100644
index 0000000..78cc6f0
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_mask_5.dump
@@ -0,0 +1,13 @@
+>1+0&#ffffff0|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|x+0#0000001#ffd7ff255@8|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|y+0#0000001#ffd7ff255@8|8+0#0000000#ffffff0|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|1|2|3|4|5|6|7|8|9|1|0|1@2|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|2|7|2|8|2|9|3|0|3|1|3|2|3@2|4|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+| +0&#e0e0e08@11|1+0&#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|2|6|â•+0#0000001#ffd7ff255@13|X|4+0#0000000#ffffff0|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|o+0&#e0e0e08|m|e| |5+0&#ffffff0|6|7|t+0&#e0e0e08| @3|1+0&#ffffff0@1|2|1|3|1|4|1|5|1|6|1|7|1|8|1|9|2|0|2|1|2@2|3|2|4|2|5|â•‘+0#0000001#ffd7ff255| @4|2+0#0000000#ffffff0|9|3| +0#0000001#ffd7ff255@6|â•‘|4+0#0000000#ffffff0|3|5|3|6|3|7|3|8|3|9|4|0|4|1|4|2
+|:| |t+0&#e0e0e08|h| +0&#ffffff0@2|l+0&#e0e0e08|i|n|e| | +0&#ffffff0@28|â•‘+0#0000001#ffd7ff255| |j|u|s|t| +0#0000000#ffffff0@2|e+0#0000001#ffd7ff255| |l|i|n|e| |â•‘|,+0#0000000#ffffff0|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_matches.dump b/src/testdir/dumps/Test_popupwin_matches.dump
new file mode 100644
index 0000000..6d9faf3
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_matches.dump
@@ -0,0 +1,10 @@
+|1+0#ffffff16#e000002@2| +0#0000000#ffffff0|2@2| |3@2| @63
+>4+0#4040ff13&@2| +0#0000000&|5@2| |6+0&#ffff4012@2| +0&#ffffff0@63
+|~+0#4040ff13&| @7|â•”+0#0000001#ffd7ff255|â•@10|â•—| +0#4040ff13#ffffff0@52
+|~| @7|â•‘+0#0000001#ffd7ff255|1+0#ffffff16#e000002@2| +0#0000001#ffd7ff255|2@2| |3@2|â•‘| +0#4040ff13#ffffff0@52
+|~| @7|â•‘+0#0000001#ffd7ff255|4@2| |5+0#4040ff13&@2| +0#0000001&|6@2|â•‘| +0#4040ff13#ffffff0@52
+|~| @7|â•š+0#0000001#ffd7ff255|â•@10|â•| +0#4040ff13#ffffff0@52
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_menu_01.dump b/src/testdir/dumps/Test_popupwin_menu_01.dump
new file mode 100644
index 0000000..b41b398
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_01.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @20|â•”+0#0000001#ffd7ff255| |m|a|k|e| |a| |c|h|o|i|c|e| |f|r|o|m| |t|h|e| |l|i|s|t| |â•—| +0#0000000#ffffff0@21
+|4| @20|â•‘+0#0000001#ffd7ff255| |o+0&#5fd7ff255|n|e| @23| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0@21
+|5| @20|â•‘+0#0000001#ffd7ff255| |t|w|o| @24|â•‘| +0#0000000#ffffff0@21
+|6| @20|â•‘+0#0000001#ffd7ff255| |a|n|o|t|h|e|r| @20|â•‘| +0#0000000#ffffff0@21
+|7| @20|â•š+0#0000001#ffd7ff255|â•@28|â•| +0#0000000#ffffff0@21
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_02.dump b/src/testdir/dumps/Test_popupwin_menu_02.dump
new file mode 100644
index 0000000..b7a40e8
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_02.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @20|â•”+0#0000001#ffd7ff255| |m|a|k|e| |a| |c|h|o|i|c|e| |f|r|o|m| |t|h|e| |l|i|s|t| |â•—| +0#0000000#ffffff0@21
+|4| @20|â•‘+0#0000001#ffd7ff255| |o|n|e| @24|â•‘| +0#0000000#ffffff0@21
+|5| @20|â•‘+0#0000001#ffd7ff255| |t|w|o| @24|â•‘| +0#0000000#ffffff0@21
+|6| @20|â•‘+0#0000001#ffd7ff255| |a+0&#5fd7ff255|n|o|t|h|e|r| @19| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0@21
+|7| @20|â•š+0#0000001#ffd7ff255|â•@28|â•| +0#0000000#ffffff0@21
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_03.dump b/src/testdir/dumps/Test_popupwin_menu_03.dump
new file mode 100644
index 0000000..1aa7fe8
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_03.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|s|e|l|e|c|t|e|d| |3| @46|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_04.dump b/src/testdir/dumps/Test_popupwin_menu_04.dump
new file mode 100644
index 0000000..2ee61fc
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_04.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @31|â•”+0#0000001#ffd7ff255|â•@6|â•—| +0#0000000#ffffff0@32
+|4| @31|â•‘+0#0000001#ffd7ff255| |o+0&#40ff4011|n|e| @1| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0@32
+|5| @31|â•‘+0#0000001#ffd7ff255| |t|w|o| @2|â•‘| +0#0000000#ffffff0@32
+|6| @31|â•‘+0#0000001#ffd7ff255| |t|h|r|e@1| |â•‘| +0#0000000#ffffff0@32
+|7| @31|â•š+0#0000001#ffd7ff255|â•@6|â•| +0#0000000#ffffff0@32
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_filter_1.dump b/src/testdir/dumps/Test_popupwin_menu_filter_1.dump
new file mode 100644
index 0000000..b99b8d0
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_filter_1.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |1@2| | +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |2+0&#e0e0e08@2| +0&#ffd7ff255| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |3@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_menu_filter_2.dump b/src/testdir/dumps/Test_popupwin_menu_filter_2.dump
new file mode 100644
index 0000000..e510f1d
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_filter_2.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |1+0&#e0e0e08@2| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |2@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |3@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_menu_filter_3.dump b/src/testdir/dumps/Test_popupwin_menu_filter_3.dump
new file mode 100644
index 0000000..77bec0e
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_filter_3.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |7@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |8@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |9+0&#e0e0e08@2| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_menu_filter_4.dump b/src/testdir/dumps/Test_popupwin_menu_filter_4.dump
new file mode 100644
index 0000000..e510f1d
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_filter_4.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @31|â•”+0#0000001#ffd7ff255|â•@5|â•—| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |1+0&#e0e0e08@2| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |2@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•‘+0#0000001#ffd7ff255| |3@2| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@33
+|~| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#4040ff13#ffffff0@33
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_menu_filter_5.dump b/src/testdir/dumps/Test_popupwin_menu_filter_5.dump
new file mode 100644
index 0000000..910e224
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_filter_5.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&> @73
diff --git a/src/testdir/dumps/Test_popupwin_menu_maxwidth_1.dump b/src/testdir/dumps/Test_popupwin_menu_maxwidth_1.dump
new file mode 100644
index 0000000..813fb36
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_maxwidth_1.dump
@@ -0,0 +1,13 @@
+>â•”+0#0000001#ffd7ff255|â•@2|â•—| +0#0000000#ffffff0@9|â•”+0#0000001#ffd7ff255|â•@11|â•—| +0#0000000#ffffff0@2|â•”+0#0000001#ffd7ff255|â•@12|â•—| +0#0000000#ffffff0@27
+|â•‘+0#0000001#ffd7ff255| |x+0&#e0e0e08| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0@9|â•‘+0#0000001#ffd7ff255| |1+0&#e0e0e08|2|3|4|5|6|7|8|9||| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0@2|â•‘+0#0000001#ffd7ff255| |1+0&#e0e0e08|2|3|4|5|6|7|8|9||| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@27
+|â•š+0#0000001#ffd7ff255|â•@2|â•| +0#0000000#ffffff0@9|â•š+0#0000001#ffd7ff255|â•@11|â•| +0#0000000#ffffff0@2|â•‘+0#0000001#ffd7ff255| |1|2|3|4|5|6|7|8|9||| | +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@27
+|4| @30|â•‘+0#0000001#ffd7ff255| |1|2|3|4|5|6|7|8|9||| | +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@27
+|5| @30|â•š+0#0000001#ffd7ff255|â•@12|â•| +0#0000000#ffffff0@27
+|6| @73
+|â•”+0#0000001#ffd7ff255|â•@11|â•—| +0#0000000#ffffff0|â•”+0#0000001#ffd7ff255|â•@11|â•—| +0#0000000#ffffff0@45
+|â•‘+0#0000001#ffd7ff255| |1+0&#e0e0e08|2|3|4|5|6|7|8|9||| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0|â•‘+0#0000001#ffd7ff255| |1+0&#e0e0e08|2|3|4|5|6|7|8|9||| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0@45
+|â•š+0#0000001#ffd7ff255|â•@11|â•| +0#0000000#ffffff0|â•š+0#0000001#ffd7ff255|â•@11|â•| +0#0000000#ffffff0@45
+|1|0| @72
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_menu_scroll_1.dump b/src/testdir/dumps/Test_popupwin_menu_scroll_1.dump
new file mode 100644
index 0000000..95d020f
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_scroll_1.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @29|â•”+0#0000001#ffd7ff255|â•@10|â•—| +0#0000000#ffffff0@30
+|4| @29|â•‘+0#0000001#ffd7ff255| |o|n|e| @5| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|5| @29|â•‘+0#0000001#ffd7ff255| |t+0&#e0e0e08|w|o| @4| +0&#ffd7ff255| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|6| @29|â•‘+0#0000001#ffd7ff255| |t|h|r|e@1| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|7| @29|â•š+0#0000001#ffd7ff255|â•@10|â•| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_scroll_2.dump b/src/testdir/dumps/Test_popupwin_menu_scroll_2.dump
new file mode 100644
index 0000000..935e3b4
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_scroll_2.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @29|â•”+0#0000001#ffd7ff255|â•@10|â•—| +0#0000000#ffffff0@30
+|4| @29|â•‘+0#0000001#ffd7ff255| |t|h|r|e@1| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|5| @29|â•‘+0#0000001#ffd7ff255| |f|o|u|r| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|6| @29|â•‘+0#0000001#ffd7ff255| |f+0&#e0e0e08|i|v|e| @3| +0&#ffd7ff255| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|7| @29|â•š+0#0000001#ffd7ff255|â•@10|â•| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_scroll_3.dump b/src/testdir/dumps/Test_popupwin_menu_scroll_3.dump
new file mode 100644
index 0000000..00fe0c8
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_scroll_3.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @29|â•”+0#0000001#ffd7ff255|â•@10|â•—| +0#0000000#ffffff0@30
+|4| @29|â•‘+0#0000001#ffd7ff255| |s|e|v|e|n| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|5| @29|â•‘+0#0000001#ffd7ff255| |e|i|g|h|t| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|6| @29|â•‘+0#0000001#ffd7ff255| |n+0&#e0e0e08|i|n|e| @3| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|7| @29|â•š+0#0000001#ffd7ff255|â•@10|â•| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_scroll_4.dump b/src/testdir/dumps/Test_popupwin_menu_scroll_4.dump
new file mode 100644
index 0000000..d019c86
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_scroll_4.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @29|â•”+0#0000001#ffd7ff255|â•@10|â•—| +0#0000000#ffffff0@30
+|4| @29|â•‘+0#0000001#ffd7ff255| |s+0&#e0e0e08|e|v|e|n| @2| +0&#ffd7ff255| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|5| @29|â•‘+0#0000001#ffd7ff255| |e|i|g|h|t| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|6| @29|â•‘+0#0000001#ffd7ff255| |n|i|n|e| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|7| @29|â•š+0#0000001#ffd7ff255|â•@10|â•| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_scroll_5.dump b/src/testdir/dumps/Test_popupwin_menu_scroll_5.dump
new file mode 100644
index 0000000..132137a
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_scroll_5.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @29|â•”+0#0000001#ffd7ff255|â•@10|â•—| +0#0000000#ffffff0@30
+|4| @29|â•‘+0#0000001#ffd7ff255| |s+0&#e0e0e08|i|x| @4| +0&#ffd7ff255| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|5| @29|â•‘+0#0000001#ffd7ff255| |s|e|v|e|n| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|6| @29|â•‘+0#0000001#ffd7ff255| |e|i|g|h|t| @3| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|7| @29|â•š+0#0000001#ffd7ff255|â•@10|â•| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_menu_scroll_6.dump b/src/testdir/dumps/Test_popupwin_menu_scroll_6.dump
new file mode 100644
index 0000000..6718bb3
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_menu_scroll_6.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @29|â•”+0#0000001#ffd7ff255|â•@10|â•—| +0#0000000#ffffff0@30
+|4| @29|â•‘+0#0000001#ffd7ff255| |o+0&#e0e0e08|n|e| @4| +0&#ffd7ff255| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|5| @29|â•‘+0#0000001#ffd7ff255| |t|w|o| @5| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|6| @29|â•‘+0#0000001#ffd7ff255| |t|h|r|e@1| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@30
+|7| @29|â•š+0#0000001#ffd7ff255|â•@10|â•| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_multibytetitle.dump b/src/testdir/dumps/Test_popupwin_multibytetitle.dump
new file mode 100644
index 0000000..95b0871
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_multibytetitle.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @25|â•”+0#0000001#ffd7ff255|â–¶|Ä|ã‚*&|ã„|ã†|ãˆ|ãŠ|â—€+&|â•@4|â•—| +0#0000000#ffffff0@27
+|5| @25|â•‘+0#0000001#ffd7ff255| |T+0&#e0e0e08|h|i|s| |i|s| |a| |l|i|n|e| @1| +0&#ffd7ff255|â•‘| +0#0000000#ffffff0@27
+|6| @25|â•‘+0#0000001#ffd7ff255| |a|n|d| |a|n|o|t|h|e|r| |l|i|n|e| |â•‘| +0#0000000#ffffff0@27
+|7| @25|â•š+0#0000001#ffd7ff255|â•@17|â•| +0#0000000#ffffff0@27
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_normal_cmd.dump b/src/testdir/dumps/Test_popupwin_normal_cmd.dump
new file mode 100644
index 0000000..95588d6
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_normal_cmd.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @33|8+0#0000001#ffd7ff255| @1| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @33|9+0#0000001#ffd7ff255| @1| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @33|1+0#0000001#ffd7ff255|0| | +0#0000000#0000001| +0#4040ff13#ffffff0@35
+|~| @33|1+0#0000001#ffd7ff255@1| | +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @33|1+0#0000001#ffd7ff255|2| | +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_nospace.dump b/src/testdir/dumps/Test_popupwin_nospace.dump
new file mode 100644
index 0000000..de8521c
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_nospace.dump
@@ -0,0 +1,12 @@
+|-+0&#ffffff0|â•”+0#0000001#ffd7ff255|â•@2|â•—|-+0#0000000#ffffff0@1|â•”+0#0000001#ffd7ff255|â•@2|â•—|-+0#0000000#ffffff0@8|â•”+0#0000001#ffd7ff255|â•@2|â•—|-+0#0000000#ffffff0@15|â•”+0#0000001#ffd7ff255|â•@2|â•—|-+0#0000000#ffffff0@11| @14
+|-|â•‘+0#0000001#ffd7ff255|o|n|e|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255|o|n|e|â•‘|-+0#0000000#ffffff0@8|â•‘+0#0000001#ffd7ff255|a@2|â•‘|-+0#0000000#ffffff0@15|â•‘+0#0000001#ffd7ff255|o|n|e|â•‘|-+0#0000000#ffffff0@11| @14
+|-|â•‘+0#0000001#ffd7ff255|t|w|o|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255|t|w|o|â•‘|-+0#0000000#ffffff0@8|â•‘+0#0000001#ffd7ff255|b@2|â•‘|-+0#0000000#ffffff0@15|â•‘+0#0000001#ffd7ff255|t|w|o|â•‘|-+0#0000000#ffffff0@11| @14
+|-|â•š+0#0000001#ffd7ff255|â•@2|â•|-+0#0000000#ffffff0@1|â•š+0#0000001#ffd7ff255|â•@2|â•|-+0#0000000#ffffff0@8|â•‘+0#0000001#ffd7ff255|c@2|â•‘|-+0#0000000#ffffff0@15|â•‘+0#0000001#ffd7ff255|t|e@1|â•‘|-+0#0000000#ffffff0@11| @14
+|-|@|-@5|#|-@5|%|-@5|â•‘+0#0000001#ffd7ff255|d@2|â•‘|-+0#0000000#ffffff0@15|â•‘+0#0000001#ffd7ff255|f|o|u|â•‘|-+0#0000000#ffffff0@1>*|-@8| @14
+|-@14|â•”+0#0000001#ffd7ff255|â•@2|â•—|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255|e@2|â•‘|-+0#0000000#ffffff0@15|â•‘+0#0000001#ffd7ff255|f|i|v|â•‘|-+0#0000000#ffffff0@1|â•”+0#0000001#ffd7ff255|â•@3|â•—|-+0#0000000#ffffff0@3| @14
+|-@14|â•‘+0#0000001#ffd7ff255|o|n|e|â•‘|-+0#0000000#ffffff0@1|â•š+0#0000001#ffd7ff255|â•@2|â•|-+0#0000000#ffffff0@15|â•š+0#0000001#ffd7ff255|â•@2|â•|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255|a@2| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255|-+0#0000000#ffffff0@3| @14
+|-@14|â•‘+0#0000001#ffd7ff255|t|w|o|â•‘|-+0#0000000#ffffff0@1|*|-@5|@|-@5|#|-@5|%|-@5|â•‘+0#0000001#ffd7ff255|b@2| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255|-+0#0000000#ffffff0@3| @14
+|-@14|â•‘+0#0000001#ffd7ff255|t|e@1|â•‘|-+0#0000000#ffffff0@8|â•”+0#0000001#ffd7ff255|â•@2|â•—|-+0#0000000#ffffff0@1|â•”+0#0000001#ffd7ff255|â•@3|â•—|-+0#0000000#ffffff0@7|â•‘+0#0000001#ffd7ff255|c@2| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255|-+0#0000000#ffffff0@3| @14
+|-@14|â•š+0#0000001#ffd7ff255|â•@2|â•|-+0#0000000#ffffff0@8|â•‘+0#0000001#ffd7ff255|o|n|e|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255|o|n|e| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255|-+0#0000000#ffffff0@7|â•‘+0#0000001#ffd7ff255|d@2| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255|-+0#0000000#ffffff0@3| @14
+|-@28|â•‘+0#0000001#ffd7ff255|t|w|o|â•‘|-+0#0000000#ffffff0@1|â•‘+0#0000001#ffd7ff255|t|w|o| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255|-+0#0000000#ffffff0@7|â•‘+0#0000001#ffd7ff255|e@2| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255|-+0#0000000#ffffff0@3| @14
+@29|â•š+0#0000001#ffd7ff255|â•@2|â•| +0#0000000#ffffff0@1|â•š+0#0000001#ffd7ff255|â•@3|â•| +0#0000000#ffffff0@7|â•š+0#0000001#ffd7ff255|â•@3|â•| +0#0000000#ffffff0|5|,|5|1| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_notify_01.dump b/src/testdir/dumps/Test_popupwin_notify_01.dump
new file mode 100644
index 0000000..bad62f6
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_notify_01.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @7|â•”+0#e000002&|â•@21|â•—| +0#0000000&@41
+|2| @7|â•‘+0#e000002&| |f|i|r|s|t| |n|o|t|i|f|i|c|a|t|i|o|n| @2|â•‘| +0#0000000&@41
+|3| @7|â•š+0#e000002&|â•@21|â•| +0#0000000&@41
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_notify_02.dump b/src/testdir/dumps/Test_popupwin_notify_02.dump
new file mode 100644
index 0000000..72e2895
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_notify_02.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @7|â•”+0#e000002&|â•@21|â•—| +0#0000000&@41
+|2| @7|â•‘+0#e000002&| |f|i|r|s|t| |n|o|t|i|f|i|c|a|t|i|o|n| @2|â•‘| +0#0000000&@41
+|3| @7|â•š+0#e000002&|â•@21|â•| +0#0000000&@41
+|4| @7|â•”+0&#5fd7ff255|â•@31|â•—| +0&#ffffff0@31
+|5| @7|â•‘+0&#5fd7ff255| |a|n|o|t|h|e|r| |i|m|p|o|r|t|a|n|t| |n|o|t|i|f|i|c|a|t|i|o|n| |â•‘| +0&#ffffff0@31
+|6| @7|â•š+0&#5fd7ff255|â•@31|â•| +0&#ffffff0@31
+|7| @73
+|8| @73
+|9| @73
+|:|c|a|l@1| |p|o|p|u|p|_|n|o|t|i|f|i|c|a|t|i|o|n|(|'|a|n|o|t|h|e|r| |i|m|p|o|r|t|a|n|t| |n|o|t|i|f|i|c|a|t|i|o|n|'|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_nowrap.dump b/src/testdir/dumps/Test_popupwin_nowrap.dump
new file mode 100644
index 0000000..6088d3b
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_nowrap.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @17|a+0#0000001#ffd7ff255| |l|o|n|g| |l|i|n| +0#0000000#ffffff0@45
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_poptermscroll_1.dump b/src/testdir/dumps/Test_popupwin_poptermscroll_1.dump
new file mode 100644
index 0000000..510b7b2
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_poptermscroll_1.dump
@@ -0,0 +1,15 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @14|â•”+0#0000001#ffd7ff255|â•@40|â•—| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|4|2| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|4|3| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|4@1| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|[+1#0000000&|N|o| |N|a|m|e|]| @6|â•‘+0#0000001#ffd7ff255|4|5| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255|0+1#0000000#ffffff0|-|1| @9|A|l@1
+| +0&&@15|â•‘+0#0000001#ffd7ff255|4|6| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@15
+|~+0#4040ff13&| @14|â•‘+0#0000001#ffd7ff255|4|7| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|4|8| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255>4|9| @37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•š+0#0000001#ffd7ff255|â•@40|â•| +0#4040ff13#ffffff0@15
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@56|5|0|,|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_popupwin_poptermscroll_2.dump b/src/testdir/dumps/Test_popupwin_poptermscroll_2.dump
new file mode 100644
index 0000000..2d77aa2
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_poptermscroll_2.dump
@@ -0,0 +1,15 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @14|â•”+0#0000001#ffd7ff255|â•@40|â•—| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|1| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2@1| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|3| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|[+1#0000000&|N|o| |N|a|m|e|]| @6|â•‘+0#0000001#ffd7ff255>2|4| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255|0+1#0000000#ffffff0|-|1| @9|A|l@1
+| +0&&@15|â•‘+0#0000001#ffd7ff255|2|5| @37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@15
+|~+0#4040ff13&| @14|â•‘+0#0000001#ffd7ff255|2|6| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|7| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|8| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•š+0#0000001#ffd7ff255|â•@40|â•| +0#4040ff13#ffffff0@15
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@56|2|5|,|1| @9|5|0|%|
diff --git a/src/testdir/dumps/Test_popupwin_poptermscroll_3.dump b/src/testdir/dumps/Test_popupwin_poptermscroll_3.dump
new file mode 100644
index 0000000..73f13fd
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_poptermscroll_3.dump
@@ -0,0 +1,15 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @14|â•”+0#0000001#ffd7ff255|â•@40|â•—| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|1| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2@1| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|3| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|[+1#0000000&|N|o| |N|a|m|e|]| @6|â•‘+0#0000001#ffd7ff255>2|4| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255|0+1#0000000#ffffff0|-|1| @9|A|l@1
+| +0&&@15|â•‘+0#0000001#ffd7ff255|2|5| @37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@15
+|~+0#4040ff13&| @14|â•‘+0#0000001#ffd7ff255|2|6| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|7| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•‘+0#0000001#ffd7ff255|2|8| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@15
+|~| @14|â•š+0#0000001#ffd7ff255|â•@40|â•| +0#4040ff13#ffffff0@15
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|E+0#ffffff16#e000002|9@1|4|:| |N|o|t| |a|l@1|o|w|e|d| |i|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| +0#0000000#ffffff0@39
diff --git a/src/testdir/dumps/Test_popupwin_poptermscroll_4.dump b/src/testdir/dumps/Test_popupwin_poptermscroll_4.dump
new file mode 100644
index 0000000..0e7b283
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_poptermscroll_4.dump
@@ -0,0 +1,15 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+|:+0&&|q| @72
diff --git a/src/testdir/dumps/Test_popupwin_popupmenu_masking_1.dump b/src/testdir/dumps/Test_popupwin_popupmenu_masking_1.dump
new file mode 100644
index 0000000..9cf4149
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_popupmenu_masking_1.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|a|w|o|r|d> @2|p+0#ffffff16#e000002|o|p|u|p| |b|e|l|o|w| @1|â•”+0#0000001#e0e0e08|â•@15|X| +0#0000000#ffffff0@9
+|~+0#4040ff13&| @23| +0#0000001#e0e0e08|w|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| |â•‘| |w|o|r|d|s| |a|r|e| |c|o@1|l| |â•‘| +0#4040ff13#ffffff0@9
+|~| @23| +0#0000001#ffd7ff255|a|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| |â•š+0&#e0e0e08|â•@15|⇲| +0#4040ff13#ffffff0@9
+|~| @19|p+0#0000000#ffff4012|o|p|u|p| |o|n| |t|o|p| +0#0000001#ffd7ff255|W| |e|x|t|r|a| |t|e|x|t| | +0#ffffff16#e000002@3| +0#4040ff13#ffffff0@23
+|~| @19|p+0#0000000#ffff4012|o|p|u|p| |o|n| |t|o|p| +0#0000001#ffd7ff255|W| |e|x|t|r|a| |t|e|x|t| | +0#4040ff13#ffffff0@27
+|~| @19|p+0#0000000#ffff4012|o|p|u|p| |o|n| |t|o|p| +0#4040ff13#ffffff0@41
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
diff --git a/src/testdir/dumps/Test_popupwin_popupmenu_masking_2.dump b/src/testdir/dumps/Test_popupwin_popupmenu_masking_2.dump
new file mode 100644
index 0000000..41fb879
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_popupmenu_masking_2.dump
@@ -0,0 +1,14 @@
+|t+0&#ffffff0|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|e|x|t| |t|a|w|o|r>d| @2|p+0#ffffff16#e000002|o|p|u|p| |b|e|l|o|w| @5| +0#0000000#ffffff0@23
+|~+0#4040ff13&| @32|p+0#ffffff16#e000002|o|p|u|p| |b|e|l|o|w| @5| +0#4040ff13#ffffff0@23
+|~| @32|p+0#ffffff16#e000002|o|p|u|p| |b|e|l|o|w| @5| +0#4040ff13#ffffff0@23
+|~| @19|p+0#0000000#ffff4012|o|p|u|p| |o|n| |t|o|p| +0#4040ff13#ffffff0|p+0#ffffff16#e000002|o|p|u|p| |b|e|l|o|w| @5| +0#4040ff13#ffffff0@23
+|~| @19|p+0#0000000#ffff4012|o|p|u|p| |o|n| |t|o|p| +0#4040ff13#ffffff0@41
+|~| @19|p+0#0000000#ffff4012|o|p|u|p| |o|n| |t|o|p| +0#4040ff13#ffffff0@41
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|3|1| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_1.dump b/src/testdir/dumps/Test_popupwin_previewpopup_1.dump
new file mode 100644
index 0000000..e65023e
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_1.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|â•”+0#0000001#ffd7ff255| |X|t|a|g|f|i|l|e| |â•@30|X| +0#0000000#ffffff0@26
+|t|h|r|e@1|â•‘+0#0000001#ffd7ff255|2|0| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|f|o|u|r| |â•‘+0#0000001#ffd7ff255|t|h|e|w|o|r|d| |i|s| |h|e|r|e| @24| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|f|i|v|e| |â•‘+0#0000001#ffd7ff255|2@1| @37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|s|i|x| @1|â•‘+0#0000001#ffd7ff255|2|3| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|s|e|v|e|n|â•š+0#0000001#ffd7ff255|â•@40|⇲| +0#0000000#ffffff0@26
+|f|i|n|d| >t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @52
+|n|i|n|e| @70
+|t|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @54
+|v|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| |a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&| @55|8|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_10.dump b/src/testdir/dumps/Test_popupwin_previewpopup_10.dump
new file mode 100644
index 0000000..fdf16b4
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_10.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|f|i|v|e| @27|â•”+0#0000001#ffd7ff255| |X|h|e|a|d|e|r|.|h| |â•@29|X
+|s+0#0000000#ffffff0|i|x| @28|â•‘+0#0000001#ffd7ff255|1+0#e000002&|0| +0#0000001&@37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|s+0#0000000#ffffff0|e|v|e|n| @26|â•‘+0#0000001#ffd7ff255|s|e|a|r|c|h|e|d| |w|o|r|d| |i|s| |h|e|r|e| @18| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|f+0#0000000#ffffff0|i|n|d| |t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @9|â•‘+0#0000001#ffd7ff255|1+0#e000002&|2| +0#0000001&@37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255
+|n+0#0000000#ffffff0|i|n|e| @27|â•‘+0#0000001#ffd7ff255|1+0#e000002&|3| +0#0000001&@37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|t+0#0000000#ffffff0|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @11|â•š+0#0000001#ffd7ff255|â•@40|⇲
+|v+0#0000000#ffffff0|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| >a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|E+0#ffffff16#e000002|9@1|4|:| |N|o|t| |a|l@1|o|w|e|d| |i|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| +0#0000000#ffffff0@21|1@1|,|3|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_2.dump b/src/testdir/dumps/Test_popupwin_previewpopup_2.dump
new file mode 100644
index 0000000..99934ef
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_2.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @3|â•”+0&#afffff255| |X|t|a|g|f|i|l|e| |â•@30|X| +0&#ffffff0@23
+|f|i|v|e| @3|â•‘+0&#afffff255|2|6| @37| +0&#a8a8a8255|â•‘+0&#afffff255| +0&#ffffff0@23
+|s|i|x| @4|â•‘+0&#afffff255|2|7| @37| +0&#a8a8a8255|â•‘+0&#afffff255| +0&#ffffff0@23
+|s|e|v|e|n| @2|â•‘+0&#afffff255|t|h|i|s| |i|s| |a|n|o|t|h|e|r| |p|l|a|c|e| @18| +0&#0000001|â•‘+0&#afffff255| +0&#ffffff0@23
+|f|i|n|d| |t|h|e|â•‘+0&#afffff255|2|9| @37| +0&#a8a8a8255|â•‘+0&#afffff255| +0&#ffffff0@23
+|n|i|n|e| @3|â•š+0&#afffff255|â•@40|⇲| +0&#ffffff0@23
+|t|h|i|s| |i|s| >a|n|o|t|h|e|r| |w|o|r|d| @54
+|v|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| |a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|/+0#0000000&|a|n|o|t|h|e|r| @48|1|0|,|9| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_3.dump b/src/testdir/dumps/Test_popupwin_previewpopup_3.dump
new file mode 100644
index 0000000..d1e77e9
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_3.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @9|â•”+0&#afffff255| |X|t|a|g|f|i|l|e| |â•@30|X| +0&#ffffff0@17
+|f|i|v|e| @9|â•‘+0&#afffff255|2|6| @37| +0&#a8a8a8255|â•‘+0&#afffff255| +0&#ffffff0@17
+|s|i|x| @10|â•‘+0&#afffff255|2|7| @37| +0&#a8a8a8255|â•‘+0&#afffff255| +0&#ffffff0@17
+|s|e|v|e|n| @8|â•‘+0&#afffff255|t|h|i|s| |i|s| |a|n|o|t|h|e|r| |p|l|a|c|e| @18| +0&#0000001|â•‘+0&#afffff255| +0&#ffffff0@17
+|f|i|n|d| |t|h|e|w|o|r|d| |s|â•‘+0&#afffff255|2|9| @37| +0&#a8a8a8255|â•‘+0&#afffff255| +0&#ffffff0@17
+|n|i|n|e| @9|â•š+0&#afffff255|â•@40|⇲| +0&#ffffff0@17
+|t|h|i|s| |i|s| >a|n|o|t|h|e|r| |w|o|r|d| @54
+|v|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| |a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&| @55|1|0|,|9| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_4.dump b/src/testdir/dumps/Test_popupwin_previewpopup_4.dump
new file mode 100644
index 0000000..f8d411d
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_4.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|f|i|v|e| @27|â•”+0&#afffff255| |X|t|a|g|f|i|l|e| |â•@30|X
+|s+0&#ffffff0|i|x| @28|â•‘+0&#afffff255|2|6| @37| +0&#a8a8a8255|â•‘+0&#afffff255
+|s+0&#ffffff0|e|v|e|n| @26|â•‘+0&#afffff255|2|7| @37| +0&#a8a8a8255|â•‘+0&#afffff255
+|f+0&#ffffff0|i|n|d| |t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @9|â•‘+0&#afffff255|t|h|i|s| |i|s| |a|n|o|t|h|e|r| |p|l|a|c|e| @18| +0&#0000001|â•‘+0&#afffff255
+|n+0&#ffffff0|i|n|e| @27|â•‘+0&#afffff255|2|9| @37| +0&#a8a8a8255|â•‘+0&#afffff255
+|t+0&#ffffff0|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @11|â•š+0&#afffff255|â•@40|⇲
+|v+0&#ffffff0|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| >a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|/+0#0000000&|a|n|o|t|h|e|r| @48|1@1|,|3|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_5.dump b/src/testdir/dumps/Test_popupwin_previewpopup_5.dump
new file mode 100644
index 0000000..8f1b98f
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_5.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|f|i|v|e| @27|â•”+0&#afffff255| |t|e|s|t|d|i|r|/|X|t|a|g|f|i|l|e| |â•@22|X
+|s+0&#ffffff0|i|x| @28|â•‘+0&#afffff255|2|6| @37| +0&#a8a8a8255|â•‘+0&#afffff255
+|s+0&#ffffff0|e|v|e|n| @26|â•‘+0&#afffff255|2|7| @37| +0&#a8a8a8255|â•‘+0&#afffff255
+|f+0&#ffffff0|i|n|d| |t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @9|â•‘+0&#afffff255|t|h|i|s| |i|s| |a|n|o|t|h|e|r| |p|l|a|c|e| @18| +0&#0000001|â•‘+0&#afffff255
+|n+0&#ffffff0|i|n|e| @27|â•‘+0&#afffff255|2|9| @37| +0&#a8a8a8255|â•‘+0&#afffff255
+|t+0&#ffffff0|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @11|â•š+0&#afffff255|â•@40|⇲
+|v+0&#ffffff0|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| >a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&| @55|1@1|,|3|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_6.dump b/src/testdir/dumps/Test_popupwin_previewpopup_6.dump
new file mode 100644
index 0000000..a9a2be4
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_6.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|f|i|v|e| @70
+|s|i|x| @71
+|s|e|v|e|n| @69
+|f|i|n|d| |t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @52
+|n|i|n|e| @70
+|t|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @54
+|v|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| >a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|1@1|,|3|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_7.dump b/src/testdir/dumps/Test_popupwin_previewpopup_7.dump
new file mode 100644
index 0000000..f510b8b
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_7.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|f|i|v|e| @27|â•”+0#0000001#ffd7ff255| |X|t|a|g|f|i|l|e| |â•@30|X
+|s+0#0000000#ffffff0|i|x| @28|â•‘+0#0000001#ffd7ff255|2|0| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|s+0#0000000#ffffff0|e|v|e|n| @26|â•‘+0#0000001#ffd7ff255|t|h|e|w|o|r|d| |i|s| |h|e|r|e| @24| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|f+0#0000000#ffffff0|i|n|d| |t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @9|â•‘+0#0000001#ffd7ff255|2@1| @37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255
+|n+0#0000000#ffffff0|i|n|e| @27|â•‘+0#0000001#ffd7ff255|2|3| @37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|t+0#0000000#ffffff0|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @11|â•š+0#0000001#ffd7ff255|â•@40|⇲
+|v+0#0000000#ffffff0|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| >a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&| @55|1@1|,|3|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_8.dump b/src/testdir/dumps/Test_popupwin_previewpopup_8.dump
new file mode 100644
index 0000000..6e88fff
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_8.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|f|i|v|e| @27|â•”+0#0000001#ffd7ff255| |X|h|e|a|d|e|r|.|h| |â•@29|X
+|s+0#0000000#ffffff0|i|x| @28|â•‘+0#0000001#ffd7ff255|1+0#e000002&|0| +0#0000001&@37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|s+0#0000000#ffffff0|e|v|e|n| @26|â•‘+0#0000001#ffd7ff255|s|e|a|r|c|h|e|d| |w|o|r|d| |i|s| |h|e|r|e| @18| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|f+0#0000000#ffffff0|i|n|d| |t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @9|â•‘+0#0000001#ffd7ff255|1+0#e000002&|2| +0#0000001&@37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255
+|n+0#0000000#ffffff0|i|n|e| @27|â•‘+0#0000001#ffd7ff255|1+0#e000002&|3| +0#0000001&@37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|t+0#0000000#ffffff0|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @11|â•š+0#0000001#ffd7ff255|â•@40|⇲
+|v+0#0000000#ffffff0|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| >a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&| @55|1@1|,|3|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_previewpopup_9.dump b/src/testdir/dumps/Test_popupwin_previewpopup_9.dump
new file mode 100644
index 0000000..09f8a45
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_previewpopup_9.dump
@@ -0,0 +1,14 @@
+|o+0&#ffffff0|n|e| @71
+|#|i|n|c|l|u|d|e| |"|X|h|e|a|d|e|r|.|h|"| @54
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|f|i|v|e| @27|â•”+0#0000001#ffd7ff255| |X|h|e|a|d|e|r|.|h| |â•@29|X
+|s+0#0000000#ffffff0|i|x| @28|â•‘+0#0000001#ffd7ff255|1+0#e000002&|0| +0#0000001&@37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|s+0#0000000#ffffff0|e|v|e|n| @26|â•‘+0#0000001#ffd7ff255|s|e|a|r|c|h|e|d| |w|o|r|d| |i|s| |h|e|r|e| @18| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|f+0#0000000#ffffff0|i|n|d| |t|h|e|w|o|r|d| |s|o|m|e|w|h|e|r|e| @9|â•‘+0#0000001#ffd7ff255|1+0#e000002&|2| +0#0000001&@37| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255
+|n+0#0000000#ffffff0|i|n|e| @27|â•‘+0#0000001#ffd7ff255|1+0#e000002&|3| +0#0000001&@37| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255
+|t+0#0000000#ffffff0|h|i|s| |i|s| |a|n|o|t|h|e|r| |w|o|r|d| @11|â•š+0#0000001#ffd7ff255|â•@40|⇲
+|v+0#0000000#ffffff0|e|r|y| |l|o|n|g| |l|i|n|e| |w|h|e|r|e| |t|h|e| |w|o|r|d| |i|s| |a|l|s|o| >a|n|o|t|h|e|r| @29
+|~+0#4040ff13&| @73
+|~| @73
+|E+0#ffffff16#e000002|3|6@1|:| |N|o|t| |a|l@1|o|w|e|d| |t|o| |e|n|t|e|r| |a| |p|o|p|u|p| |w|i|n|d|o|w| +0#0000000#ffffff0@15|1@1|,|3|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_1.dump b/src/testdir/dumps/Test_popupwin_scroll_1.dump
new file mode 100644
index 0000000..2f623e7
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_1.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|o+0#0000001#ffd7ff255|n|e| @4| +0#0000000#0000001| +0&#ffffff0@32
+|5| @31|t+0#0000001#ffd7ff255|w|o| @4| +0#0000000#0000001| +0&#ffffff0@32
+|6| @31|t+0#0000001#ffd7ff255|h|r|e@1| @2| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|7| @31|f+0#0000001#ffd7ff255|o|u|r| @3| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_10.dump b/src/testdir/dumps/Test_popupwin_scroll_10.dump
new file mode 100644
index 0000000..eec1db6
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_10.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @31|â•”+0#0000001#ffd7ff255|â•@5|X| +0#0000000#ffffff0@33
+|3| @31|â•‘+0#0000001#ffd7ff255|f|i|v|e| | +0#0000000#ff404010|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@33
+|4| @31|â•‘+0#0000001#ffd7ff255|s|i|x| @1| +0#0000000#ff404010|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@33
+|5| @31|â•‘+0#0000001#ffd7ff255|s|e|v|e|n| +0#0000000#4040ff13|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@33
+|6| @31|â•‘+0#0000001#ffd7ff255|e|i|g|h|t| +0#0000000#4040ff13|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@33
+|7| @31|â•‘+0#0000001#ffd7ff255|n|i|n|e| | +0#0000000#4040ff13|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@33
+|8| @31|â•š+0#0000001#ffd7ff255|â•@5|â•| +0#0000000#ffffff0@33
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_11.dump b/src/testdir/dumps/Test_popupwin_scroll_11.dump
new file mode 100644
index 0000000..96b3d27
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_11.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @20|1+0#0000001#ffd7ff255| @28| +0#0000000#0000001| +0&#ffffff0@21
+|5| @20|2+0#0000001#ffd7ff255| @28| +0#0000000#0000001| +0&#ffffff0@21
+|6| @20|3+0#0000001#ffd7ff255| @28| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|7| @20|4+0#0000001#ffd7ff255| @28| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|8| @73
+|9| @73
+|:|c|a|l@1| |g|:|P|o|p|u|p|S|c|r|o|l@1|(|)| @35|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_12.dump b/src/testdir/dumps/Test_popupwin_scroll_12.dump
new file mode 100644
index 0000000..e54a187
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_12.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @20|2+0#0000001#ffd7ff255| @28| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|5| @20|3+0#0000001#ffd7ff255| @28| +0#0000000#0000001| +0&#ffffff0@21
+|6| @20|4+0#0000001#ffd7ff255| @28| +0#0000000#0000001| +0&#ffffff0@21
+|7| @20|l+0#0000001#ffd7ff255|o|n|g| |l|i|n|e| |l|o|n|g| |l|i|n|e| |l|o|n|g| |l|i|n|e| | +0#0000000#a8a8a8255| +0&#ffffff0@21
+|8| @73
+|9| @73
+|:|c|a|l@1| |g|:|P|o|p|u|p|S|c|r|o|l@1|(|)| @35|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_13.dump b/src/testdir/dumps/Test_popupwin_scroll_13.dump
new file mode 100644
index 0000000..067f484
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_13.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @20|9+0#0000001#ffd7ff255@1| @27| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|2| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|3| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|4| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|5| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|6| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|7| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|8| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#a8a8a8255| +0&#ffffff0@21
+|9| @20| +0#4040ff13#ffd7ff255@29| +0#0000000#0000001| +0&#ffffff0@21
+|:|c|a|l@1| |S|c|r|o|l@1|B|o|t@1|o|m|(|)| @36|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_2.dump b/src/testdir/dumps/Test_popupwin_scroll_2.dump
new file mode 100644
index 0000000..227ba6c
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_2.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|t+0#0000001#ffd7ff255|w|o| @4| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|5| @31|t+0#0000001#ffd7ff255|h|r|e@1| @2| +0#0000000#0000001| +0&#ffffff0@32
+|6| @31|f+0#0000001#ffd7ff255|o|u|r| @3| +0#0000000#0000001| +0&#ffffff0@32
+|7| @31|f+0#0000001#ffd7ff255|i|v|e| @3| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_3.dump b/src/testdir/dumps/Test_popupwin_scroll_3.dump
new file mode 100644
index 0000000..623d217
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_3.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|s+0#0000001#ffd7ff255|i|x| @4| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|5| @31|s+0#0000001#ffd7ff255|e|v|e|n| @2| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|6| @31|e+0#0000001#ffd7ff255|i|g|h|t| @2| +0#0000000#0000001| +0&#ffffff0@32
+|7| @31|n+0#0000001#ffd7ff255|i|n|e| @3| +0#0000000#0000001| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_4.dump b/src/testdir/dumps/Test_popupwin_scroll_4.dump
new file mode 100644
index 0000000..3f7f961
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_4.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @31|n+0#0000001#ffd7ff255|i|n|e| @3| +0#0000000#0000001| +0&#ffffff0@32
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|:| @55|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_5.dump b/src/testdir/dumps/Test_popupwin_scroll_5.dump
new file mode 100644
index 0000000..c5cbe80
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_5.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|t+0#0000001#ffd7ff255|h|r|e@1| @2| +0#0000000#ff404010| +0&#ffffff0@32
+|5| @31|f+0#0000001#ffd7ff255|o|u|r| @3| +0#0000000#4040ff13| +0&#ffffff0@32
+|6| @31|f+0#0000001#ffd7ff255|i|v|e| @3| +0#0000000#4040ff13| +0&#ffffff0@32
+|7| @31|s+0#0000001#ffd7ff255|i|x| @4| +0#0000000#ff404010| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:|c|a|l@1| |S|c|r|o|l@1|U|p|(|)| @40|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_6.dump b/src/testdir/dumps/Test_popupwin_scroll_6.dump
new file mode 100644
index 0000000..aade9d0
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_6.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|f+0#0000001#ffd7ff255|i|v|e| @3| +0#0000000#ff404010| +0&#ffffff0@32
+|5| @31|s+0#0000001#ffd7ff255|i|x| @4| +0#0000000#ff404010| +0&#ffffff0@32
+|6| @31|s+0#0000001#ffd7ff255|e|v|e|n| @2| +0#0000000#4040ff13| +0&#ffffff0@32
+|7| @31|e+0#0000001#ffd7ff255|i|g|h|t| @2| +0#0000000#4040ff13| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:|c|a|l@1| |S|c|r|o|l@1|D|o|w|n|(|)| @38|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_7.dump b/src/testdir/dumps/Test_popupwin_scroll_7.dump
new file mode 100644
index 0000000..1ee91b1
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_7.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|s+0#0000001#ffd7ff255|i|x| @4| +0#0000000#ff404010| +0&#ffffff0@32
+|5| @31|s+0#0000001#ffd7ff255|e|v|e|n| @2| +0#0000000#ff404010| +0&#ffffff0@32
+|6| @31|e+0#0000001#ffd7ff255|i|g|h|t| @2| +0#0000000#4040ff13| +0&#ffffff0@32
+|7| @31|n+0#0000001#ffd7ff255|i|n|e| @3| +0#0000000#4040ff13| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:|c|a|l@1| |S|c|r|o|l@1|D|o|w|n|(|)| @38|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_8.dump b/src/testdir/dumps/Test_popupwin_scroll_8.dump
new file mode 100644
index 0000000..34b1638
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_8.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|f+0#0000001#ffd7ff255|o|u|r| @3| +0#0000000#ff404010| +0&#ffffff0@32
+|5| @31|f+0#0000001#ffd7ff255|i|v|e| @3| +0#0000000#4040ff13| +0&#ffffff0@32
+|6| @31|s+0#0000001#ffd7ff255|i|x| @4| +0#0000000#4040ff13| +0&#ffffff0@32
+|7| @31|s+0#0000001#ffd7ff255|e|v|e|n| @2| +0#0000000#ff404010| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:|c|a|l@1| |C|l|i|c|k|T|o|p|(|)| @40|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_scroll_9.dump b/src/testdir/dumps/Test_popupwin_scroll_9.dump
new file mode 100644
index 0000000..b471dc1
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_9.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @30|â•”+0#0000001#ffd7ff255|â•@8|X| +0#0000000#ffffff0@31
+|4| @30|â•‘+0#0000001#ffd7ff255|f|i|v|e| @3| +0#0000000#ff404010|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@31
+|5| @30|â•‘+0#0000001#ffd7ff255|s|i|x| @4| +0#0000000#ff404010|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@31
+|6| @30|â•‘+0#0000001#ffd7ff255|s|e|v|e|n| @2| +0#0000000#4040ff13|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@31
+|7| @30|â•‘+0#0000001#ffd7ff255|e|i|g|h|t| @2| +0#0000000#4040ff13|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@31
+|8| @30|â•š+0#0000001#ffd7ff255|â•@8|â•| +0#0000000#ffffff0@31
+|9| @73
+|:|c|a|l@1| |C|l|i|c|k|B|o|t|(|)| @40|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_select_01.dump b/src/testdir/dumps/Test_popupwin_select_01.dump
new file mode 100644
index 0000000..b6cedc5
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_select_01.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @7|â•”+0#0000001#ffd7ff255|â•@18|â•—| +0#0000000#ffffff0@44
+|4| @7|â•‘+0#0000001#ffd7ff255|t|h|e| |w+1#0000000#ffffff0|o|r|d| @9| +0&#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@44
+|5| @7|â•‘+0#0000001#ffd7ff255|s+1#0000000#ffffff0|o|m|e| |m|o|r|e| @8| +0&#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@44
+|6| @7|â•‘+0#0000001#ffd7ff255|s+1#0000000#ffffff0|e|v|e|r|a|l| |w|o|r|d|s| +0#0000001#ffd7ff255|h|e|r|e| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@44
+|7| @7|â•š+0#0000001#ffd7ff255|â•@18|â•| +0#0000000#ffffff0@44
+|8| @73
+|9| @73
+|:|c|a|l@1| |S|e|l|e|c|t|1|(|)| @41|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_select_02.dump b/src/testdir/dumps/Test_popupwin_select_02.dump
new file mode 100644
index 0000000..550e0db
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_select_02.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0>w|o|r|d| @69
+|s|o|m|e| |m|o|r|e| @65
+|s|e|v|e|r|a|l| |w|o|r|d|s| @61
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+@57|1|,|2| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_set_firstline_1.dump b/src/testdir/dumps/Test_popupwin_set_firstline_1.dump
new file mode 100644
index 0000000..7d8c20d
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_set_firstline_1.dump
@@ -0,0 +1,16 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @25|1+0#0000001#e0e0e08|0| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255@1| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|2| @17| +0#0000000#0000001| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|3| @17| +0#0000000#0000001| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|4| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|5| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|6| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|7| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|8| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|9| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|2+0#0000001#ffd7ff255|0| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_set_firstline_2.dump b/src/testdir/dumps/Test_popupwin_set_firstline_2.dump
new file mode 100644
index 0000000..5744eaf
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_set_firstline_2.dump
@@ -0,0 +1,16 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @25|5+0#0000001#ffd7ff255| @18| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|6+0#0000001#ffd7ff255| @18| +0#0000000#0000001| +0#4040ff13#ffffff0@26
+|~| @25|7+0#0000001#ffd7ff255| @18| +0#0000000#0000001| +0#4040ff13#ffffff0@26
+|~| @25|8+0#0000001#ffd7ff255| @18| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|9+0#0000001#ffd7ff255| @18| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#e0e0e08|0| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255@1| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|2| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|3| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|4| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @25|1+0#0000001#ffd7ff255|5| @17| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@26
+|~| @73
+|~| @73
+|:+0#0000000&| @55|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_showbreak.dump b/src/testdir/dumps/Test_popupwin_showbreak.dump
new file mode 100644
index 0000000..8ce0a75
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_showbreak.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @27|â•”+0#0000001#ffd7ff255|â•@13|â•—| +0#0000000#ffffff0@29
+|4| @27|â•‘+0#0000001#ffd7ff255| |a| |l|o|n|g| |l|i|n|e| @1|â•‘| +0#0000000#ffffff0@29
+|5| @27|â•‘+0#0000001#ffd7ff255| |>+0#4040ff13&@1| |h+0#0000001&|e|r|e| |t|h|a|t| |â•‘| +0#0000000#ffffff0@29
+|6| @27|â•‘+0#0000001#ffd7ff255| |>+0#4040ff13&@1| | +0#0000001&|w|r|a|p|s| @3|â•‘| +0#0000000#ffffff0@29
+|7| @27|â•š+0#0000001#ffd7ff255|â•@13|â•| +0#0000000#ffffff0@29
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_sign_1.dump b/src/testdir/dumps/Test_popupwin_sign_1.dump
new file mode 100644
index 0000000..59cc904
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_sign_1.dump
@@ -0,0 +1,10 @@
+|>+0#e000002#ffffff0@1>0+0#ffffff16#ff404010| @71
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @23|#+0#ffffff16#ff404010|!|h+0#0000001#ffff4012|e|l@1|o| @12| +0#0000000#ffffff0@27
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @23| +0#0000e05#a8a8a8255@1|b+0#0000001#ffd7ff255|r|i|g|h|t| @11| +0#0000000#ffffff0@27
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @23| +0#0000e05#a8a8a8255@1|w+0#0000001#ffd7ff255|o|r|l|d| @12| +0#0000000#ffffff0@27
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @71
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_sign_2.dump b/src/testdir/dumps/Test_popupwin_sign_2.dump
new file mode 100644
index 0000000..ddccde1
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_sign_2.dump
@@ -0,0 +1,10 @@
+|>+0#e000002#ffffff0@1>0+0#ffffff16#ff404010| @71
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @13| +0#0000e05#a8a8a8255@1|#+0#ffffff16#ff404010|!| +0#af5f00255#ffd7ff255@1|1| |a+0#0000001#ffff4012| |l|o|n|g|e|r| |l|i|n|e| |t|o| |c|h|e|c|k| |t|h|e| |w|i|d|t|h| +0#0000000#ffffff0@17
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @71
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @71
+|:|c|a|l@1| |S|e|t|O|p|t|i|o|n|s|(|)| @38|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_term_01.dump b/src/testdir/dumps/Test_popupwin_term_01.dump
new file mode 100644
index 0000000..50818c1
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_term_01.dump
@@ -0,0 +1,16 @@
+|v+0&#ffffff0|i|m|>| @70
+@75
+@34|â•”+0#0000001#ffd7ff255|â•@3|â•—| +0#0000000#ffffff0@34
+@34|â•‘+0#0000001#ffd7ff255|1@3|â•‘| +0#0000000#ffffff0@34
+|!+0#ffffff16#00e0003|/|b|i|n|/|s|h| |[|r|u|n@1|i|n|g|]| @15|â•‘+0#0000001#ffd7ff255|2@3|â•‘| +0#ffffff16#00e0003@34
+>++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@14|â•š+0#0000001#ffd7ff255|â•@3|⇲|-+0#0000e05#a8a8a8255@34
+|1+0#0000000#ffffff0@1| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|2+0#0000000#ffffff0|3| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|3+0#0000000#ffffff0|5| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|4+0#0000000#ffffff0|7| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|[+3#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @61
+| +0&&@74
diff --git a/src/testdir/dumps/Test_popupwin_term_02.dump b/src/testdir/dumps/Test_popupwin_term_02.dump
new file mode 100644
index 0000000..160834a
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_term_02.dump
@@ -0,0 +1,16 @@
+|v+0&#ffffff0|i|m|>| @70
+@75
+@14|â•”+0#0000001#ffd7ff255|â•@3|â•—| +0#0000000#ffffff0@54
+@14|â•‘+0#0000001#ffd7ff255|1@3|â•‘| +0#0000000#ffffff0@54
+|!+0#ffffff16#00e0003|/|b|i|n|/|s|h| |[|r|u|n@1|â•‘+0#0000001#ffd7ff255|2@3|â•‘| +0#ffffff16#00e0003@54
+>++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |â•š+0#0000001#ffd7ff255|â•@3|⇲|-+0#0000e05#a8a8a8255@54
+|1+0#0000000#ffffff0@1| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|2+0#0000000#ffffff0|3| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|3+0#0000000#ffffff0|5| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|4+0#0000000#ffffff0|7| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|[+3#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @61
+|:+0&&|c|a|l@1| |D|r|a|g|i|t|L|e|f|t|(|)| @56
diff --git a/src/testdir/dumps/Test_popupwin_term_03.dump b/src/testdir/dumps/Test_popupwin_term_03.dump
new file mode 100644
index 0000000..0632ee8
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_term_03.dump
@@ -0,0 +1,16 @@
+|v+0&#ffffff0|i|m|>| @70
+@75
+@75
+@75
+|!+0#ffffff16#00e0003|/|b|i|n|/|s|h| |[|r|u|n@1|i|n|g|]| @56
+>++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|1+0#0000000#ffffff0@1| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|2+0#0000000#ffffff0|3| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|3+0#0000000#ffffff0|5| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@14|â•”+0#0000001#ffd7ff255|â•@3|â•—|-+0#0000e05#a8a8a8255@34
+|4+0#0000000#ffffff0|7| @31|â•‘+0#0000001#ffd7ff255|1@3|â•‘| +0#0000000#ffffff0@34
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@14|â•‘+0#0000001#ffd7ff255|2@3|â•‘|-+0#0000e05#a8a8a8255@34
+|[+3#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @20|â•š+0#0000001#ffd7ff255|â•@3|⇲| +3#0000000#ffffff0@34
+|:+0&&|c|a|l@1| |D|r|a|g|i|t|D|o|w|n|(|)| @56
diff --git a/src/testdir/dumps/Test_popupwin_term_04.dump b/src/testdir/dumps/Test_popupwin_term_04.dump
new file mode 100644
index 0000000..1345b75
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_term_04.dump
@@ -0,0 +1,16 @@
+|v+0&#ffffff0|i|m|>| @70
+@75
+@75
+@75
+|!+0#ffffff16#00e0003|/|b|i|n|/|s|h| |[|r|u|n@1|i|n|g|]| @56
+>++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|1+0#0000000#ffffff0@1| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|2+0#0000000#ffffff0|3| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |f|o|l|d| |-@55
+|3+0#0000000#ffffff0|5| @72
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |â•”+0#0000001#ffd7ff255|â•@3|â•—|-+0#0000e05#a8a8a8255@54
+|4+0#0000000#ffffff0|7| @11|â•‘+0#0000001#ffd7ff255|1@3|â•‘| +0#0000000#ffffff0@54
+|++0#0000e05#a8a8a8255|-@1| |1@1| |l|i|n|e|s|:| |â•‘+0#0000001#ffd7ff255|2@3|â•‘|-+0#0000e05#a8a8a8255@54
+|[+3#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| |â•š+0#0000001#ffd7ff255|â•@3|⇲| +3#0000000#ffffff0@54
+|:+0&&|c|a|l@1| |D|r|a|g|i|t|D|o|w|n|L|e|f|t|(|)| @52
diff --git a/src/testdir/dumps/Test_popupwin_three_errors_1.dump b/src/testdir/dumps/Test_popupwin_three_errors_1.dump
new file mode 100644
index 0000000..64b1a75
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_three_errors_1.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @27|o+0#0000001#ffd7ff255|n|e| |t|w|o| |t|h|r|e@1|.@2| +0#4040ff13#ffffff0@29
+|~| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|1|2|5|0|:| |A|r|g|u|m|e|n|t| |o|f| |f|i|l|t|e|r|(|)| |m|u|s|t| |b|e| |a| |L|i|s|t|,| |S|t|r|i|n|g|,| |D|i|c|t|i|o|n|a|r|y| |o|r| |B|l|o|b| +0#0000000#ffffff0@4
+@57|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_three_errors_2.dump b/src/testdir/dumps/Test_popupwin_three_errors_2.dump
new file mode 100644
index 0000000..aa90915
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_three_errors_2.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|1|2|5|0|:| |A|r|g|u|m|e|n|t| |o|f| |f|i|l|t|e|r|(|)| |m|u|s|t| |b|e| |a| |L|i|s|t|,| |S|t|r|i|n|g|,| |D|i|c|t|i|o|n|a|r|y| |o|r| |B|l|o|b| +0#0000000#ffffff0@4
+@57|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_title.dump b/src/testdir/dumps/Test_popupwin_title.dump
new file mode 100644
index 0000000..ae15279
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_title.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @28| +0#0000001#ffd7ff255|T|i|t|l|e| |S|t|r|i|n|g| | +0#0000000#ffffff0@30
+|5| @28|o+0#0000001#ffd7ff255|n|e| @10| +0#0000000#ffffff0@30
+|6| @28|t+0#0000001#ffd7ff255|w|o| @10| +0#0000000#ffffff0@30
+|7| @28|a+0#0000001#ffd7ff255|n|o|t|h|e|r| @6| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_toohigh_1.dump b/src/testdir/dumps/Test_popupwin_toohigh_1.dump
new file mode 100644
index 0000000..41dbd44
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_toohigh_1.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0@9| @64
+|2@9| @64
+|3@8>3| @64
+|4@8|â•”+0#0000001#ffd7ff255|â•@8|â•—| +0#0000000#ffffff0@54
+|5@8|â•‘+0#0000001#ffd7ff255|o|n|e| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|6@8|â•‘+0#0000001#ffd7ff255|t|w|o| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|7@8|â•‘+0#0000001#ffd7ff255|t|h|r|e@1| @2| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|8@8|â•‘+0#0000001#ffd7ff255|f|o|u|r| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|9@8|â•‘+0#0000001#ffd7ff255|f|i|v|e| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+@9|â•š+0#0000001#ffd7ff255|â•@8|â•| +0#0000000#ffffff0@36|3|,|1|0| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_toohigh_2.dump b/src/testdir/dumps/Test_popupwin_toohigh_2.dump
new file mode 100644
index 0000000..911cc2f
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_toohigh_2.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0@8|â•”+0#0000001#ffd7ff255|â•@8|â•—| +0#0000000#ffffff0@54
+|2@8|â•‘+0#0000001#ffd7ff255|o|n|e| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|3@8|â•‘+0#0000001#ffd7ff255|t|w|o| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|4@8|â•‘+0#0000001#ffd7ff255|t|h|r|e@1| @2| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|5@8|â•‘+0#0000001#ffd7ff255|f|o|u|r| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|6@8|â•‘+0#0000001#ffd7ff255|f|i|v|e| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|7@8|â•š+0#0000001#ffd7ff255|â•@8|â•| +0#0000000#ffffff0@54
+|8@8>8| @64
+|9@9| @64
+|:|c|a|l@1| |S|h|o|w|P|o|p|u|p|(|)| @39|8|,|1|0| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_toohigh_3.dump b/src/testdir/dumps/Test_popupwin_toohigh_3.dump
new file mode 100644
index 0000000..8c15a40
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_toohigh_3.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0@8>1| @64
+|2@8|â•”+0#0000001#ffd7ff255|â•@8|â•—| +0#0000000#ffffff0@54
+|3@8|â•‘+0#0000001#ffd7ff255|o|n|e| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|4@8|â•‘+0#0000001#ffd7ff255|t|w|o| @4| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|5@8|â•‘+0#0000001#ffd7ff255|t|h|r|e@1| @2| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|6@8|â•‘+0#0000001#ffd7ff255|f|o|u|r| @3| +0#0000000#0000001|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|7@8|â•‘+0#0000001#ffd7ff255|f|i|v|e| @3| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|8@8|â•‘+0#0000001#ffd7ff255|s|i|x| @4| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|9@8|â•‘+0#0000001#ffd7ff255|s|e|v|e|n| @2| +0#0000000#a8a8a8255|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@54
+|:|c|a|l@1| |S|h|o|â•š+0#0000001#ffd7ff255|â•@8|â•| +0#0000000#ffffff0@36|1|,|1|0| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_win_execute.dump b/src/testdir/dumps/Test_popupwin_win_execute.dump
new file mode 100644
index 0000000..b890a28
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_win_execute.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @31|s+0#0000001#ffd7ff255|o|m|e| |t|e|x|t| +0#4040ff13#ffffff0@32
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_win_execute_cursorline.dump b/src/testdir/dumps/Test_popupwin_win_execute_cursorline.dump
new file mode 100644
index 0000000..e29dca9
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_win_execute_cursorline.dump
@@ -0,0 +1,14 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @34|1+0#0000001#ffd7ff255|3| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @34|1+0#0000001#ffd7ff255|4| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @34|1+0#0000001#ffd7ff255|5| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @34|1+0#0000001#ffd7ff255|6| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @34|1+0#0000001#e0e0e08|7| +0#0000000#0000001| +0#4040ff13#ffffff0@35
+|~| @34|1+0#0000001#ffd7ff255|8| +0#0000000#0000001| +0#4040ff13#ffffff0@35
+|~| @34|1+0#0000001#ffd7ff255|9| +0#0000000#0000001| +0#4040ff13#ffffff0@35
+|~| @34|2+0#0000001#ffd7ff255|0| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @34|2+0#0000001#ffd7ff255|1| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@35
+|~| @73
+|~| @73
+|:+0#0000000&| @55|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_popupwin_wrap.dump b/src/testdir/dumps/Test_popupwin_wrap.dump
new file mode 100644
index 0000000..3f7b000
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_wrap.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @17|a+0#0000001#ffd7ff255| |l|o|n|g| |l|i|n| +0#0000000#ffffff0@45
+|4| @17|e+0#0000001#ffd7ff255| |t|h|a|t| |w|o|n| +0#0000000#ffffff0@45
+|5| @17|t+0#0000001#ffd7ff255| |f|i|t| @4| +0#0000000#ffffff0@45
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_wrap_1.dump b/src/testdir/dumps/Test_popupwin_wrap_1.dump
new file mode 100644
index 0000000..5643dc7
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_wrap_1.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|â•”+0#0000001#ffd7ff255|â•@73
+|â•‘| |o+0&#e0e0e08|n|e| @67| +0&#ffd7ff255| +0#0000000#0000001
+|â•‘+0#0000001#ffd7ff255| |a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d| | +0#0000000#0000001
+|â•‘+0#0000001#ffd7ff255| |f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s| @52| +0#0000000#a8a8a8255
+|â•š+0#0000001#ffd7ff255|â•@73
+|8+0#0000000#ffffff0| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_wrap_2.dump b/src/testdir/dumps/Test_popupwin_wrap_2.dump
new file mode 100644
index 0000000..3a02fc5
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_wrap_2.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|â•”+0#0000001#ffd7ff255|â•@73
+|â•‘| |a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d| | +0#0000000#a8a8a8255
+|â•‘+0#0000001#ffd7ff255| |f|a|s|d|f|a|s|d|f|a|s|d|f|a|s|d|f|a|s| @52| +0#0000000#0000001
+|â•‘+0#0000001#ffd7ff255| |t+0&#e0e0e08|h|r|e@1| @65| +0&#ffd7ff255| +0#0000000#0000001
+|â•š+0#0000001#ffd7ff255|â•@73
+|8+0#0000000#ffffff0| @73
+|9| @73
+@57|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_popupwin_wrong_name.dump b/src/testdir/dumps/Test_popupwin_wrong_name.dump
new file mode 100644
index 0000000..e63ced5
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_wrong_name.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|1@1|7|:| |U|n|k|n|o|w|n| |f|u|n|c|t|i|o|n|:| |N|o|S|u|c|h|F|u|n|c| +0#0000000#ffffff0@22|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_below_empty_1.dump b/src/testdir/dumps/Test_prop_above_below_empty_1.dump
new file mode 100644
index 0000000..f470016
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_empty_1.dump
@@ -0,0 +1,16 @@
+| +0#af5f00255#ffffff0@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| |1+0#0000000&@7| @47
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| | +0#0000000&@55
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|3| |3+0#0000000&@8| @46
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|4| | +0#0000000&@55
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|5| >5+0#0000000&@10| @44
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+@42|5|,|1|-|5|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_below_empty_2.dump b/src/testdir/dumps/Test_prop_above_below_empty_2.dump
new file mode 100644
index 0000000..3954a4b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_empty_2.dump
@@ -0,0 +1,16 @@
+| +0#af5f00255#ffffff0@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| |1+0#0000000&@7|$+0#4040ff13&| +0#0000000&@46
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| |$+0#4040ff13&| +0#0000000&@54
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|3| |3+0#0000000&@8|$+0#4040ff13&| +0#0000000&@45
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|4| |$+0#4040ff13&| +0#0000000&@54
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|5| >5+0#0000000&@10|$+0#4040ff13&| +0#0000000&@43
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+|:|s|e|t| |l|i|s|t| @32|5|,|1|-|5|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_below_empty_3.dump b/src/testdir/dumps/Test_prop_above_below_empty_3.dump
new file mode 100644
index 0000000..27f9eec
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_empty_3.dump
@@ -0,0 +1,16 @@
+| +0#af5f00255#ffffff0@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| |1+0#0000000&@7| | +0&#ffd7d7255| +0&#ffffff0@45
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| | +0#0000000&@8| +0&#ffd7d7255| +0&#ffffff0@45
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|3| |3+0#0000000&@8| +0&#ffd7d7255| +0&#ffffff0@45
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|4| | +0#0000000&@8| +0&#ffd7d7255| +0&#ffffff0@45
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|5| >5+0#0000000&@8|5+0&#ffd7d7255|5+0&#ffffff0| @44
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+|:| @40|5|,|1|-|5|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_below_empty_4.dump b/src/testdir/dumps/Test_prop_above_below_empty_4.dump
new file mode 100644
index 0000000..cc3c27d
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_empty_4.dump
@@ -0,0 +1,16 @@
+| +0#af5f00255#ffffff0@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|4| |1+0#0000000&@7| @47
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|3| | +0#0000000&@55
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| |3+0#0000000&@8| @46
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| | +0#0000000&@55
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+|5+0#af5f00255&| @2>5+0#0000000&@10| @44
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+|:| @40|5|,|1|-|5|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_below_empty_5.dump b/src/testdir/dumps/Test_prop_above_below_empty_5.dump
new file mode 100644
index 0000000..5ee4d80
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_empty_5.dump
@@ -0,0 +1,16 @@
+| +0#af5f00255#ffffff0@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| |1+0#0000000&@7| @47
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| | +0#0000000&@55
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+|3+0#af5f00255&| @2>3+0#0000000&@8| @46
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| | +0#0000000&@55
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| |5+0#0000000&@10| @44
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+|:| @40|3|,|1|-|5|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_1.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_1.dump
new file mode 100644
index 0000000..d6ac04e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_1.dump
@@ -0,0 +1,8 @@
+>"+0&#ffffff0| |l|i|n|e| |1| @51
+|"| |l|i|n|e| |2| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| +0&#ffffff0@47
+|"| |l|i|n|e| |3| @51
+|"| |l|i|n|e| |4| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |1| +0&#ffffff0@45
+|@+0#4040ff13&@2| @56
+| +0#0000000&@41|1|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_10.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_10.dump
new file mode 100644
index 0000000..de1ee5c
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_10.dump
@@ -0,0 +1,8 @@
+|"+0&#ffffff0| |l|i|n|e| |7| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| +0&#ffffff0@47
+|"| |l|i|n|e| |8| @51
+>"| |l|i|n|e| |9| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |1|0| @50
+@42|9|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_11.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_11.dump
new file mode 100644
index 0000000..2368ecf
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_11.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|e+0#0000000#ffd7ff255|r|t| |b|e|l|o|w| +0&#ffffff0@47
+|"| |l|i|n|e| |8| @51
+>"| |l|i|n|e| |9| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |1|0| @50
+|~+0#4040ff13&| @58
+| +0#0000000&@41|9|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_12.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_12.dump
new file mode 100644
index 0000000..de22216
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_12.dump
@@ -0,0 +1,8 @@
+|"+0&#ffffff0| |l|i|n|e| |8| @51
+|"| |l|i|n|e| |9| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |2| +0&#ffffff0@45
+>"| |l|i|n|e| |1|0| @50
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|0|,|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_13.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_13.dump
new file mode 100644
index 0000000..6c9db8b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_13.dump
@@ -0,0 +1,8 @@
+|"+0&#ffffff0| |l|i|n|e| |9| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |2| +0&#ffffff0@45
+>"| |l|i|n|e| |1|0| @50
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|0|,|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_14.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_14.dump
new file mode 100644
index 0000000..341707e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_14.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|e+0#0000000#ffd7ff255|r|t| |b|e|l|o|w| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |2| +0&#ffffff0@45
+>"| |l|i|n|e| |1|0| @50
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|0|,|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_15.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_15.dump
new file mode 100644
index 0000000..4110a19
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_15.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2| +0#0000000&@2|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |2| +0&#ffffff0@39
+>"| |l|i|n|e| |1|0| @50
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|0|,|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_16.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_16.dump
new file mode 100644
index 0000000..d58f2d0
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_16.dump
@@ -0,0 +1,8 @@
+>"+0&#ffffff0| |l|i|n|e| |1|0| @50
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|0|,|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_2.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_2.dump
new file mode 100644
index 0000000..8e8569b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_2.dump
@@ -0,0 +1,8 @@
+|"+0&#ffffff0| |l|i|n|e| |2| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| +0&#ffffff0@47
+|"| |l|i|n|e| |3| @51
+>"| |l|i|n|e| |4| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |5| @51
+@42|4|,|1| @10|1|6|%|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_3.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_3.dump
new file mode 100644
index 0000000..f4e9d92
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_3.dump
@@ -0,0 +1,8 @@
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| +0&#ffffff0@47
+|"| |l|i|n|e| |3| @51
+>"| |l|i|n|e| |4| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |5| @51
+|"| |l|i|n|e| |6| @51
+@42|4|,|1| @10|3@1|%|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_4.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_4.dump
new file mode 100644
index 0000000..3ca1c6e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_4.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|i+0#0000000&|n|e| |3| @51
+>"| |l|i|n|e| |4| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |5| @51
+|"| |l|i|n|e| |6| @51
+|@+0#4040ff13&@2| @56
+| +0#0000000&@41|4|,|1| @10|3@1|%|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_5.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_5.dump
new file mode 100644
index 0000000..3b28dbb
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_5.dump
@@ -0,0 +1,8 @@
+|"+0&#ffffff0| |l|i|n|e| |4| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |5| @51
+>"| |l|i|n|e| |6| @51
+|"| |l|i|n|e| |7| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| +0&#ffffff0@47
+@42|6|,|1| @10|5|0|%|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_6.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_6.dump
new file mode 100644
index 0000000..7681090
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_6.dump
@@ -0,0 +1,8 @@
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |a|b|o|v|e| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |5| @51
+>"| |l|i|n|e| |6| @51
+|"| |l|i|n|e| |7| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| +0&#ffffff0@47
+|"| |l|i|n|e| |8| @51
+@42|6|,|1| @10|6@1|%|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_7.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_7.dump
new file mode 100644
index 0000000..bf9c0f9
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_7.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|e+0#0000000#ffd7ff255|r|t| |a|b|o|v|e| |2| +0&#ffffff0@45
+|"| |l|i|n|e| |5| @51
+>"| |l|i|n|e| |6| @51
+|"| |l|i|n|e| |7| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| +0&#ffffff0@47
+|"| |l|i|n|e| |8| @51
+|@+0#4040ff13&@2| @56
+| +0#0000000&@41|6|,|1| @10|6@1|%|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_8.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_8.dump
new file mode 100644
index 0000000..06d9e09
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_8.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|i+0#0000000&|n|e| |5| @51
+>"| |l|i|n|e| |6| @51
+|"| |l|i|n|e| |7| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| +0&#ffffff0@47
+|"| |l|i|n|e| |8| @51
+|"| |l|i|n|e| |9| @51
+|@+0#4040ff13&@2| @56
+| +0#0000000&@41|6|,|1| @10|6@1|%|
diff --git a/src/testdir/dumps/Test_prop_above_below_smoothscroll_9.dump b/src/testdir/dumps/Test_prop_above_below_smoothscroll_9.dump
new file mode 100644
index 0000000..cac3b28
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_below_smoothscroll_9.dump
@@ -0,0 +1,8 @@
+|"+0&#ffffff0| |l|i|n|e| |6| @51
+|"| |l|i|n|e| |7| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| +0&#ffffff0@47
+>"| |l|i|n|e| |8| @51
+|"| |l|i|n|e| |9| @51
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |1| +0&#ffffff0@45
+|i+0&#ffd7ff255|n|s|e|r|t| |b|e|l|o|w| |2| +0&#ffffff0@45
+@42|8|,|1| @10|8|3|%|
diff --git a/src/testdir/dumps/Test_prop_above_empty_1.dump b/src/testdir/dumps/Test_prop_above_empty_1.dump
new file mode 100644
index 0000000..721d0c3
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_empty_1.dump
@@ -0,0 +1,16 @@
+| +0#af5f00255#ffffff0@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| |1+0#0000000&@7| @47
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| | +0#0000000&@55
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|3| |3+0#0000000&@8| @46
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|4| | +0#0000000&@55
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|5| >5+0#0000000&@10| @44
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|5|,|1|-|5|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_empty_2.dump b/src/testdir/dumps/Test_prop_above_empty_2.dump
new file mode 100644
index 0000000..c8c99e4
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_empty_2.dump
@@ -0,0 +1,16 @@
+| +0#af5f00255#ffffff0@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|1| |1+0#0000000&@7|$+0#4040ff13&| +0#0000000&@46
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| |$+0#4040ff13&| +0#0000000&@54
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|3| |3+0#0000000&@8|$+0#4040ff13&| +0#0000000&@45
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|4| |$+0#4040ff13&| +0#0000000&@54
+| +0#af5f00255&@3|-+0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|5| >5+0#0000000&@10|$+0#4040ff13&| +0#0000000&@43
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|s|e|t| |l|i|s|t| @32|5|,|1|-|5|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_number_1.dump b/src/testdir/dumps/Test_prop_above_number_1.dump
new file mode 100644
index 0000000..d986a46
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_number_1.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| >o+0#0000000&|n|e| |o|n|e| |o|n|e| @59
+| +0#af5f00255&@3|a+0#0000000#ffd7ff255|b|o|v|e| |t|h|e| |t|e|x|t| +0&#ffffff0@56
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |t|w|o| |t|w|o| @59
+| +0#af5f00255&@1|3| |t+0#0000000&|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @53
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_above_number_2.dump b/src/testdir/dumps/Test_prop_above_number_2.dump
new file mode 100644
index 0000000..e1e5ba5
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_above_number_2.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| >o+0#0000000&|n|e| |o|n|e| |o|n|e| @59
+| +0#af5f00255&@3|a+0#0000000#ffd7ff255|b|o|v|e| |t|h|e| |t|e|x|t| +0&#ffffff0@56
+| +0#af5f00255&@3|a+0#0000000#ffd7ff255|l|s|o| |a|b|o|v|e| |t|h|e| |t|e|x|t| +0&#ffffff0@51
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |t|w|o| |t|w|o| @59
+| +0#af5f00255&@1|3| |t+0#0000000&|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @53
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_after_linebreak.dump b/src/testdir/dumps/Test_prop_after_linebreak.dump
new file mode 100644
index 0000000..7387c83
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_after_linebreak.dump
@@ -0,0 +1,10 @@
+>x+0&#ffffff0@36|+| @36
+|(+0#ffffff16#e000002|x+0#0000000#ffffff0@36|)| @35
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_after_tab.dump b/src/testdir/dumps/Test_prop_after_tab.dump
new file mode 100644
index 0000000..fd63d5e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_after_tab.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@6> |[+0#ffffff16#e000002|x+0#0000000#ffffff0@2|]| @61
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_at_same_pos.dump b/src/testdir/dumps/Test_prop_at_same_pos.dump
new file mode 100644
index 0000000..95f01fa
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_at_same_pos.dump
@@ -0,0 +1,5 @@
+>f+0&#ffffff0|u|n|c|t|i|o|n|(| |o+0#ffffff16#ff404010|n|e|:| |c+0#0000000#ffff4012|a|l@1|,+0&#ffffff0| |t+0#00e0e07&|w|o|:| |a+0#0000000#ffff4012|r|g|u|m|e|n|t|,+0&#ffffff0| |t+0&#ffd7ff255|h|r|e@1|:| |h+0&#ffff4012|e|r|e| +0&#ffffff0|)| @25
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_01.dump b/src/testdir/dumps/Test_prop_before_tab_01.dump
new file mode 100644
index 0000000..a0c264e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_01.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@3> |x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|1|,|1|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_02.dump b/src/testdir/dumps/Test_prop_before_tab_02.dump
new file mode 100644
index 0000000..4aab872
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_02.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4>x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|1|,|2|-|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_03.dump b/src/testdir/dumps/Test_prop_before_tab_03.dump
new file mode 100644
index 0000000..f89f0b9
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_03.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7> +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|2|,|1|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_04.dump b/src/testdir/dumps/Test_prop_before_tab_04.dump
new file mode 100644
index 0000000..a820a41
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_04.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0>x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|2|,|2|-|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_05.dump b/src/testdir/dumps/Test_prop_before_tab_05.dump
new file mode 100644
index 0000000..1524851
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_05.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@6> |x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|3|,|1|-|1|6| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_06.dump b/src/testdir/dumps/Test_prop_before_tab_06.dump
new file mode 100644
index 0000000..54a06cd
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_06.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7>x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|3|,|2|-|1|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_07.dump b/src/testdir/dumps/Test_prop_before_tab_07.dump
new file mode 100644
index 0000000..3b07ac6
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_07.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@5> |x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|4|,|1|-|1|6| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_08.dump b/src/testdir/dumps/Test_prop_before_tab_08.dump
new file mode 100644
index 0000000..07eb40b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_08.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6>x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|4|,|2|-|1|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_09.dump b/src/testdir/dumps/Test_prop_before_tab_09.dump
new file mode 100644
index 0000000..ed30b9e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_09.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C>x+0&#ffffff0| @62
+@8|x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|5|,|2|-|1|2| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_before_tab_10.dump b/src/testdir/dumps/Test_prop_before_tab_10.dump
new file mode 100644
index 0000000..9957336
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_before_tab_10.dump
@@ -0,0 +1,8 @@
+|1+0&#ffff4012|2|3| +0&#ffffff0@4|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7| +0&#ffffff0|x| @65
+|1+0&#ffff4012|2|3|4|5|6|7|8| +0&#ffffff0@7|x| @57
+|1+0&#ffff4012|2|3|4|5|6|7|8|9| +0&#ffffff0@6|x| @57
+@8|A+0&#ffff4012|B|C|x+0&#ffffff0| @62
+@8>x|A+0&#ffff4012|B|C| +0&#ffffff0@62
+|~+0#4040ff13&| @73
+| +0#0000000&@56|6|,|2|-|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_below_after_empty_1.dump b/src/testdir/dumps/Test_prop_below_after_empty_1.dump
new file mode 100644
index 0000000..b6773df
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_below_after_empty_1.dump
@@ -0,0 +1,8 @@
+>v+0&#ffffff0|i|m|9|s|c|r|i|p|t| @49
+@60
+@1|T+0#ffffff16#e000002|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0#0000000#ffffff0@15
+|t|h|r|e@1| @54
+@60
+|T+0&#ffd7ff255|h|e| |s|l|o|w| |f|o|x| |b|u|m|p|s| |i|n|t|o| |t|h|e| |l|a|z|y| |d|o|g| +0&#ffffff0@23
+|~+0#4040ff13&| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_below_after_empty_2.dump b/src/testdir/dumps/Test_prop_below_after_empty_2.dump
new file mode 100644
index 0000000..e4c9ad2
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_below_after_empty_2.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| >v+0#0000000&|i|m|9|s|c|r|i|p|t| @45
+| +0#af5f00255&@1|2| | +0#0000000&@55
+| +0#af5f00255&@3| +0#0000000&|T+0#ffffff16#e000002|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0#0000000#ffffff0@11
+| +0#af5f00255&@1|3| |t+0#0000000&|h|r|e@1| @50
+| +0#af5f00255&@1|4| | +0#0000000&@55
+| +0#af5f00255&@3|T+0#0000000#ffd7ff255|h|e| |s|l|o|w| |f|o|x| |b|u|m|p|s| |i|n|t|o| |t|h|e| |l|a|z|y| |d|o|g| +0&#ffffff0@19
+|~+0#4040ff13&| @58
+|:+0#0000000&|s|e|t| |n|u|m|b|e|r| @30|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_below_after_empty_3.dump b/src/testdir/dumps/Test_prop_below_after_empty_3.dump
new file mode 100644
index 0000000..8658af0
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_below_after_empty_3.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| >v+0#0000000&|i|m|9|s|c|r|i|p|t| @45
+| +0#af5f00255&@1|2| | +0#0000000&@55
+| +0#af5f00255&@3| +0#0000000&|T+0#ffffff16#e000002|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0#0000000#ffffff0@11
+| +0#af5f00255&@1|3| |t+0#0000000&|h|r|e@1| @50
+| +0#af5f00255&@1|4| | +0#0000000&@55
+| +0#af5f00255&@3|T+0#0000000#ffd7ff255|h|e| |s|l|o|w| |f|o|x| |b|u|m|p|s| |i|n|t|o| |t|h|e| |l|a|z|y| |d|o|g| +0&#ffffff0@19
+|~+0#4040ff13&| @58
+|:+0#0000000&|s|e|t| |n|o|w|r|a|p| @30|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_below_split_line_1.dump b/src/testdir/dumps/Test_prop_below_split_line_1.dump
new file mode 100644
index 0000000..c1dba26
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_below_split_line_1.dump
@@ -0,0 +1,8 @@
+|o+0&#ffffff0|n|e| |o|n|e| |o|n|e| @63
+|t|w|o| |t|w|o| |t|w|o| @63
+@3|└+0&#ffff4012|─| |V|i|r|t|u|a|l| |t|e|x|t| |b|e|l|o|w| |t|h|e| |2|n|d| |l|i|n|e| +0&#ffffff0@37
+|x@1> @72
+|t|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @57
+|~+0#4040ff13&| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|3|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_below_split_line_2.dump b/src/testdir/dumps/Test_prop_below_split_line_2.dump
new file mode 100644
index 0000000..e0fc050
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_below_split_line_2.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |o+0#0000000&|n|e| |o|n|e| |o|n|e| @59
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |t|w|o| |t|w|o| @59
+| +0#af5f00255&@3| +0#0000000&@2|└+0&#ffff4012|─| |V|i|r|t|u|a|l| |t|e|x|t| |b|e|l|o|w| |t|h|e| |2|n|d| |l|i|n|e| +0&#ffffff0@33
+| +0#af5f00255&@1|3| |x+0#0000000&>x| @68
+| +0#af5f00255&@1|4| |t+0#0000000&|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @53
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&|s|e|t| |n|u|m|b|e|r| @45|3|,|2| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_below_split_line_3.dump b/src/testdir/dumps/Test_prop_below_split_line_3.dump
new file mode 100644
index 0000000..df881af
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_below_split_line_3.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |o+0#0000000&|n|e| |o|n|e| |o|n|e| @59
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |t|w|o| |t|w|o| @59
+| +0#af5f00255&@3| +0#0000000&@2|└+0&#ffff4012|─| |V|i|r|t|u|a|l| |t|e|x|t| |b|e|l|o|w| |t|h|e| |2|n|d| |l|i|n|e| +0&#ffffff0@33
+| +0#af5f00255&@1|3| |x+0#0000000&>x| @68
+| +0#af5f00255&@1|4| |t+0#0000000&|h|r|e@1| |t|h|r|e@1| |t|h|r|e@1| @53
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&|s|e|t| |n|o|w|r|a|p| @45|3|,|2| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_delete_updates_1.dump b/src/testdir/dumps/Test_prop_delete_updates_1.dump
new file mode 100644
index 0000000..400824f
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_delete_updates_1.dump
@@ -0,0 +1,10 @@
+|s+0&#ffffff0|o|m|e| |t|e|x|t| @50
+@3|T+0&#ffd7ff255|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0&#ffffff0@13
+@5|T+0&#ffd7ff255|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0&#ffffff0@11
+|m|o|r|e| |t|e|x|t| @50
+>t|h|e| |e|n|d| @52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_delete_updates_2.dump b/src/testdir/dumps/Test_prop_delete_updates_2.dump
new file mode 100644
index 0000000..e7074f5
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_delete_updates_2.dump
@@ -0,0 +1,10 @@
+|s+0&#ffffff0|o|m|e| |t|e|x|t| @50
+|m|o|r|e| |t|e|x|t| @50
+>t|h|e| |e|n|d| @52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_delete_updates_3.dump b/src/testdir/dumps/Test_prop_delete_updates_3.dump
new file mode 100644
index 0000000..651ed4b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_delete_updates_3.dump
@@ -0,0 +1,10 @@
+|s+0&#ffffff0|o|m|e| |t|e|x|t| @50
+>m|o|r|e| |t|e|x|t| @50
+|t|h|e| |e|n|d| @52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_diff_mode_1.dump b/src/testdir/dumps/Test_prop_diff_mode_1.dump
new file mode 100644
index 0000000..069c08d
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_diff_mode_1.dump
@@ -0,0 +1,10 @@
+>0+0&#ffd7ff255@2| @26||+1&#ffffff0|<+0&#ffd7ff255|t|e|x|t|>|9+2&#ff404010|0+0&#ffd7ff255@2| @18
+|0@2| @26||+1&#ffffff0|0+0&#ffd7ff255@2|9+2&#ff404010|<+0&#ffd7ff255|t|e|x|t|>| @18
+|0@2| @26||+1&#ffffff0|0+0&#ffd7ff255@2|9+2&#ff404010| +0&#ffd7ff255@18|<|t|e|x|t|>
+|0@2| @26||+1&#ffffff0|<+0&#ffd7ff255|t|e|x|t|>| @22
+|0@2| @26||+1&#ffffff0|9+2&#ff404010|0+0&#ffd7ff255@2| @24
+|~+0#4040ff13#ffffff0| @28||+1#0000000&|0+0&#ffd7ff255@2|9+2&#ff404010| +0&#ffd7ff255@24
+|~+0#4040ff13#ffffff0| @28||+1#0000000&|<+0&#ffd7ff255|t|e|x|t|>| @22
+|~+0#4040ff13#ffffff0| @28||+1#0000000&|~+0#4040ff13&| @27
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @1|1|,|1| @8|A|l@1| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @1|1|,|1|-|7| @5|A|l@1
+| +0&&@59
diff --git a/src/testdir/dumps/Test_prop_diff_mode_2.dump b/src/testdir/dumps/Test_prop_diff_mode_2.dump
new file mode 100644
index 0000000..243ec0d
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_diff_mode_2.dump
@@ -0,0 +1,10 @@
+| +0#af5f00255#ffffff0@1|1| |0+0#0000000#ffd7ff255@2| @22||+1&#ffffff0| +0#af5f00255&@1|1| |<+0#0000000#ffd7ff255|t|e|x|t|>>9+2&#ff404010|0+0&#ffd7ff255@2| @14
+| +0#af5f00255#ffffff0@1|2| |0+0#0000000#ffd7ff255@2| @22||+1&#ffffff0| +0#af5f00255&@1|2| |0+0#0000000#ffd7ff255@2|9+2&#ff404010|<+0&#ffd7ff255|t|e|x|t|>| @14
+| +0#af5f00255#ffffff0@1|3| |0+0#0000000#ffd7ff255@2| @22||+1&#ffffff0| +0#af5f00255&@1|3| |0+0#0000000#ffd7ff255@2|9+2&#ff404010| +0&#ffd7ff255@14|<|t|e|x|t|>
+| +0#af5f00255#ffffff0@1|4| |0+0#0000000#ffd7ff255@2| @22||+1&#ffffff0| +0#af5f00255&@3|<+0#0000000#ffd7ff255|t|e|x|t|>| @18
+| +0#af5f00255#ffffff0@1|5| |0+0#0000000#ffd7ff255@2| @22||+1&#ffffff0| +0#af5f00255&@1|4| |9+2#0000000#ff404010|0+0&#ffd7ff255@2| @20
+|~+0#4040ff13#ffffff0| @28||+1#0000000&| +0#af5f00255&@1|5| |0+0#0000000#ffd7ff255@2|9+2&#ff404010| +0&#ffd7ff255@20
+|~+0#4040ff13#ffffff0| @28||+1#0000000&| +0#af5f00255&@3|<+0#0000000#ffd7ff255|t|e|x|t|>| @18
+|~+0#4040ff13#ffffff0| @28||+1#0000000&|~+0#4040ff13&| @27
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @1|1|,|1| @8|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @1|1|,|1|-|7| @5|A|l@1
+|:+0&&|w|i|n|d|o| |s|e|t| |n|u|m|b|e|r| @42
diff --git a/src/testdir/dumps/Test_prop_insert_list_mode_1.dump b/src/testdir/dumps/Test_prop_insert_list_mode_1.dump
new file mode 100644
index 0000000..3f481d5
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_list_mode_1.dump
@@ -0,0 +1,8 @@
+|T+0&#ffffff0|h|i|s| |i|s| |a| |l|i|n|e| |w|i|t|h| |q|u|i|t|e| |a| |b|i|t| |o|f| |t|e|x|t| |h|e|r|e|.|$+0#4040ff13&|T+0#ffffff16#ff404010|h|e| |q|u|i|c|k| |b|r|o|…
+>s+0#0000000#ffffff0|e|c|o|n|d| |l|i|n|e|$+0#4040ff13&| +0#0000000&@47
+|t|h|i|r|d| |l|i|n|e|$+0#4040ff13&| +0#0000000&@48
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_list_mode_2.dump b/src/testdir/dumps/Test_prop_insert_list_mode_2.dump
new file mode 100644
index 0000000..26abd88
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_list_mode_2.dump
@@ -0,0 +1,8 @@
+|T+0&#ffffff0|h|i|s| |i|s| |a| |l|i|n|e| |w|i|t|h| |q|u|i|t|e| |a| |b|i|t| |o|f| |t|e|x|t| |h|e|r|e|.|$+0#4040ff13&|T+0#ffffff16#ff404010|h|e| |q|u|i|c|k| |b|r|o|»+0#4040ff13#ffffff0
+>s+0#0000000&|e|c|o|n|d| |l|i|n|e|$+0#4040ff13&| +0#0000000&@47
+|t|h|i|r|d| |l|i|n|e|$+0#4040ff13&| +0#0000000&@48
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|s|e|t| |n|o|w|r|a|p| @30|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_list_mode_3.dump b/src/testdir/dumps/Test_prop_insert_list_mode_3.dump
new file mode 100644
index 0000000..58db9f6
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_list_mode_3.dump
@@ -0,0 +1,8 @@
+>o+0&#ffffff0|f| |t|e|x|t| |h|e|r|e|.|$+0#4040ff13&| +0#0000000&@2|T+0#ffffff16#ff404010|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g
+|s+0#0000000#ffffff0|e|c|o|n|d| |l|i|n|e|$+0#4040ff13&| +0#0000000&@47
+|t|h|i|r|d| |l|i|n|e|$+0#4040ff13&| +0#0000000&@48
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|s|e|t| |n|o|w|r|a|p| @30|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_1.dump b/src/testdir/dumps/Test_prop_insert_start_incl_1.dump
new file mode 100644
index 0000000..de1f3d4
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_1.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| >o+0#0000000&|n|e| |t|e|x|t| |b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @29
+@60
+|f|u|n|c|t|i|o|n|(|a+0&#ffd7ff255|r|g|:| |a+0&#ffffff0|r|g|)| @41
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|6|-|1|2| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_2.dump b/src/testdir/dumps/Test_prop_insert_start_incl_2.dump
new file mode 100644
index 0000000..f0c9ac0
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_2.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| >o+0#0000000&|n|e| |t|e|x|t| |b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @29
+@60
+|f|u|n|c|t|i|o|n|(|a+0&#ffd7ff255|r|g|:| |a+0&#ffffff0|r|g|)| @41
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@29|1|,|6|-|1|2| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_3.dump b/src/testdir/dumps/Test_prop_insert_start_incl_3.dump
new file mode 100644
index 0000000..5a8578b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_3.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| |x+0#0000000&>x|o|n|e| |t|e|x|t| |b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @27
+@60
+|f|u|n|c|t|i|o|n|(|a+0&#ffd7ff255|r|g|:| |a+0&#ffffff0|r|g|)| @41
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|7|-|1|3| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_4.dump b/src/testdir/dumps/Test_prop_insert_start_incl_4.dump
new file mode 100644
index 0000000..bf67a1e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_4.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| |x+0#0000000&@1|o|n|e| |t|e|x|t| >b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @27
+@60
+|f|u|n|c|t|i|o|n|(|a+0&#ffd7ff255|r|g|:| |a+0&#ffffff0|r|g|)| @41
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@29|1|,|1|7|-|2|3| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_5.dump b/src/testdir/dumps/Test_prop_insert_start_incl_5.dump
new file mode 100644
index 0000000..270c8a0
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_5.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| |x+0#0000000&@1|o|n|e| |t|e|x|t| |y>y|b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @25
+@60
+|f|u|n|c|t|i|o|n|(|a+0&#ffd7ff255|r|g|:| |a+0&#ffffff0|r|g|)| @41
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1|8|-|2|4| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_6.dump b/src/testdir/dumps/Test_prop_insert_start_incl_6.dump
new file mode 100644
index 0000000..96156a8
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_6.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| |x+0#0000000&@1|o|n|e| |t|e|x|t| |y@1|b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @25
+@60
+|f|u|n|c|t|i|o|n|(| @50
+@7> |a+0&#ffd7ff255|r|g|:| |a+0&#ffffff0|r|g|)| @42
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|4|,|1|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_7.dump b/src/testdir/dumps/Test_prop_insert_start_incl_7.dump
new file mode 100644
index 0000000..43545a8
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_7.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| |x+0#0000000&@1|o|n|e| |t|e|x|t| |y@1|b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @25
+@60
+|f|u|n|c|t|i|o|n|(| @50
+@12|a+0&#ffd7ff255|r|g|:| >a+0&#ffffff0|r|g|)| @38
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|4|,|6|-|1|8| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_insert_start_incl_8.dump b/src/testdir/dumps/Test_prop_insert_start_incl_8.dump
new file mode 100644
index 0000000..9d46839
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_insert_start_incl_8.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|e|x|t| |a+0#4040ff13&|f|t|e|r| |x+0#0000000&@1|o|n|e| |t|e|x|t| |y@1|b+0#4040ff13&|e|f|o|r|e| |t+0#0000000&|w|o| @25
+@60
+|f|u|n|c|t|i|o|n|(| @50
+@4|a+0&#ffd7ff255|r|g|:| >a+0&#ffffff0|r|g|)| @46
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|4|,|5|-|1|0| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_1.dump b/src/testdir/dumps/Test_prop_inserts_text_1.dump
new file mode 100644
index 0000000..7d1b102
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_1.dump
@@ -0,0 +1,8 @@
+|i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |S+0#ffffff16#e000002|O|M|E| |h+0#0000000#ffffff0|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |O+0&#ffff4012|T|H|E|R| |t+0&#ffffff0|h|e|r|e| |a|n|d| |s|o
+|m|e| |m|o|r|e| |t|e|x|t| |a|f|t|e|r| |M+0&#5fd7ff255|O|R|E| |w+0&#ffffff0|r|a|p@1|i|n>g| @27
+|p|r|e|s+0&#e0e0e08|ö|m|e|和*&|平|t+&|é|x|t|p+0&#ffffff0|o|s|t| @40
+|F|o@1| |f|o@1| |=| |{| |.+0#0000e05&|x|=|1+0#0000000&|,| |.+0#0000e05&|y|=|2+0#0000000&| |}|;| @34
+|e+0#ffffff16#e000002|m|p|t|y| |l|i|n|e| +0#0000000#ffffff0@49
+|l|o@1|k| |n|o| |h|i|g|h|l|i|g|h|t| @42
+|~+0#4040ff13&| @58
+| +0#0000000&@41|1|,|7|6|-|9|2| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_2.dump b/src/testdir/dumps/Test_prop_inserts_text_2.dump
new file mode 100644
index 0000000..2a5b2f3
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_2.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |S+0#ffffff16#e000002|O|M|E| |h+0#0000000#ffffff0|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |O+0&#ffff4012|T|H|E|R| |t+0&#ffffff0|h|e|r|e| |a|n|d|
+| +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t| |a|f|t|e|r| |M+0&#5fd7ff255|O|R|E| |w+0&#ffffff0|r|a|p@1|i|n>g| @23
+| +0#0000e05#a8a8a8255@1|p+0#0000000#ffffff0|r|e|s+0&#e0e0e08|ö|m|e|和*&|平|t+&|é|x|t|p+0&#ffffff0|o|s|t| @38
+| +0#0000e05#a8a8a8255@1|F+0#0000000#ffffff0|o@1| |f|o@1| |=| |{| |.+0#0000e05&|x|=|1+0#0000000&|,| |.+0#0000e05&|y|=|2+0#0000000&| |}|;| @32
+| +0#0000e05#a8a8a8255@1|e+0#ffffff16#e000002|m|p|t|y| |l|i|n|e| +0#0000000#ffffff0@47
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|o@1|k| |n|o| |h|i|g|h|l|i|g|h|t| @40
+|~+0#4040ff13&| @58
+| +0#0000000&@41|1|,|7|6|-|9|2| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_3.dump b/src/testdir/dumps/Test_prop_inserts_text_3.dump
new file mode 100644
index 0000000..36a4b8c
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_3.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |S+0#ffffff16#e000002|O|M|E| |h+0#0000000#ffffff0|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |O+0&#ffff4012|T|H|E|R| |t+0&#ffffff0|h|e|r|e| |a|n|d|
+| +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t| |a|f|t|e|r| |M+0&#5fd7ff255|O|R|E| |w+0&#ffffff0|r|a|p@1|i|n|g| @23
+| +0#0000e05#a8a8a8255@1|p+0#0000000#ffffff0|r|e|s+0&#e0e0e08|ö|m|e|和*&|平|t+&|é|x|t|p+0&#ffffff0|o|s>t| @38
+| +0#0000e05#a8a8a8255@1|F+0#0000000#ffffff0|o@1| |f|o@1| |=| |{| |.+0#0000e05&|x|=|1+0#0000000&|,| |.+0#0000e05&|y|=|2+0#0000000&| |}|;| @32
+| +0#0000e05#a8a8a8255@1|e+0#ffffff16#e000002|m|p|t|y| |l|i|n|e| +0#0000000#ffffff0@47
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|o@1|k| |n|o| |h|i|g|h|l|i|g|h|t| @40
+|~+0#4040ff13&| @58
+| +0#0000000&@41|2|,|7|-|1|9| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_4.dump b/src/testdir/dumps/Test_prop_inserts_text_4.dump
new file mode 100644
index 0000000..8bb637a
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_4.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |S+0#ffffff16#e000002|O|M|E| |h+0#0000000#ffffff0|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |O+0&#ffff4012|T|H|E|R| |t+0&#ffffff0|h|e|r|e| |a|n|d|
+| +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t| |a|f|t|e|r| |M+0&#5fd7ff255|O|R|E| |w+0&#ffffff0|r|a|p@1|i|n|g| @23
+| +0#0000e05#a8a8a8255@1|p+0#0000000#ffffff0|r|e|s+0&#e0e0e08|ö|m|e|和*&|平|t+&|é|x|t|p+0&#ffffff0|o|s|t| @38
+| +0#0000e05#a8a8a8255@1|F+0#0000000#ffffff0|o@1| |f|o@1| |=| |{| |.+0#0000e05&|x|=>1+0#0000000&|,| |.+0#0000e05&|y|=|2+0#0000000&| |}|;| @32
+| +0#0000e05#a8a8a8255@1|e+0#ffffff16#e000002|m|p|t|y| |l|i|n|e| +0#0000000#ffffff0@47
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|o@1|k| |n|o| |h|i|g|h|l|i|g|h|t| @40
+|~+0#4040ff13&| @58
+| +0#0000000&@41|3|,|1|3|-|1|6| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_5.dump b/src/testdir/dumps/Test_prop_inserts_text_5.dump
new file mode 100644
index 0000000..75194f6
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_5.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |S+0#ffffff16#e000002|O|M|E| |h+0#0000000#ffffff0|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |O+0&#ffff4012|T|H|E|R| |t+0&#ffffff0|h|e|r|e| |a|n|d|
+| +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t| |a|f|t|e|r| |M+0&#5fd7ff255|O|R|E| |w+0&#ffffff0|r|a|p@1|i|n|g| @23
+| +0#0000e05#a8a8a8255@1|p+0#0000000#ffffff0|r|e|s+0&#e0e0e08|ö|m|e|和*&|平|t+&|é|x|t|p+0&#ffffff0|o|s|t| @38
+| +0#0000e05#a8a8a8255@1|F+0#0000000#ffffff0|o@1| |f|o@1| |=| |{| |.+0#0000e05&|x|=|1+0#0000000&|,| |.+0#0000e05&|y|=>2+0#0000000&| |}|;| @32
+| +0#0000e05#a8a8a8255@1|e+0#ffffff16#e000002|m|p|t|y| |l|i|n|e| +0#0000000#ffffff0@47
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|o@1|k| |n|o| |h|i|g|h|l|i|g|h|t| @40
+|~+0#4040ff13&| @58
+| +0#0000000&@41|3|,|1|6|-|2@1| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_6.dump b/src/testdir/dumps/Test_prop_inserts_text_6.dump
new file mode 100644
index 0000000..fb25ddf
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_6.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |S+0#ffffff16#e000002|O|M|E| |h+0#0000000#ffffff0|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |O+0&#ffff4012|T|H|E|R| |t+0&#ffffff0|h|e|r|e| |a|n|d|
+| +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t| |a|f|t|e|r| |M+0&#5fd7ff255|O|R|E| |w+0&#ffffff0|r|a|p@1|i|n|g| @23
+| +0#0000e05#a8a8a8255@1|p+0#0000000#ffffff0|r|e|s+0&#e0e0e08|ö|m|e|和*&|平|t+&|é|x|t|p+0&#ffffff0|o|s|t| @38
+| +0#0000e05#a8a8a8255@1|F+0#0000000#ffffff0|o@1| |f|o@1| |=| |{| |.+0#0000e05&|x|=|1+0#0000000&|,| |.+0#0000e05&|y|=|2+0#0000000&| |}|;| @32
+| +0#0000e05#a8a8a8255@1>e+0#ffffff16#e000002|m|p|t|y| |l|i|n|e| +0#0000000#ffffff0@47
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|o@1|k| |n|o| |h|i|g|h|l|i|g|h|t| @40
+|~+0#4040ff13&| @58
+| +0#0000000&@41|4|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_hi_1.dump b/src/testdir/dumps/Test_prop_inserts_text_hi_1.dump
new file mode 100644
index 0000000..863ded3
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_hi_1.dump
@@ -0,0 +1,6 @@
+>i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| |t|e|x|t| |B+0#ffffff16#e000002|E|F|O|R|E|(+0#0000000#5fd7ff255|h+0&#ffffff0|e|r|e|)| |a|n|d| |t|h|e|r|e| @20
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_hi_2.dump b/src/testdir/dumps/Test_prop_inserts_text_hi_2.dump
new file mode 100644
index 0000000..1d9d11a
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_hi_2.dump
@@ -0,0 +1,6 @@
+|i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| >t+0&#ffff4012|e|x|t| |B+0#ffffff16#e000002|E|F|O|R|E|(+0#0000000#5fd7ff255|h+0&#ffff4012|e|r+0&#ffffff0|e|)| |a|n|d| |t|h|e|r|e| @20
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|/+0#0000000&|t|e|x|t| |(|h|e| @32|1|,|1|3| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_hi_3.dump b/src/testdir/dumps/Test_prop_inserts_text_hi_3.dump
new file mode 100644
index 0000000..85ad7dd
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_hi_3.dump
@@ -0,0 +1,6 @@
+|i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| >t+0&#ffff4012|e|x|t| |B+0#ffffff16#e000002|E|F|O|R|E|(+0#0000000#ffff4012|h|e|r+0&#ffffff0|e|)| |a|n|d| |t|h|e|r|e| @20
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|c|a|l@1| |m|a|t|c|h|d|e|l|e|t|e|(|t|h|e|m|a|t|c|h|)| @14|1|,|1|3| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_hi_4.dump b/src/testdir/dumps/Test_prop_inserts_text_hi_4.dump
new file mode 100644
index 0000000..a5592ac
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_hi_4.dump
@@ -0,0 +1,6 @@
+|i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| >t|e|x|t| |(+0&#5fd7ff255|A+0#ffffff16#e000002|F|T|E|R|h+0#0000000#ffffff0|e|r|e|)| |a|n|d| |t|h|e|r|e| @21
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|c|a|l@1| |D|o|A|f|t|e|r|(|)| @26|1|,|1|3| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_hi_5.dump b/src/testdir/dumps/Test_prop_inserts_text_hi_5.dump
new file mode 100644
index 0000000..0002aa8
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_hi_5.dump
@@ -0,0 +1,6 @@
+|i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| >t+0&#ffff4012|e|x|t| |(+0&#5fd7ff255|A+0#ffffff16#e000002|F|T|E|R|h+0#0000000#ffff4012|e|r+0&#ffffff0|e|)| |a|n|d| |t|h|e|r|e| @21
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|s+0#e000002&|e|a|r|c|h| |h|i|t| |B|O|T|.@2|t|i|n|u|i|n|g| |a|t| |T|O|P| +0#0000000&@10|1|,|1|3| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_inserts_text_hi_6.dump b/src/testdir/dumps/Test_prop_inserts_text_hi_6.dump
new file mode 100644
index 0000000..631f51a
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_inserts_text_hi_6.dump
@@ -0,0 +1,6 @@
+|i+0&#ffffff0|n|s|e|r|t| |s|o|m|e| >t+0&#ffff4012|e|x|t| |(|A+0#ffffff16#e000002|F|T|E|R|h+0#0000000#ffff4012|e|r+0&#ffffff0|e|)| |a|n|d| |t|h|e|r|e| @21
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|c|a|l@1| |m|a|t|c|h|d|e|l|e|t|e|(|t|h|e|m|a|t|c|h|)| @14|1|,|1|3| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_linebreak_1.dump b/src/testdir/dumps/Test_prop_linebreak_1.dump
new file mode 100644
index 0000000..c1a56e8
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_linebreak_1.dump
@@ -0,0 +1,10 @@
+>x+0&#ffffff0@49|]+0&#40ffff15| +0&#ffffff0@23
+|x@69| @4
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_linebreak_2.dump b/src/testdir/dumps/Test_prop_linebreak_2.dump
new file mode 100644
index 0000000..e705f71
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_linebreak_2.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@1>x+0&#ffffff0@49|]+0&#40ffff15| +0#0000000#ffffff0@21
+| +0#0000e05#a8a8a8255@1|x+0&#ffffff0@69| +0#0000000&@2
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_multibyte_below_1.dump b/src/testdir/dumps/Test_prop_multibyte_below_1.dump
new file mode 100644
index 0000000..838611b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_multibyte_below_1.dump
@@ -0,0 +1,10 @@
+| +0#af5f00255#ffffff0@1|1| |©+0#0000000&| @54
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|2| |©+0#0000000&| @54
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+| +0#af5f00255&@1|3| >©+0#0000000&| @54
+| +0#af5f00255&@3|++0#0000001#ffff4012@2| +0#0000000#ffffff0@52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_negative_error_1.dump b/src/testdir/dumps/Test_prop_negative_error_1.dump
new file mode 100644
index 0000000..47458d8
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_negative_error_1.dump
@@ -0,0 +1,8 @@
+>o+0&#ffffff0|n|e| @56
+|t|w|o| @56
+|t+0#ffffff16#ff404010|h|r|e@1| +0#0000000#ffffff0@54
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_negative_error_2.dump b/src/testdir/dumps/Test_prop_negative_error_2.dump
new file mode 100644
index 0000000..188224c
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_negative_error_2.dump
@@ -0,0 +1,8 @@
+|~+0#4040ff13#ffffff0| @58
+|~| @58
+|~| @58
+|E+0#ffffff16#e000002|r@1|o|r| |d|e|t|e|c|t|e|d| |w|h|i|l|e| |p|r|o|c|e|s@1|i|n|g| |f|u|n|c|t|i|o|n| |A|d@1|T|e|x|t|p|r|o|p|:| +0#0000000#ffffff0@6
+|l+0#af5f00255&|i|n|e| @3|5|:| +0#0000000&@49
+|E+0#ffffff16#e000002|1|3@1|9|:| |C|a|n@1|o|t| |a|d@1| |a| |t|e|x|t|p|r|o|p| |w|i|t|h| |t|e|x|t| |a|f|t|e|r| |u|s|i|n|g| |a| |t|e|x|t|p|r|o
+|p| |w|i|t|h| |a| |n|e|g|a|t|i|v|e| |i|d| +0#0000000#ffffff0@39
+|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@20
diff --git a/src/testdir/dumps/Test_prop_right_align_twice_1.dump b/src/testdir/dumps/Test_prop_right_align_twice_1.dump
new file mode 100644
index 0000000..f3f1afc
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_right_align_twice_1.dump
@@ -0,0 +1,8 @@
+|s+0&#ffffff0|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| +0&#ffd7ff255|n|o|t|h|i|n|g| |h|e|r|e| +0&#ffffff0@12|S+0#ffffff16#e000002|o|m|e| |e|r@1|o|r
+| +0#0000000#ffffff0@61|A+0#ffffff16#e000002|n|o|t|h|e|r| |e|r@1|o|r
+|l+0#0000000#ffffff0|i|n|e| |t|w>o| @66
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|2|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_right_align_twice_2.dump b/src/testdir/dumps/Test_prop_right_align_twice_2.dump
new file mode 100644
index 0000000..c4cd617
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_right_align_twice_2.dump
@@ -0,0 +1,8 @@
+|s+0&#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t|s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| +0&#ffd7ff255|n|o|t|h|i|n|g| |h|e|r|e| +0&#ffffff0@8
+@65|S+0#ffffff16#e000002|o|m|e| |e|r@1|o|r
+| +0#0000000#ffffff0@61|A+0#ffffff16#e000002|n|o|t|h|e|r| |e|r@1|o|r
+|l+0#0000000#ffffff0|i|n|e| |t|w>o| @66
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|2|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_right_align_twice_3.dump b/src/testdir/dumps/Test_prop_right_align_twice_3.dump
new file mode 100644
index 0000000..2c4cbee
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_right_align_twice_3.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t|s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| |s|o|m|e| |t|e|x|t| +0&#ffd7ff255|n|o|t|h|i|n|g| |h|e|r|e| +0&#ffffff0@6
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@62|S+0#ffffff16#e000002|o|m|e| |e|r@1|o|r
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@59|A+0#ffffff16#e000002|n|o|t|h|e|r| |e|r@1|o|r
+| +0#0000e05#a8a8a8255@1|l+0#0000000#ffffff0|i|n|e| |t|w>o| @64
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|2|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_text_change_arg_1.dump b/src/testdir/dumps/Test_prop_text_change_arg_1.dump
new file mode 100644
index 0000000..174af07
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_text_change_arg_1.dump
@@ -0,0 +1,5 @@
+>S+0&#ffffff0|e|t|E|r@1|o|r|C|o|d|e|(| |i+0&#ffd7ff255|d|:|-+0#4040ff13#ffffff0|1+0#0000000&|0|,| |i+0&#ffd7ff255|d|:|-+0#4040ff13#ffffff0|2+0#0000000&|0| |)| @29
+|s|e|c|o|n|d| |l|i|n|e| @48
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_text_change_arg_2.dump b/src/testdir/dumps/Test_prop_text_change_arg_2.dump
new file mode 100644
index 0000000..57e9a46
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_text_change_arg_2.dump
@@ -0,0 +1,5 @@
+|S+0&#ffffff0|e|t|E|r@1|o|r|C|o|d|e|(| |i+0&#ffd7ff255|d|:|-+0#4040ff13#ffffff0|1+0#0000000&|2|3>4|,| |i+0&#ffd7ff255|d|:|-+0#4040ff13#ffffff0|2+0#0000000&|0| |)| @27
+|s|e|c|o|n|d| |l|i|n|e| @48
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|1|8|-|2@1| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_text_with_padding_1.dump b/src/testdir/dumps/Test_prop_text_with_padding_1.dump
new file mode 100644
index 0000000..1fde5e9
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_text_with_padding_1.dump
@@ -0,0 +1,8 @@
+>S+0&#ffffff0|o|m|e| |t|e|x|t| |t|o| |a|d@1| |v|i|r|t|u|a|l| |t|e|x|t| |t|o|.| @2|a+0&#ffd7ff255|f|t|e|r| +0&#ffffff0@5|r+0&#ffd7ff255|i|g|h|t| |a|l|i|g|n|e|d
+| +0&#ffffff0@3|b+0&#ffd7ff255|e|l|o|w| |t|h|e| |l|i|n|e| +0&#ffffff0@41
+|s|e|c|o|n|d| |l|i|n|e| @48
+|A|n|o|t|h|e|r| |l|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |t|o| |m|a|k|e| |t|h|e| |w|r|a|p|.| @5|r+0&#ffd7ff255|i|g|h|t|m|o|s|t
+|~+0#4040ff13#ffffff0| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_text_with_padding_2.dump b/src/testdir/dumps/Test_prop_text_with_padding_2.dump
new file mode 100644
index 0000000..abedaae
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_text_with_padding_2.dump
@@ -0,0 +1,8 @@
+|x+0&#ffffff0@9|S|o|m|e| |t|e|x|t| |t|o| |a|d@1| |v|i|r|t|u|a|l| |t|e|x|t| |t|o|.| @2|a+0&#ffd7ff255|f|t|e|r| +0&#ffffff0@4|r+0&#ffd7ff255|i|g|…
+| +0&#ffffff0@3|b+0&#ffd7ff255|e|l|o|w| |t|h|e| |l|i|n|e| +0&#ffffff0@41
+|s|e|c|o|n|d| |l|i|n|e| @48
+>x|A|n|o|t|h|e|r| |l|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |t|o| |m|a|k|e| |t|h|e| |w|r|a|p|.| @13
+@51|r+0&#ffd7ff255|i|g|h|t|m|o|s|t
+|~+0#4040ff13#ffffff0| @58
+|~| @58
+| +0#0000000&@41|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_text_with_padding_3.dump b/src/testdir/dumps/Test_prop_text_with_padding_3.dump
new file mode 100644
index 0000000..2911162
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_text_with_padding_3.dump
@@ -0,0 +1,8 @@
+>x+0&#ffffff0@10|S|o|m|e| |t|e|x|t| |t|o| |a|d@1| |v|i|r|t|u|a|l| |t|e|x|t| |t|o|.| @2|a+0&#ffd7ff255|f|t|e|r| +0&#ffffff0@7
+@47|r+0&#ffd7ff255|i|g|h|t| |a|l|i|g|n|e|d
+| +0&#ffffff0@3|b+0&#ffd7ff255|e|l|o|w| |t|h|e| |l|i|n|e| +0&#ffffff0@41
+|s|e|c|o|n|d| |l|i|n|e| @48
+|x|A|n|o|t|h|e|r| |l|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |t|o| |m|a|k|e| |t|h|e| |w|r|a|p|.| @13
+@51|r+0&#ffd7ff255|i|g|h|t|m|o|s|t
+|~+0#4040ff13#ffffff0| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_text_with_padding_4.dump b/src/testdir/dumps/Test_prop_text_with_padding_4.dump
new file mode 100644
index 0000000..b0b0156
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_text_with_padding_4.dump
@@ -0,0 +1,8 @@
+>x+0&#ffffff0@10|S|o|m|e| |t|e|x|t| |t|o| |a|d@1| |v|i|r|t|u|a|l| |t|e|x|t| |t|o|.|$+0#4040ff13&| +0#0000000&@2|a+0&#ffd7ff255|f|t|e|r| +0&#ffffff0@6
+@47|r+0&#ffd7ff255|i|g|h|t| |a|l|i|g|n|e|d
+| +0&#ffffff0@3|b+0&#ffd7ff255|e|l|o|w| |t|h|e| |l|i|n|e| +0&#ffffff0@41
+|s|e|c|o|n|d| |l|i|n|e|$+0#4040ff13&| +0#0000000&@47
+|x|A|n|o|t|h|e|r| |l|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |t|o| |m|a|k|e| |t|h|e| |w|r|a|p|.|$+0#4040ff13&| +0#0000000&@12
+@51|r+0&#ffd7ff255|i|g|h|t|m|o|s|t
+| +0&#ffffff0@59
+|:|s|e|t| |l|i|s|t| @32|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_linebreak_1.dump b/src/testdir/dumps/Test_prop_with_linebreak_1.dump
new file mode 100644
index 0000000..9cae5d6
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_linebreak_1.dump
@@ -0,0 +1,6 @@
+>o+0&#ffffff0|n|e|:+0#e000e06&| |v|i|r|t|u|a|l| |t|e|x|t| +0#0000000&|t|w|o|w|o|r|d| @24
+|~+0#4040ff13&| @48
+|~| @48
+|~| @48
+|~| @48
+| +0#0000000&@31|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_linebreak_2.dump b/src/testdir/dumps/Test_prop_with_linebreak_2.dump
new file mode 100644
index 0000000..6614eb2
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_linebreak_2.dump
@@ -0,0 +1,6 @@
+|a+0&#ffffff0|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a>s|o|n|e|:+0#e000e06&| |v|i|r|t|u|a|l| |t|e|x|t| +0#0000000&@5
+|t|w|o|w|o|r|d| @42
+|~+0#4040ff13&| @48
+|~| @48
+|~| @48
+| +0#0000000&@31|1|,|2|7| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_1.dump b/src/testdir/dumps/Test_prop_with_text_above_1.dump
new file mode 100644
index 0000000..1a478f1
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_1.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@42
+|s+0&#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@41
+|o|n|e| |t|w|o| @52
+|t|h|r>e@1| |f|o|u|r| @49
+@3|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@43
+|f|i|v|e| |s|i|x| @51
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|2|,|4| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_1a.dump b/src/testdir/dumps/Test_prop_with_text_above_1a.dump
new file mode 100644
index 0000000..67d7a6b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_1a.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@42
+|s+0&#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@41
+|o|n|e| |t|w>o| @52
+|t|h|r|e@1| |f|o|u|r| @49
+@3|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@43
+|f|i|v|e| |s|i|x| @51
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|7|-|1|2|7| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_1b.dump b/src/testdir/dumps/Test_prop_with_text_above_1b.dump
new file mode 100644
index 0000000..24fffb5
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_1b.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@42
+|s+0&#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@41
+>o|n|e| |t|w|o| @52
+|t|h|r|e@1| |f|o|u|r| @49
+@3|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@43
+|f|i|v|e| |s|i|x| @51
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|1|-|1|2|1| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_1c.dump b/src/testdir/dumps/Test_prop_with_text_above_1c.dump
new file mode 100644
index 0000000..bdf8811
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_1c.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@42
+|s+0&#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@41
+|o|n>e| |t|w|o| @52
+|t|h|r|e@1| |f|o|u|r| @49
+@3|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@43
+|f|i|v|e| |s|i|x| @51
+|~+0#4040ff13&| @58
+|~| @58
+|:+0#0000000&|s|e|t| |s|h|o|w|b|r|e|a|k|=|>@1| @24|1|,|3|-|1|2|3| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_2.dump b/src/testdir/dumps/Test_prop_with_text_above_2.dump
new file mode 100644
index 0000000..3df0967
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_2.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@42
+|s+0&#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@41
+>o|n|e| |t|w|o| @52
+|t|h|r|e@1| |f|o|u|r| @49
+@3|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@43
+|f|i|v|e| |s|i|x| @51
+|~+0#4040ff13&| @58
+|~| @58
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@29|1|,|1|-|1|2|1| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_3.dump b/src/testdir/dumps/Test_prop_with_text_above_3.dump
new file mode 100644
index 0000000..f58a95b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_3.dump
@@ -0,0 +1,9 @@
+|f+0&#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@42
+|s+0&#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@41
+|i|n|s|e|r|t|e|d> |o|n|e| |t|w|o| @43
+|t|h|r|e@1| |f|o|u|r| @49
+@3|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@43
+|f|i|v|e| |s|i|x| @51
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|9|-|1|2|9| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_4.dump b/src/testdir/dumps/Test_prop_with_text_above_4.dump
new file mode 100644
index 0000000..2a207eb
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_4.dump
@@ -0,0 +1,9 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|f+0#0000000#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@36
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|s+0#0000000#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@35
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |i+0#0000000&|n|s|e|r|t|e|d> |o|n|e| |t|w|o| @37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| |t+0#0000000&|h|r|e@1| |f|o|u|r| @43
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3| +0#0000000&@2|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |f+0#0000000&|i|v|e| |s|i|x| @45
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|9|-|1@1|7| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_5.dump b/src/testdir/dumps/Test_prop_with_text_above_5.dump
new file mode 100644
index 0000000..8494bb1
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_5.dump
@@ -0,0 +1,9 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|f+0#0000000#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@36
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|s+0#0000000#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@35
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |i+0#0000000&|n|s|e|r|t|e|d| |o|n|e| |t|w|o| @37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| |t+0#0000000&|h|r|e@1| |f|o|u>r| @43
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3| +0#0000000&@2|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |f+0#0000000&|i|v|e| |s|i|x| @45
+|~+0#4040ff13&| @58
+|~| @58
+|:+0#0000000&|s|e|t| |n|o|w|r|a|p| @30|2|,|1|0| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_6.dump b/src/testdir/dumps/Test_prop_with_text_above_6.dump
new file mode 100644
index 0000000..da28228
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_6.dump
@@ -0,0 +1,9 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|f+0#0000000#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@36
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|s+0#0000000#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@35
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |i+0#0000000#e0e0e08|n|s|e|r|t|e>d+0&#ffffff0| |o|n|e| |t|w|o| @37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|b+0#0000000#5fd7ff255|e|l|o|w| +0&#ffffff0@48
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| |t+0#0000000&|h|r|e@1| |f|o|u|r| @43
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3| +0#0000000&@2|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |f+0#0000000&|i|v|e| |s|i|x| @45
+|~+0#4040ff13&| @58
+|-+2#0000000&@1| |V|I|S|U|A|L| |-@1| +0&&@19|8| @8|1|,|8|-|1@1|6| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_7.dump b/src/testdir/dumps/Test_prop_with_text_above_7.dump
new file mode 100644
index 0000000..c50749b
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_7.dump
@@ -0,0 +1,9 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|f+0#0000000#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@36
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|s+0#0000000#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@35
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |i+0#0000000#e0e0e08|n|s|e|r|t|e>d+0&#ffffff0| +0&#e0e0e08|o|n|e| |t|w|o| +0&#ffffff0@37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|b+0#0000000#5fd7ff255|e|l|o|w| +0#4040ff13#ffffff0| +0#0000000&@47
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| |t+0#0000000&|h|r|e@1| |f|o|u|r| @43
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3| +0#0000000&@2|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |f+0#0000000&|i|v|e| |s|i|x| @45
+|~+0#4040ff13&| @58
+|-+2#0000000&@1| |V|I|S|U|A|L| |L|I|N|E| |-@1| +0&&@14|1| @8|1|,|8|-|1@1|6| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_8.dump b/src/testdir/dumps/Test_prop_with_text_above_8.dump
new file mode 100644
index 0000000..b807db5
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_8.dump
@@ -0,0 +1,9 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|f+0#0000000#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@36
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|s+0#0000000#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@35
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |i+0#0000000&|n|s|e|r|t|e|d| @45
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| >o+0#0000000&|n|e| |t|w|o| @46
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|b+0#0000000#5fd7ff255|e|l|o|w| +0&#ffffff0@48
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|3| |t+0#0000000&|h|r|e@1| |f|o|u|r| @43
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3| +0#0000000&@2|a+0&#ffff4012|n|o|t|h|e|r| |t|h|i|n|g| +0&#ffffff0@37
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|4| |f+0#0000000&|i|v|e| |s|i|x| @45
+@42|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_above_9.dump b/src/testdir/dumps/Test_prop_with_text_above_9.dump
new file mode 100644
index 0000000..727f859
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_above_9.dump
@@ -0,0 +1,9 @@
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|f+0#0000000#ffff4012|i|r|s|t| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@36
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|s+0#0000000#ffd7ff255|e|c|o|n|d| |t|h|i|n|g| |a|b|o|v|e| +0&#ffffff0@35
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3| +0#0000000&@1|0+0&#ffff4012| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|…
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|1| |i+0#0000000&|n|s|e|r|t|e|d| @45
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@1|2| >o+0#0000000&|n|e| |t|w|o| @46
+| +0#0000e05#a8a8a8255@1| +0#af5f00255#ffffff0@3|b+0#0000000#5fd7ff255|e|l|o|w| +0&#ffffff0@48
+|~+0#4040ff13&| @58
+|~| @58
+|:+0#0000000&|c|a|l@1| |A|d@1|L|o|n|g|P|r|o|p|A|b|o|v|e|(|)| @17|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_1.dump b/src/testdir/dumps/Test_prop_with_text_after_1.dump
new file mode 100644
index 0000000..159e394
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_1.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@2|s|o|m|e| |t|e|x|t| |h|e|r|e| |a|n|d| |o|t|h|e|r| |t|e|x|t| |t|h|e|r|e| +0&#ffff4012|A|F|T|E|R| | +0&#ffffff0@7| +0#ffffff16#e000002|R|I|G|H|T|
+| +0#0000000#5fd7ff255|B|E|L|O|W| | +0&#ffffff0@52
+| +0&#5fd7ff255|A|L|S|O| |B|E|L|O|W| | +0&#ffffff0@47
+|L|a|s|t| |l|i|n|e>.| +0&#ffff4012|A|f|t|e|r| |L|a|s|t| | +0&#ffffff0@37
+|r|i|g|h|t| |h|e|r|e| @37|s+0#ffffff16#e000002|ö|m|e|和*&|平|t+&|é|x|t
+|~+0#4040ff13#ffffff0| @58
+|~| @58
+| +0#0000000&@41|2|,|1|0| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_below_trunc_1.dump b/src/testdir/dumps/Test_prop_with_text_after_below_trunc_1.dump
new file mode 100644
index 0000000..8481c9c
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_below_trunc_1.dump
@@ -0,0 +1,8 @@
+|o+0&#ffffff0|n|a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d| |f|a|s| |d|f|t+0#e000e06&|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|…
+| +0#0000000&@3|t+0&#ffd7ff255|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0&#ffffff0@12
+|t|w>o| @56
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|"+0#0000000&|f|o@1|b|a|r|"| |[|N|e|w|]| @27|2|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_below_trunc_2.dump b/src/testdir/dumps/Test_prop_with_text_after_below_trunc_2.dump
new file mode 100644
index 0000000..afa1a36
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_below_trunc_2.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |o+0#0000000&|n|a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d| |f|a|s| |d|f|t+0#e000e06&|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|…
+| +0#af5f00255&@3| +0#0000000&@3|t+0&#ffd7ff255|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0&#ffffff0@8
+| +0#af5f00255&@1|2| |t+0#0000000&|w>o| @52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|s|e|t| |n|u|m|b|e|r| @30|2|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_below_trunc_3.dump b/src/testdir/dumps/Test_prop_with_text_after_below_trunc_3.dump
new file mode 100644
index 0000000..7b93c7f
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_below_trunc_3.dump
@@ -0,0 +1,8 @@
+| +8#af5f00255#ffffff0@1|1| >o+8#0000000&|n|a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d| |f|a|s| |d|f|t+8#e000e06&|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|…
+| +8#af5f00255&@3| +8#0000000&@3|t+8&#ffd7ff255|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +8&#ffffff0@8
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| @52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|s|e|t| |c|u|r|s|o|r|l|i|n|e| @26|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_join_split_1.dump b/src/testdir/dumps/Test_prop_with_text_after_join_split_1.dump
new file mode 100644
index 0000000..4aab0b1
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_join_split_1.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0@1| @57
+>2@1| @57
+| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@43
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_join_split_2.dump b/src/testdir/dumps/Test_prop_with_text_after_join_split_2.dump
new file mode 100644
index 0000000..d17aacf
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_join_split_2.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0@1> |2@1| @54
+| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@43
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_join_split_3.dump b/src/testdir/dumps/Test_prop_with_text_after_join_split_3.dump
new file mode 100644
index 0000000..51a397f
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_join_split_3.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0@1> |2@1| +0&#ffff4012|a|f|t|e|r| |t|h|e| |t|e|x|t| | +0&#ffffff0@27| +0&#ffff4012|r|i|g|h|t| |h|e|r|e
+| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@43
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+|:+0#0000000&|c|a|l@1| |A|d@1|M|o|r|e|(|)| @26|1|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_join_split_4.dump b/src/testdir/dumps/Test_prop_with_text_after_join_split_4.dump
new file mode 100644
index 0000000..7ec94b1
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_join_split_4.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0@1| @57
+>2@1| +0&#ffff4012|a|f|t|e|r| |t|h|e| |t|e|x|t| | +0&#ffffff0@30| +0&#ffff4012|r|i|g|h|t| |h|e|r|e
+| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@43
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_join_split_5.dump b/src/testdir/dumps/Test_prop_with_text_after_join_split_5.dump
new file mode 100644
index 0000000..991b64d
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_join_split_5.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0@1> |2@1| +0&#ffff4012|a|f|t|e|r| |t|h|e| |t|e|x|t| | +0&#ffffff0@27| +0&#ffff4012|r|i|g|h|t| |h|e|r|e
+| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@43
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_joined_1.dump b/src/testdir/dumps/Test_prop_with_text_after_joined_1.dump
new file mode 100644
index 0000000..0a4e73d
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_joined_1.dump
@@ -0,0 +1,6 @@
+|o+0&#ffffff0|n|e| |t|w|o| @52
+|t|h|r|e@1| |f|o|u|r| +0&#ffff4012|F|O|U|R| +0&#ffffff0@44
+|a| |b| |c| |d| |e> |f| +0&#ffff4012|F@2| +0&#ffffff0@44
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|3|,|1|0| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_nowrap_1.dump b/src/testdir/dumps/Test_prop_with_text_after_nowrap_1.dump
new file mode 100644
index 0000000..610f14f
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_nowrap_1.dump
@@ -0,0 +1,12 @@
+|o+0&#ffffff0|n|e| +0&#ffd7ff255|A|f|t|e|r| |t|h|e| |t|e|x|t| +0&#ffffff0@31|r+0&#ffd7ff255|i|g|h|t| |h|e|r|e
+| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@43
+|t|w|o| @56
+|a+0&#ffff4012|n|o|t|h|e|r| +0&#ffffff0@52
+|O+0#ffffff16#e000002|n|e| |M|o|r|e| |H|e|r|e| +0#0000000#ffffff0@46
+|t|h|r|e>e| @41|r+0&#ffff4012|i|g|h|t| |a|l|i|g|n|e|d
+| +0&#ffffff0@41|a+0&#ffff4012|l|s|o| |r|i|g|h|t| |a|l|i|g|n|e|d
+|f+0&#ffffff0|o|u|r| @55
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|3|,|5| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_nowrap_2.dump b/src/testdir/dumps/Test_prop_with_text_after_nowrap_2.dump
new file mode 100644
index 0000000..1d5c534
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_nowrap_2.dump
@@ -0,0 +1,12 @@
+| +0#0000e05#a8a8a8255@4|o+0#0000000#ffffff0|n|e| +0&#ffd7ff255|A|f|t|e|r| |t|h|e| |t|e|x|t| +0&#ffffff0@26|r+0&#ffd7ff255|i|g|h|t| |h|e|r|e
+| +0#0000e05#a8a8a8255@4| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@38
+| +0#0000e05#a8a8a8255@4|t+0#0000000#ffffff0|w|o| @51
+| +0#0000e05#a8a8a8255@4|a+0#0000000#ffff4012|n|o|t|h|e|r| +0&#ffffff0@47
+| +0#0000e05#a8a8a8255@4|O+0#ffffff16#e000002|n|e| |M|o|r|e| |H|e|r|e| +0#0000000#ffffff0@41
+| +0#0000e05#a8a8a8255@4|t+8#0000000#e0e0e08|h|r|e>e| @36|r+8&#ffff4012|i|g|h|t| |a|l|i|g|n|e|d
+| +0#0000e05#a8a8a8255@4| +8#0000000#e0e0e08@36|a+8&#ffff4012|l|s|o| |r|i|g|h|t| |a|l|i|g|n|e|d
+| +0#0000e05#a8a8a8255@4|f+0#0000000#ffffff0|o|u|r| @50
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|3|,|5| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_nowrap_3.dump b/src/testdir/dumps/Test_prop_with_text_after_nowrap_3.dump
new file mode 100644
index 0000000..ca84912
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_nowrap_3.dump
@@ -0,0 +1,12 @@
+| +0#0000e05#a8a8a8255@4|o+0#0000000#ffffff0|n|e| +0&#ffd7ff255|A|f|t|e|r| |t|h|e| |t|e|x|t| +0&#ffffff0@26|r+0&#ffd7ff255|i|g|h|t| |h|e|r|e
+| +0#0000e05#a8a8a8255@4| +0#ffffff16#e000002|B|e|l|o|w| |t|h|e| |l|i|n|e| | +0#0000000#ffffff0@38
+| +0#0000e05#a8a8a8255@4|t+0#0000000#ffffff0|w|o| @51
+| +0#0000e05#a8a8a8255@4|a+0#0000000#ffff4012|n|o|t|h|e|r| +0&#ffffff0@47
+| +0#0000e05#a8a8a8255@4|O+0#ffffff16#e000002|n|e| |M|o|r|e| |H|e|r|e| +0#0000000#ffffff0@41
+| +0#0000e05#a8a8a8255@4|t+0#0000000#ffffff0|h|r|e@1| @36|r+0&#ffff4012|i|g|h|t| |a|l|i|g|n|e|d
+| +0#0000e05#a8a8a8255@4| +0#0000000#ffffff0@36|a+0&#ffff4012|l|s|o| |r|i|g|h|t| |a|l|i|g|n|e|d
+| +0#0000e05#a8a8a8255@4|f+8#0000000#e0e0e08|o|u>r| @50
+|~+0#4040ff13#ffffff0| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|4|,|4| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_trunc_1.dump b/src/testdir/dumps/Test_prop_with_text_after_trunc_1.dump
new file mode 100644
index 0000000..c8e6369
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_trunc_1.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|O|N|E| |a|n|d| |T|W|O| |a|n|d| |T|H|R|E@1| |a|n|d|…
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D|…
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @26
+| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |f|i|v|e| |l|e|t|s| |w|r|a|p| |a|f|t|e|r| |s|o|…
+|c+0&#ffffff0|u|r|s|o|r| >h|e|r|e| @48
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|4|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_after_trunc_2.dump b/src/testdir/dumps/Test_prop_with_text_after_trunc_2.dump
new file mode 100644
index 0000000..c6fd499
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_trunc_2.dump
@@ -0,0 +1,9 @@
+>o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|O|N|…||+1&#ffffff0|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v
+|o|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|o|n|…||+1&#ffffff0|e+0&&| |s|i|x| |s|e|v|e|n| +0&#ffff4012|o|n|e| |A|N|D| |t|…
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @3||+1&&|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v
+| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |…||+1&#ffffff0|e+0&&| |s|i|x| |s|e|v|e|n| @10
+|c|u|r|s|o|r| |h|e|r|e| @25||+1&&| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e|…
+|~+0#4040ff13#ffffff0| @35||+1#0000000&|c+0&&|u|r|s|o|r| |h|e|r|e| @10
+|~+0#4040ff13&| @35||+1#0000000&|~+0#4040ff13&| @20
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @5|1|,|1| @11|A|l@1| |<+1&&|N|a|m|e|]| |[|+|]| |4|,|8| @4|B|o|t
+|:+0&&|3|7|v|s|p| @53
diff --git a/src/testdir/dumps/Test_prop_with_text_after_trunc_3.dump b/src/testdir/dumps/Test_prop_with_text_after_trunc_3.dump
new file mode 100644
index 0000000..5834170
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_trunc_3.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|O|N||+1&#ffffff0|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e
+|E+0&#ffff4012| |a|n|d| |T|W|O| |a|n|d| |T|H|R|E@1| |a|n|d| |F|O|U|R| |a|n|d| |F|I|…||+1&#ffffff0| +0&&|s|i|x| |s|e|v|e|n| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o|…
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e>n| +0&#ffff4012|o|n||+1&#ffffff0|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e
+|e+0&#ffff4012| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |f|i|…||+1&#ffffff0| +0&&|s|i|x| |s|e|v|e|n| @12
+|o|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @2||+1&&| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1|…
+| |o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D|…||+1&#ffffff0|c+0&&|u|r|s|o|r| |h|e|r|e| @11
+|c|u|r|s|o|r| |h|e|r|e| @24||+1&&|~+0#4040ff13&| @21
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @4|2|,|3@1| @10|A|l@1| |<+1&&| |N|a|m|e|]| |[|+|]| |4|,|8| @4|B|o|t
+|:+0&&|3|6|w|i|n|c|m|d| ||| @48
diff --git a/src/testdir/dumps/Test_prop_with_text_after_trunc_4.dump b/src/testdir/dumps/Test_prop_with_text_after_trunc_4.dump
new file mode 100644
index 0000000..165020a
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_trunc_4.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n||+1&&|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i
+| +0&#ffff4012|O|N|E| |a|n|d| |T|W|O| |a|n|d| |T|H|R|E@1| |a|n|d| |F|O|U|R| |…||+1&#ffffff0|x+0&&| |s|e|v|e|n| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|…
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e>n||+1&&|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i
+| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |…||+1&#ffffff0|x+0&&| |s|e|v|e|n| @18
+|o|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n||+1&&| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|…
+| |o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |…||+1&#ffffff0|c+0&&|u|r|s|o|r| |h|e|r|e| @14
+|c|u|r|s|o|r| |h|e|r|e| @21||+1&&|~+0#4040ff13&| @24
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @3|2|,|3@1| @8|A|l@1| |<+1&&|o| |N|a|m|e|]| |[|+|]| |4|,|8| @6|B|o|t
+|:+0&&|3@1|w|i|n|c|m|d| ||| @48
diff --git a/src/testdir/dumps/Test_prop_with_text_after_trunc_5.dump b/src/testdir/dumps/Test_prop_with_text_after_trunc_5.dump
new file mode 100644
index 0000000..e134443
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_trunc_5.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r||+1&&|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|o|n|e| |A|N|…
+| +0&#ffffff0|f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|O|N||+1&#ffffff0|o+0&&|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @7
+|E+0&#ffff4012| |a|n|d| |T|W|O| |a|n|d| |T|H|R|…||+1&#ffffff0| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |f|i|v|e|…
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r||+1&&|c+0&&|u|r|s|o|r| |h|e|r|e| @29
+@1|f|i|v|e| |s|i>x| |s|e|v|e|n| +0&#ffff4012|o|n||+1&#ffffff0|~+0#4040ff13&| @39
+|e+0#0000000#ffff4012| |A|N|D| |t|w|o| |A|N|D| |t|h|r|…||+1&#ffffff0|~+0#4040ff13&| @39
+|@@2| @14||+1#0000000&|~+0#4040ff13&| @39
+|<+3#0000000&|m|e|]| |[|+|]| |2|,|2|7| @1|T|o|p| |[+1&&|N|o| |N|a|m|e|]| |[|+|]| @9|4|,|8| @11|B|o|t
+|:+0&&|1|8|w|i|n|c|m|d| ||| @48
diff --git a/src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump b/src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump
new file mode 100644
index 0000000..7642f8f
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_after_wraps_1.dump
@@ -0,0 +1,9 @@
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| +0&#ffff4012|O|N|E| |a|n|d| |T|W|O| |a|n|d| |T|H|R|E@1| |a|n|d|
+|F|O|U|R| |a|n|d| |F|I|V|E| +0&#ffffff0@46
+|o|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @26
+@20| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |f|i|v|e
+|o+0&#ffffff0|n|e| |t|w|o| |t|h|r|e@1| |f|o|u|r| |f|i|v|e| |s|i|x| |s|e|v|e|n| @26
+| +0&#ffff4012|o|n|e| |A|N|D| |t|w|o| |A|N|D| |t|h|r|e@1| |A|N|D| |f|o|u|r| |A|N|D| |f|i|v|e| |l|e|t|s| |w|r|a|p| |a|f|t|e|r| |s|o|m
+|e| |m|o|r|e| |t|e|x|t| +0&#ffffff0@48
+|c|u|r|s|o|r| >h|e|r|e| @48
+@42|4|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_below_after_match_1.dump b/src/testdir/dumps/Test_prop_with_text_below_after_match_1.dump
new file mode 100644
index 0000000..b4c09fa
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_below_after_match_1.dump
@@ -0,0 +1,8 @@
+| +0#0000e05#a8a8a8255@1>v+0#0000000#ffffff0|i|m|9|s|c|r|i|p|t+0&#ffff4012| +0&#ffffff0@47
+| +0#0000e05#a8a8a8255@1|T+0#ffffff16#ff404010|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| +0#0000000#ffffff0@38
+| +0#0000e05#a8a8a8255@1|s+0#0000000#ffffff0|o|m|e| |t|e|x|t| @48
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_below_cul_1.dump b/src/testdir/dumps/Test_prop_with_text_below_cul_1.dump
new file mode 100644
index 0000000..0bfd304
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_below_cul_1.dump
@@ -0,0 +1,6 @@
+>s+8&#ffffff0|o|m|e| |t|e|x|t| @50
+@4|T+8&#ffd7ff255|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +8&#ffffff0@12
+|l+0&&|a|s|t| |l|i|n|e| @50
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_below_nowrap_1.dump b/src/testdir/dumps/Test_prop_with_text_below_nowrap_1.dump
new file mode 100644
index 0000000..e7f1837
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_below_nowrap_1.dump
@@ -0,0 +1,8 @@
+|o+0&#ffffff0|n|a|s|d|f| |a|s|d|f| |a|s|d|f| |s|d|f| |d|f| |a|s|d|f| |a|s|d|f| |e| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|
+|t+0#e000e06&|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0#0000000&@16
+|t|w>o| @56
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|"+0#0000000&|f|o@1|b|a|r|"| |[|N|e|w|]| @27|2|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_below_nowrap_2.dump b/src/testdir/dumps/Test_prop_with_text_below_nowrap_2.dump
new file mode 100644
index 0000000..0bcaf66
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_below_nowrap_2.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0|a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d| |f|a|s| |d>f|t+0#e000e06&|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e
+|t|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s| |o|v|e|r| |t|h|e| |l|a|z|y| |d|o|g| +0#0000000&@16
+@60
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|"+0#0000000&|f|o@1|b|a|r|"| |[|N|e|w|]| @27|1|,|6@1| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_cursormoved_1.dump b/src/testdir/dumps/Test_prop_with_text_cursormoved_1.dump
new file mode 100644
index 0000000..6a3c9e4
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_cursormoved_1.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|h|i|s| >i|s| |l|i|n|e| |o|n|e|x+0#ffffff16#ff404010@43
+@60
+@16| +0#0000000#ffffff0@43
+|t|h|i|s| |i|s| |l|i|n|e| |t|w|o| @43
+|t|h|r|e@1| @54
+|f|o|u|r| @55
+|f|i|v|e| @55
+@42|1|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_cursormoved_2.dump b/src/testdir/dumps/Test_prop_with_text_cursormoved_2.dump
new file mode 100644
index 0000000..c1b5f0c
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_cursormoved_2.dump
@@ -0,0 +1,8 @@
+|t+0&#ffffff0|h|i|s| |i|s| |l|i|n|e| |o|n|e| @43
+|t|h|i|s| >i|s| |l|i|n|e| |t|w|o| @43
+|t|h|r|e@1| @54
+|f|o|u|r| @55
+|f|i|v|e| @55
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|2|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_empty_line_1.dump b/src/testdir/dumps/Test_prop_with_text_empty_line_1.dump
new file mode 100644
index 0000000..426d64e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_empty_line_1.dump
@@ -0,0 +1,8 @@
+>X+0&#ffff4012@59
+|a+0&#ffffff0@2| @56
+|X+0&#ffff4012@59
+@1| +0&#ffffff0@58
+|b@5| @53
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_empty_line_2.dump b/src/testdir/dumps/Test_prop_with_text_empty_line_2.dump
new file mode 100644
index 0000000..426d64e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_empty_line_2.dump
@@ -0,0 +1,8 @@
+>X+0&#ffff4012@59
+|a+0&#ffffff0@2| @56
+|X+0&#ffff4012@59
+@1| +0&#ffffff0@58
+|b@5| @53
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|1|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_empty_line_3.dump b/src/testdir/dumps/Test_prop_with_text_empty_line_3.dump
new file mode 100644
index 0000000..3e3e9ea
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_empty_line_3.dump
@@ -0,0 +1,8 @@
+|X+0&#ffff4012@59
+|a+0&#ffffff0@1>a| @56
+|X+0&#ffff4012@59
+@1| +0&#ffffff0@58
+|b@5| @53
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|2|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_empty_line_4.dump b/src/testdir/dumps/Test_prop_with_text_empty_line_4.dump
new file mode 100644
index 0000000..8689ac4
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_empty_line_4.dump
@@ -0,0 +1,8 @@
+|X+0&#ffff4012@59
+|a+0&#ffffff0@2| @56
+>X+0&#ffff4012@59
+@1| +0&#ffffff0@58
+|b@5| @53
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|3|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_empty_line_5.dump b/src/testdir/dumps/Test_prop_with_text_empty_line_5.dump
new file mode 100644
index 0000000..9ea16dc
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_empty_line_5.dump
@@ -0,0 +1,8 @@
+|X+0&#ffff4012@59
+|a+0&#ffffff0@2| @56
+|X+0&#ffff4012@59
+@1| +0&#ffffff0@58
+|b@4>b| @53
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|4|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_override_1.dump b/src/testdir/dumps/Test_prop_with_text_override_1.dump
new file mode 100644
index 0000000..50b8511
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_override_1.dump
@@ -0,0 +1,6 @@
+>s+8&#e0e0e08|o|m|e| | +8#4040ff13#40ffff15|i|n|s|e|r|t|e|d| |t+8#0000000#e0e0e08|e|x|t| |h|e|r|e| @35
+|~+0#4040ff13#ffffff0| @58
+|~| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_text_override_2.dump b/src/testdir/dumps/Test_prop_with_text_override_2.dump
new file mode 100644
index 0000000..334929e
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_text_override_2.dump
@@ -0,0 +1,6 @@
+|s+0&#ffffff0|o|m+0&#e0e0e08|e| | +0#4040ff13#40ffff15|i|n|s|e|r|t|e|d| |t+0#0000000#e0e0e08|e|x|t| |h|e>r+0&#ffffff0|e| @35
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|-+2#0000000&@1| |V|I|S|U|A|L| |-@1| +0&&@19|1@1| @7|1|,|1|3|-|2|3| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_prop_with_wrap_1.dump b/src/testdir/dumps/Test_prop_with_wrap_1.dump
new file mode 100644
index 0000000..6b6d664
--- /dev/null
+++ b/src/testdir/dumps/Test_prop_with_wrap_1.dump
@@ -0,0 +1,6 @@
+|a+0&#ffffff0|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|s+0#e000e06&|o|m|e| |v|i|r
+|t|u|a|l| |t|e|x|t|d+0#0000000&|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f| |a|s|d|f> @8
+|~+0#4040ff13&| @48
+|~| @48
+|~| @48
+| +0#0000000&@31|1|,|7|5|-|9|2| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_props_after_1.dump b/src/testdir/dumps/Test_props_after_1.dump
new file mode 100644
index 0000000..dd607f6
--- /dev/null
+++ b/src/testdir/dumps/Test_props_after_1.dump
@@ -0,0 +1,8 @@
+|o+0&#ffffff0|n|e| @2|0+0#e000e06&| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| |…
+| +0#0000000&@4|0+0#e000e06&|-|1|-|2|-|3|-|4|-|5|-|6|-|7|-|8|-|9|-|1|0|-|1@1|-|1|2|-|1|3|-|1|4|-|1|5|-|1|6|-|1|7|-|1|8|-|1|9|-|2|0|-|2|…
+| +0#0000000&|0+0#e000e06&|.|1|.|2|.|3|.|4|.|5|.|6|.|7|.|8|.|9|.|1|0|.|1@1|.|1|2|.|1|3|.|1|4|.|1|5|.|1|6|.|1|7|.|1|8|.|1|9|.|2|0|.|2|1|.|2@1|…
+|t+0#0000000&|w>o| @56
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_props_after_2.dump b/src/testdir/dumps/Test_props_after_2.dump
new file mode 100644
index 0000000..146f045
--- /dev/null
+++ b/src/testdir/dumps/Test_props_after_2.dump
@@ -0,0 +1,8 @@
+|o+0&#ffffff0|n|e|$+0#4040ff13&| +0#0000000&@2|0+0#e000e06&| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0|…
+| +0#0000000&@4|0+0#e000e06&|-|1|-|2|-|3|-|4|-|5|-|6|-|7|-|8|-|9|-|1|0|-|1@1|-|1|2|-|1|3|-|1|4|-|1|5|-|1|6|-|1|7|-|1|8|-|1|9|-|2|0|-|2|…
+| +0#0000000&|0+0#e000e06&|.|1|.|2|.|3|.|4|.|5|.|6|.|7|.|8|.|9|.|1|0|.|1@1|.|1|2|.|1|3|.|1|4|.|1|5|.|1|6|.|1|7|.|1|8|.|1|9|.|2|0|.|2|1|.|2@1|…
+|t+0#0000000&|w>o|$+0#4040ff13&| +0#0000000&@55
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|:+0#0000000&|s|e|t| |l|i|s|t| @32|2|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_pum_preview_1.dump b/src/testdir/dumps/Test_pum_preview_1.dump
new file mode 100644
index 0000000..e8f4225
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_preview_1.dump
@@ -0,0 +1,12 @@
+|o+0&#ffffff0|n|e| |o|t|h|e|r> @65
+|â•”+0#0000001#ffd7ff255| |X| +0&#e0e0e08|o|t|h|e|r| @9|â•+0&#ffd7ff255@21|X| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001&|o|n|c|e| @32|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001&|o|n|l|y| @32|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001&|o|f@1| @33|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001&|o|n|e| @33|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|c+0#af5f00255&|a|l@1| +0#0000001&|s+0#00e0e07&|y|s|t|e|m|(+0#e000e06&|'+0#e000002&|e|c|h|o| |h|e|l@1|o|'|)+0#e000e06&| +0#0000001&@14|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|"+0#0000e05&| |t|h|e| |e|n|d| +0#0000001&@30|â•‘| +0#4040ff13#ffffff0@32
+|â•š+0#0000001#ffd7ff255|â•@39|⇲| +0#4040ff13#ffffff0@32
+|~| @73
+|~| @73
+|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |5| +0#0000000&@33
diff --git a/src/testdir/dumps/Test_pum_preview_2.dump b/src/testdir/dumps/Test_pum_preview_2.dump
new file mode 100644
index 0000000..a9fcfc9
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_preview_2.dump
@@ -0,0 +1,12 @@
+|o+0&#ffffff0|n|e| |o|n|c|e> @66
+|â•”+0#0000001#ffd7ff255| |X| |o|t|h|e|r| @9|â•@21|X| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001#e0e0e08|o|n|c|e| @10| +0&#ffd7ff255@21|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001&|o|n|l|y| @32|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001&|o|f@1| @33|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001&|o|n|e| @33|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|c+0#af5f00255&|a|l@1| +0#0000001&|s+0#00e0e07&|y|s|t|e|m|(+0#e000e06&|'+0#e000002&|e|c|h|o| |h|e|l@1|o|'|)+0#e000e06&| +0#0000001&@14|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|"+0#0000e05&| |t|h|e| |e|n|d| +0#0000001&@30|â•‘| +0#4040ff13#ffffff0@32
+|â•š+0#0000001#ffd7ff255|â•@39|⇲| +0#4040ff13#ffffff0@32
+|~| @73
+|~| @73
+|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |5| +0#0000000&@33
diff --git a/src/testdir/dumps/Test_pum_preview_3.dump b/src/testdir/dumps/Test_pum_preview_3.dump
new file mode 100644
index 0000000..6b14ce3
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_preview_3.dump
@@ -0,0 +1,12 @@
+|o+0&#ffffff0|n|e| |o|n|l|y> @66
+|â•”+0#0000001#ffd7ff255| |X| |o|t|h|e|r| @9|â•@21|X| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001&|o|n|c|e| @32|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001#e0e0e08|o|n|l|y| @10| +0&#ffd7ff255@21|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001&|o|f@1| @33|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001&|o|n|e| @33|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|c+0#af5f00255&|a|l@1| +0#0000001&|s+0#00e0e07&|y|s|t|e|m|(+0#e000e06&|'+0#e000002&|e|c|h|o| |h|e|l@1|o|'|)+0#e000e06&| +0#0000001&@14|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|"+0#0000e05&| |t|h|e| |e|n|d| +0#0000001&@30|â•‘| +0#4040ff13#ffffff0@32
+|â•š+0#0000001#ffd7ff255|â•@39|⇲| +0#4040ff13#ffffff0@32
+|~| @73
+|~| @73
+|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |3| |o|f| |5| +0#0000000&@33
diff --git a/src/testdir/dumps/Test_pum_preview_4.dump b/src/testdir/dumps/Test_pum_preview_4.dump
new file mode 100644
index 0000000..40254a8
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_preview_4.dump
@@ -0,0 +1,12 @@
+|o+0&#ffffff0|n|e| |o|f@1> @67
+|â•”+0#0000001#ffd7ff255| |X| |o|t|h|e|r| @9|â•@21|X| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001&|o|n|c|e| @32|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|l+0#af5f00255&|e| +0#0000001&|o|n|l|y| @32|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001#e0e0e08|o|f@1| @11| +0&#ffd7ff255@21|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|e+0#af5f00255&|c| +0#0000001&|o|n|e| @33|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|c+0#af5f00255&|a|l@1| +0#0000001&|s+0#00e0e07&|y|s|t|e|m|(+0#e000e06&|'+0#e000002&|e|c|h|o| |h|e|l@1|o|'|)+0#e000e06&| +0#0000001&@14|â•‘| +0#0000000#ffffff0@32
+|â•‘+0#0000001#ffd7ff255|"+0#0000e05&| |t|h|e| |e|n|d| +0#0000001&@30|â•‘| +0#4040ff13#ffffff0@32
+|â•š+0#0000001#ffd7ff255|â•@39|⇲| +0#4040ff13#ffffff0@32
+|~| @73
+|~| @73
+|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |4| |o|f| |5| +0#0000000&@33
diff --git a/src/testdir/dumps/Test_pum_rightleft_01.dump b/src/testdir/dumps/Test_pum_rightleft_01.dump
new file mode 100644
index 0000000..8e8f6b4
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_rightleft_01.dump
@@ -0,0 +1,8 @@
+|w+0&#ffffff0|v|u|t|s|r|q|p|o|n|m|l|k|j|i|h|g|f|e|d|c|b|a|z|y|x|w|v|u|t|s|r|q|p|o|n|m|l|k|j|i|h|g|f|e|d|c|b|a|z|y|x|w|v|u|t|s|r|q|p|o|n|m|l|k|j|i|h|g|f|e|d|c|b|a
+| @45|z|y|x|w|v|u|t|s|r|q|p|o|n|m|l|k|j|i|h|g|f|e|d|c|b|a|z|y|x
+| @71|m|i|v
+| @67|y|r|o|t|c|i|v
+| @66> |y|r|o|t|c|i|v
+|w+0#0000001#ffd7ff255|v|u|t|s|r|q|p|o|n|m|l|k|j|i|h|g|f|e|d|c|b|a|z|y|x|w|v|u|t|s|r|q|p|o|n|m|l|k|j|i|h|g|f|e|d|c|b|a|z|y|x|w|v|u|t|s|r|q|p|o|n|m|l|k|j|i|h|g|f|e|d|c|b|a
+| @71|m|i|v
+| +0&#e0e0e08@67|y|r|o|t|c|i|v
diff --git a/src/testdir/dumps/Test_pum_rightleft_02.dump b/src/testdir/dumps/Test_pum_rightleft_02.dump
new file mode 100644
index 0000000..0c658c0
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_rightleft_02.dump
@@ -0,0 +1,7 @@
+| +0&#ffffff0@63|o|w|t| @4|e|n|o
+| @61|e@1|r|h|t| @4|e|n|o
+| @70|r|u|o|f
+| @60> |e@1|r|h|t| @4|e|n|o
+| +0#4040ff13&@59| +0#0000001#ffd7ff255@6|o|w|t| @1|e|n|o
+| +0#4040ff13#ffffff0@59| +0#0000001#e0e0e08@4|e@1|r|h|t| @1|e|n|o
+| +0#4040ff13#ffffff0@73|~
diff --git a/src/testdir/dumps/Test_pum_scrollbar_01.dump b/src/testdir/dumps/Test_pum_scrollbar_01.dump
new file mode 100644
index 0000000..af134d0
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_scrollbar_01.dump
@@ -0,0 +1,7 @@
+|o+0&#ffffff0|n|e| @71
+|t|w|o| @71
+|t|h|r|e@1| @69
+|o|n|e> @71
+|o+0#0000001#e0e0e08|n|e| @11| +0#0000000#0000001| +0#4040ff13#ffffff0@58
+|t+0#0000001#ffd7ff255|w|o| @11| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@58
+|~| @73
diff --git a/src/testdir/dumps/Test_pum_scrollbar_02.dump b/src/testdir/dumps/Test_pum_scrollbar_02.dump
new file mode 100644
index 0000000..1a462bc
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_scrollbar_02.dump
@@ -0,0 +1,7 @@
+| +0&#ffffff0@71|e|n|o
+| @71|o|w|t
+| @69|e@1|r|h|t
+| @70> |e|n|o
+| +0#4040ff13&@58| +0#0000000#0000001| +0#0000001#e0e0e08@11|e|n|o
+| +0#4040ff13#ffffff0@58| +0#0000000#a8a8a8255| +0#0000001#ffd7ff255@11|o|w|t
+| +0#4040ff13#ffffff0@73|~
diff --git a/src/testdir/dumps/Test_pum_stopped_by_timer.dump b/src/testdir/dumps/Test_pum_stopped_by_timer.dump
new file mode 100644
index 0000000..77ad5ce
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_stopped_by_timer.dump
@@ -0,0 +1,12 @@
+|h+0&#ffffff0|e|l@1|o| @69
+|h|u|l@1|o| @69
+|h|e@2>e| @69
+|h|e|l@1|o| @69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|3|,|5| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_pum_with_folds_two_tabs.dump b/src/testdir/dumps/Test_pum_with_folds_two_tabs.dump
new file mode 100644
index 0000000..2414cdc
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_with_folds_two_tabs.dump
@@ -0,0 +1,10 @@
+| +8#0000001#e0e0e08|+| |[|N|o| |N|a|m|e|]| | +2#0000000#ffffff0|+| |[|N|o| |N|a|m|e|]| | +1&&@47|X+8#0000001#e0e0e08
+|"+0#0000000#ffffff0| |x| |{@2|1| @66
+|"| |a|0> |s|o|m|e| |t|e|x|t| @60
+|"| +0#0000001#e0e0e08|a|0| @12| +0#0000000#0000001| +0&#ffffff0@56
+|"| +0#0000001#ffd7ff255|a|1| @12| +0#0000000#0000001| +0&#ffffff0@56
+|"| +0#0000001#ffd7ff255|a|2| @12| +0#0000000#0000001| +0&#ffffff0@56
+|"| +0#0000001#ffd7ff255|a|3| @12| +0#0000000#a8a8a8255| +0&#ffffff0@56
+|"| +0#0000001#ffd7ff255|a|4| @12| +0#0000000#a8a8a8255| +0&#ffffff0@56
+|"| +0#0000001#ffd7ff255|a|5| @12| +0#0000000#a8a8a8255| +0&#ffffff0@56
+|-+2&&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |1|0| +0#0000000&@32
diff --git a/src/testdir/dumps/Test_pum_with_preview_win.dump b/src/testdir/dumps/Test_pum_with_preview_win.dump
new file mode 100644
index 0000000..ad0df78
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_with_preview_win.dump
@@ -0,0 +1,12 @@
+|1+0&#ffffff0|i|n|f|o| @69
+|~+0#4040ff13&| @73
+|~| @73
+|[+1#0000000&|S|c|r|a|t|c|h|]| |[|P|r|e|v|i|e|w|]| @37|1|,|1| @11|A|l@1
+|o+0&&|n|e> @71
+|o+0#0000001#e0e0e08|n|e| @11| +0#4040ff13#ffffff0@59
+|t+0#0000001#ffd7ff255|w|o| @11| +0#4040ff13#ffffff0@59
+|t+0#0000001#ffd7ff255|h|r|e@1| @9| +0#4040ff13#ffffff0@59
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+|-+2&&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/dumps/Test_quickfix_cwindow_1.dump b/src/testdir/dumps/Test_quickfix_cwindow_1.dump
new file mode 100644
index 0000000..c2b4872
--- /dev/null
+++ b/src/testdir/dumps/Test_quickfix_cwindow_1.dump
@@ -0,0 +1,12 @@
+|s+0&#ffffff0|o|m|e| @70
+|t|e|x|t| @70
+|w|i|t|h| @70
+|m|a|t|c|h|e|s| @67
+|~+0#4040ff13&| @73
+|X+1#0000000&|C|w|i|n|d|o|w| @48|1|,|4| @11|A|l@1
+>X+0#0000e05#ffff4012|C|w|i|n|d|o|w||+0#0000000&|1+0#af5f00255&| |c|o|l| |4|-|5||+0#0000000&| |s|o|m|e| @50
+|X+0#0000e05#ffffff0|C|w|i|n|d|o|w||+0#0000000&|2+0#af5f00255&| |c|o|l| |2|-|3||+0#0000000&| |t|e|x|t| @50
+|X+0#0000e05&|C|w|i|n|d|o|w||+0#0000000&|4+0#af5f00255&| |c|o|l| |6|-|7||+0#0000000&| |m|a|t|c|h|e|s| @47
+|~+0#4040ff13&| @73
+|[+3#0000000&|Q|u|i|c|k|f|i|x| |L|i|s|t|]| |:|v|i|m|g|r|e|p| |e| |X|C|w|i|n|d|o|w| @20|1|,|1| @12|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_quickfix_cwindow_2.dump b/src/testdir/dumps/Test_quickfix_cwindow_2.dump
new file mode 100644
index 0000000..0e7fa3c
--- /dev/null
+++ b/src/testdir/dumps/Test_quickfix_cwindow_2.dump
@@ -0,0 +1,12 @@
+|s+0&#ffffff0|o|m|e| @70
+|t>e|x|t| @70
+|w|i|t|h| @70
+|m|a|t|c|h|e|s| @67
+|~+0#4040ff13&| @73
+|X+3#0000000&|C|w|i|n|d|o|w| @48|2|,|2| @11|A|l@1
+|X+0#0000e05&|C|w|i|n|d|o|w||+0#0000000&|1+0#af5f00255&| |c|o|l| |4|-|5||+0#0000000&| |s|o|m|e| @50
+|X+0#0000e05#ffff4012|C|w|i|n|d|o|w||+0#0000000&|2+0#af5f00255&| |c|o|l| |2|-|3||+0#0000000&| |t|e|x|t| @50
+|X+0#0000e05#ffffff0|C|w|i|n|d|o|w||+0#0000000&|4+0#af5f00255&| |c|o|l| |6|-|7||+0#0000000&| |m|a|t|c|h|e|s| @47
+|~+0#4040ff13&| @73
+|[+1#0000000&|Q|u|i|c|k|f|i|x| |L|i|s|t|]| |:|v|i|m|g|r|e|p| |e| |X|C|w|i|n|d|o|w| @20|2|,|1| @12|A|l@1
+|:+0&&|c|n|e|x|t| @68
diff --git a/src/testdir/dumps/Test_quickfix_cwindow_3.dump b/src/testdir/dumps/Test_quickfix_cwindow_3.dump
new file mode 100644
index 0000000..d4a2e16
--- /dev/null
+++ b/src/testdir/dumps/Test_quickfix_cwindow_3.dump
@@ -0,0 +1,12 @@
+|s+0&#ffffff0|o|m|e| @70
+|t|e|x|t| @70
+|w|i|t|h| @70
+|m|a|t|c|h|e|s| @67
+|~+0#4040ff13&| @73
+|X+1#0000000&|C|w|i|n|d|o|w| @48|2|,|2| @11|A|l@1
+|X+0#0000e05&|C|w|i|n|d|o|w||+0#0000000&|1+0#af5f00255&| |c|o|l| |4|-|5||+0#0000000&| |s|o|m|e| @50
+>X+8#0000e05#ffff4012|C|w|i|n|d|o|w||+8#0000000&|2+8#af5f00255&| |c|o|l| |2|-|3||+8#0000000&| |t|e|x|t| @50
+|X+0#0000e05#ffffff0|C|w|i|n|d|o|w||+0#0000000&|4+0#af5f00255&| |c|o|l| |6|-|7||+0#0000000&| |m|a|t|c|h|e|s| @47
+|~+0#4040ff13&| @73
+|[+3#0000000&|Q|u|i|c|k|f|i|x| |L|i|s|t|]| |:|v|i|m|g|r|e|p| |e| |X|C|w|i|n|d|o|w| @20|2|,|1| @12|A|l@1
+|:+0&&| @73
diff --git a/src/testdir/dumps/Test_quickfix_cwindow_4.dump b/src/testdir/dumps/Test_quickfix_cwindow_4.dump
new file mode 100644
index 0000000..bb92ffc
--- /dev/null
+++ b/src/testdir/dumps/Test_quickfix_cwindow_4.dump
@@ -0,0 +1,12 @@
+|s+0&#ffffff0|o|m|e| @70
+|t|e|x|t| @70
+|w|i|t|h| @70
+|m|a|t|c|h|e|s| @67
+|~+0#4040ff13&| @73
+|X+1#0000000&|C|w|i|n|d|o|w| @48|2|,|2| @11|A|l@1
+|X+0#0000e05&|C|w|i|n|d|o|w||+0#0000000&|1+0#af5f00255&| |c|o|l| |4|-|5||+0#0000000&| |s|o|m|e| @50
+|X+0#0000e05#ffff4012|C|w|i|n|d|o|w||+0#0000000&|2+0#af5f00255&| |c|o|l| |2|-|3||+0#0000000&| |t|e|x|t| @50
+>X+8#0000e05#ffffff0|C|w|i|n|d|o|w||+8#0000000&|4+8#af5f00255&| |c|o|l| |6|-|7||+8#0000000&| |m|a|t|c|h|e|s| @47
+|~+0#4040ff13&| @73
+|[+3#0000000&|Q|u|i|c|k|f|i|x| |L|i|s|t|]| |:|v|i|m|g|r|e|p| |e| |X|C|w|i|n|d|o|w| @20|3|,|1| @12|A|l@1
+|:+0&&| @73
diff --git a/src/testdir/dumps/Test_quickfix_window_fails.dump b/src/testdir/dumps/Test_quickfix_window_fails.dump
new file mode 100644
index 0000000..769e30a
--- /dev/null
+++ b/src/testdir/dumps/Test_quickfix_window_fails.dump
@@ -0,0 +1,13 @@
+|a+0&#ffffff0|n|y|t|h|i|n|g| @66
+> @74
+|t|r|y| @71
+|X+3&&|q|u|i|c|k|f|i|x|F|a|i|l|s| |[|+|]| @38|2|,|0|-|1| @9|T|o|p
+| +0&&@74
+|t|r|y| @71
+@2|a|n|y|t|h|i|n|g| @64
+|X+1&&|q|u|i|c|k|f|i|x|F|a|i|l|s| |[|+|]| @38|2|,|0|-|1| @9|5|0|%
+| +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_quit_long_message.dump b/src/testdir/dumps/Test_quit_long_message.dump
new file mode 100644
index 0000000..270a233
--- /dev/null
+++ b/src/testdir/dumps/Test_quit_long_message.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_redraw_in_autocmd_1.dump b/src/testdir/dumps/Test_redraw_in_autocmd_1.dump
new file mode 100644
index 0000000..8939b70
--- /dev/null
+++ b/src/testdir/dumps/Test_redraw_in_autocmd_1.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|f|o|r| |i| |i|n| |r|a|n|g|e|(|3|)| @56
+|:| @1> @71
diff --git a/src/testdir/dumps/Test_redraw_in_autocmd_2.dump b/src/testdir/dumps/Test_redraw_in_autocmd_2.dump
new file mode 100644
index 0000000..17b7e04
--- /dev/null
+++ b/src/testdir/dumps/Test_redraw_in_autocmd_2.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&| @1|l|e|t| |i| |=> @64
+@75
diff --git a/src/testdir/dumps/Test_redrawstatus_in_autocmd_1.dump b/src/testdir/dumps/Test_redrawstatus_in_autocmd_1.dump
new file mode 100644
index 0000000..d6b3c0d
--- /dev/null
+++ b/src/testdir/dumps/Test_redrawstatus_in_autocmd_1.dump
@@ -0,0 +1,8 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+| +3#0000000&@45|:|e|c|h|o| |"|o|n|e|\|n|t|w|o|\|n|t|h|r|e@1|\|n|f|o|u|r|"
+|o+0&&|n|e| @71
+|t|w|o| @71
+|t|h|r|e@1| @69
+|f|o|u|r| @70
+|:|f|o@1|b|a|r> @67
diff --git a/src/testdir/dumps/Test_redrawstatus_in_autocmd_2.dump b/src/testdir/dumps/Test_redrawstatus_in_autocmd_2.dump
new file mode 100644
index 0000000..0c28c20
--- /dev/null
+++ b/src/testdir/dumps/Test_redrawstatus_in_autocmd_2.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +3#0000000&@55|:|f|o|r| |i|n| |i|n| |r|a|n|g|e|(|3|)
+|:+0&&|f|o|r| |i|n| |i|n| |r|a|n|g|e|(|3|)> @55
diff --git a/src/testdir/dumps/Test_redrawstatus_in_autocmd_3.dump b/src/testdir/dumps/Test_redrawstatus_in_autocmd_3.dump
new file mode 100644
index 0000000..9b64605
--- /dev/null
+++ b/src/testdir/dumps/Test_redrawstatus_in_autocmd_3.dump
@@ -0,0 +1,8 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +3#0000000&@55|:|f|o|r| |i|n| |i|n| |r|a|n|g|e|(|3|)
+|:+0&&|f|o|r| |i|n| |i|n| |r|a|n|g|e|(|3|)| @55
+|:| @1|:|e|n|d|f|o|r> @64
diff --git a/src/testdir/dumps/Test_redrawstatus_in_autocmd_4.dump b/src/testdir/dumps/Test_redrawstatus_in_autocmd_4.dump
new file mode 100644
index 0000000..8ed408c
--- /dev/null
+++ b/src/testdir/dumps/Test_redrawstatus_in_autocmd_4.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +3#0000000&@55|:|f|o|r| |i|n| |i|n| |r|a|n|g|e|(|3|)
+|:+0&&|f|o|r| |i|n| |i|n| |r|a|n|g|e|(|3|)> @55
+@75
diff --git a/src/testdir/dumps/Test_redrawstatus_in_autocmd_5.dump b/src/testdir/dumps/Test_redrawstatus_in_autocmd_5.dump
new file mode 100644
index 0000000..d3e28a8
--- /dev/null
+++ b/src/testdir/dumps/Test_redrawstatus_in_autocmd_5.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +3#0000000&@66|:@1|e|n|d|f|o|r
+|:+0&&|f|o|r| |i|n| |i|n| |r|a|n|g|e|(|3|)| @55
+|:| @1|:|e|n|d|f|o|r> @64
diff --git a/src/testdir/dumps/Test_relativenumber_callback_1.dump b/src/testdir/dumps/Test_relativenumber_callback_1.dump
new file mode 100644
index 0000000..f25612d
--- /dev/null
+++ b/src/testdir/dumps/Test_relativenumber_callback_1.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|0| >a+0#0000000&@4| @65
+| +0#af5f00255&@1|1| |b+0#0000000&@4| @65
+| +0#af5f00255&@1|2| |c+0#0000000&@4| @65
+| +0#af5f00255&@1|3| |d+0#0000000&@4| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|4|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_relnr_colors_1.dump b/src/testdir/dumps/Test_relnr_colors_1.dump
new file mode 100644
index 0000000..ad0ddd9
--- /dev/null
+++ b/src/testdir/dumps/Test_relnr_colors_1.dump
@@ -0,0 +1,10 @@
+| +0#ff404010#ffffff0@1|4| |1+0#0000000&|0|6| @42
+| +0#ff404010&@1|3| |1+0#0000000&|0|7| @42
+| +0#ff404010&@1|2| |1+0#0000000&|0|8| @42
+| +0#ff404010&@1|1| |1+0#0000000&|0|9| @42
+|1+0#ff404010&@2| >1+0#0000000&@1|0| @42
+| +0#ff404010&@1|1| |1+0#0000000&@2| @42
+| +0#ff404010&@1|2| |1+0#0000000&@1|2| @42
+| +0#ff404010&@1|3| |1+0#0000000&@1|3| @42
+| +0#ff404010&@1|4| |1+0#0000000&@1|4| @42
+@32|1@2|,|1| @8|5@1|%|
diff --git a/src/testdir/dumps/Test_relnr_colors_2.dump b/src/testdir/dumps/Test_relnr_colors_2.dump
new file mode 100644
index 0000000..e9de7fa
--- /dev/null
+++ b/src/testdir/dumps/Test_relnr_colors_2.dump
@@ -0,0 +1,10 @@
+| +0#4040ff13#ffffff0@1|4| |1+0#0000000&|0|6| @42
+| +0#4040ff13&@1|3| |1+0#0000000&|0|7| @42
+| +0#4040ff13&@1|2| |1+0#0000000&|0|8| @42
+| +0#4040ff13&@1|1| |1+0#0000000&|0|9| @42
+|1+0#ff404010&@2| >1+0#0000000&@1|0| @42
+| +0#ff404010&@1|1| |1+0#0000000&@2| @42
+| +0#ff404010&@1|2| |1+0#0000000&@1|2| @42
+| +0#ff404010&@1|3| |1+0#0000000&@1|3| @42
+| +0#ff404010&@1|4| |1+0#0000000&@1|4| @42
+|:| @30|1@2|,|1| @8|5@1|%|
diff --git a/src/testdir/dumps/Test_relnr_colors_3.dump b/src/testdir/dumps/Test_relnr_colors_3.dump
new file mode 100644
index 0000000..65f637a
--- /dev/null
+++ b/src/testdir/dumps/Test_relnr_colors_3.dump
@@ -0,0 +1,10 @@
+| +0#4040ff13#ffffff0@1|4| |1+0#0000000&|0|6| @42
+| +0#4040ff13&@1|3| |1+0#0000000&|0|7| @42
+| +0#4040ff13&@1|2| |1+0#0000000&|0|8| @42
+| +0#4040ff13&@1|1| |1+0#0000000&|0|9| @42
+|1+0#ff404010&@2| >1+0#0000000&@1|0| @42
+| +0#40ff4011&@1|1| |1+0#0000000&@2| @42
+| +0#40ff4011&@1|2| |1+0#0000000&@1|2| @42
+| +0#40ff4011&@1|3| |1+0#0000000&@1|3| @42
+| +0#40ff4011&@1|4| |1+0#0000000&@1|4| @42
+|:| @30|1@2|,|1| @8|5@1|%|
diff --git a/src/testdir/dumps/Test_relnr_colors_4.dump b/src/testdir/dumps/Test_relnr_colors_4.dump
new file mode 100644
index 0000000..8202819
--- /dev/null
+++ b/src/testdir/dumps/Test_relnr_colors_4.dump
@@ -0,0 +1,10 @@
+| +0#ff404010#ffffff0@1|4| |1+0#0000000&|0|6| @42
+| +0#ff404010&@1|3| |1+0#0000000&|0|7| @42
+| +0#ff404010&@1|2| |1+0#0000000&|0|8| @42
+| +0#ff404010&@1|1| |1+0#0000000&|0|9| @42
+|1+0#ff404010&@2| >1+0#0000000&@1|0| @42
+| +0#40ff4011&@1|1| |1+0#0000000&@2| @42
+| +0#40ff4011&@1|2| |1+0#0000000&@1|2| @42
+| +0#40ff4011&@1|3| |1+0#0000000&@1|3| @42
+| +0#40ff4011&@1|4| |1+0#0000000&@1|4| @42
+|:|h|i| |c|l|e|a|r| |L|i|n|e|N|r|A|b|o|v|e| @10|1@2|,|1| @8|5@1|%|
diff --git a/src/testdir/dumps/Test_scroll_no_region_1.dump b/src/testdir/dumps/Test_scroll_no_region_1.dump
new file mode 100644
index 0000000..c0c21b5
--- /dev/null
+++ b/src/testdir/dumps/Test_scroll_no_region_1.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+| +0&&@74
diff --git a/src/testdir/dumps/Test_scroll_no_region_2.dump b/src/testdir/dumps/Test_scroll_no_region_2.dump
new file mode 100644
index 0000000..b8f7c03
--- /dev/null
+++ b/src/testdir/dumps/Test_scroll_no_region_2.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+|2| @73
+>4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1| @11|T|o|p
+| +0&&@74
diff --git a/src/testdir/dumps/Test_scroll_no_region_3.dump b/src/testdir/dumps/Test_scroll_no_region_3.dump
new file mode 100644
index 0000000..7ec5a3c
--- /dev/null
+++ b/src/testdir/dumps/Test_scroll_no_region_3.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+|2| @73
+|4| @73
+|5| @73
+>3| @73
+|6| @73
+|7| @73
+|8| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|5|,|1| @11|T|o|p
+| +0&&@74
diff --git a/src/testdir/dumps/Test_scroll_no_region_4.dump b/src/testdir/dumps/Test_scroll_no_region_4.dump
new file mode 100644
index 0000000..835a991
--- /dev/null
+++ b/src/testdir/dumps/Test_scroll_no_region_4.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+|2| @73
+>3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|:|s|e|t| |l|a|s|t|s|t|a|t|u|s|=|0| @39|3|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_scroll_no_region_5.dump b/src/testdir/dumps/Test_scroll_no_region_5.dump
new file mode 100644
index 0000000..98152c7
--- /dev/null
+++ b/src/testdir/dumps/Test_scroll_no_region_5.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+|2| @73
+>4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|1|0| @72
+@57|3|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_scroll_no_region_6.dump b/src/testdir/dumps/Test_scroll_no_region_6.dump
new file mode 100644
index 0000000..0394608
--- /dev/null
+++ b/src/testdir/dumps/Test_scroll_no_region_6.dump
@@ -0,0 +1,10 @@
+|1+0&#ffffff0| @73
+|2| @73
+|4| @73
+|5| @73
+>3| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+@57|5|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_scrollbar_on_wide_char.dump b/src/testdir/dumps/Test_scrollbar_on_wide_char.dump
new file mode 100644
index 0000000..04d7e7e
--- /dev/null
+++ b/src/testdir/dumps/Test_scrollbar_on_wide_char.dump
@@ -0,0 +1,10 @@
+|a+0&#ffffff0@1|0|b@1> @69
+|a+0#0000001#e0e0e08@1|0|b@1| @9| +0#0000000#0000001|å•Š*0&#ffffff0| +&@56
+|a+0#0000001#ffd7ff255@1|1|b@1| @9| +0#0000000#0000001| +0&#ffffff0|哦*&| +&@55
+|a+0#0000001#ffd7ff255@1|2|b@1| @9| +0#0000000#0000001|呃*0&#ffffff0@1| +&@54
+|a+0#0000001#ffd7ff255@1|3|b@1| @9| +0#0000000#0000001| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255@1|4|b@1| @9| +0#0000000#0000001| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255@1|5|b@1| @9| +0#0000000#0000001| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255@1|6|b@1| @9| +0#0000000#a8a8a8255| +0&#ffffff0@58
+|a+0#0000001#ffd7ff255@1|7|b@1| @9| +0#0000000#a8a8a8255| +0&#ffffff0@58
+|-+2&&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |1|0| +0#0000000&@32
diff --git a/src/testdir/dumps/Test_searchstat_1.dump b/src/testdir/dumps/Test_searchstat_1.dump
new file mode 100644
index 0000000..52b13f2
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstat_1.dump
@@ -0,0 +1,10 @@
+|f+0&#ffffff0|o@1|b|a|r| @68
+>f|i|n|d| |t|h|i|s| @65
+|f|o@4|b|a|r| @65
+|f|o|b|a| @70
+|f|o@1|b|a|r| @68
+|f|o@1|b|a|r| @68
+|f|o@1| @71
+|f|o@4|b|a|r| @65
+|f|o|b|a| @70
+|/|f|i|n|d| |t|h|i|s| @29|[|1|/|2|]| @11|2|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_searchstat_2.dump b/src/testdir/dumps/Test_searchstat_2.dump
new file mode 100644
index 0000000..a6ced4f
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstat_2.dump
@@ -0,0 +1,10 @@
+|f+0&#ffffff0|o@1|b|a|r| @68
+>f|i|n|d| |t|h|i|s| @65
+|f|o@4|b|a|r| @65
+|f|o|b|a| @70
+|f|o@1|b|a|r| @68
+|f|o@1|b|a|r| @68
+|f|o@1| @71
+|f|o@4|b|a|r| @65
+|f|o|b|a| @70
+@40|[|1|/|2|]| @11|2|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_searchstat_3.dump b/src/testdir/dumps/Test_searchstat_3.dump
new file mode 100644
index 0000000..a21b47e
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstat_3.dump
@@ -0,0 +1,10 @@
+|i+0&#ffffff0|f| @72
+>++0#0000e05#a8a8a8255|-@1| @1|2| |l|i|n|e|s|:| |f|o@1|-@57
+|e+0#0000000#ffffff0|n|d|i|f| @69
+@75
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|/+0#0000000&|f|o@1| @35|[|1|/|2|]| @11|2|,|2|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_searchstat_4.dump b/src/testdir/dumps/Test_searchstat_4.dump
new file mode 100644
index 0000000..899c8fb
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstat_4.dump
@@ -0,0 +1,10 @@
+|t+0&#ffffff0|h|i|s| |i|s| |s+1&&|o|m|e|t|h|i|n|g| +0&&@57
+@75
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|1|/|1|]| @69
+|/+0&&|s|o|m|e|t|h|i|n|g> @64
diff --git a/src/testdir/dumps/Test_searchstat_inc_1.dump b/src/testdir/dumps/Test_searchstat_inc_1.dump
new file mode 100644
index 0000000..aa4b6c5
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstat_inc_1.dump
@@ -0,0 +1,10 @@
+| +1&#ffffff0@74
+|a+0#0000001#ffff4012|b|c|-+0#0000000#ffffff0@1|c| @68
+|-@7|a+1&&|b|c| +0&&@63
+|-@1|a+0#0000001#ffff4012|b|c| +0#0000000#ffffff0@69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|/+0#0000000&|a|b|c> @70
diff --git a/src/testdir/dumps/Test_searchstat_inc_2.dump b/src/testdir/dumps/Test_searchstat_inc_2.dump
new file mode 100644
index 0000000..3b580ef
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstat_inc_2.dump
@@ -0,0 +1,10 @@
+|3+1&#ffffff0|/|3| @71
+|a+0#0000001#ffff4012|b|c|-+0#0000000#ffffff0@1|c| @68
+|-@7|a+0#0000001#ffff4012|b|c| +0#0000000#ffffff0@63
+|-@1|a+1&&|b|c| +0&&@69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|/+0#0000000&|a|b|c> @70
diff --git a/src/testdir/dumps/Test_searchstat_inc_3.dump b/src/testdir/dumps/Test_searchstat_inc_3.dump
new file mode 100644
index 0000000..a57b4d1
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstat_inc_3.dump
@@ -0,0 +1,10 @@
+|1+1&#ffffff0|/|3| @71
+|a|b|c|-+0&&@1|c| @68
+|-@7|a+0#0000001#ffff4012|b|c| +0#0000000#ffffff0@63
+|-@1|a+0#0000001#ffff4012|b|c| +0#0000000#ffffff0@69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|/+0#0000000&|a|b|c> @70
diff --git a/src/testdir/dumps/Test_searchstatgd_1.dump b/src/testdir/dumps/Test_searchstatgd_1.dump
new file mode 100644
index 0000000..672af91
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstatgd_1.dump
@@ -0,0 +1,10 @@
+|i+0&#ffffff0|n|t| |c|a|t|;| @66
+|i|n|t| >d+0&#ffff4012|o|g|;+0&#ffffff0| @66
+|c|a|t| |=| |d+0&#ffff4012|o|g|;+0&#ffffff0| @64
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|/+0#0000000&|d|o|g| @35|[|1|/|2|]| @11|2|,|5| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_searchstatgd_2.dump b/src/testdir/dumps/Test_searchstatgd_2.dump
new file mode 100644
index 0000000..ea1ac4d
--- /dev/null
+++ b/src/testdir/dumps/Test_searchstatgd_2.dump
@@ -0,0 +1,10 @@
+|i+0&#ffffff0|n|t| >c+0&#ffff4012|a|t|;+0&#ffffff0| @66
+|i|n|t| |d|o|g|;| @66
+|c+0&#ffff4012|a|t| +0&#ffffff0|=| |d|o|g|;| @64
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|5| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_setcellwidths_dump_1.dump b/src/testdir/dumps/Test_setcellwidths_dump_1.dump
new file mode 100644
index 0000000..0bb2788
--- /dev/null
+++ b/src/testdir/dumps/Test_setcellwidths_dump_1.dump
@@ -0,0 +1,6 @@
+>î—¿+0&#ffffff0|D|e|s|k|t|o|p| @66
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_setcellwidths_dump_2.dump b/src/testdir/dumps/Test_setcellwidths_dump_2.dump
new file mode 100644
index 0000000..f7b4b0f
--- /dev/null
+++ b/src/testdir/dumps/Test_setcellwidths_dump_2.dump
@@ -0,0 +1,6 @@
+>î—¿+0&#ffffff0| |D|e|s|k|t|o|p| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_sign_cursor_1.dump b/src/testdir/dumps/Test_sign_cursor_1.dump
new file mode 100644
index 0000000..9ca4481
--- /dev/null
+++ b/src/testdir/dumps/Test_sign_cursor_1.dump
@@ -0,0 +1,6 @@
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@72
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@1| @70
+|=+0&#ffff4012|>>m+0&#ffffff0@3| @68
+| +0#0000e05#a8a8a8255@1|y+0#0000000#ffffff0@3| @68
+|~+0#4040ff13&| @73
+| +0#0000000&@56|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_sign_cursor_2.dump b/src/testdir/dumps/Test_sign_cursor_2.dump
new file mode 100644
index 0000000..2d10a2c
--- /dev/null
+++ b/src/testdir/dumps/Test_sign_cursor_2.dump
@@ -0,0 +1,6 @@
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@72
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@1| @70
+|-+0&#ffff4012|)>m+0&#ffffff0@3| @68
+| +0#0000e05#a8a8a8255@1|y+0#0000000#ffffff0@3| @68
+|~+0#4040ff13&| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |s|1| |t|e|x|t|=|-|)| @33|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_sign_cursor_3.dump b/src/testdir/dumps/Test_sign_cursor_3.dump
new file mode 100644
index 0000000..88264e4
--- /dev/null
+++ b/src/testdir/dumps/Test_sign_cursor_3.dump
@@ -0,0 +1,6 @@
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@72
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@1| @70
+|-+0&#ffff4012|)>m+0#0000001#ffd7ff255@3| @68
+| +0#0000e05#a8a8a8255@1|y+0#0000000#ffffff0@3| @68
+|~+0#4040ff13&| @73
+|:+0#0000000&|s|i|g|n| |p|l|a|c|e| |1@1| |l|i|n|e|=|2| |n|a|m|e|=|s|2| @27|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_sign_cursor_4.dump b/src/testdir/dumps/Test_sign_cursor_4.dump
new file mode 100644
index 0000000..92643c3
--- /dev/null
+++ b/src/testdir/dumps/Test_sign_cursor_4.dump
@@ -0,0 +1,6 @@
+|x+0&#ffffff0@74
+>m@3| @70
+|y@3| @70
+|~+0#4040ff13&| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e| |1|0| @40|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_sign_cursor_5.dump b/src/testdir/dumps/Test_sign_cursor_5.dump
new file mode 100644
index 0000000..2f256a5
--- /dev/null
+++ b/src/testdir/dumps/Test_sign_cursor_5.dump
@@ -0,0 +1,6 @@
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@72
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@1| @70
+| +0#0000e05#a8a8a8255@1>m+8#0000001#40ff4011@3| @68
+| +0#0000e05#a8a8a8255@1|y+0#0000000#ffffff0@3| @68
+|~+0#4040ff13&| @73
+|:+0#0000000&| @55|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_sign_cursor_6.dump b/src/testdir/dumps/Test_sign_cursor_6.dump
new file mode 100644
index 0000000..efd9042
--- /dev/null
+++ b/src/testdir/dumps/Test_sign_cursor_6.dump
@@ -0,0 +1,6 @@
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@72
+| +0#0000e05#a8a8a8255@1|x+0#0000000#ffffff0@1| @70
+| +0#0000e05#a8a8a8255@1>m+8#0000001#ffd7ff255@3| @68
+| +0#0000e05#a8a8a8255@1|y+0#0000000#ffffff0@3| @68
+|~+0#4040ff13&| @73
+|:+0#0000000&| @55|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_diff_1.dump b/src/testdir/dumps/Test_smooth_diff_1.dump
new file mode 100644
index 0000000..4e2696e
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_diff_1.dump
@@ -0,0 +1,8 @@
+|-+0#0000e05#a8a8a8255| >j+0#0000000#ffffff0|u|s|t| |s|o|m|e| |t|e|x|t| |h|e|r|e| @53
+|~+0#4040ff13&| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+|-+0#0000e05#a8a8a8255| |j+0#0000000#ffffff0|u|s|t| |s|o|m|e| |t|e|x|t| |h|e|r|e| @53
+|~+0#4040ff13&| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_smooth_list_1.dump b/src/testdir/dumps/Test_smooth_list_1.dump
new file mode 100644
index 0000000..b155df2
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_list_1.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y|
+>l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t|
+|v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g|
+|t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |$+0#4040ff13&| +0#0000000&@18
+|t|h|r|e@1|$+0#4040ff13&| +0#0000000&@33
+|~+0#4040ff13&| @38
+|~| @38
+| +0#0000000&@21|2|,|8|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_list_2.dump b/src/testdir/dumps/Test_smooth_list_2.dump
new file mode 100644
index 0000000..82d17b4
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_list_2.dump
@@ -0,0 +1,8 @@
+|#+0#4040ff13#ffffff0|e+0#0000000&|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y|
+>l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t|
+|v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g|
+|t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |$+0#4040ff13&| +0#0000000&@18
+|t|h|r|e@1|$+0#4040ff13&| +0#0000000&@33
+|~+0#4040ff13&| @38
+|~| @38
+|:+0#0000000&|s|e|t| |l|i|s|t|c|h|a|r|s|+|=|p|r|e|c|e|d|2|,|8|1| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_long_1.dump b/src/testdir/dumps/Test_smooth_long_1.dump
new file mode 100644
index 0000000..86c27ed
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_1.dump
@@ -0,0 +1,6 @@
+|L+0&#ffffff0|i|n|e| |w|i|t|h> |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t
+| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+| @21|3|,|1|0| @9|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_10.dump b/src/testdir/dumps/Test_smooth_long_10.dump
new file mode 100644
index 0000000..10634be
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_10.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|o+0#0000000&|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |e|n|d| @11
+>f|o|u|r| @35
+|~+0#4040ff13&| @38
+|~| @38
+|:+0#0000000&|s|e|t| |s|c|r|o|l@1|o| @9|4|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_long_11.dump b/src/testdir/dumps/Test_smooth_long_11.dump
new file mode 100644
index 0000000..0aa7a4b
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_11.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |e|n|d| @11
+>f|o|u|r| @35
+|:|s|e|t| |s|c|r|o|l@1|o| @9|4|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_long_12.dump b/src/testdir/dumps/Test_smooth_long_12.dump
new file mode 100644
index 0000000..10634be
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_12.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|o+0#0000000&|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |e|n|d| @11
+>f|o|u|r| @35
+|~+0#4040ff13&| @38
+|~| @38
+|:+0#0000000&|s|e|t| |s|c|r|o|l@1|o| @9|4|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_long_13.dump b/src/testdir/dumps/Test_smooth_long_13.dump
new file mode 100644
index 0000000..62d7992
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_13.dump
@@ -0,0 +1,6 @@
+>f+0&#ffffff0|o|u|r| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|4|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_long_14.dump b/src/testdir/dumps/Test_smooth_long_14.dump
new file mode 100644
index 0000000..7f5bff7
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_14.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |e|n|d| @11
+>f|o|u|r| @35
+@22|4|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_long_15.dump b/src/testdir/dumps/Test_smooth_long_15.dump
new file mode 100644
index 0000000..b82fca5
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_15.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |e|n|d| @11
+|f|o|u>r| @35
+@22|4|,|4| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_long_2.dump b/src/testdir/dumps/Test_smooth_long_2.dump
new file mode 100644
index 0000000..6800ba4
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_2.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t>s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w
+| @21|3|,|5|0| @9|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_3.dump b/src/testdir/dumps/Test_smooth_long_3.dump
new file mode 100644
index 0000000..63d9794
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_3.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2| +0#0000000&|l|o|t|s| >o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h|
+|l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s
+| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f|
+|t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t
+| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+| @21|3|,|2|5|0| @8|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_4.dump b/src/testdir/dumps/Test_smooth_long_4.dump
new file mode 100644
index 0000000..479dee4
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_4.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+|x|t| |w|i|t|h| |l>o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w
+| @21|3|,|2|1|0| @8|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_5.dump b/src/testdir/dumps/Test_smooth_long_5.dump
new file mode 100644
index 0000000..0b728b2
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_5.dump
@@ -0,0 +1,6 @@
+|L+0&#ffffff0|i|n|e| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t
+| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i>t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+| @21|3|,|1|7|0| @8|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_6.dump b/src/testdir/dumps/Test_smooth_long_6.dump
new file mode 100644
index 0000000..ba48c28
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_6.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f> |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w
+| @21|3|,|9|0| @9|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_7.dump b/src/testdir/dumps/Test_smooth_long_7.dump
new file mode 100644
index 0000000..222e001
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_7.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i>t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w
+| @21|3|,|1|7|0| @8|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_8.dump b/src/testdir/dumps/Test_smooth_long_8.dump
new file mode 100644
index 0000000..6d2f13c
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_8.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x>t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w
+| @21|3|,|1|3|0| @8|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_9.dump b/src/testdir/dumps/Test_smooth_long_9.dump
new file mode 100644
index 0000000..6d2f13c
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_9.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t
+|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o
+|t|s| |o|f| |t|e|x>t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o
+|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e
+|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w
+| @21|3|,|1|3|0| @8|6@1|%|
diff --git a/src/testdir/dumps/Test_smooth_long_showbreak_1.dump b/src/testdir/dumps/Test_smooth_long_showbreak_1.dump
new file mode 100644
index 0000000..0071dee
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_showbreak_1.dump
@@ -0,0 +1,6 @@
+>w+0&#ffffff0|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |i|n| |o|n|e| |l|i|n|e| |w|i|t|h| |l|o|t|s|
+|++0#4040ff13&@2| |o+0#0000000&|f| |t|e|x|t| |i|n| |o|n|e| |l|i|n|e| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x
+|++0#4040ff13&@2| |t+0#0000000&| |i|n| |o|n|e| |l|i|n|e| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |i|n| |o
+|++0#4040ff13&@2| |n+0#0000000&|e| |l|i|n|e| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |i|n| |o|n|e| |l|i|n
+|++0#4040ff13&@2| |e+0#0000000&| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |i|n| |o|n|e| |l|i|n|e| @4
+@22|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_long_showbreak_2.dump b/src/testdir/dumps/Test_smooth_long_showbreak_2.dump
new file mode 100644
index 0000000..646b8e2
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_long_showbreak_2.dump
@@ -0,0 +1,6 @@
+|++0#4040ff13#ffffff0@2| >o+0#0000000&|f| |t|e|x|t| |i|n| |o|n|e| |l|i|n|e| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x
+|++0#4040ff13&@2| |t+0#0000000&| |i|n| |o|n|e| |l|i|n|e| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |i|n| |o
+|++0#4040ff13&@2| |n+0#0000000&|e| |l|i|n|e| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |i|n| |o|n|e| |l|i|n
+|++0#4040ff13&@2| |e+0#0000000&| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |i|n| |o|n|e| |l|i|n|e| @4
+|~+0#4040ff13&| @38
+| +0#0000000&@21|1|,|4|1|-|4|5| @6|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_number_1.dump b/src/testdir/dumps/Test_smooth_number_1.dump
new file mode 100644
index 0000000..7953319
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_number_1.dump
@@ -0,0 +1,12 @@
+| +0#af5f00255#ffffff0@1|1| |o+0#0000000&|n|e| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o
+|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o
+|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @12
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o
+|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @2
+| +0#af5f00255&@1|3| >l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|4| |l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|5| |l+0#0000000&|i|n|e| @31
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_number_2.dump b/src/testdir/dumps/Test_smooth_number_2.dump
new file mode 100644
index 0000000..110775d
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_number_2.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2|w+0#0000000&|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o
+|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @12
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o
+|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @2
+| +0#af5f00255&@1|3| >l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|4| |l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|5| |l+0#0000000&|i|n|e| @31
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_number_3.dump b/src/testdir/dumps/Test_smooth_number_3.dump
new file mode 100644
index 0000000..3f35de6
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_number_3.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2|w+0#0000000&|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @12
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o
+|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @2
+| +0#af5f00255&@1|3| >l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|4| |l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|5| |l+0#0000000&|i|n|e| @31
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_number_4.dump b/src/testdir/dumps/Test_smooth_number_4.dump
new file mode 100644
index 0000000..6ea393d
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_number_4.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2| +0#af5f00255&|d+0#0000000&| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @4
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o
+| +0#af5f00255&@3|n+0#0000000&|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r
+| +0#af5f00255&@3|d+0#0000000&| @34
+| +0#af5f00255&@1|3| >l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|4| |l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|5| |l+0#0000000&|i|n|e| @31
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|:+0#0000000&|s|e|t| |c|p|o|-|=|n| @10|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_number_5.dump b/src/testdir/dumps/Test_smooth_number_5.dump
new file mode 100644
index 0000000..4602de2
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_number_5.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2| +0#af5f00255&|r+0#0000000&|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r
+| +0#af5f00255&@3|d+0#0000000&| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @4
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o
+| +0#af5f00255&@3|n+0#0000000&|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r
+| +0#af5f00255&@3|d+0#0000000&| @34
+| +0#af5f00255&@1|3| >l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|4| |l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|5| |l+0#0000000&|i|n|e| @31
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|:+0#0000000&|s|e|t| |c|p|o|-|=|n| @10|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_number_6.dump b/src/testdir/dumps/Test_smooth_number_6.dump
new file mode 100644
index 0000000..bec58b5
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_number_6.dump
@@ -0,0 +1,12 @@
+| +0#af5f00255#ffffff0@1|1| |o+0#0000000&|n|e| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o
+| +0#af5f00255&@3|r+0#0000000&|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r
+| +0#af5f00255&@3|d+0#0000000&| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @4
+| +0#af5f00255&@1|2| |t+0#0000000&|w|o| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o
+| +0#af5f00255&@3|n+0#0000000&|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r
+| +0#af5f00255&@3|d+0#0000000&| @34
+| +0#af5f00255&@1|3| >l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|4| |l+0#0000000&|i|n|e| @31
+| +0#af5f00255&@1|5| |l+0#0000000&|i|n|e| @31
+|~+0#4040ff13&| @38
+|~| @38
+|:+0#0000000&|s|e|t| |c|p|o|-|=|n| @10|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_number_7.dump b/src/testdir/dumps/Test_smooth_number_7.dump
new file mode 100644
index 0000000..b617dc0
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_number_7.dump
@@ -0,0 +1,12 @@
+|2+0#af5f00255#ffffff0|<+0#4040ff13&@2>o+0#0000000&|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e
+| +0#af5f00255&@3|x+0#0000000&|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r
+| +0#af5f00255&@3|y+0#0000000&| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g
+| +0#af5f00255&@3| +0#0000000&|t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t| |v|e|r|y| |l|o|n|g| |t|e|x|t|
+| +0#af5f00255&@1|1| |t+0#0000000&|h|r|e@1| @30
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|-+0#0000000&@1|N|o|.@2|e|r|-@1| @10|2|,|3|7| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_one_long_1.dump b/src/testdir/dumps/Test_smooth_one_long_1.dump
new file mode 100644
index 0000000..82c1ea3
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_one_long_1.dump
@@ -0,0 +1,6 @@
+>w+0&#ffffff0|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h
+| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t
+|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f
+| |t|e|x|t| @34
+|~+0#4040ff13&| @38
+| +0#0000000&@21|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_one_long_2.dump b/src/testdir/dumps/Test_smooth_one_long_2.dump
new file mode 100644
index 0000000..46e4f59
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_one_long_2.dump
@@ -0,0 +1,6 @@
+|<+0#4040ff13#ffffff0@2|t+0#0000000&|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t
+>s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f
+| |t|e|x|t| @34
+|~+0#4040ff13&| @38
+|~| @38
+| +0#0000000&@21|1|,|8|1| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_smooth_wrap_1.dump b/src/testdir/dumps/Test_smooth_wrap_1.dump
new file mode 100644
index 0000000..0b52d40
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_wrap_1.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|h+0#0000000&| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+>L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+@22|3|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_smooth_wrap_2.dump b/src/testdir/dumps/Test_smooth_wrap_2.dump
new file mode 100644
index 0000000..65a357d
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_wrap_2.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|h+0#0000000&| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+>L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+@22|4|,|1| @10|2|5|%|
diff --git a/src/testdir/dumps/Test_smooth_wrap_3.dump b/src/testdir/dumps/Test_smooth_wrap_3.dump
new file mode 100644
index 0000000..4bf5130
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_wrap_3.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|h+0#0000000&| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+>L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+@22|5|,|1| @10|5|0|%|
diff --git a/src/testdir/dumps/Test_smooth_wrap_4.dump b/src/testdir/dumps/Test_smooth_wrap_4.dump
new file mode 100644
index 0000000..c23f494
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_wrap_4.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2|h+0#0000000&| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+>L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+@22|7|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_wrap_5.dump b/src/testdir/dumps/Test_smooth_wrap_5.dump
new file mode 100644
index 0000000..31f1544
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_wrap_5.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffffff0@2>h+0#0000000&| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+@22|5|,|8|4| @9|B|o|t|
diff --git a/src/testdir/dumps/Test_smooth_wrap_6.dump b/src/testdir/dumps/Test_smooth_wrap_6.dump
new file mode 100644
index 0000000..04a17f9
--- /dev/null
+++ b/src/testdir/dumps/Test_smooth_wrap_6.dump
@@ -0,0 +1,8 @@
+>L+0&#ffffff0|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|L|i|n|e| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h|
+|s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t|
+|w|i|t|h| |s|o|m|e| |t|e|x|t| |w|i|t|h| |s|o|m|e| |t|e|x|t| @10
+|@+0#4040ff13&@2| @36
+| +0#0000000&@21|5|,|1| @10|8|0|%|
diff --git a/src/testdir/dumps/Test_smoothscroll_1.dump b/src/testdir/dumps/Test_smoothscroll_1.dump
new file mode 100644
index 0000000..7a8c020
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_1.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+>l|i|n|e| @35
+|l|i|n|e| @35
+|l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+| +0#0000000&@21|5|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smoothscroll_2.dump b/src/testdir/dumps/Test_smoothscroll_2.dump
new file mode 100644
index 0000000..e8ca1a7
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_2.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2|d+0#0000000&| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+>l|i|n|e| @35
+|l|i|n|e| @35
+|l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|5|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smoothscroll_3.dump b/src/testdir/dumps/Test_smoothscroll_3.dump
new file mode 100644
index 0000000..5c2ed94
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_3.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2|d+0#0000000&| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+>l|i|n|e| @35
+|l|i|n|e| @35
+|l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|5|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smoothscroll_4.dump b/src/testdir/dumps/Test_smoothscroll_4.dump
new file mode 100644
index 0000000..63539c9
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_4.dump
@@ -0,0 +1,12 @@
+|l+0&#ffffff0|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smoothscroll_5.dump b/src/testdir/dumps/Test_smoothscroll_5.dump
new file mode 100644
index 0000000..b88bd3d
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_5.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2|d+0#0000000&| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smoothscroll_6.dump b/src/testdir/dumps/Test_smoothscroll_6.dump
new file mode 100644
index 0000000..fb73356
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_6.dump
@@ -0,0 +1,12 @@
+|<+0#4040ff13#ffffff0@2|d+0#0000000&| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smoothscroll_7.dump b/src/testdir/dumps/Test_smoothscroll_7.dump
new file mode 100644
index 0000000..0579f6f
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_7.dump
@@ -0,0 +1,12 @@
+|w+0&#ffffff0|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+|~| @38
+| +0#0000000&@21|7|,|1| @10|B|o|t|
diff --git a/src/testdir/dumps/Test_smoothscroll_8.dump b/src/testdir/dumps/Test_smoothscroll_8.dump
new file mode 100644
index 0000000..b7a5bce
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_8.dump
@@ -0,0 +1,12 @@
+|l+0&#ffffff0|i|n|e| |o|n|e| @31
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d|
+|w|o|r|d| |w|o|r|d| |w|o|r|d| |w|o|r|d| @20
+|l|i|n|e| |t|h|r|e@1| @29
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d|
+|l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| |l|o|n|g| |w|o|r|d| @10
+|l|i|n|e| @35
+|l|i|n|e| @35
+>l|i|n|e| @35
+|~+0#4040ff13&| @38
+| +0#0000000&@21|7|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_smoothscroll_zero_1.dump b/src/testdir/dumps/Test_smoothscroll_zero_1.dump
new file mode 100644
index 0000000..5a99f57
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_zero_1.dump
@@ -0,0 +1,6 @@
+> +0&#ffffff0@59
+@60
+@60
+@60
+@60
+@60
diff --git a/src/testdir/dumps/Test_smoothscroll_zero_2.dump b/src/testdir/dumps/Test_smoothscroll_zero_2.dump
new file mode 100644
index 0000000..1879381
--- /dev/null
+++ b/src/testdir/dumps/Test_smoothscroll_zero_2.dump
@@ -0,0 +1,6 @@
+|:+0&#ffffff0|s|i|l| |n|o|r|m| |^|W|^|N| @45
+> @59
+@60
+@60
+@60
+@60
diff --git a/src/testdir/dumps/Test_spell_1.dump b/src/testdir/dumps/Test_spell_1.dump
new file mode 100644
index 0000000..2025be1
--- /dev/null
+++ b/src/testdir/dumps/Test_spell_1.dump
@@ -0,0 +1,8 @@
+>T+0&#ffffff0|h|i|s| |i|s| |s|o|m|e| |t|e|x|t| |w|i|t|h|o|u|t| |a|n|y| |s|p|e|l@1| |e|r@1|o|r|s|.| @1|E|v|e|r|y|t|h|i|n|g| @19
+|s|h|o|u|l|d| |j|u|s|t| |b|e| |b|l|a|c|k|,| |n|o|t|h|i|n|g| |w|r|o|n|g| |h|e|r|e|.| @33
+@75
+|T|h|i|s| |l|i|n|e| |h|a|s| |a| |s+0&#ffd7d7255|e|p|l@1| +0&#ffffff0|e|r@1|o|r|.| |a+0&#5fd7ff255|n|d| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p|s|.| @28
+|A+0&#ffd7d7255|n|d| |a|n|d| +0&#ffffff0|t|h|i|s| |i|s| |t+0&#ffd7d7255|h|e| |t|h|e| +0&#ffffff0|d|u|p|l|i|c|a|t|i|o|n|.| @38
+|w+0&#5fd7ff255|i|t|h| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p|s| |h|e|r|e|.| @51
+|~+0#4040ff13&| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_spell_2.dump b/src/testdir/dumps/Test_spell_2.dump
new file mode 100644
index 0000000..961949b
--- /dev/null
+++ b/src/testdir/dumps/Test_spell_2.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@2|T|h|i|s| |l|i|n|e| |h|a|s| |a| |s+0&#ffd7d7255|e|p|l@1| +0&#ffffff0|e|r@1|o|r|.| |a+0&#5fd7ff255|n|d| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p|s| |a|n|d| |t|r|a|i|l|i|n|g| |s|p|a|c|e|s|.| @5
+|a+0&#5fd7ff255|n|o|t|h|e|r| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p| |h|e|r|e|.| @49
+@75
+|a+0&#5fd7ff255|n|d| +0&#ffffff0|h|e|r|e|.| @65
+@75
+|a+0&#5fd7ff255|n|d| +0&#ffffff0|h|e|r|e|.| @65
+|~+0#4040ff13&| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_spell_3.dump b/src/testdir/dumps/Test_spell_3.dump
new file mode 100644
index 0000000..8dcd045
--- /dev/null
+++ b/src/testdir/dumps/Test_spell_3.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@2|T|h|i|s| |l|i|n|e| |h|a|s| |a| |s+0&#ffd7d7255|e|p|l@1| +0&#ffffff0|e|r@1|o|r|.| |a+0&#5fd7ff255|n|d| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p|s| |a|n|d| |t|r|a|i|l|i|n|g| |s|p|a|c|e|s|.| @5
+|a+0&#5fd7ff255|n|o|t|h|e|r| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p| |h|e|r|e|.| @49
+|N|o>t| @71
+|a|n|d| |h|e|r|e|.| @65
+@75
+|a+0&#5fd7ff255|n|d| +0&#ffffff0|h|e|r|e|.| @65
+|~+0#4040ff13&| @73
+| +0#0000000&@56|3|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_spell_4.dump b/src/testdir/dumps/Test_spell_4.dump
new file mode 100644
index 0000000..c49dc84
--- /dev/null
+++ b/src/testdir/dumps/Test_spell_4.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@2|T|h|i|s| |l|i|n|e| |h|a|s| |a| |s+0&#ffd7d7255|e|p|l@1| +0&#ffffff0|e|r@1|o|r|.| |a+0&#5fd7ff255|n|d| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p|s| |a|n|d| |t|r|a|i|l|i|n|g| |s|p|a|c|e|s|.| @5
+|a+0&#5fd7ff255|n|o|t|h|e|r| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p| |h|e|r|e|.| @49
+|N|o|t| @71
+|a|n|d| |h|e|r>e| @66
+|a|n|d| |h|e|r|e|.| @65
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|4|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_spell_5.dump b/src/testdir/dumps/Test_spell_5.dump
new file mode 100644
index 0000000..a23ce5c
--- /dev/null
+++ b/src/testdir/dumps/Test_spell_5.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@2|T|h|i|s| |l|i|n|e| |h|a|s| |a| |s+0&#ffd7d7255|e|p|l@1| +0&#ffffff0|e|r@1|o|r|.| |a+0&#5fd7ff255|n|d| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p|s| |a|n|d| |t|r|a|i|l|i|n|g| |s|p|a|c|e|s|.| @5
+|a+0&#5fd7ff255|n|o|t|h|e|r| +0&#ffffff0|m|i|s@1|i|n|g| |c|a|p| |h|e|r|e|.| @49
+|N|o|t| @71
+|a|n|d| |h|e|r|e>.| @65
+|a+0&#5fd7ff255|n|d| +0&#ffffff0|h|e|r|e|.| @65
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|4|,|9| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_spell_compatible_1.dump b/src/testdir/dumps/Test_spell_compatible_1.dump
new file mode 100644
index 0000000..4f6b268
--- /dev/null
+++ b/src/testdir/dumps/Test_spell_compatible_1.dump
@@ -0,0 +1,8 @@
+|t+0&#5fd7ff255|e|s|t| +0&#ffffff0|t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| >t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t|
+|t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t|$| @49
+@75
+|e+0&#5fd7ff255|n|d| +0&#ffffff0@71
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|1|,|5|1| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_spell_compatible_2.dump b/src/testdir/dumps/Test_spell_compatible_2.dump
new file mode 100644
index 0000000..e1243a6
--- /dev/null
+++ b/src/testdir/dumps/Test_spell_compatible_2.dump
@@ -0,0 +1,8 @@
+|t+0&#5fd7ff255|e|s|t| +0&#ffffff0|t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |x>e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t|
+|t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t| |t|e|s|t|$| @49
+@75
+|e+0&#5fd7ff255|n|d| +0&#ffffff0@71
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@44|1|,|5|2| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_splitkeep_callback_1.dump b/src/testdir/dumps/Test_splitkeep_callback_1.dump
new file mode 100644
index 0000000..4f3a851
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_callback_1.dump
@@ -0,0 +1,8 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+>4+0&&| @73
+|5| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|5|,|1| @11|6@1|%
+| +0&&@74
diff --git a/src/testdir/dumps/Test_splitkeep_callback_2.dump b/src/testdir/dumps/Test_splitkeep_callback_2.dump
new file mode 100644
index 0000000..1537db5
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_callback_2.dump
@@ -0,0 +1,8 @@
+>0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+|4+0&&| @73
+|5| @73
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|6@1|%
+| +0&&@74
diff --git a/src/testdir/dumps/Test_splitkeep_callback_3.dump b/src/testdir/dumps/Test_splitkeep_callback_3.dump
new file mode 100644
index 0000000..7f265b9
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_callback_3.dump
@@ -0,0 +1,8 @@
+>1+0&#ffffff0| @73
+|2| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|2|,|1| @11|1|6|%
+|4+0&&| @73
+|5| @73
+|6| @73
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|6|,|1| @11|8|0|%
+| +0&&@74
diff --git a/src/testdir/dumps/Test_splitkeep_callback_4.dump b/src/testdir/dumps/Test_splitkeep_callback_4.dump
new file mode 100644
index 0000000..25da180
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_callback_4.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0| @73
+|2| @73
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|8|,|1| @11|1|6|%
+|4+0&&| @73
+>5| @73
+|6| @73
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|6|,|1| @11|8|0|%
+| +0&&@74
diff --git a/src/testdir/dumps/Test_splitkeep_fold_1.dump b/src/testdir/dumps/Test_splitkeep_fold_1.dump
new file mode 100644
index 0000000..748a3c8
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_fold_1.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@1|1| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0@1|8| >a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255@1|9| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|1|6| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|8|,|1| @11|T|o|p
+| +0#af5f00255&|2|4| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|2|5| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|3|2| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+|[+1&&|N|o| |N|a|m|e|]| |[|+|]| @43|2|3|,|1| @10|3|2|%
+|:+0&&|w|i|n|c|m|d| |s| @65
diff --git a/src/testdir/dumps/Test_splitkeep_fold_2.dump b/src/testdir/dumps/Test_splitkeep_fold_2.dump
new file mode 100644
index 0000000..959cb64
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_fold_2.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@1|1| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0@1|8| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255@1|9| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|1|6| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|1|7| >+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|2|4| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|2|5| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|3|2| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|3@1| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+|:+0#0000000#ffffff0|q|u|i|t| @51|1|7|,|1| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_splitkeep_fold_3.dump b/src/testdir/dumps/Test_splitkeep_fold_3.dump
new file mode 100644
index 0000000..6880104
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_fold_3.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@1|1| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0@1|8| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255@1|9| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|1| @11|T|o|p
+| +0#0000e05#a8a8a8255|1|7| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|2|4| >a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|2|5| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|3|2| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+|[+3&&|N|o| |N|a|m|e|]| |[|+|]| @43|2|4|,|1| @10|2|5|%
+|:+0&&|b|e|l|o|w| |s|p|l|i|t| @62
diff --git a/src/testdir/dumps/Test_splitkeep_fold_4.dump b/src/testdir/dumps/Test_splitkeep_fold_4.dump
new file mode 100644
index 0000000..959cb64
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_fold_4.dump
@@ -0,0 +1,10 @@
+| +0#0000e05#a8a8a8255@1|1| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0@1|8| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255@1|9| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|1|6| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|1|7| >+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|2|4| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|2|5| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+| +0#af5f00255#ffffff0|3|2| |a+0#0000000&|f|t|e|r| |f|o|l|d| @60
+| +0#0000e05#a8a8a8255|3@1| |+|-@1| @1|7| |l|i|n|e|s|:| |i|n|t| |F|u|n|c|N|a|m|e|(|)| |{|-@40
+|:+0#0000000#ffffff0|q|u|i|t| @51|1|7|,|1| @9|T|o|p|
diff --git a/src/testdir/dumps/Test_splitkeep_status_1.dump b/src/testdir/dumps/Test_splitkeep_status_1.dump
new file mode 100644
index 0000000..1131c69
--- /dev/null
+++ b/src/testdir/dumps/Test_splitkeep_status_1.dump
@@ -0,0 +1,10 @@
+|a+0&#ffffff0| @73
+|b| @73
+|c| @73
+|~+0#4040ff13&| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|1|,|1| @11|A|l@1
+>b+0&&| @73
+|c| @73
+|[+3&&|N|o| |N|a|m|e|]| @47|2|,|1| @11|B|o|t
+|:+0&&|c|a|l@1| |w|i|n|_|m|o|v|e|_|s|t|a|t|u|s|l|i|n|e|(|w|i|n|,| |1|)| @41
diff --git a/src/testdir/dumps/Test_start_with_tabs.dump b/src/testdir/dumps/Test_start_with_tabs.dump
new file mode 100644
index 0000000..0659466
--- /dev/null
+++ b/src/testdir/dumps/Test_start_with_tabs.dump
@@ -0,0 +1,20 @@
+| +2&#ffffff0|a| | +8#0000001#e0e0e08|b| @1|c| | +1#0000000#ffffff0@64|X+8#0000001#e0e0e08
+> +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|"+0#0000000&|a|"| |[|N|e|w|]| @47|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_statusline_1.dump b/src/testdir/dumps/Test_statusline_1.dump
new file mode 100644
index 0000000..f981685
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_1.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@49
+|~+0#4040ff13&| @48
+|~| @48
+|~| @48
+|~| @48
+|~| @48
+|~| @48
+|~| @48
+| +3#0000000&|Q|≡| @46
+| +0&&@49
diff --git a/src/testdir/dumps/Test_statusline_hl.dump b/src/testdir/dumps/Test_statusline_hl.dump
new file mode 100644
index 0000000..cebece5
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_hl.dump
@@ -0,0 +1,6 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|<+0#ffffff16#e000002|F|G+0#0000e05#ffffff0|H|I| @69
+| +0#0000000&@74
diff --git a/src/testdir/dumps/Test_statusline_mode_1.dump b/src/testdir/dumps/Test_statusline_mode_1.dump
new file mode 100644
index 0000000..9d111c9
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_mode_1.dump
@@ -0,0 +1,7 @@
+> +0&#ffffff0@49
+|~+0#4040ff13&| @48
+|++3#0000000&|n|+| @46
+| +0&&@49
+|~+0#4040ff13&| @48
+|-+1#0000000&|n|-| @46
+| +0&&@49
diff --git a/src/testdir/dumps/Test_statusline_mode_2.dump b/src/testdir/dumps/Test_statusline_mode_2.dump
new file mode 100644
index 0000000..f195543
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_mode_2.dump
@@ -0,0 +1,7 @@
+| +0&#ffffff0@49
+|~+0#4040ff13&| @48
+|++3#0000000&|c|+| @46
+| +0&&@49
+|~+0#4040ff13&| @48
+|-+1#0000000&|c|-| @46
+|:+0&&> @48
diff --git a/src/testdir/dumps/Test_statusline_showcmd_1.dump b/src/testdir/dumps/Test_statusline_showcmd_1.dump
new file mode 100644
index 0000000..626f60b
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_showcmd_1.dump
@@ -0,0 +1,6 @@
+|++0#0000e05#a8a8a8255|-@1| @1|2| |l|i|n|e|s|:| |a|-@59
+>c+0#0000000#ffffff0| @73
+|~+0#4040ff13&| @73
+|~| @73
+|g+3#0000000&| @73
+| +0&&@74
diff --git a/src/testdir/dumps/Test_statusline_showcmd_2.dump b/src/testdir/dumps/Test_statusline_showcmd_2.dump
new file mode 100644
index 0000000..2844f12
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_showcmd_2.dump
@@ -0,0 +1,6 @@
+>a+0&#ffffff0| @73
+|b| @73
+|c| @73
+|~+0#4040ff13&| @73
+| +3#0000000&@74
+| +0&&@74
diff --git a/src/testdir/dumps/Test_statusline_showcmd_3.dump b/src/testdir/dumps/Test_statusline_showcmd_3.dump
new file mode 100644
index 0000000..049e0f7
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_showcmd_3.dump
@@ -0,0 +1,6 @@
+|a+0&#e0e0e08| +0&#ffffff0@73
+|b+0&#e0e0e08| +0&#ffffff0@73
+|c+0&#e0e0e08> +0&#ffffff0@73
+|~+0#4040ff13&| @73
+|3+3#0000000&|x|2| @71
+|-+2&&@1| |V|I|S|U|A|L| |B|L|O|C|K| |-@1| +0&&@56
diff --git a/src/testdir/dumps/Test_statusline_showcmd_4.dump b/src/testdir/dumps/Test_statusline_showcmd_4.dump
new file mode 100644
index 0000000..c443662
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_showcmd_4.dump
@@ -0,0 +1,6 @@
+|a+0&#ffffff0| @73
+|b| @73
+>c| @73
+|~+0#4040ff13&| @73
+|1+3#0000000&|2|3|4| @70
+| +0&&@74
diff --git a/src/testdir/dumps/Test_statusline_showcmd_5.dump b/src/testdir/dumps/Test_statusline_showcmd_5.dump
new file mode 100644
index 0000000..26ba377
--- /dev/null
+++ b/src/testdir/dumps/Test_statusline_showcmd_5.dump
@@ -0,0 +1,6 @@
+|a+0&#ffffff0| @73
+|b| @73
+>c| @73
+|~+0#4040ff13&| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @32|1|2|3|4| @6|3|,|1| @11|A|l@1
+|:+0&&| @73
diff --git a/src/testdir/dumps/Test_sub_highlight_zer_match_1.dump b/src/testdir/dumps/Test_sub_highlight_zer_match_1.dump
new file mode 100644
index 0000000..481d0bb
--- /dev/null
+++ b/src/testdir/dumps/Test_sub_highlight_zer_match_1.dump
@@ -0,0 +1,8 @@
+|o+1&#ffffff0|n+0&&|e| @56
+|t|w|o| @56
+|t|h|r|e@1| @54
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|~| @58
+|r+0#00e0003&|e|p|l|a|c|e| |w|i|t|h| @1|.@2|/|a|/|q|/|l|/|^|E|/|^|Y|)|?> +0#0000000&@10|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_syntax_c_01.dump b/src/testdir/dumps/Test_syntax_c_01.dump
new file mode 100644
index 0000000..7224216
--- /dev/null
+++ b/src/testdir/dumps/Test_syntax_c_01.dump
@@ -0,0 +1,20 @@
+|/+0#0000e05#ffffff0|*| |c|o|m@1|e|n|t| |l|i|n|e| |a|t| |t|h|e| |t|o|p| |*|/| +0#0000000&@45
+|i+0#00e0003&|n|t| +0#0000000&|m|a|i|n|(|i+0#00e0003&|n|t| +0#0000000&|a|r|g|c|,| |c+0#00e0003&|h|a|r| +0#0000000&|*@1|a|r|g|v|)| |{| |/+0#0000e05&@1| |a|n|o|t|h|e|r| |c|o|m@1|e|n|t| +0#0000000&@22
+|#+0#e000e06&|i|f| |0| +0#0000000&@69
+| +0#0000e05&@2|i|n|t| @2|n|o|t|_|u|s|e|d|;| +0#0000000&@56
+|#+0#e000e06&|e|l|s|e| +0#0000000&@69
+@3|i+0#00e0003&|n|t| +0#0000000&@2|u|s|e|d|;| @60
+|#+0#e000e06&|e+0&#e0e0e08|n|d|i|f| +0#0000000&| +0&#ffffff0@67
+| +0&#e0e0e08@2|p|r|i|n|t|f|(|"+0#e000002&|J|u|s|t| |a|n| |e|x|a|m|p|l|e| |p|i|e|c|e| |o|f| >C+0&#ffffff0| |c|o|d|e|\+0#e000e06&|n|"+0#e000002&|)+0#0000000&|;| @27
+@3|r+0#af5f00255&|e|t|u|r|n| +0#0000000&|0+0#e000002&|x|0|f@1|;+0#0000000&| @58
+|}| @73
+| +0#ffffff16#ff404010@16| +0#0000000#ffffff0@57
+@3|s+0#00e0003&|t|a|t|i|c| +0#0000000&|v+0#00e0003&|o|i|d| +0#0000000&@60
+|m|y|F|u|n|c|t|i|o|n|(|c+0#00e0003&|o|n|s|t| +0#0000000&|d+0#00e0003&|o|u|b|l|e| +0#0000000&|c|o|u|n|t|,| |s+0#00e0003&|t|r|u|c|t| +0#0000000&|n|o|t|h|i|n|g|,| |l+0#00e0003&|o|n|g| +0#0000000&|t|h|e|r|e|)| |{| @14
+@8|/+0#0000e05&@1| |1+0#e000002&|2|3|:+0#0000e05&| |n|o|t|h|i|n|g| |t|o| |e+0&#ffff4012|n|d|i|f| +0&#ffffff0|h|e|r|e| +0#0000000&@37
+@8|f+0#af5f00255&|o|r| +0#0000000&|(|i+0#00e0003&|n|t| +0#0000000&|i| |=| |0+0#e000002&|;+0#0000000&| |i| |<| |c|o|u|n|t|;| |+@1|i|)| |{| @33
+@11|b+0#af5f00255&|r|e|a|k|;+0#0000000&| @57
+@8|}| @65
+@8|N+0&#ffff4012|o|t|e|:+0&#ffffff0| |a|s|d|f| @56
+|}| @73
+|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@34|2| @8|8|,|3|7| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_tabline_showcmd_1.dump b/src/testdir/dumps/Test_tabline_showcmd_1.dump
new file mode 100644
index 0000000..40fea4e
--- /dev/null
+++ b/src/testdir/dumps/Test_tabline_showcmd_1.dump
@@ -0,0 +1,6 @@
+|g+1&#ffffff0| @73
+|++0#0000e05#a8a8a8255|-@1| @1|2| |l|i|n|e|s|:| |a|-@59
+>c+0#0000000#ffffff0| @73
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_tabline_showcmd_2.dump b/src/testdir/dumps/Test_tabline_showcmd_2.dump
new file mode 100644
index 0000000..9765de4
--- /dev/null
+++ b/src/testdir/dumps/Test_tabline_showcmd_2.dump
@@ -0,0 +1,6 @@
+| +1&#ffffff0@74
+>a+0&&| @73
+|b| @73
+|c| @73
+|~+0#4040ff13&| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_tabline_showcmd_3.dump b/src/testdir/dumps/Test_tabline_showcmd_3.dump
new file mode 100644
index 0000000..3e29cda
--- /dev/null
+++ b/src/testdir/dumps/Test_tabline_showcmd_3.dump
@@ -0,0 +1,6 @@
+|3+1&#ffffff0|x|2| @71
+|a+0&#e0e0e08| +0&#ffffff0@73
+|b+0&#e0e0e08| +0&#ffffff0@73
+|c+0&#e0e0e08> +0&#ffffff0@73
+|~+0#4040ff13&| @73
+|-+2#0000000&@1| |V|I|S|U|A|L| |B|L|O|C|K| |-@1| +0&&@38|3|,|2| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_tabline_showcmd_4.dump b/src/testdir/dumps/Test_tabline_showcmd_4.dump
new file mode 100644
index 0000000..f9aac49
--- /dev/null
+++ b/src/testdir/dumps/Test_tabline_showcmd_4.dump
@@ -0,0 +1,6 @@
+|1+1&#ffffff0|2|3|4| @70
+|a+0&&| @73
+|b| @73
+>c| @73
+|~+0#4040ff13&| @73
+| +0#0000000&@56|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_tabline_showcmd_5.dump b/src/testdir/dumps/Test_tabline_showcmd_5.dump
new file mode 100644
index 0000000..4eaa198
--- /dev/null
+++ b/src/testdir/dumps/Test_tabline_showcmd_5.dump
@@ -0,0 +1,6 @@
+| +2&#ffffff0|+| |[|N|o| |N|a|m|e|]| | +1&&@51|1+8#0000001#e0e0e08|2|3|4| +1#0000000#ffffff0@5
+|a+0&&| @73
+|b| @73
+>c| @73
+|~+0#4040ff13&| @73
+|:+0#0000000&| @55|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_tabpage_cmdheight.dump b/src/testdir/dumps/Test_tabpage_cmdheight.dump
new file mode 100644
index 0000000..74ac734
--- /dev/null
+++ b/src/testdir/dumps/Test_tabpage_cmdheight.dump
@@ -0,0 +1,20 @@
+| +8#0000001#e0e0e08|[|N|o| |N|a|m|e|]| | +2#0000000#ffffff0|[|N|o| |N|a|m|e|]| | +1&&@51|X+8#0000001#e0e0e08
+> +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
+@75
+@75
diff --git a/src/testdir/dumps/Test_tenc_euc_jp_01.dump b/src/testdir/dumps/Test_tenc_euc_jp_01.dump
new file mode 100644
index 0000000..14a4313
--- /dev/null
+++ b/src/testdir/dumps/Test_tenc_euc_jp_01.dump
@@ -0,0 +1,10 @@
+>E+0&#ffffff0|8|9|:| |ãƒ*&|ッ|フ|ã‚¡| +&|%|l|d| |ã®*&|変|æ›´|ã¯|ä¿|å­˜|ã•|ã‚Œ|ã¦|ã„|ã¾|ã›|ã‚“| +&|(|!| |ã§*&|変|æ›´|ã‚’|ç ´|棄|)+&| @13
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|X+3#0000000&|e|u|c|_|j|p|.|t|x|t| @45|1|,|1| @11|A|l@1
+|E+0&&|8|3|:| |ãƒ*&|ッ|フ|ã‚¡|ã‚’|作|æˆ|ã§|ã|ãª|ã„|ã®|ã§|ã€|ä»–|ã®|ã‚’|使|用|ã—|ã¾|ã™|.+&@2| @22
+|~+0#4040ff13&| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|1|,|5|2| @10|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_term_popup_bufline.dump b/src/testdir/dumps/Test_term_popup_bufline.dump
new file mode 100644
index 0000000..50ba4a6
--- /dev/null
+++ b/src/testdir/dumps/Test_term_popup_bufline.dump
@@ -0,0 +1,15 @@
+|1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @73
+>5| @35|0+0#0000001#ffd7ff255| +0#0000000#ffffff0@36
+|~+0#4040ff13&| @35|1+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|!+2#ffffff16#00e0003|s|e|q| |1| |5| |[|f|i|n|i|s|h|e|d|]| @17|2+0#0000001#ffd7ff255| +2#ffffff16#00e0003@18|5|,|1| @11|A|l@1
+| +0#0000000#ffffff0@36|3+0#0000001#ffd7ff255| +0#0000000#ffffff0@36
+|~+0#4040ff13&| @35|4+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @35| +0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_terminal_all_ansi_colors.dump b/src/testdir/dumps/Test_terminal_all_ansi_colors.dump
new file mode 100644
index 0000000..2a0d481
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_all_ansi_colors.dump
@@ -0,0 +1,10 @@
+>A+0#0000001#8080809@1|B+0#e000002#ff404010@1|C+0#00e0003#40ff4011@1|D+0#e0e0004#ffff4012@1|E+0#0000e05#4040ff13@1|F+0#e000e06#ff40ff14@1|G+0#00e0e07#40ffff15@1|H+0#e0e0e08#ffffff16@1|I+0#8080809#0000001@1|J+0#ff404010#e000002@1|K+0#40ff4011#00e0003@1|L+0#ffff4012#e0e0004@1|M+0#4040ff13#0000e05@1|N+0#ff40ff14#e000e06@1|O+0#40ffff15#00e0e07@1|P+0#ffffff16#e0e0e08@1| +0#0000000#ffffff0|X+2#e000002&@1|Y+2#40ff4011&@1|Z+2#ff40ff14#e000e06@1| +0#0000000#ffffff0@35
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_terminal_color_MyTermCol.dump b/src/testdir/dumps/Test_terminal_color_MyTermCol.dump
new file mode 100644
index 0000000..8b16c28
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_MyTermCol.dump
@@ -0,0 +1,15 @@
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_MyTermCol_over_Terminal.dump b/src/testdir/dumps/Test_terminal_color_MyTermCol_over_Terminal.dump
new file mode 100644
index 0000000..8b16c28
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_MyTermCol_over_Terminal.dump
@@ -0,0 +1,15 @@
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_MyWinCol.dump b/src/testdir/dumps/Test_terminal_color_MyWinCol.dump
new file mode 100644
index 0000000..e89d056
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_MyWinCol.dump
@@ -0,0 +1,15 @@
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#ff404010#e0e0004@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_MyWinCol_over_group.dump b/src/testdir/dumps/Test_terminal_color_MyWinCol_over_group.dump
new file mode 100644
index 0000000..e89d056
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_MyWinCol_over_group.dump
@@ -0,0 +1,15 @@
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#ff404010#e0e0004@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_Terminal.dump b/src/testdir/dumps/Test_terminal_color_Terminal.dump
new file mode 100644
index 0000000..9480ccf
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_Terminal.dump
@@ -0,0 +1,15 @@
+|h+0#4040ff13#ffff4012|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#4040ff13#ffff4012|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#4040ff13#ffff4012@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_gui_MyTermCol.dump b/src/testdir/dumps/Test_terminal_color_gui_MyTermCol.dump
new file mode 100644
index 0000000..363582f
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_gui_MyTermCol.dump
@@ -0,0 +1,15 @@
+|h+0#007800255#6789ff255|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#007800255#6789ff255|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#007800255#6789ff255@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#007800255#6789ff255@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff255#006400255|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_gui_MyWinCol.dump b/src/testdir/dumps/Test_terminal_color_gui_MyWinCol.dump
new file mode 100644
index 0000000..cb3d0c8
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_gui_MyWinCol.dump
@@ -0,0 +1,15 @@
+|h+0#fe1122255#818100255|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#fe1122255#818100255|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#fe1122255#818100255@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#fe1122255#818100255@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff255#006400255|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_gui_Terminal.dump b/src/testdir/dumps/Test_terminal_color_gui_Terminal.dump
new file mode 100644
index 0000000..c5455e4
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_gui_Terminal.dump
@@ -0,0 +1,15 @@
+|h+0#3344ff255#b0a700255|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#3344ff255#b0a700255|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|5+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|6+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|7+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|8+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#3344ff255#b0a700255@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+2#ffffff255#006400255|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_gui_transp_MyTermCol.dump b/src/testdir/dumps/Test_terminal_color_gui_transp_MyTermCol.dump
new file mode 100644
index 0000000..337c5a3
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_gui_transp_MyTermCol.dump
@@ -0,0 +1,15 @@
+|h+0#007800255#ffffff0|e|l@1|o| @31||+1#0000000&|0+0&&| @35
+|h+0#007800255&|e|l@1|o| @31||+1#0000000&|1+0&&| @35
+> +0#007800255&@36||+1#0000000&|2+0&&| @35
+| +0#007800255&@36||+1#0000000&|3+0&&| @35
+| +0#007800255&@36||+1#0000000&|4+0&&| @35
+| +0#007800255&@36||+1#0000000&|5+0&&| @35
+| +0#007800255&@36||+1#0000000&|6+0&&| @35
+| +0#007800255&@36||+1#0000000&|7+0&&| @35
+| +0#007800255&@36||+1#0000000&|8+0&&| @35
+| +0#007800255&@36||+1#0000000&|9+0&&| @35
+| +0#007800255&@36||+1#0000000&|1+0&&|0| @34
+| +0#007800255&@36||+1#0000000&|1+0&&@1| @34
+| +0#007800255&@36||+1#0000000&|1+0&&|2| @34
+|!+2#ffffff255#006400255|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_gui_transp_MyWinCol.dump b/src/testdir/dumps/Test_terminal_color_gui_transp_MyWinCol.dump
new file mode 100644
index 0000000..42cb856
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_gui_transp_MyWinCol.dump
@@ -0,0 +1,15 @@
+|h+0#fe1122255#ffffff0|e|l@1|o| @31||+1#0000000&|0+0&&| @35
+|h+0#fe1122255&|e|l@1|o| @31||+1#0000000&|1+0&&| @35
+> +0#fe1122255&@36||+1#0000000&|2+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|3+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|4+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|5+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|6+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|7+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|8+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|9+0&&| @35
+| +0#fe1122255&@36||+1#0000000&|1+0&&|0| @34
+| +0#fe1122255&@36||+1#0000000&|1+0&&@1| @34
+| +0#fe1122255&@36||+1#0000000&|1+0&&|2| @34
+|!+2#ffffff255#006400255|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_gui_transp_Terminal.dump b/src/testdir/dumps/Test_terminal_color_gui_transp_Terminal.dump
new file mode 100644
index 0000000..3e640bf
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_gui_transp_Terminal.dump
@@ -0,0 +1,15 @@
+|h+0#3344ff255#ffffff0|e|l@1|o| @31||+1#0000000&|0+0&&| @35
+|h+0#3344ff255&|e|l@1|o| @31||+1#0000000&|1+0&&| @35
+> +0#3344ff255&@36||+1#0000000&|2+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|3+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|4+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|5+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|6+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|7+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|8+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|9+0&&| @35
+| +0#3344ff255&@36||+1#0000000&|1+0&&|0| @34
+| +0#3344ff255&@36||+1#0000000&|1+0&&@1| @34
+| +0#3344ff255&@36||+1#0000000&|1+0&&|2| @34
+|!+2#ffffff255#006400255|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_transp_MyTermCol.dump b/src/testdir/dumps/Test_terminal_color_transp_MyTermCol.dump
new file mode 100644
index 0000000..3fe2ec1
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_transp_MyTermCol.dump
@@ -0,0 +1,15 @@
+|h+0#00e0003#ffffff0|e|l@1|o| @31||+1#0000000&|0+0&&| @35
+|h+0#00e0003&|e|l@1|o| @31||+1#0000000&|1+0&&| @35
+> +0#00e0003&@36||+1#0000000&|2+0&&| @35
+| +0#00e0003&@36||+1#0000000&|3+0&&| @35
+| +0#00e0003&@36||+1#0000000&|4+0&&| @35
+| +0#00e0003&@36||+1#0000000&|5+0&&| @35
+| +0#00e0003&@36||+1#0000000&|6+0&&| @35
+| +0#00e0003&@36||+1#0000000&|7+0&&| @35
+| +0#00e0003&@36||+1#0000000&|8+0&&| @35
+| +0#00e0003&@36||+1#0000000&|9+0&&| @35
+| +0#00e0003&@36||+1#0000000&|1+0&&|0| @34
+| +0#00e0003&@36||+1#0000000&|1+0&&@1| @34
+| +0#00e0003&@36||+1#0000000&|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_transp_MyWinCol.dump b/src/testdir/dumps/Test_terminal_color_transp_MyWinCol.dump
new file mode 100644
index 0000000..2648d5a
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_transp_MyWinCol.dump
@@ -0,0 +1,15 @@
+|h+0#ff404010#ffffff0|e|l@1|o| @31||+1#0000000&|0+0&&| @35
+|h+0#ff404010&|e|l@1|o| @31||+1#0000000&|1+0&&| @35
+> +0#ff404010&@36||+1#0000000&|2+0&&| @35
+| +0#ff404010&@36||+1#0000000&|3+0&&| @35
+| +0#ff404010&@36||+1#0000000&|4+0&&| @35
+| +0#ff404010&@36||+1#0000000&|5+0&&| @35
+| +0#ff404010&@36||+1#0000000&|6+0&&| @35
+| +0#ff404010&@36||+1#0000000&|7+0&&| @35
+| +0#ff404010&@36||+1#0000000&|8+0&&| @35
+| +0#ff404010&@36||+1#0000000&|9+0&&| @35
+| +0#ff404010&@36||+1#0000000&|1+0&&|0| @34
+| +0#ff404010&@36||+1#0000000&|1+0&&@1| @34
+| +0#ff404010&@36||+1#0000000&|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_color_transp_Terminal.dump b/src/testdir/dumps/Test_terminal_color_transp_Terminal.dump
new file mode 100644
index 0000000..33d0281
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_color_transp_Terminal.dump
@@ -0,0 +1,15 @@
+|h+0#4040ff13#ffffff0|e|l@1|o| @31||+1#0000000&|0+0&&| @35
+|h+0#4040ff13&|e|l@1|o| @31||+1#0000000&|1+0&&| @35
+> +0#4040ff13&@36||+1#0000000&|2+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|3+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|4+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|5+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|6+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|7+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|8+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|9+0&&| @35
+| +0#4040ff13&@36||+1#0000000&|1+0&&|0| @34
+| +0#4040ff13&@36||+1#0000000&|1+0&&@1| @34
+| +0#4040ff13&@36||+1#0000000&|1+0&&|2| @34
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|c|a|l@1| |O|p|e|n|T|e|r|m|(|)| @58
diff --git a/src/testdir/dumps/Test_terminal_combining.dump b/src/testdir/dumps/Test_terminal_combining.dump
new file mode 100644
index 0000000..390da0a
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_combining.dump
@@ -0,0 +1,9 @@
+>ポ*0&#ffffff0| +&@72
+|~+0#4040ff13&| @73
+|~| @73
+|!+2#ffffff16#00e0003|/|b|i|n|/|s|h| |[|f|i|n|i|s|h|e|d|]| @37|1|,|1| @11|A|l@1
+| +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_terminal_dumpload.dump b/src/testdir/dumps/Test_terminal_dumpload.dump
new file mode 100644
index 0000000..7d2e1b0
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_dumpload.dump
@@ -0,0 +1,15 @@
+>1+0&#ffffff0| @73
+|2| |â•”+0&#5fd7ff255|â•@11|â•—| +0&#ffffff0@5|â•”+0&#dadada255|â•@11|â•—+0&#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|3| |â•‘+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#5fd7ff255| +0&#ffffff0@5|â•‘+0&#a8a8a8255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|4| |â•š+0&#5fd7ff255|â•@11|â•| +0&#ffffff0@5|â•‘+0&#a8a8a8255|a+0#0000001#ffd7ff255|n|d| |m|o|r|e| @3|â•‘+0#0000000#8a8a8a255| +0&#ffffff0@5|x+0&#5fd7ff255|l+0#0000001#ffd7ff255|i|n|e|s| |o|n|l|y| @1|x+0#0000000#5fd7ff255| +0&#ffffff0@2|x+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |c|o|r|n|e|r|s|x+0#0000000#5fd7ff255| +0&#ffffff0@1
+|5| @20|â•š+0&#585858255|â•@11|â•| +0&#ffffff0@5|x+0&#5fd7ff255@13| +0&#ffffff0@2|#+0&#5fd7ff255|x@11|#| +0&#ffffff0@1
+|6| |4+0&#5fd7ff255|0@11|5| +0&#ffffff0@58
+|7| |3+0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r|1+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255@13| +0&#ffffff0@38
+|8| |3+0&#5fd7ff255|w+0#0000001#ffd7ff255|i|t|h| |n|u|m|b|e|r|s|1+0#0000000#5fd7ff255| +0&#ffffff0@5| +0&#5fd7ff255|h+0#0000001#ffd7ff255|e|l@1|o| |b|o|r|d|e|r| +0#0000000#5fd7ff255| +0&#ffffff0@38
+|9| |7+0&#5fd7ff255|2@11|6| +0&#ffffff0@5| +0&#5fd7ff255|j+0#0000001#ffd7ff255|u|s|t| |b|l|a|n|k|s| | +0#0000000#5fd7ff255| +0&#ffffff0@38
+|1|0| @19| +0&#5fd7ff255@13| +0&#ffffff0@38
+|1@1| @72
+|d+2#ffffff16#00e0003|u|m|p| |d|i|f@1| |d|u|m|p|s|/|T|e|s|t|_|p|o|p|u|p|w|i|n|_|2@1|.|d|u|m|p| |[|f|i|n|i|s|h|e|d|]| @8|1|,|1| @11|T|o|p
+| +0#0000000#ffffff0@74
+|[+1&&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_terminal_focus_1.dump b/src/testdir/dumps/Test_terminal_focus_1.dump
new file mode 100644
index 0000000..caf67e7
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_focus_1.dump
@@ -0,0 +1,6 @@
+>I+0&#ffffff0| |a|m| |l|o|s|t| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_terminal_focus_2.dump b/src/testdir/dumps/Test_terminal_focus_2.dump
new file mode 100644
index 0000000..d02c151
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_focus_2.dump
@@ -0,0 +1,6 @@
+>I+0&#ffffff0| |a|m| |b|a|c|k| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_terminal_focus_3.dump b/src/testdir/dumps/Test_terminal_focus_3.dump
new file mode 100644
index 0000000..8ece5a2
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_focus_3.dump
@@ -0,0 +1,6 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|x@73
+@6> @68
diff --git a/src/testdir/dumps/Test_terminal_from_cmd.dump b/src/testdir/dumps/Test_terminal_from_cmd.dump
new file mode 100644
index 0000000..5eea73f
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_from_cmd.dump
@@ -0,0 +1,20 @@
+| +0&#ffffff0@74
+@75
+@75
+@75
+@75
+@75
+@75
+@75
+@75
+|!+2#ffffff16#00e0003|/|b|i|n|/|s|h| |[|f|i|n|i|s|h|e|d|]| @37|0|,|0|-|1| @9|A|l@1
+|a+0#0000000#ffffff0| @73
+|b| @73
+|c| @73
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @43|3|,|1| @11|A|l@1
+|/+0&&> @73
diff --git a/src/testdir/dumps/Test_terminal_normal_1.dump b/src/testdir/dumps/Test_terminal_normal_1.dump
new file mode 100644
index 0000000..471419a
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_normal_1.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&@4| @65
+| +0#af5f00255&@1|2| |1+0#0000000&@3|2| @65
+| +8#af5f00255&@1|3| >1+8#0000000&@3|3| @65
+| +0#af5f00255&@1|4| |1+0#0000000&@3|4| @65
+| +0#af5f00255&@1|5| |1+0#0000000&@3|5| @65
+| +0#af5f00255&@1|6| |1+0#0000000&@3|6| @65
+| +0#af5f00255&@1|7| |1+0#0000000&@3|7| @65
+|:|s|e|t| |n|u|m|b|e|r| |c|u|r|s|o|r|l|i|n|e| |c|u|l|o|p|t|=|b|o|t|h| @22|3|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_terminal_normal_2.dump b/src/testdir/dumps/Test_terminal_normal_2.dump
new file mode 100644
index 0000000..b255d89
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_normal_2.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&@4| @65
+| +0#af5f00255&@1|2| |1+0#0000000&@3|2| @65
+| +8#af5f00255&@1|3| >1+0#0000000&@3|3| @65
+| +0#af5f00255&@1|4| |1+0#0000000&@3|4| @65
+| +0#af5f00255&@1|5| |1+0#0000000&@3|5| @65
+| +0#af5f00255&@1|6| |1+0#0000000&@3|6| @65
+| +0#af5f00255&@1|7| |1+0#0000000&@3|7| @65
+|:|s|e|t| |c|u|l|o|p|t|=|n|u|m|b|e|r| @38|3|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_terminal_normal_3.dump b/src/testdir/dumps/Test_terminal_normal_3.dump
new file mode 100644
index 0000000..aa82fe8
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_normal_3.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |1+0#0000000&@4| @65
+| +0#af5f00255&@1|2| |1+0#0000000&@3|2| @65
+| +0#af5f00255&@1|3| >1+8#0000000&@3|3| @65
+| +0#af5f00255&@1|4| |1+0#0000000&@3|4| @65
+| +0#af5f00255&@1|5| |1+0#0000000&@3|5| @65
+| +0#af5f00255&@1|6| |1+0#0000000&@3|6| @65
+| +0#af5f00255&@1|7| |1+0#0000000&@3|7| @65
+|:|s|e|t| |c|u|l|o|p|t|=|l|i|n|e| @40|3|,|1| @10|T|o|p|
diff --git a/src/testdir/dumps/Test_terminal_popup_1.dump b/src/testdir/dumps/Test_terminal_popup_1.dump
new file mode 100644
index 0000000..d4311ba
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_1.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @12|â•”+0#0000001#ffd7ff255|â•@44|â•—| +0#0000000#ffffff0@13
+|4| @12|â•‘+0#0000001#ffd7ff255>s+0#0000000#ffffff0|o|m|e| |t|e|x|t| @35|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|5| @12|â•‘+0#0000001#ffd7ff255|t+0#0000000#ffffff0|o| |e|d|i|t| @37|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|6| @12|â•‘+0#0000001#ffd7ff255|i+0#0000000#ffffff0|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| @27|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|7| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|8| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|9| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|1|0| @11|â•‘+0#0000001#ffd7ff255|:+0#0000000#ffffff0| @25|1|,|1| @10|A|l@1| |â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|1@1| @11|â•š+0#0000001#ffd7ff255|â•@44|⇲| +0#0000000#ffffff0@13
+|1|2| @72
+|1|3| @72
+|t|e|r|m|i|n|a|l| |p|o|p|u|p| @60
diff --git a/src/testdir/dumps/Test_terminal_popup_2.dump b/src/testdir/dumps/Test_terminal_popup_2.dump
new file mode 100644
index 0000000..5e53e71
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_2.dump
@@ -0,0 +1,15 @@
+>0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|1|0| @72
+|1@1| @72
+|1|2| @72
+|1|3| @72
+|"|[|N|o| |N|a|m|e|]|"| |[|M|o|d|i|f|i|e|d|]| |l|i|n|e| |1| |o|f| |2|0| |-@1|5|%|-@1| |c|o|l| |1| @26
diff --git a/src/testdir/dumps/Test_terminal_popup_3.dump b/src/testdir/dumps/Test_terminal_popup_3.dump
new file mode 100644
index 0000000..9177082
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_3.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @12|â•”+0&#a8a8a8255|â•@44|â•—| +0&#ffffff0@13
+|4| @12|â•‘+0&#a8a8a8255|s|o|m|e| |t|e|x|t| @35|â•‘| +0&#ffffff0@13
+|5| @12|â•‘+0&#a8a8a8255|t|o| >e+0&#ffff4012|d|i|t| +0&#a8a8a8255@37|â•‘| +0&#ffffff0@13
+|6| @12|â•‘+0&#a8a8a8255|i|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| @27|â•‘| +0&#ffffff0@13
+|7| @12|â•‘+0&#a8a8a8255|~+0#4040ff13&| @43|â•‘+0#0000000&| +0&#ffffff0@13
+|8| @12|â•‘+0&#a8a8a8255|~+0#4040ff13&| @43|â•‘+0#0000000&| +0&#ffffff0@13
+|9| @12|â•‘+0&#a8a8a8255|~+0#4040ff13&| @43|â•‘+0#0000000&| +0&#ffffff0@13
+|1|0| @11|â•‘+0&#a8a8a8255|/|e|d|i|t| @21|2|,|4| @10|A|l@1| |â•‘| +0&#ffffff0@13
+|1@1| @11|â•š+0&#a8a8a8255|â•@44|⇲| +0&#ffffff0@13
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_4.dump b/src/testdir/dumps/Test_terminal_popup_4.dump
new file mode 100644
index 0000000..de1e6c9
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_4.dump
@@ -0,0 +1,15 @@
+|3+0&#ffffff0| @12|â•”+0&#a8a8a8255|â•@44|â•—| +0&#ffffff0@13
+|4| @12|â•‘+0&#a8a8a8255|s|o|m|e| |t|e|x|t| @35|â•‘| +0&#ffffff0@13
+|5| @12|â•‘+0&#a8a8a8255|t|o| |e+0&#ffff4012|d|i|t| +0&#a8a8a8255@37|â•‘| +0&#ffffff0@13
+|6| @12|â•‘+0&#a8a8a8255|i|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| @27|â•‘| +0&#ffffff0@13
+|7| @12|â•‘+0&#a8a8a8255|~+0#4040ff13&| @43|â•‘+0#0000000&| +0&#ffffff0@13
+|8| @12|â•‘+0&#a8a8a8255|~+0#4040ff13&| @43|â•‘+0#0000000&| +0&#ffffff0@13
+|9| @12|â•‘+0&#a8a8a8255|~+0#4040ff13&| @43|â•‘+0#0000000&| +0&#ffffff0@13
+|1|0| @11|â•‘+0&#a8a8a8255|/|e|d|i|t| @21|2|,|4| @10|A|l@1| |â•‘| +0&#ffffff0@13
+|1@1| @11|â•š+0&#a8a8a8255|â•@44|⇲| +0&#ffffff0@13
+|1|2| @72
+|1|3| @72
+|E+0#ffffff16#e000002|r@1|o|r| |d|e|t|e|c|t|e|d| |w|h|i|l|e| |p|r|o|c|e|s@1|i|n|g| |f|u|n|c|t|i|o|n| |H|i|d|e|P|o|p|u|p|:| +0#0000000#ffffff0@23
+|l+0#af5f00255&|i|n|e| @3|1|:| +0#0000000&@64
+|E+0#ffffff16#e000002|8|6|3|:| |N|o|t| |a|l@1|o|w|e|d| |f|o|r| |a| |t|e|r|m|i|n|a|l| |i|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| +0#0000000#ffffff0@24
+|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@35
diff --git a/src/testdir/dumps/Test_terminal_popup_5.dump b/src/testdir/dumps/Test_terminal_popup_5.dump
new file mode 100644
index 0000000..42808b0
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_5.dump
@@ -0,0 +1,15 @@
+>0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|1|0| @72
+|1@1| @72
+|1|2| @72
+|1|3| @72
+|:|c|a|l@1| |C|l|o|s|e|P|o|p|u|p|(|)| @56
diff --git a/src/testdir/dumps/Test_terminal_popup_6.dump b/src/testdir/dumps/Test_terminal_popup_6.dump
new file mode 100644
index 0000000..da5cf46
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_6.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @12|â•”+0#0000001#ffd7ff255|â•@44|â•—| +0#0000000#ffffff0@13
+|4| @12|â•‘+0#0000001#ffd7ff255|s+0#0000000#ffffff0|o|m|e| |t|e|x|t| @35|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|5| @12|â•‘+0#0000001#ffd7ff255|t+0#0000000#ffffff0|o| >e+0&#ffff4012|d|i|t| +0&#ffffff0@37|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|6| @12|â•‘+0#0000001#ffd7ff255|i+0#0000000#ffffff0|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| @27|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|7| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|8| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|9| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|1|0| @11|â•‘+0#0000001#ffd7ff255|/+0#0000000#ffffff0|e|d|i|t| @21|2|,|4| @10|A|l@1| |â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|1@1| @11|â•š+0#0000001#ffd7ff255|â•@44|â•| +0#0000000#ffffff0@13
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_7.dump b/src/testdir/dumps/Test_terminal_popup_7.dump
new file mode 100644
index 0000000..e7e5aae
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_7.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @12|â•”+0#0000001#ffd7ff255|â•@44|â•—| +0#0000000#ffffff0@13
+|4| @12|â•‘+0#0000001#ffd7ff255|s|o|m|e| |t|e|x|t| @35|â•‘| +0#0000000#ffffff0@13
+|5| @12|â•‘+0#0000001#ffd7ff255|t|o| |e+0&#ffff4012|d|i|t| +0&#ffd7ff255@37|â•‘| +0#0000000#ffffff0@13
+|6| @12|â•‘+0#0000001#ffd7ff255|i+0&#e0e0e08|n| |a| >p+0&#ffd7ff255|o|p|u|p| |w|i|n|d|o|w| @27|â•‘| +0#0000000#ffffff0@13
+|7| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13&| @43|â•‘+0#0000001&| +0#0000000#ffffff0@13
+|8| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13&| @43|â•‘+0#0000001&| +0#0000000#ffffff0@13
+|9| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13&| @43|â•‘+0#0000001&| +0#0000000#ffffff0@13
+|1|0| @11|â•‘+0#0000001#ffd7ff255|/|e|d|i|t| @21|2|,|4| @10|A|l@1| |â•‘| +0#0000000#ffffff0@13
+|1@1| @11|â•š+0#0000001#ffd7ff255|â•@44|â•| +0#0000000#ffffff0@13
+|1|2| @72
+|1|3| @72
+|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@51|6| @9
diff --git a/src/testdir/dumps/Test_terminal_popup_8.dump b/src/testdir/dumps/Test_terminal_popup_8.dump
new file mode 100644
index 0000000..da5cf46
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_8.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @12|â•”+0#0000001#ffd7ff255|â•@44|â•—| +0#0000000#ffffff0@13
+|4| @12|â•‘+0#0000001#ffd7ff255|s+0#0000000#ffffff0|o|m|e| |t|e|x|t| @35|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|5| @12|â•‘+0#0000001#ffd7ff255|t+0#0000000#ffffff0|o| >e+0&#ffff4012|d|i|t| +0&#ffffff0@37|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|6| @12|â•‘+0#0000001#ffd7ff255|i+0#0000000#ffffff0|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| @27|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|7| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|8| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|9| @12|â•‘+0#0000001#ffd7ff255|~+0#4040ff13#ffffff0| @43|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|1|0| @11|â•‘+0#0000001#ffd7ff255|/+0#0000000#ffffff0|e|d|i|t| @21|2|,|4| @10|A|l@1| |â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@13
+|1@1| @11|â•š+0#0000001#ffd7ff255|â•@44|â•| +0#0000000#ffffff0@13
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_MyPopupHlCol.dump b/src/testdir/dumps/Test_terminal_popup_MyPopupHlCol.dump
new file mode 100644
index 0000000..639ba98
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_MyPopupHlCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#40ffff15#40ff4011|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#40ffff15#40ff4011|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#40ffff15#40ff4011|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#40ffff15#40ff4011> @19|â•‘| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#40ffff15#40ff4011| @19|â•‘| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#40ffff15#40ff4011| @19|â•‘| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#40ffff15#40ff4011|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_MyTermCol.dump b/src/testdir/dumps/Test_terminal_popup_MyTermCol.dump
new file mode 100644
index 0000000..82fcec7
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_MyTermCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#0000001#ffd7ff255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#0000001#ffd7ff255|h+0#00e0003#5fd7ff255|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#0000001#ffd7ff255|h+0#00e0003#5fd7ff255|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#0000001#ffd7ff255> +0#00e0003#5fd7ff255@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#0000001#ffd7ff255| +0#00e0003#5fd7ff255@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#0000001#ffd7ff255| +0#00e0003#5fd7ff255@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#0000001#ffd7ff255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_MyTermCol_over_Terminal.dump b/src/testdir/dumps/Test_terminal_popup_MyTermCol_over_Terminal.dump
new file mode 100644
index 0000000..82fcec7
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_MyTermCol_over_Terminal.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#0000001#ffd7ff255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#0000001#ffd7ff255|h+0#00e0003#5fd7ff255|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#0000001#ffd7ff255|h+0#00e0003#5fd7ff255|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#0000001#ffd7ff255> +0#00e0003#5fd7ff255@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#0000001#ffd7ff255| +0#00e0003#5fd7ff255@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#0000001#ffd7ff255| +0#00e0003#5fd7ff255@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#0000001#ffd7ff255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_MyWinCol.dump b/src/testdir/dumps/Test_terminal_popup_MyWinCol.dump
new file mode 100644
index 0000000..502782a
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_MyWinCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#ff404010#e0e0004|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#ff404010#e0e0004|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#ff404010#e0e0004|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#ff404010#e0e0004> @19|â•‘| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#ff404010#e0e0004| @19|â•‘| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#ff404010#e0e0004| @19|â•‘| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#ff404010#e0e0004|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_MyWinCol_over_group.dump b/src/testdir/dumps/Test_terminal_popup_MyWinCol_over_group.dump
new file mode 100644
index 0000000..502782a
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_MyWinCol_over_group.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#ff404010#e0e0004|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#ff404010#e0e0004|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#ff404010#e0e0004|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#ff404010#e0e0004> @19|â•‘| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#ff404010#e0e0004| @19|â•‘| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#ff404010#e0e0004| @19|â•‘| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#ff404010#e0e0004|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_Terminal.dump b/src/testdir/dumps/Test_terminal_popup_Terminal.dump
new file mode 100644
index 0000000..938b419
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_Terminal.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#0000001#ffd7ff255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#0000001#ffd7ff255|h+0#4040ff13#ffff4012|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#0000001#ffd7ff255|h+0#4040ff13#ffff4012|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#0000001#ffd7ff255> +0#4040ff13#ffff4012@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffff4012@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffff4012@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#0000001#ffd7ff255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_MyPopupHlCol.dump b/src/testdir/dumps/Test_terminal_popup_gui_MyPopupHlCol.dump
new file mode 100644
index 0000000..5eb125f
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_MyPopupHlCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#00e8f0255#126521255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#00e8f0255#126521255|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#00e8f0255#126521255|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#00e8f0255#126521255> @19|â•‘| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#00e8f0255#126521255| @19|â•‘| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#00e8f0255#126521255| @19|â•‘| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#00e8f0255#126521255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_MyTermCol.dump b/src/testdir/dumps/Test_terminal_popup_gui_MyTermCol.dump
new file mode 100644
index 0000000..a4570e4
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_MyTermCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0&#ff8bff255|â•@19|â•—| +0&#ffffff0@26
+|5| @24|â•‘+0&#ff8bff255|h+0#007800255#6789ff255|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|6| @24|â•‘+0&#ff8bff255|h+0#007800255#6789ff255|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|7| @24|â•‘+0&#ff8bff255> +0#007800255#6789ff255@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|8| @24|â•‘+0&#ff8bff255| +0#007800255#6789ff255@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|9| @24|â•‘+0&#ff8bff255| +0#007800255#6789ff255@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|1|0| @23|â•š+0&#ff8bff255|â•@19|â•| +0&#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_MyWinCol.dump b/src/testdir/dumps/Test_terminal_popup_gui_MyWinCol.dump
new file mode 100644
index 0000000..10ed686
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_MyWinCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#fe1122255#818100255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#fe1122255#818100255|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#fe1122255#818100255|h|e|l@1|o| @14|â•‘| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#fe1122255#818100255> @19|â•‘| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#fe1122255#818100255| @19|â•‘| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#fe1122255#818100255| @19|â•‘| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#fe1122255#818100255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_Terminal.dump b/src/testdir/dumps/Test_terminal_popup_gui_Terminal.dump
new file mode 100644
index 0000000..eff82b7
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_Terminal.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0&#ff8bff255|â•@19|â•—| +0&#ffffff0@26
+|5| @24|â•‘+0&#ff8bff255|h+0#3344ff255#b0a700255|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|6| @24|â•‘+0&#ff8bff255|h+0#3344ff255#b0a700255|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|7| @24|â•‘+0&#ff8bff255> +0#3344ff255#b0a700255@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|8| @24|â•‘+0&#ff8bff255| +0#3344ff255#b0a700255@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|9| @24|â•‘+0&#ff8bff255| +0#3344ff255#b0a700255@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|1|0| @23|â•š+0&#ff8bff255|â•@19|â•| +0&#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_transp_MyPopupHlCol.dump b/src/testdir/dumps/Test_terminal_popup_gui_transp_MyPopupHlCol.dump
new file mode 100644
index 0000000..763929f
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_transp_MyPopupHlCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#00e8f0255&|â•@19|â•—| +0#0000000&@26
+|5| @24|â•‘+0#00e8f0255&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|6| @24|â•‘+0#00e8f0255&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|7| @24|â•‘+0#00e8f0255&> @19|â•‘| +0#0000000&@26
+|8| @24|â•‘+0#00e8f0255&| @19|â•‘| +0#0000000&@26
+|9| @24|â•‘+0#00e8f0255&| @19|â•‘| +0#0000000&@26
+|1|0| @23|â•š+0#00e8f0255&|â•@19|â•| +0#0000000&@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_transp_MyTermCol.dump b/src/testdir/dumps/Test_terminal_popup_gui_transp_MyTermCol.dump
new file mode 100644
index 0000000..f0621c1
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_transp_MyTermCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0&#ff8bff255|â•@19|â•—| +0&#ffffff0@26
+|5| @24|â•‘+0&#ff8bff255|h+0#007800255#ffffff0|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|6| @24|â•‘+0&#ff8bff255|h+0#007800255#ffffff0|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|7| @24|â•‘+0&#ff8bff255> +0#007800255#ffffff0@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|8| @24|â•‘+0&#ff8bff255| +0#007800255#ffffff0@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|9| @24|â•‘+0&#ff8bff255| +0#007800255#ffffff0@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|1|0| @23|â•š+0&#ff8bff255|â•@19|â•| +0&#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_transp_MyWinCol.dump b/src/testdir/dumps/Test_terminal_popup_gui_transp_MyWinCol.dump
new file mode 100644
index 0000000..d7067c1
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_transp_MyWinCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#fe1122255&|â•@19|â•—| +0#0000000&@26
+|5| @24|â•‘+0#fe1122255&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|6| @24|â•‘+0#fe1122255&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|7| @24|â•‘+0#fe1122255&> @19|â•‘| +0#0000000&@26
+|8| @24|â•‘+0#fe1122255&| @19|â•‘| +0#0000000&@26
+|9| @24|â•‘+0#fe1122255&| @19|â•‘| +0#0000000&@26
+|1|0| @23|â•š+0#fe1122255&|â•@19|â•| +0#0000000&@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_gui_transp_Terminal.dump b/src/testdir/dumps/Test_terminal_popup_gui_transp_Terminal.dump
new file mode 100644
index 0000000..072e2f8
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_gui_transp_Terminal.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0&#ff8bff255|â•@19|â•—| +0&#ffffff0@26
+|5| @24|â•‘+0&#ff8bff255|h+0#3344ff255#ffffff0|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|6| @24|â•‘+0&#ff8bff255|h+0#3344ff255#ffffff0|e|l@1|o| @14|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|7| @24|â•‘+0&#ff8bff255> +0#3344ff255#ffffff0@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|8| @24|â•‘+0&#ff8bff255| +0#3344ff255#ffffff0@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|9| @24|â•‘+0&#ff8bff255| +0#3344ff255#ffffff0@19|â•‘+0#0000000#ff8bff255| +0&#ffffff0@26
+|1|0| @23|â•š+0&#ff8bff255|â•@19|â•| +0&#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_m1.dump b/src/testdir/dumps/Test_terminal_popup_m1.dump
new file mode 100644
index 0000000..0ab9534
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_m1.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#0000001#ffd7ff255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#0000001#ffd7ff255|a|n|o|t|h|e|r| |t|e|x|t| @7|â•‘| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#0000001#ffd7ff255|t|o| |s|h|o|w| @12|â•‘| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#0000001#ffd7ff255>i|n| |a| |p|o|p|u|p| |w|i|n|d|o|w| @2|â•‘| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#0000001#ffd7ff255| +0#4040ff13&@19|â•‘+0#0000001&| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#0000001#ffd7ff255| +0#4040ff13&@19|â•‘+0#0000001&| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#0000001#ffd7ff255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+|:| @73
diff --git a/src/testdir/dumps/Test_terminal_popup_transp_MyPopupHlCol.dump b/src/testdir/dumps/Test_terminal_popup_transp_MyPopupHlCol.dump
new file mode 100644
index 0000000..508d812
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_transp_MyPopupHlCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#40ffff15&|â•@19|â•—| +0#0000000&@26
+|5| @24|â•‘+0#40ffff15&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|6| @24|â•‘+0#40ffff15&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|7| @24|â•‘+0#40ffff15&> @19|â•‘| +0#0000000&@26
+|8| @24|â•‘+0#40ffff15&| @19|â•‘| +0#0000000&@26
+|9| @24|â•‘+0#40ffff15&| @19|â•‘| +0#0000000&@26
+|1|0| @23|â•š+0#40ffff15&|â•@19|â•| +0#0000000&@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_transp_MyTermCol.dump b/src/testdir/dumps/Test_terminal_popup_transp_MyTermCol.dump
new file mode 100644
index 0000000..9336e76
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_transp_MyTermCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#0000001#ffd7ff255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#0000001#ffd7ff255|h+0#00e0003#ffffff0|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#0000001#ffd7ff255|h+0#00e0003#ffffff0|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#0000001#ffd7ff255> +0#00e0003#ffffff0@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#0000001#ffd7ff255| +0#00e0003#ffffff0@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#0000001#ffd7ff255| +0#00e0003#ffffff0@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#0000001#ffd7ff255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_transp_MyWinCol.dump b/src/testdir/dumps/Test_terminal_popup_transp_MyWinCol.dump
new file mode 100644
index 0000000..9669964
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_transp_MyWinCol.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#ff404010&|â•@19|â•—| +0#0000000&@26
+|5| @24|â•‘+0#ff404010&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|6| @24|â•‘+0#ff404010&|h|e|l@1|o| @14|â•‘| +0#0000000&@26
+|7| @24|â•‘+0#ff404010&> @19|â•‘| +0#0000000&@26
+|8| @24|â•‘+0#ff404010&| @19|â•‘| +0#0000000&@26
+|9| @24|â•‘+0#ff404010&| @19|â•‘| +0#0000000&@26
+|1|0| @23|â•š+0#ff404010&|â•@19|â•| +0#0000000&@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_popup_transp_Terminal.dump b/src/testdir/dumps/Test_terminal_popup_transp_Terminal.dump
new file mode 100644
index 0000000..828b0f5
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_popup_transp_Terminal.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @24|â•”+0#0000001#ffd7ff255|â•@19|â•—| +0#0000000#ffffff0@26
+|5| @24|â•‘+0#0000001#ffd7ff255|h+0#4040ff13#ffffff0|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|6| @24|â•‘+0#0000001#ffd7ff255|h+0#4040ff13#ffffff0|e|l@1|o| @14|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|7| @24|â•‘+0#0000001#ffd7ff255> +0#4040ff13#ffffff0@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|8| @24|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|9| @24|â•‘+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@19|â•‘+0#0000001#ffd7ff255| +0#0000000#ffffff0@26
+|1|0| @23|â•š+0#0000001#ffd7ff255|â•@19|â•| +0#0000000#ffffff0@26
+|1@1| @72
+|1|2| @72
+|1|3| @72
+@75
diff --git a/src/testdir/dumps/Test_terminal_scrollback_1.dump b/src/testdir/dumps/Test_terminal_scrollback_1.dump
new file mode 100644
index 0000000..a242bb7
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_scrollback_1.dump
@@ -0,0 +1,20 @@
+|4+0&#ffffff0|2| @72
+|4|3| @72
+|4@1| @72
+|4|5| @72
+|4|6| @72
+|4|7| @72
+|4|8| @72
+>4|9| @72
+|~+0#4040ff13&| @73
+|!+2#ffffff16#00e0003|/|b|i|n|/|s|h| |[|T|e|r|m|i|n|a|l|]| @55
+| +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @65
+| +0&&@74
diff --git a/src/testdir/dumps/Test_terminal_scrollback_2.dump b/src/testdir/dumps/Test_terminal_scrollback_2.dump
new file mode 100644
index 0000000..427a9ad
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_scrollback_2.dump
@@ -0,0 +1,20 @@
+|4+0&#ffffff0|2| @72
+|4|3| @72
+|4@1| @72
+|4|5| @72
+|4|6| @72
+|4|7| @72
+>4|8| @72
+|4|9| @72
+|~+0#4040ff13&| @73
+|!+2#ffffff16#00e0003|/|b|i|n|/|s|h| |[|T|e|r|m|i|n|a|l|]| @55
+| +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @65
+| +0&&@74
diff --git a/src/testdir/dumps/Test_terminal_scrollback_3.dump b/src/testdir/dumps/Test_terminal_scrollback_3.dump
new file mode 100644
index 0000000..0796088
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_scrollback_3.dump
@@ -0,0 +1,20 @@
+|4+0&#ffffff0|3| @72
+|4@1| @72
+|4|5| @72
+|4|6| @72
+|4|7| @72
+|4|8| @72
+|4|9| @72
+|o|n|e| |m|o|r|e| |l|i|n|e| @61
+> @74
+|!+2#ffffff16#00e0003|/|b|i|n|/|s|h| |[|r|u|n@1|i|n|g|]| @56
+| +0#0000000#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+1#0000000&|N|o| |N|a|m|e|]| @65
+| +0&&@74
diff --git a/src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol.dump b/src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol.dump
new file mode 100644
index 0000000..3633ffe
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol.dump
@@ -0,0 +1,15 @@
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+> +0#ff404010#e0e0004@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0|5+0&&| @35
+|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @22||+1#0000000#ffffff0|6+0&&| @35
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|7+0&&| @35
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|8+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|9+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&|0| @34
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&@1| @34
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|1+0&&|2| @34
+|!+0#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|s|e|t| |w|i|n|c|o|l|o|r|=|M|y|W|i|n|C|o|l| @52
diff --git a/src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol2.dump b/src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol2.dump
new file mode 100644
index 0000000..ce69e3b
--- /dev/null
+++ b/src/testdir/dumps/Test_terminal_wincolor_split_MyWinCol2.dump
@@ -0,0 +1,15 @@
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|h+0#0000001#4040ff13|e|l@1|o| @31
+|h+0#ff404010#e0e0004|e|l@1|o| @31||+1#0000000#ffffff0|h+0#0000001#4040ff13|e|l@1|o| @31
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0> +0#0000001#4040ff13@36
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0| +0#0000001#4040ff13@36
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0| +0#0000001#4040ff13@36
+| +0#ff404010#e0e0004@36||+1#0000000#ffffff0| +0#0000001#4040ff13@36
+|!+0#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @22||+1#0000000#ffffff0|!+2#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @22
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|0+0&&| @35
+|h+0#00e0003#5fd7ff255|e|l@1|o| @31||+1#0000000#ffffff0|1+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|2+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|3+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|4+0&&| @35
+| +0#00e0003#5fd7ff255@36||+1#0000000#ffffff0|5+0&&| @35
+|!+0#ffffff16#00e0003|c|a|t| |[|r|u|n@1|i|n|g|]| @23|[+1#0000000#ffffff0|N|o| |N|a|m|e|]| |[|+|]| @23
+|:+0&&|s|e|t| |w|i|n|c|o|l|o|r|=|M|y|W|i|n|C|o|l|2| @51
diff --git a/src/testdir/dumps/Test_text_after_nowrap_1.dump b/src/testdir/dumps/Test_text_after_nowrap_1.dump
new file mode 100644
index 0000000..0781e6c
--- /dev/null
+++ b/src/testdir/dumps/Test_text_after_nowrap_1.dump
@@ -0,0 +1,8 @@
+|f+0&#ffffff0|i|r|s|t| |l|i|n|e| @1|r+0&#ffd7ff255|i|g|h|t| |a|f|t|e|r| |t|h|e| |t|e|x|t| |r|i|g|h|t| |a|f|t|e|r| |t|h|e| |t|e|x|t| |r|i|g|h|t|
+|0+0&#ffffff0| >1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| |2|1| |2@1| |2
+|t|h|i|r|d| @54
+|f|o|u|r|t|h| @53
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_text_after_nowrap_2.dump b/src/testdir/dumps/Test_text_after_nowrap_2.dump
new file mode 100644
index 0000000..28a45f3
--- /dev/null
+++ b/src/testdir/dumps/Test_text_after_nowrap_2.dump
@@ -0,0 +1,8 @@
+| +0&#ffd7ff255|r|i|g|h|t| |a|f|t|e|r| |t|h|e| | +0&#ffffff0@2|i+0&#ffd7ff255|n| |t|h|e| |m|i|d@1|l|e| |i|n| |t|h|e| |m|i|d@1|l|e| |i|n| |t|h|e| |m|i|d@1|l
+|2+0&#ffffff0|1| |2@1| |2|3| |2|4| |2|5| |2|6| |2|7| |2|8| |2|9| |3|0| >3|1| |3|2| |3@1| |3|4| |3|5| |3|6| |3|7| |3|8| |3|9| |4|0|
+@60
+@60
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|8|4| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_text_after_nowrap_3.dump b/src/testdir/dumps/Test_text_after_nowrap_3.dump
new file mode 100644
index 0000000..0a7cb3b
--- /dev/null
+++ b/src/testdir/dumps/Test_text_after_nowrap_3.dump
@@ -0,0 +1,8 @@
+|h+0&#ffd7ff255|e| |m|i|d@1|l|e| | +0&#ffffff0|t+0&#ffd7ff255|h|e| |l|a|s|t| |o|n|e| |t|h|e| |l|a|s|t| |o|n|e| |t|h|e| |l|a|s|t| |o|n|e| | +0&#ffffff0@9
+|4|3| |4@1| |4|5| |4|6| |4|7| |4|8| |4|9| |5|0| |5|1| |5|2| >5|3| |5|4| |5@1| |5|6| |5|7| |5|8| |5|9| |6|0| |6|1| |6|2|
+@60
+@60
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|1|5|0| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_text_after_nowrap_4.dump b/src/testdir/dumps/Test_text_after_nowrap_4.dump
new file mode 100644
index 0000000..ffe1c76
--- /dev/null
+++ b/src/testdir/dumps/Test_text_after_nowrap_4.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@59
+|9| |7|0| |7|1| |7|2| |7|3| |7|4| |7|5| |7|6| |7@1| |7|8| |7>9| @28
+@60
+@60
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+| +0#0000000&@41|2|,|2@1|9| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_text_after_nowrap_5.dump b/src/testdir/dumps/Test_text_after_nowrap_5.dump
new file mode 100644
index 0000000..ffd5708
--- /dev/null
+++ b/src/testdir/dumps/Test_text_after_nowrap_5.dump
@@ -0,0 +1,8 @@
+|f+0&#ffffff0|i|r|s|t| |l|i|n|e|$+0#4040ff13&| +0#0000000&@1|j+0&#ffd7ff255|u|s|t| |a|f|t|e|r| |t|x|t| |j|u|s|t| |a|f|t|e|r| |t|x|t| |j|u|s|t| |a|f|t|e|r| |t|x|t| | +0&#ffffff0|i+0&#ffd7ff255
+>0+0&#ffffff0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| |2|1| |2@1| |2
+|t|h|i|r|d|$+0#4040ff13&| +0#0000000&@53
+|f|o|u|r|t|h|$+0#4040ff13&| +0#0000000&@52
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|:+0#0000000&|c|a|l@1| |C|h|a|n|g|e|T|e|x|t|(|)| @23|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_text_after_nowrap_list_1.dump b/src/testdir/dumps/Test_text_after_nowrap_list_1.dump
new file mode 100644
index 0000000..bbbbe28
--- /dev/null
+++ b/src/testdir/dumps/Test_text_after_nowrap_list_1.dump
@@ -0,0 +1,6 @@
+|s+0&#ffffff0|o|m|e| |t|e|x|t| |h|e|r|e|$+0#4040ff13&| +0#0000000&@1|T+0&#ffd7ff255|h|e| |q|u|i|c|k| |b|r|o|w|n| |f|o|x| |j|u|m|p|s|.| +0&#ffffff0@1|â– +0&#ffd7ff255| |T|h|e| |f|o|x| |j|u|m|p|>+0#4040ff13#ffffff0
+|$| +0#0000000&@58
+|l|a|s|t| |l|i|n>e|$+0#4040ff13&| +0#0000000&@49
+|~+0#4040ff13&| @58
+|~| @58
+| +0#0000000&@41|3|,|9| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_text_below_nowrap_1.dump b/src/testdir/dumps/Test_text_below_nowrap_1.dump
new file mode 100644
index 0000000..3325411
--- /dev/null
+++ b/src/testdir/dumps/Test_text_below_nowrap_1.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |f+0#0000000&|i|r|s|t| |l|i|n|e| @45
+| +0#af5f00255&@3| +0#0000000&@1|o+0&#ffd7ff255|n|e| |b|e|l|o|w| |t|h|e| |t|e|x|t| |o|n|e| |b|e|l|o|w| |t|h|e| |t|e|x|t| |o|n|e| |b|e|l|o|w| |t|h|e| |t|e
+| +0#af5f00255#ffffff0@3| +0#0000000&@1|t+0&#ffd7ff255|w|o| |b|e|l|o|w| |t|h|e| |t|e|x|t| |t|w|o| |b|e|l|o|w| |t|h|e| |t|e|x|t| |t|w|o| |b|e|l|o|w| |t|h|e| |t|e
+| +0#af5f00255#ffffff0@1|2| |s+0#0000000&|e|c|o|n|d| >l|i|n|e| |s|e|c|o|n|d| |l|i|n|e| |s|e|c|o|n|d| |l|i|n|e| |s|e|c|o|n|d| |l|i|n|e| |s|e|c|o|n|d| |l
+| +0#af5f00255&@1|3| |t+0#0000000&|h|i|r|d| @50
+| +0#af5f00255&@1|4| |f+0#0000000&|o|u|r|t|h| @49
+|~+0#4040ff13&| @58
+| +0#0000000&@41|2|,|8| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_01.dump b/src/testdir/dumps/Test_textprop_01.dump
new file mode 100644
index 0000000..b8a50be
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_01.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffffff0@1|1| |O+0#0000000&|n|e| +0&#ffff4012|t|w|o| +0&#ffffff0@63
+| +0#af5f00255&@1|2| |N+0#0000000#ffff4012|u|m|b|é|r| |1+0#4040ff13&|2|3| +0#0000000&|ä|n|d| |t|h|œ|n| |4+0#4040ff13&|¾|7|.+0#0000000&| +0&#ffffff0@46
+| +8#af5f00255&@1|3| >-+8#0000000#ffff4012|x+8&#ffffff0|a+8#4040ff13&@1|x+8#0000000&|-@1|x+8#4040ff13&|b@1|x+8#0000000&|-@1|x|c+8#4040ff13&@1|x|-+8#0000000&@1|x+8#4040ff13&|d@1|x|-+8#0000000&@1| @45
+| +0#af5f00255&@1|4| |/+0#40ff4011&@1| |c|o|m+0#0000000#e0e0e08@1|e|n+0#40ff4011#ffffff0|t| |w+0&#e0e0e08|i|t|h| |e+8&&|r@1|o|r| +0&#ffffff0|i|n| |i|t| +0#0000000&@43
+| +0#af5f00255&@1|5| |f+0#0000000&|i|r|s|t| |l+0&#ffff4012|i|n|e| @1|s|e|c|o|n|d| +0&#ffffff0|l|i|n|e| @1|t|h|i|r|d| |l|i|n|e| +0&#ffff4012|f+0&#ffffff0|o|u|r|t|h| |l+0&#ffff4012|i|n|e| +0&#ffffff0@23
+|~+0#4040ff13&| @73
+|~| @73
+| +0#0000000&@56|3|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_hl_override_1.dump b/src/testdir/dumps/Test_textprop_hl_override_1.dump
new file mode 100644
index 0000000..ce447e7
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_hl_override_1.dump
@@ -0,0 +1,8 @@
+|O+0&#ffffff0|n|e| |o+0#4040ff13#ffff4012|n|e| |o+0#0000000#ffffff0|n|e| |o+0#4040ff13#ffff4012|n|e| |o+0#0000000#ffffff0|n|e| @55
+>T+10#ff404010#40ff4011|w|o| |t|w|o| |t|w|o| |t+10#4040ff13#ffff4012|w|o| |t+10#ff404010#40ff4011|w|o| @55
+|T+0#0000000#ffffff0|h|r|e|e+0#4040ff13#ffff4012| |t|h|r+0#0000000#ffffff0|e@1| |t+0#4040ff13#ffff4012|h|r|e|e+0#0000000#ffffff0| |t|h|r|e@1| @51
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_hl_override_2.dump b/src/testdir/dumps/Test_textprop_hl_override_2.dump
new file mode 100644
index 0000000..187233d
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_hl_override_2.dump
@@ -0,0 +1,8 @@
+|O+0&#ffffff0|n|e| |o+0#4040ff13#ffff4012|n|e| |o+0#0000000#ffffff0|n|e| |o+0#4040ff13#ffff4012|n|e| |o+0#0000000#ffffff0|n|e| @55
+|T|w|o| |t+0#4040ff13#ffff4012|w|o| |t+0#0000000#ffffff0|w|o| |t+0#4040ff13#ffff4012|w|o| |t+0#0000000#ffffff0|w|o| @55
+|T|h|r+0&#e0e0e08|e|e+0#4040ff13&| |t|h|r+0#0000000&|e@1| |t+0#4040ff13#ffff4012|h|r|e|e+0#0000000#e0e0e08| |t|h|r>e+0&#ffffff0@1| @51
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |V|I|S|U|A|L| |-@1| +0&&@34|2|0| @7|3|,|2@1| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_nesting.dump b/src/testdir/dumps/Test_textprop_nesting.dump
new file mode 100644
index 0000000..e02cc54
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_nesting.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|c|o|n|s|t| |f|u|n|c|:| |f|u|n|c|.|I|F|u|n|c|t|i|o|n| |=| |(|{+0#ffffff16#e000002|t|e|x|t| |l|o|n|g| |e|n|o|u|g|h| |t|o| |w|r|a|p| |l|i|n|e|,| |t|e|x|t| |l|o|n|g| |e|n|…
+| @3|s|e|t|L|o|a|d|i|n|g| +0#0000000#ffffff0@60
+| +0#ffffff16#e000002| +0#0000000#ffffff0|}|)| |=|>| |{| @65
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_nowrap_01.dump b/src/testdir/dumps/Test_textprop_nowrap_01.dump
new file mode 100644
index 0000000..f0fe7db
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_nowrap_01.dump
@@ -0,0 +1,6 @@
+>a+0&#ffffff0|l@1|e|r| |t|h|a|n| |4+0#ffffff16#e000002|5|6|7|.+0#0000000#ffffff0|X@58
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|2|1| @9|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_nowrap_02.dump b/src/testdir/dumps/Test_textprop_nowrap_02.dump
new file mode 100644
index 0000000..104c674
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_nowrap_02.dump
@@ -0,0 +1,6 @@
+|X+0&#ffffff0@36>X| @36
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1@2| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_syn_1.dump b/src/testdir/dumps/Test_textprop_syn_1.dump
new file mode 100644
index 0000000..6f5aff5
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_syn_1.dump
@@ -0,0 +1,6 @@
+>(+0&#40ffff15|a+0#e000e06#ffffff0|b|c|)+0#0000000#40ffff15| +0&#ffffff0@69
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_tab.dump b/src/testdir/dumps/Test_textprop_tab.dump
new file mode 100644
index 0000000..a1b7705
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_tab.dump
@@ -0,0 +1,6 @@
+| +0&#ffffff0@6> |x+0&#ffff4012@2| +0&#ffffff0@63
+|x| @6|x+0&#ffff4012@2| +0&#ffffff0@63
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1|-|8| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_vis_01.dump b/src/testdir/dumps/Test_textprop_vis_01.dump
new file mode 100644
index 0000000..f411d30
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_vis_01.dump
@@ -0,0 +1,12 @@
+|x+0&#ffffff0@4>x@1| |1+0&#ffff4012|2|3| +0&#ffffff0|x| @61
+|x@5| |1+0&#ffff4012|2|3| +0&#ffffff0|x| @62
+|x@4| |1+0&#ffff4012|2|3| +0&#ffffff0|x| @63
+|x@4|1+0&#ffff4012|2|3| +0&#ffffff0|x| @64
+|x@4|2+0&#ffff4012|3| +0&#ffffff0|x| @65
+|x@3| |3+0&#ffff4012| +0&#ffffff0|x@1| @65
+|x@2| |1+0&#ffff4012| +0&#ffffff0|x@2| @65
+|x@1| |1+0&#ffff4012|2|x+0&#ffffff0@3| @65
+|x| |1+0&#ffff4012|2|3|x+0&#ffffff0@3| @65
+@1|1+0&#ffff4012|2|3| +0&#ffffff0|x@3| @65
+|~+0#4040ff13&| @73
+| +0#0000000&@56|1|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_textprop_vis_02.dump b/src/testdir/dumps/Test_textprop_vis_02.dump
new file mode 100644
index 0000000..d999ae1
--- /dev/null
+++ b/src/testdir/dumps/Test_textprop_vis_02.dump
@@ -0,0 +1,12 @@
+|x+0&#ffffff0@4> |1+0&#ffff4012|2|3| +0&#ffffff0|x| @63
+|x@4|1+0&#ffff4012|2|3| +0&#ffffff0|x| @64
+|x@4|2+0&#ffff4012|3| +0&#ffffff0|x| @65
+|x@4|3+0&#ffff4012| +0&#ffffff0|x| @66
+|x@4| |x| @67
+|x@3| |x@1| @67
+|x@2| |1+0&#ffff4012|x+0&#ffffff0@1| @67
+|x@1| |1+0&#ffff4012|2|x+0&#ffffff0@1| @67
+|x| |1+0&#ffff4012|2|3|x+0&#ffffff0@1| @67
+@1|1+0&#ffff4012|2|3| +0&#ffffff0|x@1| @67
+|~+0#4040ff13&| @73
+| +0#0000000&@56|1|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_tselect_1.dump b/src/testdir/dumps/Test_tselect_1.dump
new file mode 100644
index 0000000..9090122
--- /dev/null
+++ b/src/testdir/dumps/Test_tselect_1.dump
@@ -0,0 +1,10 @@
+>i+0#00e0003#ffffff0|n|t| +0#0000000&|m|a|i|n|(|)| @39
+|v+0#00e0003&|o|i|d| +0#0000000&|t|e|s|t|(|)| @38
+|~+0#4040ff13&| @48
+|~| @48
+|~| @48
+|~| @48
+|~| @48
+|~| @48
+|~| @48
+|"+0#0000000&|X|t|e|s|t|.|c|"| |2|L|,| |2|3|B| @14|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_undo_after_write_1.dump b/src/testdir/dumps/Test_undo_after_write_1.dump
new file mode 100644
index 0000000..92410d4
--- /dev/null
+++ b/src/testdir/dumps/Test_undo_after_write_1.dump
@@ -0,0 +1,6 @@
+|t+0&#ffffff0|e|s>t| @70
+@75
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|1+0#0000000&| |l|i|n|e| |l|e|s@1|;| |b|e|f|o|r|e| |#|2| @1|0| |s|e|c|o|n|d|s| |a|g|o| @19|1|,|4| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_undo_after_write_2.dump b/src/testdir/dumps/Test_undo_after_write_2.dump
new file mode 100644
index 0000000..029e324
--- /dev/null
+++ b/src/testdir/dumps/Test_undo_after_write_2.dump
@@ -0,0 +1,6 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|2+0#0000000&| |f|e|w|e|r| |l|i|n|e|s|;| |b|e|f|o|r|e| |#|1| @1|1| |s|e|c|o|n|d| |a|g|o| @18|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_undo_after_write_2.vim b/src/testdir/dumps/Test_undo_after_write_2.vim
new file mode 100644
index 0000000..d1d3c3d
--- /dev/null
+++ b/src/testdir/dumps/Test_undo_after_write_2.vim
@@ -0,0 +1,2 @@
+" Filter that changes the "1 second ago" message to "0 seconds ago".
+6s+|1| |s|e|c|o|n|d| |a|g|o| @18|+|0| |s|e|c|o|n|d|s| |a|g|o| @17|+e
diff --git a/src/testdir/dumps/Test_verbose_option_1.dump b/src/testdir/dumps/Test_verbose_option_1.dump
new file mode 100644
index 0000000..192d102
--- /dev/null
+++ b/src/testdir/dumps/Test_verbose_option_1.dump
@@ -0,0 +1,12 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|E+0#0000000&|x|e|c|u|t|i|n|g|:| |D|o|S|o|m|e|t|h|i|n|g| @52
+|E|x|e|c|u|t|i|n|g|:| |e|c|h|o| |'|h|e|l@1|o|'| |||s|e|t| |t|s|=|4| |||l|e|t| |v| |=| |'|1|2|3|'| |||e|c|h|o| |v| @18
+|h|e|l@1|o| @69
+|E|x|e|c|u|t|i|n|g|:| |s|e|t| |t|s|=|4| |||l|e|t| |v| |=| |'|1|2|3|'| |||e|c|h|o| |v| @32
+|E|x|e|c|u|t|i|n|g|:| |l|e|t| |v| |=| |'|1|2|3|'| |||e|c|h|o| |v| @42
+|E|x|e|c|u|t|i|n|g|:| |e|c|h|o| |v| @57
+|1|2|3| @71
+|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@35
diff --git a/src/testdir/dumps/Test_verbose_system_1.dump b/src/testdir/dumps/Test_verbose_system_1.dump
new file mode 100644
index 0000000..6723b54
--- /dev/null
+++ b/src/testdir/dumps/Test_verbose_system_1.dump
@@ -0,0 +1,10 @@
+|C+0&#ffffff0|a|l@1|i|n|g| |s|h|e|l@1| |t|o| |e|x|e|c|u|t|e|:| |"|(|s|e|q| |2|0|)|>|/|t|m|p|/|v|W|4|d|k|0|a|/|0| |2|>|&|1|"| @18
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|-+0#00e0003&@1| |M|o|r|e| |-@1> +0#0000000&@64
diff --git a/src/testdir/dumps/Test_verbose_system_1.vim b/src/testdir/dumps/Test_verbose_system_1.vim
new file mode 100644
index 0000000..65e9c8a
--- /dev/null
+++ b/src/testdir/dumps/Test_verbose_system_1.vim
@@ -0,0 +1,5 @@
+" Filter that removes the ever changing temp directory name from the screendump
+" that shows the system() command executed.
+" This should be on the first line, but if it isn't there ignore the error,
+" the screendump will then show the problem.
+1s+|t|m|p|/|.|.|.*| |+|t|m|p|/|x|x|x|x|x|x|x|/|1| |+e
diff --git a/src/testdir/dumps/Test_verbose_system_2.dump b/src/testdir/dumps/Test_verbose_system_2.dump
new file mode 100644
index 0000000..ed65719
--- /dev/null
+++ b/src/testdir/dumps/Test_verbose_system_2.dump
@@ -0,0 +1,10 @@
+|C+0&#ffffff0|a|l@1|i|n|g| |s|h|e|l@1| |t|o| |e|x|e|c|u|t|e|:| |"|(|s|e|q| |2|0|)|>|/|t|m|p|/|v|V|v|w|c|g|V|/|1| |2|>|&|1|"| @18
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @73
+|6| @73
+|7| @73
+|8| @73
+|-+0#00e0003&@1| |M|o|r|e| |-@1> +0#0000000&@64
diff --git a/src/testdir/dumps/Test_verbose_system_2.vim b/src/testdir/dumps/Test_verbose_system_2.vim
new file mode 100644
index 0000000..65e9c8a
--- /dev/null
+++ b/src/testdir/dumps/Test_verbose_system_2.vim
@@ -0,0 +1,5 @@
+" Filter that removes the ever changing temp directory name from the screendump
+" that shows the system() command executed.
+" This should be on the first line, but if it isn't there ignore the error,
+" the screendump will then show the problem.
+1s+|t|m|p|/|.|.|.*| |+|t|m|p|/|x|x|x|x|x|x|x|/|1| |+e
diff --git a/src/testdir/dumps/Test_vim9_closure_fails.dump b/src/testdir/dumps/Test_vim9_closure_fails.dump
new file mode 100644
index 0000000..dd0103c
--- /dev/null
+++ b/src/testdir/dumps/Test_vim9_closure_fails.dump
@@ -0,0 +1,6 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|0+0#0000000&| @55|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_vim9_no_redraw.dump b/src/testdir/dumps/Test_vim9_no_redraw.dump
new file mode 100644
index 0000000..1d77a08
--- /dev/null
+++ b/src/testdir/dumps/Test_vim9_no_redraw.dump
@@ -0,0 +1,6 @@
+|s+0&#ffffff0|o+0&#e0e0e08|m|e| |t|e|x|t| | +0&#ffffff0@64
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|'|<|,|'|>> @68
diff --git a/src/testdir/dumps/Test_vim9_reject_declaration_1.dump b/src/testdir/dumps/Test_vim9_reject_declaration_1.dump
new file mode 100644
index 0000000..e4f5623
--- /dev/null
+++ b/src/testdir/dumps/Test_vim9_reject_declaration_1.dump
@@ -0,0 +1,6 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|E+0#ffffff16#e000002|1|0|7|9|:| |C|a|n@1|o|t| |d|e|c|l|a|r|e| |a| |v|a|r|i|a|b|l|e| |o|n| |t|h|e| |c|o|m@1|a|n|d| |l|i|n|e| +0#0000000#ffffff0@22
+|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@35
diff --git a/src/testdir/dumps/Test_vim9_reject_declaration_2.dump b/src/testdir/dumps/Test_vim9_reject_declaration_2.dump
new file mode 100644
index 0000000..a6d516c
--- /dev/null
+++ b/src/testdir/dumps/Test_vim9_reject_declaration_2.dump
@@ -0,0 +1,6 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|1+0#0000000&|2|3| @53|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_vim9_silent_echo.dump b/src/testdir/dumps/Test_vim9_silent_echo.dump
new file mode 100644
index 0000000..f5f7927
--- /dev/null
+++ b/src/testdir/dumps/Test_vim9_silent_echo.dump
@@ -0,0 +1,6 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|a|b|c> @70
diff --git a/src/testdir/dumps/Test_virtual_text_in_popup_highlight_1.dump b/src/testdir/dumps/Test_virtual_text_in_popup_highlight_1.dump
new file mode 100644
index 0000000..b8c5b14
--- /dev/null
+++ b/src/testdir/dumps/Test_virtual_text_in_popup_highlight_1.dump
@@ -0,0 +1,8 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @17| +0#0000001#ffd7ff255@16| +0#4040ff13#ffffff0@38
+|~| @17| +0#0000001#ffd7ff255| +0#0000e05&|+| |S+0#0000001&|o|m|e| | +0#4040ff13#afffff255|x| |t+0#0000001#ffd7ff255|e|x|t| | +0#4040ff13#ffffff0@38
+|~| @17| +0#0000001#ffd7ff255@16| +0#4040ff13#ffffff0@38
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_visual_block_with_virtualedit.dump b/src/testdir/dumps/Test_visual_block_with_virtualedit.dump
new file mode 100644
index 0000000..2991a63
--- /dev/null
+++ b/src/testdir/dumps/Test_visual_block_with_virtualedit.dump
@@ -0,0 +1,8 @@
+|a+0&#e0e0e08@5> +0&#ffffff0@43
+|b+0&#e0e0e08@3| @2| +0&#ffffff0@42
+|c+0&#e0e0e08@1| @4| +0&#ffffff0@42
+|~+0#4040ff13&| @48
+|~| @48
+|~| @48
+|~| @48
+|-+2#0000000&@1| |V|I|S|U|A|L| |B|L|O|C|K| |-@1| +0&&@3|3|x|7| @6|1|,|7| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_visual_block_with_virtualedit2.dump b/src/testdir/dumps/Test_visual_block_with_virtualedit2.dump
new file mode 100644
index 0000000..3c62156
--- /dev/null
+++ b/src/testdir/dumps/Test_visual_block_with_virtualedit2.dump
@@ -0,0 +1,8 @@
+|a+0&#e0e0e08@5| | +0&#ffffff0@42
+|b+0&#e0e0e08@3| @2| +0&#ffffff0@42
+|c+0&#e0e0e08@1> +0&#ffffff0| +0&#e0e0e08@3| +0&#ffffff0@42
+|~+0#4040ff13&| @48
+|~| @48
+|~| @48
+|~| @48
+|-+2#0000000&@1| |V|I|S|U|A|L| |B|L|O|C|K| |-@1| +0&&@3|3|x|3| @6|3|,|3| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_wildmenu_1.dump b/src/testdir/dumps/Test_wildmenu_1.dump
new file mode 100644
index 0000000..76b930d
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_1.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|v+0#0000001#ffff4012|i|m|9|c|m|d| +3#0000000#ffffff0@1|v|i|m|9|s|c|r|i|p|t| @1|v|i|m|g|r|e|p| @1|v|i|m|g|r|e|p|a|d@1| @34
+|:+0&&|v|i|m|9|c|m|d> @66
diff --git a/src/testdir/dumps/Test_wildmenu_2.dump b/src/testdir/dumps/Test_wildmenu_2.dump
new file mode 100644
index 0000000..17b6b02
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_2.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|v+3#0000000&|i|m|9|c|m|d| @1|v+0#0000001#ffff4012|i|m|9|s|c|r|i|p|t| +3#0000000#ffffff0@1|v|i|m|g|r|e|p| @1|v|i|m|g|r|e|p|a|d@1| @34
+|:+0&&|v|i|m|9|s|c|r|i|p|t> @63
diff --git a/src/testdir/dumps/Test_wildmenu_3.dump b/src/testdir/dumps/Test_wildmenu_3.dump
new file mode 100644
index 0000000..578c8f0
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_3.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|v+3#0000000&|i|m|9|c|m|d| @1|v|i|m|9|s|c|r|i|p|t| @1|v+0#0000001#ffff4012|i|m|g|r|e|p| +3#0000000#ffffff0@1|v|i|m|g|r|e|p|a|d@1| @34
+|:+0&&|v|i|m|g|r|e|p> @66
diff --git a/src/testdir/dumps/Test_wildmenu_4.dump b/src/testdir/dumps/Test_wildmenu_4.dump
new file mode 100644
index 0000000..4b16a37
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_4.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|v+3#0000000&|i|m|9|c|m|d| @1|v|i|m|9|s|c|r|i|p|t| @1|v|i|m|g|r|e|p| @1|v|i|m|g|r|e|p|a|d@1| @34
+|:+0&&|v|i|m> @70
diff --git a/src/testdir/dumps/Test_wildmenu_pum_01.dump b/src/testdir/dumps/Test_wildmenu_pum_01.dump
new file mode 100644
index 0000000..8c48229
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_01.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#e0e0e08|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e> @62
diff --git a/src/testdir/dumps/Test_wildmenu_pum_02.dump b/src/testdir/dumps/Test_wildmenu_pum_02.dump
new file mode 100644
index 0000000..f118df7
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_02.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |l|i|s|t> @64
diff --git a/src/testdir/dumps/Test_wildmenu_pum_03.dump b/src/testdir/dumps/Test_wildmenu_pum_03.dump
new file mode 100644
index 0000000..c3276ab
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_03.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |p|l|a|c|e> @63
diff --git a/src/testdir/dumps/Test_wildmenu_pum_04.dump b/src/testdir/dumps/Test_wildmenu_pum_04.dump
new file mode 100644
index 0000000..f118df7
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_04.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |l|i|s|t> @64
diff --git a/src/testdir/dumps/Test_wildmenu_pum_05.dump b/src/testdir/dumps/Test_wildmenu_pum_05.dump
new file mode 100644
index 0000000..1c3a2e8
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_05.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |j|u|m|p> @64
diff --git a/src/testdir/dumps/Test_wildmenu_pum_06.dump b/src/testdir/dumps/Test_wildmenu_pum_06.dump
new file mode 100644
index 0000000..4b60c57
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_06.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| > @68
diff --git a/src/testdir/dumps/Test_wildmenu_pum_07.dump b/src/testdir/dumps/Test_wildmenu_pum_07.dump
new file mode 100644
index 0000000..20a39ab
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_07.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e> @61
diff --git a/src/testdir/dumps/Test_wildmenu_pum_08.dump b/src/testdir/dumps/Test_wildmenu_pum_08.dump
new file mode 100644
index 0000000..7354738
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_08.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |u|n> @66
diff --git a/src/testdir/dumps/Test_wildmenu_pum_09.dump b/src/testdir/dumps/Test_wildmenu_pum_09.dump
new file mode 100644
index 0000000..2e8a0a1
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_09.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @3| +0#0000001#e0e0e08|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u|n|d|e|f|i|n|e> @60
diff --git a/src/testdir/dumps/Test_wildmenu_pum_10.dump b/src/testdir/dumps/Test_wildmenu_pum_10.dump
new file mode 100644
index 0000000..9851b7c
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_10.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e> @61
diff --git a/src/testdir/dumps/Test_wildmenu_pum_11.dump b/src/testdir/dumps/Test_wildmenu_pum_11.dump
new file mode 100644
index 0000000..4697c8a
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_11.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @10| +0#0000001#e0e0e08|c|u|l|h|l|=| @8| +0#4040ff13#ffffff0@46
+|~| @10| +0#0000001#ffd7ff255|i|c|o|n|=| @9| +0#4040ff13#ffffff0@46
+|~| @10| +0#0000001#ffd7ff255|l|i|n|e|h|l|=| @7| +0#4040ff13#ffffff0@46
+|~| @10| +0#0000001#ffd7ff255|n|u|m|h|l|=| @8| +0#4040ff13#ffffff0@46
+|~| @10| +0#0000001#ffd7ff255|t|e|x|t|=| @9| +0#4040ff13#ffffff0@46
+|~| @10| +0#0000001#ffd7ff255|t|e|x|t|h|l|=| @7| +0#4040ff13#ffffff0@46
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |c|u|l|h|l|=> @55
diff --git a/src/testdir/dumps/Test_wildmenu_pum_12.dump b/src/testdir/dumps/Test_wildmenu_pum_12.dump
new file mode 100644
index 0000000..d93631d
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_12.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @17| +0#0000001#e0e0e08|c|u|l|h|l|=| @8| +0#4040ff13#ffffff0@39
+|~| @17| +0#0000001#ffd7ff255|i|c|o|n|=| @9| +0#4040ff13#ffffff0@39
+|~| @17| +0#0000001#ffd7ff255|l|i|n|e|h|l|=| @7| +0#4040ff13#ffffff0@39
+|~| @17| +0#0000001#ffd7ff255|n|u|m|h|l|=| @8| +0#4040ff13#ffffff0@39
+|~| @17| +0#0000001#ffd7ff255|t|e|x|t|=| @9| +0#4040ff13#ffffff0@39
+|~| @17| +0#0000001#ffd7ff255|t|e|x|t|h|l|=| @7| +0#4040ff13#ffffff0@39
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |c|u|l|h|l|=| |c|u|l|h|l|=> @48
diff --git a/src/testdir/dumps/Test_wildmenu_pum_13.dump b/src/testdir/dumps/Test_wildmenu_pum_13.dump
new file mode 100644
index 0000000..b2b1424
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_13.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @24| +0#0000001#e0e0e08|c|u|l|h|l|=| @8| +0#4040ff13#ffffff0@32
+|~| @24| +0#0000001#ffd7ff255|i|c|o|n|=| @9| +0#4040ff13#ffffff0@32
+|~| @24| +0#0000001#ffd7ff255|l|i|n|e|h|l|=| @7| +0#4040ff13#ffffff0@32
+|~| @24| +0#0000001#ffd7ff255|n|u|m|h|l|=| @8| +0#4040ff13#ffffff0@32
+|~| @24| +0#0000001#ffd7ff255|t|e|x|t|=| @9| +0#4040ff13#ffffff0@32
+|~| @24| +0#0000001#ffd7ff255|t|e|x|t|h|l|=| @7| +0#4040ff13#ffffff0@32
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |c|u|l|h|l|=| |c|u|l|h|l|=| |c|u|l|h|l|=> @41
diff --git a/src/testdir/dumps/Test_wildmenu_pum_14.dump b/src/testdir/dumps/Test_wildmenu_pum_14.dump
new file mode 100644
index 0000000..7f5546b
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_14.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @9| +0#0000001#e0e0e08|X|d|i|r|A|/| @8| +0#4040ff13#ffffff0@47
+|~| @9| +0#0000001#ffd7ff255|X|f|i|l|e|A| @8| +0#4040ff13#ffffff0@47
+|:+0#0000000&|e| |X|n|a|m|e|d|i|r|/|X|d|i|r|A|/> @56
diff --git a/src/testdir/dumps/Test_wildmenu_pum_15.dump b/src/testdir/dumps/Test_wildmenu_pum_15.dump
new file mode 100644
index 0000000..59f1fc5
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_15.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @15| +0#0000001#e0e0e08|X|d|i|r|B|/| @8| +0#4040ff13#ffffff0@41
+|~| @15| +0#0000001#ffd7ff255|X|f|i|l|e|B| @8| +0#4040ff13#ffffff0@41
+|:+0#0000000&|e| |X|n|a|m|e|d|i|r|/|X|d|i|r|A|/|X|d|i|r|B|/> @50
diff --git a/src/testdir/dumps/Test_wildmenu_pum_16.dump b/src/testdir/dumps/Test_wildmenu_pum_16.dump
new file mode 100644
index 0000000..7f5546b
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_16.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @9| +0#0000001#e0e0e08|X|d|i|r|A|/| @8| +0#4040ff13#ffffff0@47
+|~| @9| +0#0000001#ffd7ff255|X|f|i|l|e|A| @8| +0#4040ff13#ffffff0@47
+|:+0#0000000&|e| |X|n|a|m|e|d|i|r|/|X|d|i|r|A|/> @56
diff --git a/src/testdir/dumps/Test_wildmenu_pum_17.dump b/src/testdir/dumps/Test_wildmenu_pum_17.dump
new file mode 100644
index 0000000..5eef94d
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_17.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |j|u|m|p| |l|i|s|t| |p|l|a|c|e| |u|n|d|e|f|i|n|e| |u|n|p|l|a|c|e> @29
diff --git a/src/testdir/dumps/Test_wildmenu_pum_18.dump b/src/testdir/dumps/Test_wildmenu_pum_18.dump
new file mode 100644
index 0000000..09b0b3e
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_18.dump
@@ -0,0 +1,10 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| @62
+|d|e|f|i|n|e| @68
+|:|s|i|g|n| |d|e|f|i|n|e> @62
diff --git a/src/testdir/dumps/Test_wildmenu_pum_19.dump b/src/testdir/dumps/Test_wildmenu_pum_19.dump
new file mode 100644
index 0000000..fa51c76
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_19.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u|n|d|e|f|i|n|e> @60
diff --git a/src/testdir/dumps/Test_wildmenu_pum_20.dump b/src/testdir/dumps/Test_wildmenu_pum_20.dump
new file mode 100644
index 0000000..99c2900
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_20.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@74
diff --git a/src/testdir/dumps/Test_wildmenu_pum_21.dump b/src/testdir/dumps/Test_wildmenu_pum_21.dump
new file mode 100644
index 0000000..ca37931
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_21.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e|x> @61
diff --git a/src/testdir/dumps/Test_wildmenu_pum_22.dump b/src/testdir/dumps/Test_wildmenu_pum_22.dump
new file mode 100644
index 0000000..3fd00ad
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_22.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|[+1&&|N|o| |N|a|m|e|]| @65
+|:+0#4040ff13&|s+0#af5f00255&|e|t| +0#0000000&|w+0#e000e06&|i|l|d|m|o|d|e|=+0#0000000&|l|o|n|g|e|s|t|,+0#af5f00255&|f+0#0000000&|u|l@1| @48
+|:+0#4040ff13&|s+0#af5f00255&|e|t| +0#0000000&|w+0#e000e06&|i|l|d|m|o|d|e|=+0#0000000&|f|u|l@1| @56
+|:+0#4040ff13&|s+0#af5f00255&|i|g|n| +0#0000000&|d|e|f|i|n|e| @62
+|:+0#4040ff13&|s+0#af5f00255&|i|g|n| +0#0000000&|d|e|f|i|n|e> @62
+|~+0#4040ff13&| @73
+|~| @73
+|[+3#0000000&|C|o|m@1|a|n|d| |L|i|n|e|]| @60
+|Y+0#0000001#ffff4012|o|u| |d|i|s|c|o|v|e|r|e|d| |t|h|e| |c|o|m@1|a|n|d|-|l|i|n|e| |w|i|n|d|o|w|!| |Y|o|u| |c|a|n| |c|l|o|s|e| |i|t| |w|i|t|h| |"|:|q|"|.| +0#0000000#ffffff0@7
diff --git a/src/testdir/dumps/Test_wildmenu_pum_23.dump b/src/testdir/dumps/Test_wildmenu_pum_23.dump
new file mode 100644
index 0000000..0bd6243
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_23.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u> @67
diff --git a/src/testdir/dumps/Test_wildmenu_pum_24.dump b/src/testdir/dumps/Test_wildmenu_pum_24.dump
new file mode 100644
index 0000000..f5767f7
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_24.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000001#e0e0e08|b|u|f|d|o| @9| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|b|u|f@1|e|r| @8| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|b|u|f@1|e|r|s| @7| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|b|u|n|l|o|a|d| @7| +0#4040ff13#ffffff0@58
+|:+0#0000000&|b|u|f|d|o> @68
diff --git a/src/testdir/dumps/Test_wildmenu_pum_25.dump b/src/testdir/dumps/Test_wildmenu_pum_25.dump
new file mode 100644
index 0000000..b7d117d
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_25.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|b|u|f|d>o| @68
diff --git a/src/testdir/dumps/Test_wildmenu_pum_26.dump b/src/testdir/dumps/Test_wildmenu_pum_26.dump
new file mode 100644
index 0000000..30786c9
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_26.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n> @63
diff --git a/src/testdir/dumps/Test_wildmenu_pum_27.dump b/src/testdir/dumps/Test_wildmenu_pum_27.dump
new file mode 100644
index 0000000..4b60c57
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_27.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| > @68
diff --git a/src/testdir/dumps/Test_wildmenu_pum_28.dump b/src/testdir/dumps/Test_wildmenu_pum_28.dump
new file mode 100644
index 0000000..910e224
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_28.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&> @73
diff --git a/src/testdir/dumps/Test_wildmenu_pum_29.dump b/src/testdir/dumps/Test_wildmenu_pum_29.dump
new file mode 100644
index 0000000..7a82fce
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_29.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |x|y|z> @65
diff --git a/src/testdir/dumps/Test_wildmenu_pum_30.dump b/src/testdir/dumps/Test_wildmenu_pum_30.dump
new file mode 100644
index 0000000..76e4780
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_30.dump
@@ -0,0 +1,10 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|n| @71
+|c|n|e|w|e|r| @6|c|n|f|i|l|e| @6|c|n|o|r|e|m|a|p| @40
+|c|n|e|x|t| @7|c|n|o|r|e|a|b@1|r|e|v| @1|c|n|o|r|e|m|e|n|u| @39
+|:|c|n> @71
diff --git a/src/testdir/dumps/Test_wildmenu_pum_31.dump b/src/testdir/dumps/Test_wildmenu_pum_31.dump
new file mode 100644
index 0000000..157f16c
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_31.dump
@@ -0,0 +1,10 @@
+|~+0#4040ff13#ffffff0| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|n| @71
+|c|n|e|w|e|r| @6|c|n|f|i|l|e| @6|c|n|o|r|e|m|a|p| @40
+|c|n|e|x|t| @7|c|n|o|r|e|a|b@1|r|e|v| @1|c|n|o|r|e|m|e|n|u| @39
+|:|c|n|s> @70
diff --git a/src/testdir/dumps/Test_wildmenu_pum_32.dump b/src/testdir/dumps/Test_wildmenu_pum_32.dump
new file mode 100644
index 0000000..9bff1a5
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_32.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @16| +0#0000001#e0e0e08|1|2|3| @11| +0#4040ff13#ffffff0@40
+|~| @16| +0#0000001#ffd7ff255|a|b|c| @11| +0#4040ff13#ffffff0@40
+|~| @16| +0#0000001#ffd7ff255|x|y|z| @11| +0#4040ff13#ffffff0@40
+|:+0#0000000&|e| |X|n|a|m|e|d|i|r|/|ã‚*&|ã„|ã†|/+&|1|2|3> @52
diff --git a/src/testdir/dumps/Test_wildmenu_pum_33.dump b/src/testdir/dumps/Test_wildmenu_pum_33.dump
new file mode 100644
index 0000000..3955edb
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_33.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |j|u|m|p| |l|i|s|t| |p|l|a|c|e| |u|n|d|e|f|i|n|e| |u|n|p|l|a|c|e|x> @28
diff --git a/src/testdir/dumps/Test_wildmenu_pum_34.dump b/src/testdir/dumps/Test_wildmenu_pum_34.dump
new file mode 100644
index 0000000..e28b913
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_34.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |j|u|m|p| |l|i|s|t| |p|l|a|c|e| |u|n|d|e|f|i|n|e| |u|n|p|l|a|c>e| @29
diff --git a/src/testdir/dumps/Test_wildmenu_pum_35.dump b/src/testdir/dumps/Test_wildmenu_pum_35.dump
new file mode 100644
index 0000000..3fbd30a
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_35.dump
@@ -0,0 +1,10 @@
+|~+0#4040ff13#ffffff0| @73
+|:+0#0000000&|T|c|m|d| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3| |a@3
+| |a@1>a@1| @69
diff --git a/src/testdir/dumps/Test_wildmenu_pum_36.dump b/src/testdir/dumps/Test_wildmenu_pum_36.dump
new file mode 100644
index 0000000..9d7b6b0
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_36.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|e| |X|d|i|r|B|/| |X|f|i|l|e|B> @58
diff --git a/src/testdir/dumps/Test_wildmenu_pum_37.dump b/src/testdir/dumps/Test_wildmenu_pum_37.dump
new file mode 100644
index 0000000..5eef94d
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_37.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e| |j|u|m|p| |l|i|s|t| |p|l|a|c|e| |u|n|d|e|f|i|n|e| |u|n|p|l|a|c|e> @29
diff --git a/src/testdir/dumps/Test_wildmenu_pum_38.dump b/src/testdir/dumps/Test_wildmenu_pum_38.dump
new file mode 100644
index 0000000..44c66ed
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_38.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|o|m|c|l|e|a|r| > @64
diff --git a/src/testdir/dumps/Test_wildmenu_pum_39.dump b/src/testdir/dumps/Test_wildmenu_pum_39.dump
new file mode 100644
index 0000000..27be763
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_39.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|s+3#0000000&|t|a|t|u|s| @68
+| +0&&@74
diff --git a/src/testdir/dumps/Test_wildmenu_pum_40.dump b/src/testdir/dumps/Test_wildmenu_pum_40.dump
new file mode 100644
index 0000000..75aa01a
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_40.dump
@@ -0,0 +1,10 @@
+|m+1&#ffffff0|y| |t|a|b| |l|i|n|e| @63
+> +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|b+3#0000000&|a|r| @71
+| +0&&@74
diff --git a/src/testdir/dumps/Test_wildmenu_pum_41.dump b/src/testdir/dumps/Test_wildmenu_pum_41.dump
new file mode 100644
index 0000000..6f23d92
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_41.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000001#e0e0e08|a|b@1|r|e|v|i|a|t|e| @4| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|a|b|c|l|e|a|r| @7| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|a|b|o|v|e|l|e|f|t| @5| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|a|b|s|t|r|a|c|t| @6| +0#4040ff13#ffffff0@58
+|:+0#0000000&|a|b@1|r|e|v|i|a|t|e> @63
diff --git a/src/testdir/dumps/Test_wildmenu_pum_42.dump b/src/testdir/dumps/Test_wildmenu_pum_42.dump
new file mode 100644
index 0000000..fa51c76
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_42.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u|n|d|e|f|i|n|e> @60
diff --git a/src/testdir/dumps/Test_wildmenu_pum_43.dump b/src/testdir/dumps/Test_wildmenu_pum_43.dump
new file mode 100644
index 0000000..372fc8e
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_43.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e> @61
diff --git a/src/testdir/dumps/Test_wildmenu_pum_44.dump b/src/testdir/dumps/Test_wildmenu_pum_44.dump
new file mode 100644
index 0000000..7b99b47
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_44.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| > @68
diff --git a/src/testdir/dumps/Test_wildmenu_pum_45.dump b/src/testdir/dumps/Test_wildmenu_pum_45.dump
new file mode 100644
index 0000000..8c48229
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_45.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#e0e0e08|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e> @62
diff --git a/src/testdir/dumps/Test_wildmenu_pum_46.dump b/src/testdir/dumps/Test_wildmenu_pum_46.dump
new file mode 100644
index 0000000..372fc8e
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_46.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e> @61
diff --git a/src/testdir/dumps/Test_wildmenu_pum_47.dump b/src/testdir/dumps/Test_wildmenu_pum_47.dump
new file mode 100644
index 0000000..7b99b47
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_47.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| > @68
diff --git a/src/testdir/dumps/Test_wildmenu_pum_48.dump b/src/testdir/dumps/Test_wildmenu_pum_48.dump
new file mode 100644
index 0000000..372fc8e
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_48.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e> @61
diff --git a/src/testdir/dumps/Test_wildmenu_pum_49.dump b/src/testdir/dumps/Test_wildmenu_pum_49.dump
new file mode 100644
index 0000000..1c3a2e8
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_49.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#ffd7ff255|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#e0e0e08|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |j|u|m|p> @64
diff --git a/src/testdir/dumps/Test_wildmenu_pum_50.dump b/src/testdir/dumps/Test_wildmenu_pum_50.dump
new file mode 100644
index 0000000..8c48229
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_50.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @3| +0#0000001#e0e0e08|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@53
+|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e> @62
diff --git a/src/testdir/dumps/Test_wildmenu_pum_clear_entries_1.dump b/src/testdir/dumps/Test_wildmenu_pum_clear_entries_1.dump
new file mode 100644
index 0000000..b5825eb
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_clear_entries_1.dump
@@ -0,0 +1,10 @@
+| +0#0000001#e0e0e08|!| @14| +0#0000000#0000001| +0&#ffffff0@56
+| +0#0000001#ffd7ff255|#| @14| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+| +0#0000001#ffd7ff255|&| @14| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+| +0#0000001#ffd7ff255|*| @14| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+| +0#0000001#ffd7ff255|+@1| @13| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+| +0#0000001#ffd7ff255|-@1| @13| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+| +0#0000001#ffd7ff255|<| @14| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+| +0#0000001#ffd7ff255|=| @14| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+| +0#0000001#ffd7ff255|>| @14| +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@56
+|:+0#0000000&|!> @72
diff --git a/src/testdir/dumps/Test_wildmenu_pum_term_01.dump b/src/testdir/dumps/Test_wildmenu_pum_term_01.dump
new file mode 100644
index 0000000..b619875
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_term_01.dump
@@ -0,0 +1,10 @@
+| +0&#ffffff0@74
+@75
+@75
+@5| +0#0000001#e0e0e08|d|e|f|i|n|e| @8| +0#0000000#ffffff0@53
+|<+2#ffffff16#00e0003|o|r|t| | +0#0000001#ffd7ff255|j|u|m|p| @10|w+2#ffffff16#00e0003|r|i|t|e|(|s|y|s|.|s|t|d|i|n|.|r|e|a|d|(|)@1|"| |[|r|u|n@1|i|n|g|]| @1|0|,|0|-|1| @9|A|l@1
+| +0#0000000#ffffff0@4| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#0000000#ffffff0@53
+|~+0#4040ff13&| @3| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@53
+|~| @3| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@53
+|[+1#0000000&|N|o| |N| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +1#0000000#ffffff0@35|0|,|0|-|1| @9|A|l@1
+|:+0&&|s|i|g|n| |d|e|f|i|n|e> @62
diff --git a/src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_1.dump b/src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_1.dump
new file mode 100644
index 0000000..ad57067
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_1.dump
@@ -0,0 +1,10 @@
+|f+0#0000e05#a8a8a8255|o@1|-@71
+|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t| @60
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+| +0#0000001#e0e0e08|s|e|t| @11| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|s|e|t|f|i|l|e|t|y|p|e| @3| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|s|e|t|g|l|o|b|a|l| @5| +0#4040ff13#ffffff0@58
+| +0#0000001#ffd7ff255|s|e|t|l|o|c|a|l| @6| +0#4040ff13#ffffff0@58
+|:+0#0000000&|s|e|t> @70
diff --git a/src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_2.dump b/src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_2.dump
new file mode 100644
index 0000000..93270a5
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_with_pum_foldexpr_2.dump
@@ -0,0 +1,10 @@
+>f+0#0000e05#a8a8a8255|o@1|-@71
+|s+0#0000000#ffffff0|o|m|e| |m|o|r|e| |t|e|x|t| @60
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|1|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_win_gotoid_1.dump b/src/testdir/dumps/Test_win_gotoid_1.dump
new file mode 100644
index 0000000..0faf579
--- /dev/null
+++ b/src/testdir/dumps/Test_win_gotoid_1.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|[+1&&|N|o| |N|a|m|e|]| @47|1|,|1| @11|T|o|p
+>2+0&&|1| @72
+|2@1| @72
+|2|3| @72
+|2|4| @72
+|2|5| @72
+|2|6| @72
+|[+3&&|N|o| |N|a|m|e|]| @47|1|,|1| @11|T|o|p
+|r+0&&|e|g| |=| |"|f|o@1|"| @63
+@75
diff --git a/src/testdir/dumps/Test_win_gotoid_2.dump b/src/testdir/dumps/Test_win_gotoid_2.dump
new file mode 100644
index 0000000..7a778f5
--- /dev/null
+++ b/src/testdir/dumps/Test_win_gotoid_2.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+|2| @73
+|3| @73
+|4| @73
+|[+1&&|N|o| |N|a|m|e|]| @47|1|,|1| @11|T|o|p
+|2+0&&|1| @72
+|2@1| @72
+|2+0&#e0e0e08>3+0&#ffffff0| @72
+|2|4| @72
+|2|5| @72
+|2|6| @72
+|[+3&&|N|o| |N|a|m|e|]| @47|3|,|2| @11|T|o|p
+|r+0&&|e|g| |=| |"|2|3|"| @64
+|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@51|2| @9
diff --git a/src/testdir/dumps/Test_win_gotoid_3.dump b/src/testdir/dumps/Test_win_gotoid_3.dump
new file mode 100644
index 0000000..2de7346
--- /dev/null
+++ b/src/testdir/dumps/Test_win_gotoid_3.dump
@@ -0,0 +1,15 @@
+|0+0&#ffffff0| @73
+|1| @73
+>2| @73
+|3| @73
+|4| @73
+|[+3&&|N|o| |N|a|m|e|]| @47|3|,|1| @11|T|o|p
+|2+0&&|1| @72
+|2@1| @72
+|2|3| @72
+|2|4| @72
+|2|5| @72
+|2|6| @72
+|[+1&&|N|o| |N|a|m|e|]| @47|3|,|1| @11|T|o|p
+|r+0&&|e|g| |=| |"|2|3|"| @64
+@75
diff --git a/src/testdir/dumps/Test_winbar_not_visible.dump b/src/testdir/dumps/Test_winbar_not_visible.dump
new file mode 100644
index 0000000..894ac21
--- /dev/null
+++ b/src/testdir/dumps/Test_winbar_not_visible.dump
@@ -0,0 +1,10 @@
+|[+1&#ffffff0|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+> +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|[+3#0000000&|N|o| |N|a|m|e|]| @47|0|,|0|-|1| @9|A|l@1
+| +0&&@74
diff --git a/src/testdir/dumps/Test_winbar_not_visible_custom_statusline.dump b/src/testdir/dumps/Test_winbar_not_visible_custom_statusline.dump
new file mode 100644
index 0000000..9eff5ba
--- /dev/null
+++ b/src/testdir/dumps/Test_winbar_not_visible_custom_statusline.dump
@@ -0,0 +1,10 @@
+|a+1&#ffffff0|b|c|d|e| @69
+> +0&&@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|a+3#0000000&|b|c|d|e| @69
+| +0&&@74
diff --git a/src/testdir/dumps/Test_wincolor_01.dump b/src/testdir/dumps/Test_wincolor_01.dump
new file mode 100644
index 0000000..5a5ccf5
--- /dev/null
+++ b/src/testdir/dumps/Test_wincolor_01.dump
@@ -0,0 +1,8 @@
+| +0#af5f00255#ffd7ff255@1|2| | +0#0000001&@4| +0&#e0e0e08| +0&#ffd7ff255@64
+| +0#af5f00255&@1|1| |1+0#0000001&@4|1+0&#e0e0e08@4| | +0&#ffd7ff255@59
+| +8#af5f00255&@1|0| |2+0#0000001#e0e0e08@4>2+0&#ffd7ff255@5| +8&&@59
+| +0#af5f00255&@1|1| |3+0#0000001&| |h|e|r|e+0&#e0e0e08| +0&#ffd7ff255|3| @62
+| +0#af5f00255&@1|2| | +0#0000001&@4| +0&#e0e0e08| +0&#ffd7ff255@64
+| +0#af5f00255&@1|3| |t+0#40ff4011&|h|e| |c|a+0&#e0e0e08|t+0&#ffd7ff255| |i|s| |o+1&&|u|t| +0&&|o|f| |t|h|e| |b|a|g| +0#0000001&@45
+|~+0#4040ff13&| @73
+|-+2#0000000#ffffff0@1| |V|I|S|U|A|L| |-@1| +0&&@34|2| @8|3|,|6| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_wincolor_lcs.dump b/src/testdir/dumps/Test_wincolor_lcs.dump
new file mode 100644
index 0000000..1e146b2
--- /dev/null
+++ b/src/testdir/dumps/Test_wincolor_lcs.dump
@@ -0,0 +1,8 @@
+|<+0#4040ff13#ffff4012| +0#0000001&@73
+|-+0#0000e05&@2|>|-@6>s+0#0000001&|o|m|e|.+0#0000e05&|r+0#0000001&|a|n|d|o|m|.+0#0000e05&|*+0#e0e0e08#6c6c6c255|.+0#0000e05#ffff4012|e+0#0000001&|n|o|u|g|h|.+0#0000e05&|l+0#0000001&|o|n|g|.+0#0000e05&|t+0#0000001&|o|.+0#0000e05&|s+0#0000001&|h|o|w|.+0#0000e05&|'+0#0000001&|e|x|t|e|n|d|s|'|.+0#0000e05&|a+0#0000001&|n|d|.+0#0000e05&|'+0#0000001&|p|r|e|c|e|d|e|s|'|.+0#0000e05&|i+0#0000001&|n|c|l|>+0#4040ff13&
+|<| +0#0000001&@73
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000#ffffff0@56|2|,|3|-|1|7| @7|A|l@1|
diff --git a/src/testdir/dumps/Test_winline_rnu.dump b/src/testdir/dumps/Test_winline_rnu.dump
new file mode 100644
index 0000000..31d579e
--- /dev/null
+++ b/src/testdir/dumps/Test_winline_rnu.dump
@@ -0,0 +1,5 @@
+|1+8#5fafaf255#303030255| @2> +0#0000000#ffffff0@45
+| +0#af5f00255&@1|1| |a+0#0000000&@45
+| +0#af5f00255&@3|a+0#0000000&@45
+| +0#af5f00255&@3|a+0#0000000&@17| @27
+@32|1|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_winscrolled_not_when_defined_1.dump b/src/testdir/dumps/Test_winscrolled_not_when_defined_1.dump
new file mode 100644
index 0000000..ef42464
--- /dev/null
+++ b/src/testdir/dumps/Test_winscrolled_not_when_defined_1.dump
@@ -0,0 +1,10 @@
+>a+0&#ffffff0@2| @56
+|b@2| @56
+|~+0#4040ff13&| @58
+|~| @58
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @28|1|,|1| @11|A|l@1
+|a+0&&@2| @56
+|b@2| @56
+|~+0#4040ff13&| @58
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @28|1|,|1| @11|A|l@1
+|:+0&&|a|u| |W|i|n|S|c|r|o|l@1|e|d| |*| |c|a|l@1| |t|i|m|e|r|_|s|t|a|r|t|(|1|0@1|,| |'|S|h|o|w|T|r|i|g@1|e|r|e|d|'|)| @3
diff --git a/src/testdir/dumps/Test_winscrolled_not_when_defined_2.dump b/src/testdir/dumps/Test_winscrolled_not_when_defined_2.dump
new file mode 100644
index 0000000..b5cbca4
--- /dev/null
+++ b/src/testdir/dumps/Test_winscrolled_not_when_defined_2.dump
@@ -0,0 +1,10 @@
+>b+0&#ffffff0@2| @56
+|~+0#4040ff13&| @58
+|~| @58
+|~| @58
+|[+3#0000000&|N|o| |N|a|m|e|]| |[|+|]| @28|2|,|1| @11|B|o|t
+|a+0&&@2| @56
+|b@2| @56
+|~+0#4040ff13&| @58
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @28|1|,|1| @11|A|l@1
+|t+0&&|r|i|g@1|e|r|e|d| @50
diff --git a/src/testdir/dumps/Test_winscrolled_once_only_1.dump b/src/testdir/dumps/Test_winscrolled_once_only_1.dump
new file mode 100644
index 0000000..56d6401
--- /dev/null
+++ b/src/testdir/dumps/Test_winscrolled_once_only_1.dump
@@ -0,0 +1,10 @@
+|a+0&#ffffff0@2| @26||+1&&>b+0&&@2| @25
+|b@2| @26||+1&&|~+0#4040ff13&| @27
+|~| @28||+1#0000000&|~+0#4040ff13&| @27
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @1|1|,|1| @8|A|l@1|||~+0#4040ff13&| @27
+|a+0#0000000&@2| @26||+1&&|~+0#4040ff13&| @27
+|b+0#0000000&@2| @26||+1&&|~+0#4040ff13&| @27
+|~| @28||+1#0000000&|~+0#4040ff13&| @27
+|[+1#0000000&|N|o| |N|a|m|e|]| |[|+|]| @1|1|,|1| @8|A|l@1| |[+3&&|N|o| |N|a|m|e|]| |[|+|]| @1|2|,|1| @7|B|o|t
+|1+0&&| |1|0@2| |[|'|r|o|w|'|,| |[@1|'|c|o|l|'|,| |[@1|'|l|e|a|f|'|,| |1|0@1|2|]|,| |[|'|l|e|a|f|'|,| |1|0@1|1|]@2|,| |[
+|'|l|e|a|f|'|,| |1|0@2|]@2| @44
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
new file mode 100644
index 0000000..f205d43
--- /dev/null
+++ b/src/testdir/gen_opt_test.vim
@@ -0,0 +1,241 @@
+" Script to generate testdir/opt_test.vim from option.c
+
+set cpo=&vim
+
+" Only do this when build with the +eval feature.
+if 1
+
+set nomore
+
+" The terminal size is restored at the end.
+" Clear out t_WS, we don't want to resize the actual terminal.
+let script = [
+ \ '" DO NOT EDIT: Generated with gen_opt_test.vim',
+ \ '" Used by test_options.vim.',
+ \ '',
+ \ 'let save_columns = &columns',
+ \ 'let save_lines = &lines',
+ \ 'set t_WS=',
+ \ ]
+
+/#define p_term
+let end = line('.')
+
+" font name that works everywhere (hopefully)
+let fontname = has('win32') ? 'fixedsys' : 'fixed'
+
+" Two lists with values: values that work and values that fail.
+" When not listed, "othernum" or "otherstring" is used.
+let test_values = {
+ \ 'cmdheight': [[1, 2, 10], [-1, 0]],
+ \ 'cmdwinheight': [[1, 2, 10], [-1, 0]],
+ \ 'columns': [[12, 80], [-1, 0, 10]],
+ \ 'conceallevel': [[0, 1, 2, 3], [-1, 4, 99]],
+ \ 'foldcolumn': [[0, 1, 4, 12], [-1, 13, 999]],
+ \ 'helpheight': [[0, 10, 100], [-1]],
+ \ 'history': [[0, 1, 100], [-1, 10001]],
+ \ 'iminsert': [[0, 1], [-1, 3, 999]],
+ \ 'imsearch': [[-1, 0, 1], [-2, 3, 999]],
+ \ 'imstyle': [[0, 1], [-1, 2, 999]],
+ \ 'lines': [[2, 24], [-1, 0, 1]],
+ \ 'linespace': [[0, 2, 4], ['']],
+ \ 'numberwidth': [[1, 4, 8, 10, 11, 20], [-1, 0, 21]],
+ \ 'regexpengine': [[0, 1, 2], [-1, 3, 999]],
+ \ 'report': [[0, 1, 2, 9999], [-1]],
+ \ 'scroll': [[0, 1, 2, 20], [-1]],
+ \ 'scrolljump': [[-50, -1, 0, 1, 2, 20], [999]],
+ \ 'scrolloff': [[0, 1, 2, 20], [-1]],
+ \ 'shiftwidth': [[0, 1, 8, 999], [-1]],
+ \ 'sidescroll': [[0, 1, 8, 999], [-1]],
+ \ 'sidescrolloff': [[0, 1, 8, 999], [-1]],
+ \ 'tabstop': [[1, 4, 8, 12], [-1, 0]],
+ \ 'textwidth': [[0, 1, 8, 99], [-1]],
+ \ 'timeoutlen': [[0, 8, 99999], [-1]],
+ \ 'titlelen': [[0, 1, 8, 9999], [-1]],
+ \ 'updatecount': [[0, 1, 8, 9999], [-1]],
+ \ 'updatetime': [[0, 1, 8, 9999], [-1]],
+ \ 'verbose': [[-1, 0, 1, 8, 9999], []],
+ \ 'wildcharm': [[-1, 0, 100], []],
+ \ 'winheight': [[1, 10, 999], [-1, 0]],
+ \ 'winminheight': [[0, 1], [-1]],
+ \ 'winminwidth': [[0, 1, 10], [-1]],
+ \ 'winwidth': [[1, 10, 999], [-1, 0]],
+ \
+ \ 'ambiwidth': [['', 'single'], ['xxx']],
+ \ 'background': [['', 'light', 'dark'], ['xxx']],
+ \ 'backspace': [[0, 2, 3, '', 'eol', 'eol,start', 'indent,eol,nostop'], ['4', 'xxx']],
+ \ 'backupcopy': [['yes', 'auto'], ['', 'xxx', 'yes,no']],
+ \ 'backupext': [['xxx'], ['']],
+ \ 'belloff': [['', 'all', 'copy,error'], ['xxx']],
+ \ 'breakindentopt': [['', 'min:3', 'sbr'], ['xxx', 'min', 'min:x']],
+ \ 'browsedir': [['', 'last', '/'], ['xxx']],
+ \ 'bufhidden': [['', 'hide', 'wipe'], ['xxx', 'hide,wipe']],
+ \ 'buftype': [['', 'help', 'nofile'], ['xxx', 'help,nofile']],
+ \ 'casemap': [['', 'internal'], ['xxx']],
+ \ 'cedit': [['', '\<Esc>'], ['xxx', 'f']],
+ \ 'clipboard': [['', 'unnamed', 'autoselect,unnamed', 'html', 'exclude:vimdisplay'], ['xxx', '\ze*', 'exclude:\\%(']],
+ \ 'colorcolumn': [['', '8', '+2'], ['xxx']],
+ \ 'comments': [['', 'b:#'], ['xxx']],
+ \ 'commentstring': [['', '/*%s*/'], ['xxx']],
+ \ 'complete': [['', 'w,b'], ['xxx']],
+ \ 'concealcursor': [['', 'n', 'nvic'], ['xxx']],
+ \ 'completeopt': [['', 'menu', 'menu,longest'], ['xxx', 'menu,,,longest,']],
+ \ 'completepopup': [['', 'height:13', 'highlight:That', 'width:10,height:234,highlight:Mine'], ['height:yes', 'width:no', 'xxx', 'xxx:99', 'border:maybe', 'border:1']],
+ \ 'completeslash': [['', 'slash', 'backslash'], ['xxx']],
+ \ 'cryptmethod': [['', 'zip'], ['xxx']],
+ \ 'cscopequickfix': [['', 's-', 's-,c+,e0'], ['xxx', 's,g,d']],
+ \ 'cursorlineopt': [['both', 'line', 'number', 'screenline', 'line,number'], ['', 'xxx', 'line,screenline']],
+ \ 'debug': [['', 'msg', 'msg', 'beep'], ['xxx']],
+ \ 'diffopt': [['', 'filler', 'icase,iwhite'], ['xxx', 'algorithm:xxx', 'algorithm:']],
+ \ 'display': [['', 'lastline', 'lastline,uhex'], ['xxx']],
+ \ 'eadirection': [['', 'both', 'ver'], ['xxx', 'ver,hor']],
+ \ 'encoding': [['latin1'], ['xxx', '']],
+ \ 'eventignore': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter'], ['xxx']],
+ \ 'fileencoding': [['', 'latin1', 'xxx'], []],
+ \ 'fileformat': [['', 'dos', 'unix'], ['xxx']],
+ \ 'fileformats': [['', 'dos', 'dos,unix'], ['xxx']],
+ \ 'fillchars': [['', 'vert:x'], ['xxx']],
+ \ 'foldclose': [['', 'all'], ['xxx']],
+ \ 'foldmethod': [['manual', 'indent'], ['', 'xxx', 'expr,diff']],
+ \ 'foldopen': [['', 'all', 'hor,jump'], ['xxx']],
+ \ 'foldmarker': [['((,))'], ['', 'xxx']],
+ \ 'formatoptions': [['', 'vt', 'v,t'], ['xxx']],
+ \ 'guicursor': [['', 'n:block-Cursor'], ['xxx']],
+ \ 'guifont': [['', fontname], []],
+ \ 'guifontwide': [['', fontname], []],
+ \ 'guifontset': [['', fontname], []],
+ \ 'guioptions': [['', 'a'], ['Q']],
+ \ 'helplang': [['', 'de', 'de,it'], ['xxx']],
+ \ 'highlight': [['', 'e:Error'], ['xxx']],
+ \ 'imactivatekey': [['', 'S-space'], ['xxx']],
+ \ 'isfname': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'isident': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'iskeyword': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'isprint': [['', '@', '@,48-52'], ['xxx', '@48']],
+ \ 'keymap': [['', 'accents'], ['xxx']],
+ \ 'keymodel': [['', 'startsel', 'startsel,stopsel'], ['xxx']],
+ \ 'keyprotocol': [['', 'xxx:none', 'yyy:mok2', 'zzz:kitty'],
+ \ [':none', 'xxx:', 'x:non', 'y:mok3', 'z:kittty']],
+ \ 'langmap': [['', 'xX', 'aA,bB'], ['xxx']],
+ \ 'lispoptions': [['', 'expr:0', 'expr:1'], ['xxx']],
+ \ 'listchars': [['', 'eol:x', 'eol:x,space:y'], ['xxx']],
+ \ 'matchpairs': [['', '(:)', '(:),<:>'], ['xxx']],
+ \ 'mkspellmem': [['10000,100,12'], ['', 'xxx']],
+ \ 'mouse': [['', 'a', 'nvi'], ['xxx', 'n,v,i']],
+ \ 'mousemodel': [['', 'popup'], ['xxx']],
+ \ 'mouseshape': [['', 'n:arrow'], ['xxx']],
+ \ 'nrformats': [['', 'alpha', 'alpha,hex,bin'], ['xxx']],
+ \ 'previewpopup': [['', 'height:13', 'width:10,height:234'], ['height:yes', 'xxx', 'xxx:99']],
+ \ 'printmbfont': [['', 'r:some', 'b:Bold,c:yes'], ['xxx']],
+ \ 'printoptions': [['', 'header:0', 'left:10pc,top:5pc'], ['xxx']],
+ \ 'scrollopt': [['', 'ver', 'ver,hor'], ['xxx']],
+ \ 'renderoptions': [[''], ['xxx']],
+ \ 'rightleftcmd': [['search'], ['xxx']],
+ \ 'selection': [['old', 'inclusive'], ['', 'xxx']],
+ \ 'selectmode': [['', 'mouse', 'key,cmd'], ['xxx']],
+ \ 'sessionoptions': [['', 'blank', 'help,options,slash'], ['xxx']],
+ \ 'showcmdloc': [['last', 'statusline', 'tabline'], ['xxx']],
+ \ 'signcolumn': [['', 'auto', 'no'], ['xxx', 'no,yes']],
+ \ 'spellfile': [['', 'file.en.add', '/tmp/dir\ with\ space/en.utf-8.add'], ['xxx', '/tmp/file']],
+ \ 'spelllang': [['', 'xxx', 'sr@latin'], ['not&lang', "that\\\rthere"]],
+ \ 'spelloptions': [['', 'camel'], ['xxx']],
+ \ 'spellsuggest': [['', 'best', 'double,33'], ['xxx']],
+ \ 'splitkeep': [['cursor', 'screen', 'topline'], ['xxx']],
+ \ 'swapsync': [['', 'sync', 'fsync'], ['xxx']],
+ \ 'switchbuf': [['', 'useopen', 'split,newtab'], ['xxx']],
+ \ 'tagcase': [['smart', 'match'], ['', 'xxx', 'smart,match']],
+ \ 'term': [[], []],
+ \ 'termguicolors': [[], []],
+ \ 'termencoding': [has('gui_gtk') ? [] : ['', 'utf-8'], ['xxx']],
+ \ 'termwinsize': [['', '24x80', '0x80', '32x0', '0x0'], ['xxx', '80', '8ax9', '24x80b']],
+ \ 'termwintype': [['', 'winpty', 'conpty'], ['xxx']],
+ \ 'toolbar': [['', 'icons', 'text'], ['xxx']],
+ \ 'toolbariconsize': [['', 'tiny', 'huge'], ['xxx']],
+ \ 'ttymouse': [['', 'xterm'], ['xxx']],
+ \ 'ttytype': [[], []],
+ \ 'varsofttabstop': [['8', '4,8,16,32'], ['xxx', '-1', '4,-1,20']],
+ \ 'vartabstop': [['8', '4,8,16,32'], ['xxx', '-1', '4,-1,20']],
+ \ 'viewoptions': [['', 'cursor', 'unix,slash'], ['xxx']],
+ \ 'viminfo': [['', '''50', '"30'], ['xxx']],
+ \ 'virtualedit': [['', 'all', 'all,block'], ['xxx']],
+ \ 'whichwrap': [['', 'b,s', 'bs'], ['xxx']],
+ \ 'wildmode': [['', 'full', 'list:full', 'full,longest'], ['xxx', 'a4', 'full,full,full,full,full']],
+ \ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']],
+ \ 'winaltkeys': [['menu', 'no'], ['', 'xxx']],
+ \
+ \ 'luadll': [[], []],
+ \ 'perldll': [[], []],
+ \ 'pythondll': [[], []],
+ \ 'pythonthreedll': [[], []],
+ \ 'pyxversion': [[], []],
+ \ 'rubydll': [[], []],
+ \ 'tcldll': [[], []],
+ \
+ \ 'othernum': [[-1, 0, 100], ['']],
+ \ 'otherstring': [['', 'xxx'], []],
+ \}
+
+1
+/struct vimoption options
+while 1
+ /{"
+ if line('.') > end
+ break
+ endif
+ let line = getline('.')
+ let name = substitute(line, '.*{"\([^"]*\)".*', '\1', '')
+ let shortname = substitute(line, '.*"\([^"]*\)".*', '\1', '')
+
+ if has_key(test_values, name)
+ let a = test_values[name]
+ elseif line =~ 'P_NUM'
+ let a = test_values['othernum']
+ else
+ let a = test_values['otherstring']
+ endif
+ if len(a[0]) > 0 || len(a[1]) > 0
+ if line =~ 'P_BOOL'
+ call add(script, 'set ' . name)
+ call add(script, 'set ' . shortname)
+ call add(script, 'set no' . name)
+ call add(script, 'set no' . shortname)
+ else
+ for val in a[0]
+ call add(script, 'set ' . name . '=' . val)
+ call add(script, 'set ' . shortname . '=' . val)
+ endfor
+
+ " setting an option can only fail when it's implemented.
+ call add(script, "if exists('+" . name . "')")
+ for val in a[1]
+ call add(script, "silent! call assert_fails('set " . name . "=" . val . "')")
+ call add(script, "silent! call assert_fails('set " . shortname . "=" . val . "')")
+ endfor
+ call add(script, "endif")
+ endif
+
+ " cannot change 'termencoding' in GTK
+ if name != 'termencoding' || !has('gui_gtk')
+ call add(script, 'set ' . name . '&')
+ call add(script, 'set ' . shortname . '&')
+ endif
+ if name == 'verbosefile'
+ call add(script, 'call delete("xxx")')
+ endif
+
+ if name == 'more'
+ call add(script, 'set nomore')
+ elseif name == 'lines'
+ call add(script, 'let &lines = save_lines')
+ endif
+ endif
+endwhile
+
+call add(script, 'let &columns = save_columns')
+call add(script, 'let &lines = save_lines')
+
+call writefile(script, 'opt_test.vim')
+
+endif
+
+qa!
diff --git a/src/testdir/gui_init.vim b/src/testdir/gui_init.vim
new file mode 100644
index 0000000..4fa6cbc
--- /dev/null
+++ b/src/testdir/gui_init.vim
@@ -0,0 +1,6 @@
+" gvimrc for test_gui_init.vim
+
+if has('gui_motif') || has('gui_gtk2') || has('gui_gtk3')
+ set guiheadroom=0
+ set guioptions+=p
+endif
diff --git a/src/testdir/gui_preinit.vim b/src/testdir/gui_preinit.vim
new file mode 100644
index 0000000..c351b72
--- /dev/null
+++ b/src/testdir/gui_preinit.vim
@@ -0,0 +1,7 @@
+" vimrc for test_gui_init.vim
+
+" Note that this flag must be added in the .vimrc file, before switching on
+" syntax or filetype recognition (when the |gvimrc| file is sourced the system
+" menu has already been loaded; the ":syntax on" and ":filetype on" commands
+" load the menu too).
+set guioptions+=M
diff --git a/src/testdir/keycode_check.json b/src/testdir/keycode_check.json
new file mode 100644
index 0000000..b06e3c5
--- /dev/null
+++ b/src/testdir/keycode_check.json
@@ -0,0 +1 @@
+{"31kitty":{"Space":"20","modkeys":"","version":"1b5b3e313b343030303b323163","C-Tab":"","A-Esc":"1b5b32373b313175","C-Space":"1b5b33323b3575","S-C-I":"1b5b3130353b3675","C-I":"1b5b3130353b3575","S-Tab":"1b5b393b3275","Tab":"09","resource":"","A-Tab":"1b5b393b313175","S-Space":"20","C-Esc":"1b5b32373b3575","kitty":"1b5b3f3175","protocol":"kitty","A-Space":"1b5b33323b313175","S-Esc":"1b5b32373b3275","Esc":"1b5b323775"},"32libvterm":{"Space":"20","modkeys":"","version":"1b5b3e303b3130303b3063","C-Tab":"","A-Esc":"1b5b32373b3375","C-Space":"1b5b33323b3575","S-C-I":"1b5b3130353b3675","C-I":"1b5b3130353b3575","S-Tab":"1b5b393b3275","Tab":"09","resource":"","A-Tab":"1b5b393b3375","S-Space":"20","C-Esc":"1b5b32373b3575","kitty":"1b5b3f3175","protocol":"kitty","A-Space":"1b5b33323b3375","S-Esc":"1b5b32373b3275","Esc":"1b5b323775"},"22libvterm":{"Space":"20","modkeys":"\u001b[>4;2m","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","resource":"","A-Tab":"1b5b32373b333b397e","S-Space":"1b5b32373b323b33327e","C-Esc":"1b5b32373b353b32377e","kitty":"","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b5b32373b323b32377e","Esc":"1b"},"13kitty":{"Space":"20","modkeys":"","version":"1b5b3e313b343030303b323163","C-Tab":"","A-Esc":"1b1b","S-C-I":"1b5b3130353b3675","C-I":"09","S-Tab":"1b5b5a","Tab":"09","S-Space":"20","A-Tab":"1b09","resource":"","C-Esc":"1b","kitty":"1b5b3f3075","protocol":"none","A-Space":"1b5b33323b313175","S-Esc":"1b","Esc":"1b"},"21xterm":{"Space":"20","modkeys":"\u001b[>4;2m","version":"1b5b3e34313b3337373b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","resource":"=30","A-Tab":"1b5b32373b333b397e","S-Space":"1b5b32373b323b33327e","C-Esc":"1b5b32373b353b32377e","kitty":"","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b5b32373b323b32377e","Esc":"1b"},"12libvterm":{"Space":"20","modkeys":"\u001b[>4;0m","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b393b3575","A-Esc":"9b00","S-C-I":"1b5b5a","C-I":"09","S-Tab":"1b5b5a","Tab":"09","resource":"","A-Tab":"8900","S-Space":"1b5b33323b3275","C-Esc":"1b5b32373b3575","kitty":"1b5b3f3075","protocol":"none","A-Space":"a000","S-Esc":"1b5b32373b3275","Esc":"1b"},"11xterm":{"Space":"20","modkeys":"\u001b[>4;0m","version":"1b5b3e34313b3337373b3063","C-Tab":"09","A-Esc":"9b00","S-C-I":"09","C-I":"09","S-Tab":"1b5b5a","Tab":"09","S-Space":"20","A-Tab":"8900","resource":"","C-Esc":"1b","kitty":"","protocol":"none","A-Space":"a000","S-Esc":"1b","Esc":"1b"}}
diff --git a/src/testdir/keycode_check.vim b/src/testdir/keycode_check.vim
new file mode 100644
index 0000000..8320341
--- /dev/null
+++ b/src/testdir/keycode_check.vim
@@ -0,0 +1,470 @@
+vim9script
+
+# Script to get various codes that keys send, depending on the protocol used.
+#
+# Usage: vim -u NONE -S keycode_check.vim
+#
+# Author: Bram Moolenaar
+# Last Update: 2022 Nov 15
+#
+# The codes are stored in the file "keycode_check.json", so that you can
+# compare the results of various terminals.
+#
+# You can select what protocol to enable:
+# - None
+# - modifyOtherKeys level 2
+# - kitty keyboard protocol
+
+# Change directory to where this script is, so that the json file is found
+# there.
+exe 'cd ' .. expand('<sfile>:h')
+echo 'working in directory: ' .. getcwd()
+
+const filename = 'keycode_check.json'
+
+# Dictionary of dictionaries with the results in the form:
+# {'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
+# 'xterm2': {protocol: 'mok2', 'Tab': '09', 'S-Tab': '09'},
+# 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '09'},
+# }
+# The values are in hex form.
+var keycodes = {}
+
+if filereadable(filename)
+ keycodes = readfile(filename)->join()->json_decode()
+else
+ # Use some dummy entries to try out with
+ keycodes = {
+ 'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
+ 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '1b5b393b3275'},
+ }
+endif
+var orig_keycodes = deepcopy(keycodes) # used to detect something changed
+
+# Write the "keycodes" variable in JSON form to "filename".
+def WriteKeycodes()
+ # If the file already exists move it to become the backup file.
+ if filereadable(filename)
+ if rename(filename, filename .. '~')
+ echoerr $'Renaming {filename} to {filename}~ failed!'
+ return
+ endif
+ endif
+
+ if writefile([json_encode(keycodes)], filename) != 0
+ echoerr $'Writing {filename} failed!'
+ endif
+enddef
+
+# The key entries that we want to list, in this order.
+# The first item is displayed in the prompt, the second is the key in
+# the keycodes dictionary.
+var key_entries = [
+ ['Tab', 'Tab'],
+ ['Shift-Tab', 'S-Tab'],
+ ['Ctrl-Tab', 'C-Tab'],
+ ['Alt-Tab', 'A-Tab'],
+ ['Ctrl-I', 'C-I'],
+ ['Shift-Ctrl-I', 'S-C-I'],
+ ['Esc', 'Esc'],
+ ['Shift-Esc', 'S-Esc'],
+ ['Ctrl-Esc', 'C-Esc'],
+ ['Alt-Esc', 'A-Esc'],
+ ['Space', 'Space'],
+ ['Shift-Space', 'S-Space'],
+ ['Ctrl-Space', 'C-Space'],
+ ['Alt-Space', 'A-Space'],
+ ]
+
+# Given a terminal name and a item name, return the text to display.
+def GetItemDisplay(term: string, item: string): string
+ var val = get(keycodes[term], item, '')
+
+ # see if we can pretty-print this one
+ var pretty = val
+ if val[0 : 1] == '1b'
+ pretty = 'ESC'
+ var idx = 2
+
+ if val[0 : 3] == '1b5b'
+ pretty = 'CSI'
+ idx = 4
+ endif
+
+ var digits = false
+ while idx < len(val)
+ var cc = val[idx : idx + 1]
+ var nr = str2nr('0x' .. cc, 16)
+ idx += 2
+ if nr >= char2nr('0') && nr <= char2nr('9')
+ if !digits
+ pretty ..= ' '
+ endif
+ digits = true
+ pretty ..= cc[1]
+ else
+ if nr == char2nr(';') && digits
+ # don't use space between semicolon and digits to keep it short
+ pretty ..= ';'
+ else
+ digits = false
+ if nr >= char2nr(' ') && nr <= char2nr('~')
+ # printable character
+ pretty ..= ' ' .. printf('%c', nr)
+ else
+ # non-printable, use hex code
+ pretty = val
+ break
+ endif
+ endif
+ endif
+ endwhile
+ endif
+
+ return pretty
+enddef
+
+
+# Action: list the information in "keycodes" in a more or less nice way.
+def ActionList()
+ var terms = keys(keycodes)
+ if len(terms) == 0
+ echo 'No terminal results yet'
+ return
+ endif
+ sort(terms)
+
+ var items = ['protocol', 'version', 'kitty', 'modkeys']
+ + key_entries->copy()->map((_, v) => v[1])
+
+ # For each terminal compute the needed width, add two.
+ # You may need to increase the terminal width to avoid wrapping.
+ var widths = []
+ for [idx, term] in items(terms)
+ widths[idx] = len(term) + 2
+ endfor
+
+ for item in items
+ for [idx, term] in items(terms)
+ var l = len(GetItemDisplay(term, item))
+ if widths[idx] < l + 2
+ widths[idx] = l + 2
+ endif
+ endfor
+ endfor
+
+ # Use one column of width 10 for the item name.
+ echo "\n"
+ echon ' '
+ for [idx, term] in items(terms)
+ echon printf('%-' .. widths[idx] .. 's', term)
+ endfor
+ echo "\n"
+
+ for item in items
+ echon printf('%8s ', item)
+ for [idx, term] in items(terms)
+ echon printf('%-' .. widths[idx] .. 's', GetItemDisplay(term, item))
+ endfor
+ echo ''
+ endfor
+ echo "\n"
+enddef
+
+# Convert the literal string after "raw key input" into hex form.
+def Literal2hex(code: string): string
+ var hex = ''
+ for i in range(len(code))
+ hex ..= printf('%02x', char2nr(code[i]))
+ endfor
+ return hex
+enddef
+
+def GetTermName(): string
+ var name = input('Enter the name of the terminal: ')
+ return name
+enddef
+
+# Gather key codes for terminal "name".
+def DoTerm(name: string)
+ var proto = inputlist([$'What protocol to enable for {name}:',
+ '1. None',
+ '2. modifyOtherKeys level 2',
+ '3. kitty',
+ ])
+ echo "\n"
+ &t_TE = "\<Esc>[>4;m"
+ var proto_name = 'unknown'
+ if proto == 1
+ # Request the XTQMODKEYS value and request the kitty keyboard protocol status.
+ &t_TI = "\<Esc>[?4m" .. "\<Esc>[?u"
+ proto_name = 'none'
+ elseif proto == 2
+ # Enable modifyOtherKeys level 2 and request the XTQMODKEYS value.
+ &t_TI = "\<Esc>[>4;2m" .. "\<Esc>[?4m"
+ proto_name = 'mok2'
+ elseif proto == 3
+ # Enable Kitty keyboard protocol and request the status.
+ &t_TI = "\<Esc>[>1u" .. "\<Esc>[?u"
+ proto_name = 'kitty'
+ else
+ echoerr 'invalid protocol choice'
+ return
+ endif
+
+ # Append the request for the version response, this is used to check we have
+ # the results.
+ &t_TI ..= "\<Esc>[>c"
+
+ # Pattern that matches the line with the version response.
+ const version_pattern = "\<Esc>\\[>\\d\\+;\\d\\+;\\d*c"
+
+ # Pattern that matches the XTQMODKEYS response:
+ # CSI > 4;Pv m
+ # where Pv indicates the modifyOtherKeys level
+ const modkeys_pattern = "\<Esc>\\[>4;\\dm"
+
+ # Pattern that matches the line with the status. Currently what terminals
+ # return for the Kitty keyboard protocol.
+ const kitty_status_pattern = "\<Esc>\\[?\\d\\+u"
+
+ ch_logfile('keylog', 'w')
+
+ # executing a dummy shell command will output t_TI
+ !echo >/dev/null
+
+ # Wait until the log file has the version response.
+ var startTime = reltime()
+ var seenVersion = false
+ while !seenVersion
+ var log = readfile('keylog')
+ if len(log) > 2
+ for line in log
+ if line =~ 'raw key input'
+ var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
+ if code =~ version_pattern
+ seenVersion = true
+ echo 'Found the version response'
+ break
+ endif
+ endif
+ endfor
+ endif
+ if reltime(startTime)->reltimefloat() > 3
+ # break out after three seconds
+ break
+ endif
+ endwhile
+
+ echo 'seenVersion: ' seenVersion
+
+ # Prepare the terminal entry, set protocol and clear status and version.
+ if !has_key(keycodes, name)
+ keycodes[name] = {}
+ endif
+ keycodes[name]['protocol'] = proto_name
+ keycodes[name]['version'] = ''
+ keycodes[name]['kitty'] = ''
+ keycodes[name]['modkeys'] = ''
+
+ # Check the log file for a status and the version response
+ ch_logfile('', '')
+ var log = readfile('keylog')
+ delete('keylog')
+
+ for line in log
+ if line =~ 'raw key input'
+ var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
+
+ # Check for the XTQMODKEYS response.
+ if code =~ modkeys_pattern
+ var modkeys = substitute(code, '.*\(' .. modkeys_pattern .. '\).*', '\1', '')
+ # We could get the level out of the response, but showing the response
+ # itself provides more information.
+ # modkeys = substitute(modkeys, '.*4;\(\d\)m', '\1', '')
+
+ if keycodes[name]['modkeys'] != ''
+ echomsg 'Another modkeys found after ' .. keycodes[name]['modkeys']
+ endif
+ keycodes[name]['modkeys'] = modkeys
+ endif
+
+ # Check for kitty keyboard protocol status
+ if code =~ kitty_status_pattern
+ var status = substitute(code, '.*\(' .. kitty_status_pattern .. '\).*', '\1', '')
+ # use the response itself as the status
+ status = Literal2hex(status)
+
+ if keycodes[name]['kitty'] != ''
+ echomsg 'Another status found after ' .. keycodes[name]['kitty']
+ endif
+ keycodes[name]['kitty'] = status
+ endif
+
+ if code =~ version_pattern
+ var version = substitute(code, '.*\(' .. version_pattern .. '\).*', '\1', '')
+ keycodes[name]['version'] = Literal2hex(version)
+ break
+ endif
+ endif
+ endfor
+
+ echo "For Alt to work you may need to press the Windows/Super key as well"
+ echo "When a key press doesn't get to Vim (e.g. when using Alt) press x"
+
+ # The log of ignored typeahead is left around for debugging, start with an
+ # empty file here.
+ delete('keylog-ignore')
+
+ for entry in key_entries
+ # Consume any typeahead. Wait a bit for any responses to arrive.
+ ch_logfile('keylog-ignore', 'a')
+ while 1
+ sleep 100m
+ if getchar(1) == 0
+ break
+ endif
+ while getchar(1) != 0
+ getchar()
+ endwhile
+ endwhile
+ ch_logfile('', '')
+
+ ch_logfile('keylog', 'w')
+ echo $'Press the {entry[0]} key (q to quit):'
+ var r = getcharstr()
+ ch_logfile('', '')
+ if r == 'q'
+ break
+ endif
+
+ log = readfile('keylog')
+ delete('keylog')
+ if len(log) < 2
+ echoerr 'failed to read result'
+ return
+ endif
+ var done = false
+ for line in log
+ if line =~ 'raw key input'
+ var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
+
+ # Remove any version termresponse
+ code = substitute(code, version_pattern, '', 'g')
+
+ # Remove any XTGETTCAP replies.
+ const cappat = "\<Esc>P[01]+\\k\\+=\\x*\<Esc>\\\\"
+ code = substitute(code, cappat, '', 'g')
+
+ # Remove any kitty status reply
+ code = substitute(code, kitty_status_pattern, '', 'g')
+ if code == ''
+ continue
+ endif
+
+ # Convert the literal bytes into hex. If 'x' was pressed then clear
+ # the entry.
+ var hex = ''
+ if code != 'x'
+ hex = Literal2hex(code)
+ endif
+
+ keycodes[name][entry[1]] = hex
+ done = true
+ break
+ endif
+ endfor
+ if !done
+ echo 'Code not found in log'
+ endif
+ endfor
+enddef
+
+# Action: Add key codes for a new terminal.
+def ActionAdd()
+ var name = input('Enter name of the terminal: ')
+ echo "\n"
+ if index(keys(keycodes), name) >= 0
+ echoerr $'Terminal {name} already exists'
+ return
+ endif
+
+ DoTerm(name)
+enddef
+
+# Action: Replace key codes for an already known terminal.
+def ActionReplace()
+ var terms = keys(keycodes)
+ if len(terms) == 0
+ echo 'No terminal results yet'
+ return
+ endif
+
+ var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg))
+ echo "\n"
+ if choice > 0 && choice <= len(terms)
+ DoTerm(terms[choice - 1])
+ else
+ echo 'invalid index'
+ endif
+enddef
+
+# Action: Clear key codes for an already known terminal.
+def ActionClear()
+ var terms = keys(keycodes)
+ if len(terms) == 0
+ echo 'No terminal results yet'
+ return
+ endif
+
+ var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg))
+ echo "\n"
+ if choice > 0 && choice <= len(terms)
+ remove(keycodes, terms[choice - 1])
+ else
+ echo 'invalid index'
+ endif
+enddef
+
+# Action: Quit, possibly after saving the results first.
+def ActionQuit()
+ # If nothing was changed just quit
+ if keycodes == orig_keycodes
+ quit
+ endif
+
+ while true
+ var res = input("Save the changed key codes (y/n)? ")
+ if res == 'n'
+ quit
+ endif
+ if res == 'y'
+ WriteKeycodes()
+ quit
+ endif
+ echo 'invalid reply'
+ endwhile
+enddef
+
+# The main loop
+while true
+ var action = inputlist(['Select operation:',
+ '1. List results',
+ '2. Add results for a new terminal',
+ '3. Replace results',
+ '4. Clear results',
+ '5. Quit',
+ ])
+ echo "\n"
+ if action == 1
+ ActionList()
+ elseif action == 2
+ ActionAdd()
+ elseif action == 3
+ ActionReplace()
+ elseif action == 4
+ ActionClear()
+ elseif action == 5
+ ActionQuit()
+ endif
+endwhile
diff --git a/src/testdir/lsan-suppress.txt b/src/testdir/lsan-suppress.txt
new file mode 100644
index 0000000..8e1451e
--- /dev/null
+++ b/src/testdir/lsan-suppress.txt
@@ -0,0 +1,13 @@
+# Suppress leaks from X libraries on Ubuntu focal.
+leak:libX11.so.6
+leak:libXt.so.6
+leak:libcairo.so.2
+leak:libfontconfig.so.1
+leak:libglib-2.0.so.0
+# Matches leaks from libtinfo.so.5 and .6
+leak:libtinfo.so
+# Suppress leaks from other language libraries.
+leak:libperl.so.*
+leak:libpython*.so.*
+leak:libruby*.so.*
+leak:libxcb*.so.*
diff --git a/src/testdir/mouse.vim b/src/testdir/mouse.vim
new file mode 100644
index 0000000..e2979b7
--- /dev/null
+++ b/src/testdir/mouse.vim
@@ -0,0 +1,372 @@
+" Helper functions for generating mouse events
+
+" xterm2 and sgr always work, urxvt is optional.
+let g:Ttymouse_values = ['xterm2', 'sgr']
+if has('mouse_urxvt')
+ call add(g:Ttymouse_values, 'urxvt')
+endif
+
+" dec doesn't support all the functionality
+if has('mouse_dec')
+ let g:Ttymouse_dec = ['dec']
+else
+ let g:Ttymouse_dec = []
+endif
+
+" netterm only supports left click
+if has('mouse_netterm')
+ let g:Ttymouse_netterm = ['netterm']
+else
+ let g:Ttymouse_netterm = []
+endif
+
+" Vim Mouse Codes.
+" Used by the GUI and by MS-Windows Consoles.
+" Keep these in sync with vim.h
+let s:MOUSE_CODE = {
+ \ 'BTN_LEFT' : 0x00,
+ \ 'BTN_MIDDLE' : 0x01,
+ \ 'BTN_RIGHT' : 0x02,
+ \ 'BTN_RELEASE' : 0x03,
+ \ 'BTN_X1' : 0x300,
+ \ 'BTN_X2' : 0x400,
+ \ 'SCRL_DOWN' : 0x100,
+ \ 'SCRL_UP' : 0x200,
+ \ 'SCRL_LEFT' : 0x500,
+ \ 'SCRL_RIGHT' : 0x600,
+ \ 'MOVE' : 0x700,
+ \ 'MOD_SHIFT' : 0x04,
+ \ 'MOD_ALT' : 0x08,
+ \ 'MOD_CTRL' : 0x10,
+ \ }
+
+
+" Helper function to emit a terminal escape code.
+func TerminalEscapeCode(code, row, col, m)
+ if &ttymouse ==# 'xterm2'
+ " need to use byte encoding here.
+ let str = list2str([a:code + 0x20, a:col + 0x20, a:row + 0x20])
+ if has('iconv')
+ let bytes = str->iconv('utf-8', 'latin1')
+ else
+ " Hopefully the numbers are not too big.
+ let bytes = str
+ endif
+ return "\<Esc>[M" .. bytes
+ elseif &ttymouse ==# 'sgr'
+ return printf("\<Esc>[<%d;%d;%d%s", a:code, a:col, a:row, a:m)
+ elseif &ttymouse ==# 'urxvt'
+ return printf("\<Esc>[%d;%d;%dM", a:code + 0x20, a:col, a:row)
+ endif
+endfunc
+
+func DecEscapeCode(code, down, row, col)
+ return printf("\<Esc>[%d;%d;%d;%d&w", a:code, a:down, a:row, a:col)
+endfunc
+
+func NettermEscapeCode(row, col)
+ return printf("\<Esc>}%d,%d\r", a:row, a:col)
+endfunc
+
+" Send low level mouse event to MS-Windows consoles or GUI
+func MSWinMouseEvent(button, row, col, move, multiclick, modifiers)
+ let args = { }
+ let args.button = a:button
+ " Scroll directions are inverted in the GUI, no idea why.
+ if has('gui_running')
+ if a:button == s:MOUSE_CODE.SCRL_UP
+ let args.button = s:MOUSE_CODE.SCRL_DOWN
+ elseif a:button == s:MOUSE_CODE.SCRL_DOWN
+ let args.button = s:MOUSE_CODE.SCRL_UP
+ elseif a:button == s:MOUSE_CODE.SCRL_LEFT
+ let args.button = s:MOUSE_CODE.SCRL_RIGHT
+ elseif a:button == s:MOUSE_CODE.SCRL_RIGHT
+ let args.button = s:MOUSE_CODE.SCRL_LEFT
+ endif
+ endif
+ let args.row = a:row
+ let args.col = a:col
+ let args.move = a:move
+ let args.multiclick = a:multiclick
+ let args.modifiers = a:modifiers
+ call test_mswin_event("mouse", args)
+ unlet args
+endfunc
+
+func MouseLeftClickCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(2, 4, a:row, a:col)
+ elseif &ttymouse ==# 'netterm'
+ return NettermEscapeCode(a:row, a:col)
+ else
+ return TerminalEscapeCode(0, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseLeftClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseLeftClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseMiddleClickCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(4, 2, a:row, a:col)
+ else
+ return TerminalEscapeCode(1, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseMiddleClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_MIDDLE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseMiddleClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseRightClickCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(6, 1, a:row, a:col)
+ else
+ return TerminalEscapeCode(2, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseRightClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RIGHT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseRightClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseCtrlLeftClickCode(row, col)
+ let ctrl = 0x10
+ return TerminalEscapeCode(0 + ctrl, a:row, a:col, 'M')
+endfunc
+
+func MouseCtrlLeftClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_CTRL)
+ else
+ call feedkeys(MouseCtrlLeftClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseCtrlRightClickCode(row, col)
+ let ctrl = 0x10
+ return TerminalEscapeCode(2 + ctrl, a:row, a:col, 'M')
+endfunc
+
+func MouseCtrlRightClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RIGHT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_CTRL)
+ else
+ call feedkeys(MouseCtrlRightClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseAltLeftClickCode(row, col)
+ let alt = 0x8
+ return TerminalEscapeCode(0 + alt, a:row, a:col, 'M')
+endfunc
+
+func MouseAltLeftClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_ALT)
+ else
+ call feedkeys(MouseAltLeftClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseAltRightClickCode(row, col)
+ let alt = 0x8
+ return TerminalEscapeCode(2 + alt, a:row, a:col, 'M')
+endfunc
+
+func MouseAltRightClick(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RIGHT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_ALT)
+ else
+ call feedkeys(MouseAltRightClickCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseLeftReleaseCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(3, 0, a:row, a:col)
+ elseif &ttymouse ==# 'netterm'
+ return ''
+ else
+ return TerminalEscapeCode(3, a:row, a:col, 'm')
+ endif
+endfunc
+
+func MouseLeftRelease(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RELEASE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseLeftReleaseCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseMiddleReleaseCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(5, 0, a:row, a:col)
+ else
+ return TerminalEscapeCode(3, a:row, a:col, 'm')
+ endif
+endfunc
+
+func MouseMiddleRelease(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RELEASE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseMiddleReleaseCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseRightReleaseCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(7, 0, a:row, a:col)
+ else
+ return TerminalEscapeCode(3, a:row, a:col, 'm')
+ endif
+endfunc
+
+func MouseRightRelease(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_RELEASE, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseRightReleaseCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseLeftDragCode(row, col)
+ if &ttymouse ==# 'dec'
+ return DecEscapeCode(1, 4, a:row, a:col)
+ else
+ return TerminalEscapeCode(0x20, a:row, a:col, 'M')
+ endif
+endfunc
+
+func MouseLeftDrag(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 1, 0, 0)
+ else
+ call feedkeys(MouseLeftDragCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelUpCode(row, col)
+ return TerminalEscapeCode(0x40, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelUp(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_UP, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelUpCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelDownCode(row, col)
+ return TerminalEscapeCode(0x41, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelDown(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_DOWN, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelDownCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelLeftCode(row, col)
+ return TerminalEscapeCode(0x42, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelLeft(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_LEFT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelLeftCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseWheelRightCode(row, col)
+ return TerminalEscapeCode(0x43, a:row, a:col, 'M')
+endfunc
+
+func MouseWheelRight(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_RIGHT, a:row, a:col, 0, 0, 0)
+ else
+ call feedkeys(MouseWheelRightCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelUpCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x40, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelUp(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_UP, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelUpCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelDownCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x41, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelDown(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_DOWN, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelDownCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelLeftCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x42, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelLeft(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_LEFT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelLeftCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+func MouseShiftWheelRightCode(row, col)
+ " todo feed shift mod.
+ return TerminalEscapeCode(0x43, a:row, a:col, 'M')
+endfunc
+
+func MouseShiftWheelRight(row, col)
+ if has('win32')
+ call MSWinMouseEvent(s:MOUSE_CODE.SCRL_RIGHT, a:row, a:col, 0, 0,
+ \ s:MOUSE_CODE.MOD_SHIFT)
+ else
+ call feedkeys(MouseShiftWheelRightCode(a:row, a:col), 'Lx!')
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/popupbounce.vim b/src/testdir/popupbounce.vim
new file mode 100644
index 0000000..b9f7bd1
--- /dev/null
+++ b/src/testdir/popupbounce.vim
@@ -0,0 +1,80 @@
+" Use this script to measure the redrawing performance when a popup is being
+" displayed. Usage with gcc:
+" cd src
+" # Edit Makefile to uncomment PROFILE_CFLAGS and PROFILE_LIBS
+" make reconfig
+" ./vim --clean -S testdir/popupbounce.vim main.c
+" gprof vim gmon.out | vim -
+
+" using line continuation
+set nocp
+
+" don't switch screens when quitting, so we can read the frames/sec
+set t_te=
+
+let winid = popup_create(['line1', 'line2', 'line3', 'line4'], {
+ \ 'line' : 1,
+ \ 'col' : 1,
+ \ 'zindex' : 101,
+ \ })
+redraw
+
+let start = reltime()
+let framecount = 0
+
+let line = 1.0
+let col = 1
+let downwards = 1
+let col_inc = 1
+let initial_speed = 0.2
+let speed = initial_speed
+let accel = 1.1
+let time = 0.1
+
+let countdown = 0
+
+while 1
+ if downwards
+ let speed += time * accel
+ let line += speed
+ else
+ let speed -= time * accel
+ let line -= speed
+ endif
+
+ if line + 3 >= &lines
+ let downwards = 0
+ let speed = speed * 0.8
+ let line = &lines - 3
+ endif
+ if !downwards && speed < 1.0
+ let downwards = 1
+ let speed = initial_speed
+ if line + 4 > &lines && countdown == 0
+ let countdown = 50
+ endif
+ endif
+
+ let col += col_inc
+ if col + 4 >= &columns
+ let col_inc = -1
+ elseif col <= 1
+ let col_inc = 1
+ endif
+
+ call popup_move(winid, {'line': float2nr(line), 'col': col})
+ redraw
+ let framecount += 1
+ if countdown > 0
+ let countdown -= 1
+ if countdown == 0
+ break
+ endif
+ endif
+
+endwhile
+
+let elapsed = reltimefloat(reltime(start))
+echomsg framecount .. ' frames in ' .. string(elapsed) .. ' seconds, ' .. string(framecount / elapsed) .. ' frames/sec'
+
+qa
diff --git a/src/testdir/python2/module.py b/src/testdir/python2/module.py
new file mode 100644
index 0000000..e90106a
--- /dev/null
+++ b/src/testdir/python2/module.py
@@ -0,0 +1,2 @@
+import before_1
+dir = '2'
diff --git a/src/testdir/python3/module.py b/src/testdir/python3/module.py
new file mode 100644
index 0000000..24bd036
--- /dev/null
+++ b/src/testdir/python3/module.py
@@ -0,0 +1,2 @@
+import before_1
+dir = '3'
diff --git a/src/testdir/python_after/after.py b/src/testdir/python_after/after.py
new file mode 100644
index 0000000..5cf8fa4
--- /dev/null
+++ b/src/testdir/python_after/after.py
@@ -0,0 +1,2 @@
+import before_2
+dir = "after"
diff --git a/src/testdir/python_before/before.py b/src/testdir/python_before/before.py
new file mode 100644
index 0000000..531e81a
--- /dev/null
+++ b/src/testdir/python_before/before.py
@@ -0,0 +1 @@
+dir = "before"
diff --git a/src/testdir/python_before/before_1.py b/src/testdir/python_before/before_1.py
new file mode 100644
index 0000000..fa81ada
--- /dev/null
+++ b/src/testdir/python_before/before_1.py
@@ -0,0 +1 @@
+# empty file
diff --git a/src/testdir/python_before/before_2.py b/src/testdir/python_before/before_2.py
new file mode 100644
index 0000000..fa81ada
--- /dev/null
+++ b/src/testdir/python_before/before_2.py
@@ -0,0 +1 @@
+# empty file
diff --git a/src/testdir/pythonx/failing.py b/src/testdir/pythonx/failing.py
new file mode 100644
index 0000000..30e73a0
--- /dev/null
+++ b/src/testdir/pythonx/failing.py
@@ -0,0 +1 @@
+raise NotImplementedError
diff --git a/src/testdir/pythonx/failing_import.py b/src/testdir/pythonx/failing_import.py
new file mode 100644
index 0000000..511cae7
--- /dev/null
+++ b/src/testdir/pythonx/failing_import.py
@@ -0,0 +1 @@
+raise ImportError
diff --git a/src/testdir/pythonx/module.py b/src/testdir/pythonx/module.py
new file mode 100644
index 0000000..6bf5a64
--- /dev/null
+++ b/src/testdir/pythonx/module.py
@@ -0,0 +1 @@
+dir = 'x'
diff --git a/src/testdir/pythonx/modulex.py b/src/testdir/pythonx/modulex.py
new file mode 100644
index 0000000..ec6a706
--- /dev/null
+++ b/src/testdir/pythonx/modulex.py
@@ -0,0 +1 @@
+ddir = 'xx'
diff --git a/src/testdir/pythonx/topmodule/__init__.py b/src/testdir/pythonx/topmodule/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/src/testdir/pythonx/topmodule/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/src/testdir/pythonx/topmodule/submodule/__init__.py b/src/testdir/pythonx/topmodule/submodule/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/src/testdir/pythonx/topmodule/submodule/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/src/testdir/pythonx/topmodule/submodule/subsubmodule/__init__.py b/src/testdir/pythonx/topmodule/submodule/subsubmodule/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/src/testdir/pythonx/topmodule/submodule/subsubmodule/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/src/testdir/pythonx/topmodule/submodule/subsubmodule/subsubsubmodule.py b/src/testdir/pythonx/topmodule/submodule/subsubmodule/subsubsubmodule.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/src/testdir/pythonx/topmodule/submodule/subsubmodule/subsubsubmodule.py
@@ -0,0 +1 @@
+#
diff --git a/src/testdir/pyxfile/py2_magic.py b/src/testdir/pyxfile/py2_magic.py
new file mode 100644
index 0000000..819892f
--- /dev/null
+++ b/src/testdir/pyxfile/py2_magic.py
@@ -0,0 +1,4 @@
+# requires python 2.x
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/py2_shebang.py b/src/testdir/pyxfile/py2_shebang.py
new file mode 100644
index 0000000..13bfc49
--- /dev/null
+++ b/src/testdir/pyxfile/py2_shebang.py
@@ -0,0 +1,4 @@
+#!/usr/bin/python2
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/py3_magic.py b/src/testdir/pyxfile/py3_magic.py
new file mode 100644
index 0000000..d4b7ee0
--- /dev/null
+++ b/src/testdir/pyxfile/py3_magic.py
@@ -0,0 +1,4 @@
+# requires python 3.x
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/py3_shebang.py b/src/testdir/pyxfile/py3_shebang.py
new file mode 100644
index 0000000..ec05808
--- /dev/null
+++ b/src/testdir/pyxfile/py3_shebang.py
@@ -0,0 +1,4 @@
+#!/usr/bin/python3
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/pyx.py b/src/testdir/pyxfile/pyx.py
new file mode 100644
index 0000000..261a651
--- /dev/null
+++ b/src/testdir/pyxfile/pyx.py
@@ -0,0 +1,2 @@
+import sys
+print(sys.version)
diff --git a/src/testdir/runtest.vim b/src/testdir/runtest.vim
new file mode 100644
index 0000000..1cdeeef
--- /dev/null
+++ b/src/testdir/runtest.vim
@@ -0,0 +1,618 @@
+" This script is sourced while editing the .vim file with the tests.
+" When the script is successful the .res file will be created.
+" Errors are appended to the test.log file.
+"
+" To execute only specific test functions, add a second argument. It will be
+" matched against the names of the Test_ function. E.g.:
+" ../vim -u NONE -S runtest.vim test_channel.vim open_delay
+" The output can be found in the "messages" file.
+"
+" If the environment variable $TEST_FILTER is set then only test functions
+" matching this pattern are executed. E.g. for sh/bash:
+" export TEST_FILTER=Test_channel
+" For csh:
+" setenv TEST_FILTER Test_channel
+"
+" If the environment variable $TEST_SKIP_PAT is set then test functions
+" matching this pattern will be skipped. It's the opposite of $TEST_FILTER.
+"
+" While working on a test you can make $TEST_NO_RETRY non-empty to not retry:
+" export TEST_NO_RETRY=yes
+"
+" To ignore failure for tests that are known to fail in a certain environment,
+" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for
+" sh/bash:
+" export TEST_MAY_FAIL=Test_channel_one,Test_channel_other
+" The failure report will then not be included in the test.log file and
+" "make test" will not fail.
+"
+" The test script may contain anything, only functions that start with
+" "Test_" are special. These will be invoked and should contain assert
+" functions. See test_assert.vim for an example.
+"
+" It is possible to source other files that contain "Test_" functions. This
+" can speed up testing, since Vim does not need to restart. But be careful
+" that the tests do not interfere with each other.
+"
+" If an error cannot be detected properly with an assert function add the
+" error to the v:errors list:
+" call add(v:errors, 'test foo failed: Cannot find xyz')
+"
+" If preparation for each Test_ function is needed, define a SetUp function.
+" It will be called before each Test_ function.
+"
+" If cleanup after each Test_ function is needed, define a TearDown function.
+" It will be called after each Test_ function.
+"
+" When debugging a test it can be useful to add messages to v:errors:
+" call add(v:errors, "this happened")
+
+
+" Without the +eval feature we can't run these tests, bail out.
+silent! while 0
+ qa!
+silent! endwhile
+
+" In the GUI we can always change the screen size.
+if has('gui_running')
+ set columns=80 lines=25
+endif
+
+" Check that the screen size is at least 24 x 80 characters.
+if &lines < 24 || &columns < 80
+ let error = 'Screen size too small! Tests require at least 24 lines with 80 characters, got ' .. &lines .. ' lines with ' .. &columns .. ' characters'
+ echoerr error
+ split test.log
+ $put =error
+ write
+ split messages
+ call append(line('$'), '')
+ call append(line('$'), 'From ' . expand('%') . ':')
+ call append(line('$'), error)
+ write
+ qa!
+endif
+
+if has('reltime')
+ let s:run_start_time = reltime()
+
+ if !filereadable('starttime')
+ " first test, store the overall test starting time
+ let s:test_start_time = localtime()
+ call writefile([string(s:test_start_time)], 'starttime')
+ else
+ " second or later test, read the overall test starting time
+ let s:test_start_time = readfile('starttime')[0]->str2nr()
+ endif
+endif
+
+" Always use forward slashes.
+set shellslash
+
+" Common with all tests on all systems.
+source setup.vim
+
+" For consistency run all tests with 'nocompatible' set.
+" This also enables use of line continuation.
+set nocp viminfo+=nviminfo
+
+" Use utf-8 by default, instead of whatever the system default happens to be.
+" Individual tests can overrule this at the top of the file and use
+" g:orig_encoding if needed.
+let g:orig_encoding = &encoding
+set encoding=utf-8
+
+" REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for
+" the test_name.vim file itself. Replace it here with a more restrictive one,
+" so we still catch mistakes.
+if has("win32")
+ " replace any '/' directory separators by '\\'
+ let s:test_script_fname = substitute(expand('%'), '/', '\\', 'g')
+else
+ let s:test_script_fname = expand('%')
+endif
+au! SwapExists * call HandleSwapExists()
+func HandleSwapExists()
+ if exists('g:ignoreSwapExists')
+ return
+ endif
+ " Ignore finding a swap file for the test script (the user might be
+ " editing it and do ":make test_name") and the output file.
+ " Report finding another swap file and chose 'q' to avoid getting stuck.
+ if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname
+ let v:swapchoice = 'e'
+ else
+ call assert_report('Unexpected swap file: ' .. v:swapname)
+ let v:swapchoice = 'q'
+ endif
+endfunc
+
+" Avoid stopping at the "hit enter" prompt
+set nomore
+
+" Output all messages in English.
+lang mess C
+
+" suppress menu translation
+if has('gui_running') && exists('did_install_default_menus')
+ source $VIMRUNTIME/delmenu.vim
+ set langmenu=none
+ source $VIMRUNTIME/menu.vim
+endif
+
+let s:srcdir = expand('%:p:h:h')
+
+if has('win32')
+ " avoid prompt that is long or contains a line break
+ let $PROMPT = '$P$G'
+ " On MS-Windows t_md and t_me are Vim specific escape sequences.
+ let s:t_bold = "\x1b[1m"
+ let s:t_normal = "\x1b[m"
+else
+ let s:t_bold = &t_md
+ let s:t_normal = &t_me
+endif
+
+if has('mac')
+ " In macOS, when starting a shell in a terminal, a bash deprecation warning
+ " message is displayed. This breaks the terminal test. Disable the warning
+ " message.
+ let $BASH_SILENCE_DEPRECATION_WARNING = 1
+endif
+
+
+" Prepare for calling test_garbagecollect_now().
+let v:testing = 1
+
+" By default, copy each buffer line into allocated memory, so that valgrind can
+" detect accessing memory before and after it.
+call test_override('alloc_lines', 1)
+
+" Support function: get the alloc ID by name.
+function GetAllocId(name)
+ exe 'split ' . s:srcdir . '/alloc.h'
+ let top = search('typedef enum')
+ if top == 0
+ call add(v:errors, 'typedef not found in alloc.h')
+ endif
+ let lnum = search('aid_' . a:name . ',')
+ if lnum == 0
+ call add(v:errors, 'Alloc ID ' . a:name . ' not defined')
+ endif
+ close
+ return lnum - top - 1
+endfunc
+
+if has('reltime')
+ let g:func_start = reltime()
+endif
+
+" Get the list of swap files in the current directory.
+func s:GetSwapFileList()
+ let save_dir = &directory
+ let &directory = '.'
+ let files = swapfilelist()
+ let &directory = save_dir
+
+ " remove a match with runtest.vim
+ let idx = indexof(files, 'v:val =~ "runtest.vim."')
+ if idx >= 0
+ call remove(files, idx)
+ endif
+
+ return files
+endfunc
+
+" A previous (failed) test run may have left swap files behind. Delete them
+" before running tests again, they might interfere.
+for name in s:GetSwapFileList()
+ call delete(name)
+endfor
+unlet name
+
+
+" Invoked when a test takes too much time.
+func TestTimeout(id)
+ split test.log
+ call append(line('$'), '')
+ call append(line('$'), 'Test timed out: ' .. g:testfunc)
+ write
+ call add(v:errors, 'Test timed out: ' . g:testfunc)
+
+ cquit! 42
+endfunc
+
+func RunTheTest(test)
+ let prefix = ''
+ if has('reltime')
+ let prefix = strftime('%M:%S', localtime() - s:test_start_time) .. ' '
+ let g:func_start = reltime()
+ endif
+ echoconsole prefix .. 'Executing ' .. a:test
+
+ if has('timers')
+ " No test should take longer than 30 seconds. If it takes longer we
+ " assume we are stuck and need to break out.
+ let test_timeout_timer = timer_start(30000, 'TestTimeout')
+ endif
+
+ " Avoid stopping at the "hit enter" prompt
+ set nomore
+
+ " Avoid a three second wait when a message is about to be overwritten by the
+ " mode message.
+ set noshowmode
+
+ " Clear any overrides, except "alloc_lines".
+ call test_override('ALL', 0)
+
+ " Some tests wipe out buffers. To be consistent, always wipe out all
+ " buffers.
+ %bwipe!
+
+ " The test may change the current directory. Save and restore the
+ " directory after executing the test.
+ let save_cwd = getcwd()
+
+ if exists("*SetUp")
+ try
+ call SetUp()
+ catch
+ call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
+ endtry
+ endif
+
+ au VimLeavePre * call EarlyExit(g:testfunc)
+ if a:test =~ 'Test_nocatch_'
+ " Function handles errors itself. This avoids skipping commands after the
+ " error.
+ let g:skipped_reason = ''
+ exe 'call ' . a:test
+ if g:skipped_reason != ''
+ call add(s:messages, ' Skipped')
+ call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason)
+ endif
+ else
+ try
+ exe 'call ' . a:test
+ catch /^\cskipped/
+ call add(s:messages, ' Skipped')
+ call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', ''))
+ catch
+ call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
+ endtry
+ endif
+ au! VimLeavePre
+
+ if a:test =~ '_terminal_'
+ " Terminal tests sometimes hang, give extra information
+ echoconsole 'After executing ' .. a:test
+ endif
+
+ " In case 'insertmode' was set and something went wrong, make sure it is
+ " reset to avoid trouble with anything else.
+ set noinsertmode
+
+ if exists("*TearDown")
+ try
+ call TearDown()
+ catch
+ call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
+ endtry
+ endif
+
+ if has('timers')
+ call timer_stop(test_timeout_timer)
+ endif
+
+ " Clear any autocommands and put back the catch-all for SwapExists.
+ au!
+ au SwapExists * call HandleSwapExists()
+
+ " Check for and close any stray popup windows.
+ if has('popupwin')
+ call assert_equal([], popup_list(), 'Popup is still present')
+ call popup_clear(1)
+ endif
+
+ if filereadable('guidialogfile')
+ call add(v:errors, "Unexpected dialog: " .. readfile('guidialogfile')->join('<NL>'))
+ call delete('guidialogfile')
+ endif
+
+ " Close any extra tab pages and windows and make the current one not modified.
+ while tabpagenr('$') > 1
+ let winid = win_getid()
+ quit!
+ if winid == win_getid()
+ echoerr 'Could not quit window'
+ break
+ endif
+ endwhile
+
+ while 1
+ let wincount = winnr('$')
+ if wincount == 1
+ break
+ endif
+ bwipe!
+ if wincount == winnr('$')
+ " Did not manage to close a window.
+ only!
+ break
+ endif
+ endwhile
+
+ exe 'cd ' . save_cwd
+
+ if a:test =~ '_terminal_'
+ " Terminal tests sometimes hang, give extra information
+ echoconsole 'Finished ' . a:test
+ endif
+
+ let message = 'Executed ' . a:test
+ if has('reltime')
+ let message ..= repeat(' ', 50 - len(message))
+ let time = reltime(g:func_start)
+ if reltimefloat(time) > 0.1
+ let message = s:t_bold .. message
+ endif
+ let message ..= ' in ' .. reltimestr(time) .. ' seconds'
+ if reltimefloat(time) > 0.1
+ let message ..= s:t_normal
+ endif
+ endif
+ call add(s:messages, message)
+ let s:done += 1
+
+ " close any split windows
+ while winnr('$') > 1
+ bwipe!
+ endwhile
+
+ " May be editing some buffer, wipe it out. Then we may end up in another
+ " buffer, continue until we end up in an empty no-name buffer without a swap
+ " file.
+ while bufname() != '' || execute('swapname') !~ 'No swap file'
+ let bn = bufnr()
+
+ noswapfile bwipe!
+
+ if bn == bufnr()
+ " avoid getting stuck in the same buffer
+ break
+ endif
+ endwhile
+
+ " Check if the test has left any swap files behind. Delete them before
+ " running tests again, they might interfere.
+ let swapfiles = s:GetSwapFileList()
+ if len(swapfiles) > 0
+ call add(s:messages, "Found swap files: " .. string(swapfiles))
+ for name in swapfiles
+ call delete(name)
+ endfor
+ endif
+endfunc
+
+func AfterTheTest(func_name)
+ if len(v:errors) > 0
+ if match(s:may_fail_list, '^' .. a:func_name) >= 0
+ let s:fail_expected += 1
+ call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':')
+ call extend(s:errors_expected, v:errors)
+ else
+ let s:fail += 1
+ call add(s:errors, 'Found errors in ' . g:testfunc . ':')
+ call extend(s:errors, v:errors)
+ endif
+ let v:errors = []
+ endif
+endfunc
+
+func EarlyExit(test)
+ " It's OK for the test we use to test the quit detection.
+ if a:test != 'Test_zz_quit_detected()'
+ call add(v:errors, v:errmsg)
+ call add(v:errors, 'Test caused Vim to exit: ' . a:test)
+ endif
+
+ call FinishTesting()
+endfunc
+
+" This function can be called by a test if it wants to abort testing.
+func FinishTesting()
+ call AfterTheTest('')
+
+ " Don't write viminfo on exit.
+ set viminfo=
+
+ " Clean up files created by setup.vim
+ call delete('XfakeHOME', 'rf')
+
+ if s:fail == 0 && s:fail_expected == 0
+ " Success, create the .res file so that make knows it's done.
+ exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
+ write
+ endif
+
+ if len(s:errors) > 0
+ " Append errors to test.log
+ split test.log
+ call append(line('$'), '')
+ call append(line('$'), 'From ' . g:testname . ':')
+ call append(line('$'), s:errors)
+ write
+ endif
+
+ if s:done == 0
+ if s:filtered > 0
+ if $TEST_FILTER != ''
+ let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
+ else
+ let message = "ALL tests match $TEST_SKIP_PAT: '" .. $TEST_SKIP_PAT .. "'"
+ endif
+ else
+ let message = 'NO tests executed'
+ endif
+ else
+ if s:filtered > 0
+ call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER and $TEST_SKIP_PAT")
+ endif
+ let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
+ endif
+ if s:done > 0 && has('reltime')
+ let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
+ let message ..= ' in ' .. reltimestr(reltime(s:run_start_time)) .. ' seconds'
+ let message ..= s:t_normal
+ endif
+ echo message
+ call add(s:messages, message)
+ if s:fail > 0
+ let message = s:fail . ' FAILED:'
+ echo message
+ call add(s:messages, message)
+ call extend(s:messages, s:errors)
+ endif
+ if s:fail_expected > 0
+ let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
+ echo message
+ call add(s:messages, message)
+ call extend(s:messages, s:errors_expected)
+ endif
+
+ " Add SKIPPED messages
+ call extend(s:messages, s:skipped)
+
+ " Append messages to the file "messages"
+ split messages
+ call append(line('$'), '')
+ call append(line('$'), 'From ' . g:testname . ':')
+ call append(line('$'), s:messages)
+ write
+
+ qall!
+endfunc
+
+" Source the test script. First grab the file name, in case the script
+" navigates away. g:testname can be used by the tests.
+let g:testname = expand('%')
+let s:done = 0
+let s:fail = 0
+let s:fail_expected = 0
+let s:errors = []
+let s:errors_expected = []
+let s:messages = []
+let s:skipped = []
+if expand('%') =~ 'test_vimscript.vim'
+ " this test has intentional errors, don't use try/catch.
+ source %
+else
+ try
+ source %
+ catch /^\cskipped/
+ call add(s:messages, ' Skipped')
+ call add(s:skipped, 'SKIPPED ' . expand('%') . ': ' . substitute(v:exception, '^\S*\s\+', '', ''))
+ catch
+ let s:fail += 1
+ call add(s:errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint)
+ endtry
+endif
+
+" Delete the .res file, it may change behavior for completion
+call delete(fnamemodify(g:testname, ':r') .. '.res')
+
+" Locate Test_ functions and execute them.
+redir @q
+silent function /^Test_
+redir END
+let s:tests = split(substitute(@q, '\(function\|def\) \(\k*()\)', '\2', 'g'))
+
+" If there is an extra argument filter the function names against it.
+if argc() > 1
+ let s:tests = filter(s:tests, 'v:val =~ argv(1)')
+endif
+
+" If the environment variable $TEST_FILTER is set then filter the function
+" names against it.
+let s:filtered = 0
+if $TEST_FILTER != ''
+ let s:filtered = len(s:tests)
+ let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
+ let s:filtered -= len(s:tests)
+endif
+
+let s:may_fail_list = []
+if $TEST_MAY_FAIL != ''
+ " Split the list at commas and add () to make it match g:testfunc.
+ let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'})
+endif
+
+" Execute the tests in alphabetical order.
+for g:testfunc in sort(s:tests)
+ if $TEST_SKIP_PAT != '' && g:testfunc =~ $TEST_SKIP_PAT
+ call add(s:messages, g:testfunc .. ' matches $TEST_SKIP_PAT')
+ let s:filtered += 1
+ continue
+ endif
+
+ " Silence, please!
+ set belloff=all
+ let prev_error = ''
+ let total_errors = []
+ let g:run_nr = 1
+
+ " A test can set g:test_is_flaky to retry running the test.
+ let g:test_is_flaky = 0
+
+ let starttime = strftime("%H:%M:%S")
+ call RunTheTest(g:testfunc)
+
+ " Repeat a flaky test. Give up when:
+ " - $TEST_NO_RETRY is not empty
+ " - it fails again with the same message
+ " - it fails five times (with a different message)
+ if len(v:errors) > 0
+ \ && $TEST_NO_RETRY == ''
+ \ && g:test_is_flaky
+ while 1
+ call add(s:messages, 'Found errors in ' .. g:testfunc .. ':')
+ call extend(s:messages, v:errors)
+
+ let endtime = strftime("%H:%M:%S")
+ call add(total_errors, $'Run {g:run_nr}, {starttime} - {endtime}:')
+ call extend(total_errors, v:errors)
+
+ if g:run_nr >= 5 || prev_error == v:errors[0]
+ call add(total_errors, 'Flaky test failed too often, giving up')
+ let v:errors = total_errors
+ break
+ endif
+
+ call add(s:messages, 'Flaky test failed, running it again')
+
+ " Flakiness is often caused by the system being very busy. Sleep a
+ " couple of seconds to have a higher chance of succeeding the second
+ " time.
+ sleep 2
+
+ let prev_error = v:errors[0]
+ let v:errors = []
+ let g:run_nr += 1
+
+ let starttime = strftime("%H:%M:%S")
+ call RunTheTest(g:testfunc)
+
+ if len(v:errors) == 0
+ " Test passed on rerun.
+ break
+ endif
+ endwhile
+ endif
+
+ call AfterTheTest(g:testfunc)
+endfor
+
+call FinishTesting()
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/samples/crypt_sodium_invalid.txt b/src/testdir/samples/crypt_sodium_invalid.txt
new file mode 100644
index 0000000..35e31b5
--- /dev/null
+++ b/src/testdir/samples/crypt_sodium_invalid.txt
Binary files differ
diff --git a/src/testdir/samples/quickfix.txt b/src/testdir/samples/quickfix.txt
new file mode 100644
index 0000000..2de3835
--- /dev/null
+++ b/src/testdir/samples/quickfix.txt
@@ -0,0 +1,4 @@
+samples/quickfix.txt:1:1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+samples/quickfix.txt:2:1:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+samples/quickfix.txt:3:1:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+samples/quickfix.txt:4:1:dddddddddd
diff --git a/src/testdir/samples/re.freeze.txt b/src/testdir/samples/re.freeze.txt
new file mode 100644
index 0000000..d768c23
--- /dev/null
+++ b/src/testdir/samples/re.freeze.txt
@@ -0,0 +1,6 @@
+:set re=0 or 2
+Search for the pattern: /\s\+\%#\@<!$/
+vim should not freeze.
+
+<td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td>
+
diff --git a/src/testdir/samples/test000 b/src/testdir/samples/test000
new file mode 100644
index 0000000..af8abe9
--- /dev/null
+++ b/src/testdir/samples/test000
Binary files differ
diff --git a/src/testdir/sautest/autoload/Test104.vim b/src/testdir/sautest/autoload/Test104.vim
new file mode 100644
index 0000000..d1e0e17
--- /dev/null
+++ b/src/testdir/sautest/autoload/Test104.vim
@@ -0,0 +1 @@
+let Test104#numvar = 123
diff --git a/src/testdir/sautest/autoload/auto9.vim b/src/testdir/sautest/autoload/auto9.vim
new file mode 100644
index 0000000..fad1fa7
--- /dev/null
+++ b/src/testdir/sautest/autoload/auto9.vim
@@ -0,0 +1,9 @@
+vim9script
+
+export func Getsome()
+ return 'some'
+endfunc
+
+export def Add42(count: number): number
+ return count + 42
+enddef
diff --git a/src/testdir/sautest/autoload/foo.vim b/src/testdir/sautest/autoload/foo.vim
new file mode 100644
index 0000000..21d33a0
--- /dev/null
+++ b/src/testdir/sautest/autoload/foo.vim
@@ -0,0 +1,15 @@
+let g:loaded_foo_vim += 1
+
+let foo#bar = {}
+
+func foo#bar.echo()
+ let g:called_foo_bar_echo += 1
+endfunc
+
+func foo#addFoo(head)
+ return a:head .. 'foo'
+endfunc
+
+func foo#()
+ return 'empty'
+endfunc
diff --git a/src/testdir/sautest/autoload/footest.vim b/src/testdir/sautest/autoload/footest.vim
new file mode 100644
index 0000000..1e78963
--- /dev/null
+++ b/src/testdir/sautest/autoload/footest.vim
@@ -0,0 +1,5 @@
+" Autoload script used by test_listdict.vim, test_exists.vim and test_let.vim
+let footest#x = 1
+func footest#F()
+ return 0
+endfunc
diff --git a/src/testdir/sautest/autoload/globone.vim b/src/testdir/sautest/autoload/globone.vim
new file mode 100644
index 0000000..98c9a10
--- /dev/null
+++ b/src/testdir/sautest/autoload/globone.vim
@@ -0,0 +1 @@
+" used by Test_globpath()
diff --git a/src/testdir/sautest/autoload/globtwo.vim b/src/testdir/sautest/autoload/globtwo.vim
new file mode 100644
index 0000000..98c9a10
--- /dev/null
+++ b/src/testdir/sautest/autoload/globtwo.vim
@@ -0,0 +1 @@
+" used by Test_globpath()
diff --git a/src/testdir/sautest/autoload/sourced.vim b/src/testdir/sautest/autoload/sourced.vim
new file mode 100644
index 0000000..aac96b1
--- /dev/null
+++ b/src/testdir/sautest/autoload/sourced.vim
@@ -0,0 +1,4 @@
+let g:loaded_sourced_vim += 1
+func sourced#something()
+endfunc
+call sourced#something()
diff --git a/src/testdir/screendump.vim b/src/testdir/screendump.vim
new file mode 100644
index 0000000..a2a54f4
--- /dev/null
+++ b/src/testdir/screendump.vim
@@ -0,0 +1,123 @@
+" Functions shared by tests making screen dumps.
+
+" Only load this script once.
+if exists('*VerifyScreenDump')
+ finish
+endif
+
+source shared.vim
+source term_util.vim
+
+" Skip the rest if there is no terminal feature at all.
+if !has('terminal')
+ finish
+endif
+
+" Read a dump file "fname" and if "filter" exists apply it to the text.
+def ReadAndFilter(fname: string, filter: string): list<string>
+ var contents = readfile(fname)
+
+ if filereadable(filter)
+ # do this in the bottom window so that the terminal window is unaffected
+ wincmd j
+ enew
+ setline(1, contents)
+ exe "source " .. filter
+ contents = getline(1, '$')
+ enew!
+ wincmd k
+ redraw
+ endif
+
+ return contents
+enddef
+
+
+" Verify that Vim running in terminal buffer "buf" matches the screen dump.
+" "options" is passed to term_dumpwrite().
+" Additionally, the "wait" entry can specify the maximum time to wait for the
+" screen dump to match in msec (default 1000 msec).
+" The file name used is "dumps/{filename}.dump".
+"
+" To ignore part of the dump, provide a "dumps/{filename}.vim" file with
+" Vim commands to be applied to both the reference and the current dump, so
+" that parts that are irrelevant are not used for the comparison. The result
+" is NOT written, thus "term_dumpdiff()" shows the difference anyway.
+"
+" Optionally an extra argument can be passed which is prepended to the error
+" message. Use this when using the same dump file with different options.
+" Returns non-zero when verification fails.
+func VerifyScreenDump(buf, filename, options, ...)
+ let reference = 'dumps/' . a:filename . '.dump'
+ let filter = 'dumps/' . a:filename . '.vim'
+ let testfile = 'failed/' . a:filename . '.dump'
+
+ let max_loops = get(a:options, 'wait', 1000) / 10
+
+ " Starting a terminal to make a screendump is always considered flaky.
+ let g:test_is_flaky = 1
+
+ " wait for the pending updates to be handled.
+ call TermWait(a:buf)
+
+ " Redraw to execute the code that updates the screen. Otherwise we get the
+ " text and attributes only from the internal buffer.
+ redraw
+
+ if filereadable(reference)
+ let refdump = ReadAndFilter(reference, filter)
+ else
+ " Must be a new screendump, always fail
+ let refdump = []
+ endif
+
+ let did_mkdir = 0
+ if !isdirectory('failed')
+ let did_mkdir = 1
+ call mkdir('failed')
+ endif
+
+ let i = 0
+ while 1
+ " leave some time for updating the original window
+ sleep 10m
+ call delete(testfile)
+ call term_dumpwrite(a:buf, testfile, a:options)
+ let testdump = ReadAndFilter(testfile, filter)
+ if refdump == testdump
+ call delete(testfile)
+ if did_mkdir
+ call delete('failed', 'd')
+ endif
+ break
+ endif
+ if i == max_loops
+ " Leave the failed dump around for inspection.
+ if filereadable(reference)
+ let msg = 'See dump file difference: call term_dumpdiff("testdir/' .. testfile .. '", "testdir/' .. reference .. '")'
+ if a:0 == 1
+ let msg = a:1 . ': ' . msg
+ endif
+ if len(testdump) != len(refdump)
+ let msg = msg . '; line count is ' . len(testdump) . ' instead of ' . len(refdump)
+ endif
+ else
+ let msg = 'See new dump file: call term_dumpload("testdir/' .. testfile .. '")'
+ " no point in retrying
+ let g:run_nr = 10
+ endif
+ for i in range(len(refdump))
+ if i >= len(testdump)
+ break
+ endif
+ if testdump[i] != refdump[i]
+ let msg = msg . '; difference in line ' . (i + 1) . ': "' . testdump[i] . '"'
+ endif
+ endfor
+ call assert_report(msg)
+ return 1
+ endif
+ let i += 1
+ endwhile
+ return 0
+endfunc
diff --git a/src/testdir/script_util.vim b/src/testdir/script_util.vim
new file mode 100644
index 0000000..28d6a62
--- /dev/null
+++ b/src/testdir/script_util.vim
@@ -0,0 +1,69 @@
+" Functions shared by the tests for Vim Script
+
+" Commands to track the execution path of a script
+com! XpathINIT let g:Xpath = ''
+com! -nargs=1 -bar Xpath let g:Xpath ..= <args>
+com! XloopINIT let g:Xloop = 1
+com! -nargs=1 -bar Xloop let g:Xpath ..= <args> .. g:Xloop
+com! XloopNEXT let g:Xloop += 1
+
+" MakeScript() - Make a script file from a function. {{{2
+"
+" Create a script that consists of the body of the function a:funcname.
+" Replace any ":return" by a ":finish", any argument variable by a global
+" variable, and every ":call" by a ":source" for the next following argument
+" in the variable argument list. This function is useful if similar tests are
+" to be made for a ":return" from a function call or a ":finish" in a script
+" file.
+func MakeScript(funcname, ...)
+ let script = tempname()
+ execute "redir! >" . script
+ execute "function" a:funcname
+ redir END
+ execute "edit" script
+ " Delete the "function" and the "endfunction" lines. Do not include the
+ " word "function" in the pattern since it might be translated if LANG is
+ " set. When MakeScript() is being debugged, this deletes also the debugging
+ " output of its line 3 and 4.
+ exec '1,/.*' . a:funcname . '(.*)/d'
+ /^\d*\s*endfunction\>/,$d
+ %s/^\d*//e
+ %s/return/finish/e
+ %s/\<a:\(\h\w*\)/g:\1/ge
+ normal gg0
+ let cnt = 0
+ while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0
+ let cnt = cnt + 1
+ s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/
+ endwhile
+ g/^\s*$/d
+ write
+ bwipeout
+ return script
+endfunc
+
+" ExecAsScript - Source a temporary script made from a function. {{{2
+"
+" Make a temporary script file from the function a:funcname, ":source" it, and
+" delete it afterwards. However, if an exception is thrown the file may remain,
+" the caller should call DeleteTheScript() afterwards.
+let s:script_name = ''
+func ExecAsScript(funcname)
+ " Make a script from the function passed as argument.
+ let s:script_name = MakeScript(a:funcname)
+
+ " Source and delete the script.
+ exec "source" s:script_name
+ call delete(s:script_name)
+ let s:script_name = ''
+endfunc
+
+func DeleteTheScript()
+ if s:script_name
+ call delete(s:script_name)
+ let s:script_name = ''
+ endif
+endfunc
+
+com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>)
+
diff --git a/src/testdir/setup.vim b/src/testdir/setup.vim
new file mode 100644
index 0000000..ca94bae
--- /dev/null
+++ b/src/testdir/setup.vim
@@ -0,0 +1,39 @@
+" Common preparations for running tests.
+
+" Only load this once.
+if 1
+
+ " When using xterm version 377 the response to the modifyOtherKeys status
+ " interferes with some tests. Remove the request from the t_TI termcap
+ " entry.
+ let &t_TI = substitute(&t_TI, "\<Esc>\\[?4m", '', '')
+
+ if exists('s:did_load')
+ finish
+ endif
+ let s:did_load = 1
+endif
+
+" Make sure 'runtimepath' and 'packpath' does not include $HOME.
+set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after
+if has('packages')
+ let &packpath = &rtp
+endif
+
+" Only when the +eval feature is present.
+if 1
+ " Make sure the .Xauthority file can be found after changing $HOME.
+ if $XAUTHORITY == ''
+ let $XAUTHORITY = $HOME . '/.Xauthority'
+ endif
+
+ " Avoid storing shell history.
+ let $HISTFILE = ""
+
+ " Make sure $HOME does not get read or written.
+ " It must exist, gnome tries to create $HOME/.gnome2
+ let $HOME = getcwd() . '/XfakeHOME'
+ if !isdirectory($HOME)
+ call mkdir($HOME)
+ endif
+endif
diff --git a/src/testdir/setup_gui.vim b/src/testdir/setup_gui.vim
new file mode 100644
index 0000000..2e5e777
--- /dev/null
+++ b/src/testdir/setup_gui.vim
@@ -0,0 +1,31 @@
+" Common preparations for running GUI tests.
+
+let g:x11_based_gui = has('gui_motif')
+ \ || has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+
+" Reasons for 'skipped'.
+let g:not_supported = "Skipped: Feature/Option not supported by this GUI: "
+let g:not_hosted = "Skipped: Test not hosted by the system/environment"
+
+" For KDE set a font, empty 'guifont' may cause a hang.
+func GUISetUpCommon()
+ if has("gui_kde")
+ set guifont=Courier\ 10\ Pitch/8/-1/5/50/0/0/0/0/0
+ endif
+
+ " Gnome insists on creating $HOME/.gnome2/, set $HOME to avoid changing the
+ " actual home directory. But avoid triggering fontconfig by setting the
+ " cache directory. Only needed for Unix.
+ if $XDG_CACHE_HOME == '' && exists('g:tester_HOME')
+ let $XDG_CACHE_HOME = g:tester_HOME . '/.cache'
+ endif
+ call mkdir('Xhome')
+ let $HOME = fnamemodify('Xhome', ':p')
+endfunc
+
+func GUITearDownCommon()
+ call delete('Xhome', 'rf')
+endfunc
+
+" Ignore the "failed to create input context" error.
+call test_ignore_error('E285')
diff --git a/src/testdir/shared.vim b/src/testdir/shared.vim
new file mode 100644
index 0000000..d373a6e
--- /dev/null
+++ b/src/testdir/shared.vim
@@ -0,0 +1,411 @@
+" Functions shared by several tests.
+
+" Only load this script once.
+if exists('*PythonProg')
+ finish
+endif
+
+source view_util.vim
+
+" When 'term' is changed some status requests may be sent. The responses may
+" interfere with what is being tested. A short sleep is used to process any of
+" those responses first.
+func WaitForResponses()
+ sleep 50m
+endfunc
+
+" Get the name of the Python executable.
+" Also keeps it in s:python.
+func PythonProg()
+ " This test requires the Python command to run the test server.
+ " This most likely only works on Unix and Windows.
+ if has('unix')
+ " We also need the job feature or the pkill command to make sure the server
+ " can be stopped.
+ if !(has('job') || executable('pkill'))
+ return ''
+ endif
+ if executable('python')
+ let s:python = 'python'
+ elseif executable('python3')
+ let s:python = 'python3'
+ else
+ return ''
+ end
+ elseif has('win32')
+ " Use Python Launcher for Windows (py.exe) if available.
+ " NOTE: if you get a "Python was not found" error, disable the Python
+ " shortcuts in "Windows menu / Settings / Manage App Execution Aliases".
+ if executable('py.exe')
+ let s:python = 'py.exe'
+ elseif executable('python.exe')
+ let s:python = 'python.exe'
+ else
+ return ''
+ endif
+ else
+ return ''
+ endif
+ return s:python
+endfunc
+
+" Run "cmd". Returns the job if using a job.
+func RunCommand(cmd)
+ " Running an external command can occasionally be slow or fail.
+ let g:test_is_flaky = 1
+
+ let job = 0
+ if has('job')
+ let job = job_start(a:cmd, {"stoponexit": "hup"})
+ call job_setoptions(job, {"stoponexit": "kill"})
+ elseif has('win32')
+ exe 'silent !start cmd /c start "test_channel" ' . a:cmd
+ else
+ exe 'silent !' . a:cmd . '&'
+ endif
+ return job
+endfunc
+
+" Read the port number from the Xportnr file.
+func GetPort()
+ let l = []
+ " with 200 it sometimes failed, with 400 is rarily failed
+ for i in range(600)
+ try
+ let l = readfile("Xportnr")
+ catch
+ endtry
+ if len(l) >= 1
+ break
+ endif
+ sleep 10m
+ endfor
+ call delete("Xportnr")
+
+ if len(l) == 0
+ " Can't make the connection, give up.
+ return 0
+ endif
+ return l[0]
+endfunc
+
+" Run a Python server for "cmd" and call "testfunc".
+" Always kills the server before returning.
+func RunServer(cmd, testfunc, args)
+ " The Python program writes the port number in Xportnr.
+ call delete("Xportnr")
+
+ if len(a:args) == 1
+ let arg = ' ' . a:args[0]
+ else
+ let arg = ''
+ endif
+ let pycmd = s:python . " " . a:cmd . arg
+
+ try
+ let g:currentJob = RunCommand(pycmd)
+
+ " Wait for some time for the port number to be there.
+ let port = GetPort()
+ if port == 0
+ call assert_report(strftime("%H:%M:%S") .. " Can't start " .. a:cmd)
+ return
+ endif
+
+ call call(function(a:testfunc), [port])
+ catch
+ call assert_report('Caught exception: "' . v:exception . '" in ' . v:throwpoint)
+ finally
+ call s:kill_server(a:cmd)
+ endtry
+endfunc
+
+func s:kill_server(cmd)
+ if has('job')
+ if exists('g:currentJob')
+ call job_stop(g:currentJob)
+ unlet g:currentJob
+ endif
+ elseif has('win32')
+ let cmd = substitute(a:cmd, ".py", '', '')
+ call system('taskkill /IM ' . s:python . ' /T /F /FI "WINDOWTITLE eq ' . cmd . '"')
+ else
+ call system("pkill -f " . a:cmd)
+ endif
+endfunc
+
+" Wait for up to five seconds for "expr" to become true. "expr" can be a
+" stringified expression to evaluate, or a funcref without arguments.
+" Using a lambda works best. Example:
+" call WaitFor({-> status == "ok"})
+"
+" A second argument can be used to specify a different timeout in msec.
+"
+" When successful the time slept is returned.
+" When running into the timeout an exception is thrown, thus the function does
+" not return.
+func WaitFor(expr, ...)
+ let timeout = get(a:000, 0, 5000)
+ let slept = s:WaitForCommon(a:expr, v:null, timeout)
+ if slept < 0
+ throw 'WaitFor() timed out after ' . timeout . ' msec'
+ endif
+ return slept
+endfunc
+
+" Wait for up to five seconds for "assert" to return zero. "assert" must be a
+" (lambda) function containing one assert function. Example:
+" call WaitForAssert({-> assert_equal("dead", job_status(job)})
+"
+" A second argument can be used to specify a different timeout in msec.
+"
+" Return zero for success, one for failure (like the assert function).
+func WaitForAssert(assert, ...)
+ let timeout = get(a:000, 0, 5000)
+ if s:WaitForCommon(v:null, a:assert, timeout) < 0
+ return 1
+ endif
+ return 0
+endfunc
+
+" Common implementation of WaitFor() and WaitForAssert().
+" Either "expr" or "assert" is not v:null
+" Return the waiting time for success, -1 for failure.
+func s:WaitForCommon(expr, assert, timeout)
+ " using reltime() is more accurate, but not always available
+ let slept = 0
+ if exists('*reltimefloat')
+ let start = reltime()
+ endif
+
+ while 1
+ if type(a:expr) == v:t_func
+ let success = a:expr()
+ elseif type(a:assert) == v:t_func
+ let success = a:assert() == 0
+ else
+ let success = eval(a:expr)
+ endif
+ if success
+ return slept
+ endif
+
+ if slept >= a:timeout
+ break
+ endif
+ if type(a:assert) == v:t_func
+ " Remove the error added by the assert function.
+ call remove(v:errors, -1)
+ endif
+
+ sleep 10m
+ if exists('*reltimefloat')
+ let slept = float2nr(reltimefloat(reltime(start)) * 1000)
+ else
+ let slept += 10
+ endif
+ endwhile
+
+ return -1 " timed out
+endfunc
+
+
+" Wait for up to a given milliseconds.
+" With the +timers feature this waits for key-input by getchar(), Resume()
+" feeds key-input and resumes process. Return time waited in milliseconds.
+" Without +timers it uses simply :sleep.
+func Standby(msec)
+ if has('timers') && exists('*reltimefloat')
+ let start = reltime()
+ let g:_standby_timer = timer_start(a:msec, function('s:feedkeys'))
+ call getchar()
+ return float2nr(reltimefloat(reltime(start)) * 1000)
+ else
+ execute 'sleep ' a:msec . 'm'
+ return a:msec
+ endif
+endfunc
+
+func Resume()
+ if exists('g:_standby_timer')
+ call timer_stop(g:_standby_timer)
+ call s:feedkeys(0)
+ unlet g:_standby_timer
+ endif
+endfunc
+
+func s:feedkeys(timer)
+ call feedkeys('x', 'nt')
+endfunc
+
+" Get the name of the Vim executable that we expect has been build in the src
+" directory.
+func s:GetJustBuildVimExe()
+ if has("win32")
+ if !filereadable('..\vim.exe') && filereadable('..\vimd.exe')
+ " looks like the debug executable was intentionally build, so use it
+ return '..\vimd.exe'
+ endif
+ return '..\vim.exe'
+ endif
+ return '../vim'
+endfunc
+
+" Get $VIMPROG to run the Vim executable.
+" The Makefile writes it as the first line in the "vimcmd" file.
+" Falls back to the Vim executable in the src directory.
+func GetVimProg()
+ if filereadable('vimcmd')
+ return readfile('vimcmd')[0]
+ endif
+ echo 'Cannot read the "vimcmd" file, falling back to ../vim.'
+
+ " Probably the script was sourced instead of running "make".
+ " We assume Vim was just build in the src directory then.
+ return s:GetJustBuildVimExe()
+endfunc
+
+let g:valgrind_cnt = 1
+
+" Get the command to run Vim, with -u NONE and --not-a-term arguments.
+" If there is an argument use it instead of "NONE".
+func GetVimCommand(...)
+ if filereadable('vimcmd')
+ let lines = readfile('vimcmd')
+ else
+ echo 'Cannot read the "vimcmd" file, falling back to ../vim.'
+ let lines = [s:GetJustBuildVimExe()]
+ endif
+
+ if a:0 == 0
+ let name = 'NONE'
+ else
+ let name = a:1
+ endif
+ " For Unix Makefile writes the command to use in the second line of the
+ " "vimcmd" file, including environment options.
+ " Other Makefiles just write the executable in the first line, so fall back
+ " to that if there is no second line or it is empty.
+ if len(lines) > 1 && lines[1] != ''
+ let cmd = lines[1]
+ else
+ let cmd = lines[0]
+ endif
+
+ let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '')
+ if cmd !~ '-u '. name
+ let cmd = cmd . ' -u ' . name
+ endif
+ let cmd .= ' --not-a-term'
+ let cmd .= ' --gui-dialog-file guidialogfile'
+ let cmd = substitute(cmd, 'VIMRUNTIME=\S\+', '', '')
+
+ " If using valgrind, make sure every run uses a different log file.
+ if cmd =~ 'valgrind.*--log-file='
+ let cmd = substitute(cmd, '--log-file=\(\S*\)', '--log-file=\1.' . g:valgrind_cnt, '')
+ let g:valgrind_cnt += 1
+ endif
+
+ return cmd
+endfunc
+
+" Return one when it looks like the tests are run with valgrind, which means
+" that everything is much slower.
+func RunningWithValgrind()
+ return GetVimCommand() =~ '\<valgrind\>'
+endfunc
+
+" Get the command to run Vim, with --clean instead of "-u NONE".
+func GetVimCommandClean()
+ let cmd = GetVimCommand()
+ let cmd = substitute(cmd, '-u NONE', '--clean', '')
+ let cmd = substitute(cmd, '--not-a-term', '', '')
+
+ " Force using utf-8, Vim may pick up something else from the environment.
+ let cmd ..= ' --cmd "set enc=utf8" '
+
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
+ return cmd
+endfunc
+
+" Get the command to run Vim, with --clean, and force to run in terminal so it
+" won't start a new GUI.
+func GetVimCommandCleanTerm()
+ " Add -v to have gvim run in the terminal (if possible)
+ return GetVimCommandClean() .. ' -v '
+endfunc
+
+" Run Vim, using the "vimcmd" file and "-u NORC".
+" "before" is a list of Vim commands to be executed before loading plugins.
+" "after" is a list of Vim commands to be executed after loading plugins.
+" Plugins are not loaded, unless 'loadplugins' is set in "before".
+" Return 1 if Vim could be executed.
+func RunVim(before, after, arguments)
+ return RunVimPiped(a:before, a:after, a:arguments, '')
+endfunc
+
+func RunVimPiped(before, after, arguments, pipecmd)
+ let cmd = GetVimCommand()
+ let args = ''
+ if len(a:before) > 0
+ call writefile(a:before, 'Xbefore.vim')
+ let args .= ' --cmd "so Xbefore.vim"'
+ endif
+ if len(a:after) > 0
+ call writefile(a:after, 'Xafter.vim')
+ let args .= ' -S Xafter.vim'
+ endif
+
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
+ exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments
+
+ if len(a:before) > 0
+ call delete('Xbefore.vim')
+ endif
+ if len(a:after) > 0
+ call delete('Xafter.vim')
+ endif
+ return 1
+endfunc
+
+func IsRoot()
+ if !has('unix')
+ return v:false
+ elseif $USER == 'root' || system('id -un') =~ '\<root\>'
+ return v:true
+ endif
+ return v:false
+endfunc
+
+" Get all messages but drop the maintainer entry.
+func GetMessages()
+ redir => result
+ redraw | messages
+ redir END
+ let msg_list = split(result, "\n")
+ if msg_list->len() > 0 && msg_list[0] =~ 'Messages maintainer:'
+ return msg_list[1:]
+ endif
+ return msg_list
+endfunc
+
+" Run the list of commands in 'cmds' and look for 'errstr' in exception.
+" Note that assert_fails() cannot be used in some places and this function
+" can be used.
+func AssertException(cmds, errstr)
+ let save_exception = ''
+ try
+ for cmd in a:cmds
+ exe cmd
+ endfor
+ catch
+ let save_exception = v:exception
+ endtry
+ call assert_match(a:errstr, save_exception)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/silent.wav b/src/testdir/silent.wav
new file mode 100644
index 0000000..4631a7e
--- /dev/null
+++ b/src/testdir/silent.wav
Binary files differ
diff --git a/src/testdir/summarize.vim b/src/testdir/summarize.vim
new file mode 100644
index 0000000..d0d4e00
--- /dev/null
+++ b/src/testdir/summarize.vim
@@ -0,0 +1,62 @@
+set cpo&vim
+if 1
+ " This is executed only with the eval feature
+ set nocompatible
+ set viminfo=
+ func Count(match, type)
+ if a:type ==# 'executed'
+ let g:executed += (a:match+0)
+ elseif a:type ==# 'failed'
+ let g:failed += a:match+0
+ elseif a:type ==# 'skipped'
+ let g:skipped += 1
+ call extend(g:skipped_output, ["\t" .. a:match])
+ endif
+ endfunc
+
+ let g:executed = 0
+ let g:skipped = 0
+ let g:failed = 0
+ let g:skipped_output = []
+ let g:failed_output = []
+ let output = [""]
+
+ if $TEST_FILTER != ''
+ call extend(g:skipped_output, ["\tAll tests not matching $TEST_FILTER: '" .. $TEST_FILTER .. "'"])
+ endif
+
+ try
+ " This uses the :s command to just fetch and process the output of the
+ " tests, it doesn't actually replace anything.
+ " And it uses "silent" to avoid reporting the number of matches.
+ silent %s/Executed\s\+\zs\d\+\ze\s\+tests\?/\=Count(submatch(0),'executed')/egn
+ silent %s/^SKIPPED \zs.*/\=Count(submatch(0), 'skipped')/egn
+ silent %s/^\(\d\+\)\s\+FAILED:/\=Count(submatch(1), 'failed')/egn
+
+ call extend(output, ["Skipped:"])
+ call extend(output, skipped_output)
+
+ call extend(output, [
+ \ "",
+ \ "-------------------------------",
+ \ printf("Executed: %5d Tests", g:executed),
+ \ printf(" Skipped: %5d Tests", g:skipped),
+ \ printf(" %s: %5d Tests", g:failed == 0 ? 'Failed' : 'FAILED', g:failed),
+ \ "",
+ \ ])
+ if filereadable('test.log')
+ " outputs and indents the failed test result
+ call extend(output, ["", "Failures: "])
+ let failed_output = filter(readfile('test.log'), { v,k -> !empty(k)})
+ call extend(output, map(failed_output, { v,k -> "\t".k}))
+ " Add a final newline
+ call extend(output, [""])
+ endif
+
+ catch " Catch-all
+ finally
+ call writefile(output, 'test_result.log') " overwrites an existing file
+ endtry
+endif
+
+q!
diff --git a/src/testdir/term_util.vim b/src/testdir/term_util.vim
new file mode 100644
index 0000000..0f03731
--- /dev/null
+++ b/src/testdir/term_util.vim
@@ -0,0 +1,196 @@
+" Functions about terminal shared by several tests
+
+" Only load this script once.
+if exists('*CanRunVimInTerminal')
+ finish
+endif
+
+source shared.vim
+
+" For most tests we need to be able to run terminal Vim with 256 colors. On
+" MS-Windows the console only has 16 colors and the GUI can't run in a
+" terminal.
+func CanRunVimInTerminal()
+ return has('terminal') && !has('win32')
+endfunc
+
+" Skip the rest if there is no terminal feature at all.
+if !has('terminal')
+ finish
+endif
+
+" Stops the shell running in terminal "buf".
+func StopShellInTerminal(buf)
+ call term_sendkeys(a:buf, "exit\r")
+ let job = term_getjob(a:buf)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call TermWait(a:buf)
+endfunc
+
+" Wrapper around term_wait() to allow more time for re-runs of flaky tests
+" The second argument is the minimum time to wait in msec, 10 if omitted.
+func TermWait(buf, ...)
+ let wait_time = a:0 ? a:1 : 10
+ if exists('g:run_nr')
+ if g:run_nr == 2
+ let wait_time *= 4
+ elseif g:run_nr > 2
+ let wait_time *= 10
+ endif
+ endif
+ call term_wait(a:buf, wait_time)
+
+ " In case it wasn't set yet.
+ let g:test_is_flaky = 1
+endfunc
+
+" Run Vim with "arguments" in a new terminal window.
+" By default uses a size of 20 lines and 75 columns.
+" Returns the buffer number of the terminal.
+"
+" Options is a dictionary, these items are recognized:
+" "keep_t_u7" - when 1 do not make t_u7 empty (resetting t_u7 avoids clearing
+" parts of line 2 and 3 on the display)
+" "rows" - height of the terminal window (max. 20)
+" "cols" - width of the terminal window (max. 78)
+" "statusoff" - number of lines the status is offset from default
+" "wait_for_ruler" - if zero then don't wait for ruler to show
+func RunVimInTerminal(arguments, options)
+ " If Vim doesn't exit a swap file remains, causing other tests to fail.
+ " Remove it here.
+ call delete(".swp")
+
+ if exists('$COLORFGBG')
+ " Clear $COLORFGBG to avoid 'background' being set to "dark", which will
+ " only be corrected if the response to t_RB is received, which may be too
+ " late.
+ let $COLORFGBG = ''
+ endif
+
+ " Make a horizontal and vertical split, so that we can get exactly the right
+ " size terminal window. Works only when the current window is full width.
+ call assert_equal(&columns, winwidth(0))
+ split
+ vsplit
+
+ " Always do this with 256 colors and a light background.
+ set t_Co=256 background=light
+ hi Normal ctermfg=NONE ctermbg=NONE
+
+ " Make the window 20 lines high and 75 columns, unless told otherwise or
+ " 'termwinsize' is set.
+ let rows = get(a:options, 'rows', 20)
+ let cols = get(a:options, 'cols', 75)
+ let statusoff = get(a:options, 'statusoff', 1)
+
+ if get(a:options, 'keep_t_u7', 0)
+ let reset_u7 = ''
+ else
+ let reset_u7 = ' --cmd "set t_u7=" '
+ endif
+
+ let cmd = GetVimCommandCleanTerm() .. reset_u7 .. a:arguments
+
+ let options = #{curwin: 1}
+ if &termwinsize == ''
+ let options.term_rows = rows
+ let options.term_cols = cols
+ endif
+
+ " Accept other options whose name starts with 'term_'.
+ call extend(options, filter(copy(a:options), 'v:key =~# "^term_"'))
+
+ let buf = term_start(cmd, options)
+
+ if &termwinsize == ''
+ " in the GUI we may end up with a different size, try to set it.
+ if term_getsize(buf) != [rows, cols]
+ call term_setsize(buf, rows, cols)
+ endif
+ call assert_equal([rows, cols], term_getsize(buf))
+ else
+ let rows = term_getsize(buf)[0]
+ let cols = term_getsize(buf)[1]
+ endif
+
+ call TermWait(buf)
+
+ if get(a:options, 'wait_for_ruler', 1)
+ " Wait for "All" or "Top" of the ruler to be shown in the last line or in
+ " the status line of the last window. This can be quite slow (e.g. when
+ " using valgrind).
+ " If it fails then show the terminal contents for debugging.
+ try
+ call WaitFor({-> len(term_getline(buf, rows)) >= cols - 1 || len(term_getline(buf, rows - statusoff)) >= cols - 1})
+ catch /timed out after/
+ let lines = map(range(1, rows), {key, val -> term_getline(buf, val)})
+ call assert_report('RunVimInTerminal() failed, screen contents: ' . join(lines, "<NL>"))
+ endtry
+ endif
+
+ " Starting a terminal to run Vim is always considered flaky.
+ let g:test_is_flaky = 1
+
+ return buf
+endfunc
+
+" Stop a Vim running in terminal buffer "buf".
+func StopVimInTerminal(buf, kill = 1)
+ " Using a terminal to run Vim is always considered flaky.
+ let g:test_is_flaky = 1
+
+ call assert_equal("running", term_getstatus(a:buf))
+
+ " Wait for all the pending updates to terminal to complete
+ call TermWait(a:buf)
+
+ " CTRL-O : works both in Normal mode and Insert mode to start a command line.
+ " In Command-line it's inserted, the CTRL-U removes it again.
+ call term_sendkeys(a:buf, "\<C-O>:\<C-U>qa!\<cr>")
+
+ " Wait for all the pending updates to terminal to complete
+ call TermWait(a:buf)
+
+ " Wait for the terminal to end.
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(a:buf))})
+
+ " If the buffer still exists forcefully wipe it.
+ if a:kill && bufexists(a:buf)
+ exe a:buf .. 'bwipe!'
+ endif
+endfunc
+
+" Open a terminal with a shell, assign the job to g:job and return the buffer
+" number.
+func Run_shell_in_terminal(options)
+ if has('win32')
+ let buf = term_start([&shell, '/k'], a:options)
+ else
+ let buf = term_start(&shell, a:options)
+ endif
+ let g:test_is_flaky = 1
+
+ let termlist = term_list()
+ call assert_equal(1, len(termlist))
+ call assert_equal(buf, termlist[0])
+
+ let g:job = term_getjob(buf)
+ call assert_equal(v:t_job, type(g:job))
+
+ let string = string({'job': buf->term_getjob()})
+ call assert_match("{'job': 'process \\d\\+ run'}", string)
+
+ " On slower systems it may take a bit of time before the shell is ready to
+ " accept keys. This mainly matters when using term_sendkeys() next.
+ call TermWait(buf)
+
+ return buf
+endfunc
+
+" Return concatenated lines in terminal.
+func Term_getlines(buf, lines)
+ return join(map(a:lines, 'term_getline(a:buf, v:val)'), '')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test10.in b/src/testdir/test10.in
new file mode 100644
index 0000000..40f8e08
--- /dev/null
+++ b/src/testdir/test10.in
@@ -0,0 +1,21 @@
+Test that vim9script also works without the +eval feature.
+
+STARTTEST
+:/^START/+1,/^END/-1:w! Xvim9
+:so Xvim9
+ENDTEST
+
+START
+vim9script
+
+if 1
+ echo 'this is skipped without +eval'
+endif
+
+# colon required for a range
+:$-1,$w! test.out
+qa!
+END
+
+first line
+last line
diff --git a/src/testdir/test10.ok b/src/testdir/test10.ok
new file mode 100644
index 0000000..5776cea
--- /dev/null
+++ b/src/testdir/test10.ok
@@ -0,0 +1,2 @@
+first line
+last line
diff --git a/src/testdir/test20.in b/src/testdir/test20.in
new file mode 100644
index 0000000..1ffbed5
--- /dev/null
+++ b/src/testdir/test20.in
@@ -0,0 +1,27 @@
+Tests Blockwise Visual when there are TABs before the text.
+First test for undo working properly when executing commands from a register.
+Also test this in an empty buffer.
+
+STARTTEST
+G0"ay$k@au
+:new
+@auY:quit!
+GP
+/start here$
+"by$jjlld
+/456$
+jj"bP
+:/56$/,$-1w! test.out
+:qa!
+ENDTEST
+
+123456
+234567
+345678
+
+test text test tex start here
+ some text
+ test text
+test text
+
+OxjAykdd
diff --git a/src/testdir/test20.ok b/src/testdir/test20.ok
new file mode 100644
index 0000000..7c50ea8
--- /dev/null
+++ b/src/testdir/test20.ok
@@ -0,0 +1,10 @@
+123start here56
+234start here67
+345start here78
+
+test text test tex rt here
+ somext
+ tesext
+test text
+
+
diff --git a/src/testdir/test21.in b/src/testdir/test21.in
new file mode 100644
index 0000000..f5cc046
--- /dev/null
+++ b/src/testdir/test21.in
@@ -0,0 +1,13 @@
+Tests for file with some lines ending in CTRL-M, some not
+
+STARTTEST
+:set ta tx
+:e!
+:$-3,$w! test.out
+:qa!
+ENDTEST
+
+this lines ends in a
+this one doesn't
+this one does
+and the last one doesn't
diff --git a/src/testdir/test21.ok b/src/testdir/test21.ok
new file mode 100644
index 0000000..38ff89e
--- /dev/null
+++ b/src/testdir/test21.ok
@@ -0,0 +1,4 @@
+this lines ends in a
+this one doesn't
+this one does
+and the last one doesn't
diff --git a/src/testdir/test22.in b/src/testdir/test22.in
new file mode 100644
index 0000000..0e0e605
--- /dev/null
+++ b/src/testdir/test22.in
@@ -0,0 +1,15 @@
+Tests for complicated + argument to :edit command
+
+STARTTEST
+:$-1w! Xfile1
+:$w! Xfile2
+:edit +1|s/|/PIPE/|w Xfile1| e Xfile2|1 | s/\//SLASH/|w
+:w! test.out
+:e Xfile1
+:w >> test.out
+:qa!
+ENDTEST
+
+The result should be in Xfile1: "fooPIPEbar", in Xfile2: "fooSLASHbar"
+foo|bar
+foo/bar
diff --git a/src/testdir/test22.ok b/src/testdir/test22.ok
new file mode 100644
index 0000000..f1930ab
--- /dev/null
+++ b/src/testdir/test22.ok
@@ -0,0 +1,2 @@
+fooSLASHbar
+fooPIPEbar
diff --git a/src/testdir/test23.in b/src/testdir/test23.in
new file mode 100644
index 0000000..7dfc1af
--- /dev/null
+++ b/src/testdir/test23.in
Binary files differ
diff --git a/src/testdir/test23.ok b/src/testdir/test23.ok
new file mode 100644
index 0000000..cd61210
--- /dev/null
+++ b/src/testdir/test23.ok
@@ -0,0 +1,32 @@
+start
+test text test text
+test text test text
+test text test text
+test text test text
+test text test text
+test text test text
+test text test text x61
+test text test text x60-x64
+test text test text x78 5
+test text test text o143
+test text test text o140-o144
+test text test text o41 7
+test text test text \%x42
+test text test text \%o103
+test text test text [\x00]
+test text test text [\x00-\x10]
+test text test text [\x-z]
+test text test text [\u-z]
+xx xx a
+xx aaaaa xx a
+xx aaaaa xx a
+xx Aaa xx
+xx Aaaa xx
+xx Aaa xx
+xx foobar xA xx
+xx an A xx
+XX 9;
+YY 77;
+ xyz
+ bcd
+ BB
diff --git a/src/testdir/test24.in b/src/testdir/test24.in
new file mode 100644
index 0000000..5542c92
--- /dev/null
+++ b/src/testdir/test24.in
Binary files differ
diff --git a/src/testdir/test24.ok b/src/testdir/test24.ok
new file mode 100644
index 0000000..911d854
--- /dev/null
+++ b/src/testdir/test24.ok
@@ -0,0 +1,2 @@
+sd
+map __2 asdsecondsdsd0map __5 asd0fifth
diff --git a/src/testdir/test25.in b/src/testdir/test25.in
new file mode 100644
index 0000000..5e8ce44
--- /dev/null
+++ b/src/testdir/test25.in
@@ -0,0 +1,108 @@
+Test character classes in regexp using regexpengine 0, 1, 2.
+
+STARTTEST
+/^start-here/+1
+Y:s/\%#=0\d//g
+p:s/\%#=1\d//g
+p:s/\%#=2\d//g
+p:s/\%#=0[0-9]//g
+p:s/\%#=1[0-9]//g
+p:s/\%#=2[0-9]//g
+p:s/\%#=0\D//g
+p:s/\%#=1\D//g
+p:s/\%#=2\D//g
+p:s/\%#=0[^0-9]//g
+p:s/\%#=1[^0-9]//g
+p:s/\%#=2[^0-9]//g
+p:s/\%#=0\o//g
+p:s/\%#=1\o//g
+p:s/\%#=2\o//g
+p:s/\%#=0[0-7]//g
+p:s/\%#=1[0-7]//g
+p:s/\%#=2[0-7]//g
+p:s/\%#=0\O//g
+p:s/\%#=1\O//g
+p:s/\%#=2\O//g
+p:s/\%#=0[^0-7]//g
+p:s/\%#=1[^0-7]//g
+p:s/\%#=2[^0-7]//g
+p:s/\%#=0\x//g
+p:s/\%#=1\x//g
+p:s/\%#=2\x//g
+p:s/\%#=0[0-9A-Fa-f]//g
+p:s/\%#=1[0-9A-Fa-f]//g
+p:s/\%#=2[0-9A-Fa-f]//g
+p:s/\%#=0\X//g
+p:s/\%#=1\X//g
+p:s/\%#=2\X//g
+p:s/\%#=0[^0-9A-Fa-f]//g
+p:s/\%#=1[^0-9A-Fa-f]//g
+p:s/\%#=2[^0-9A-Fa-f]//g
+p:s/\%#=0\w//g
+p:s/\%#=1\w//g
+p:s/\%#=2\w//g
+p:s/\%#=0[0-9A-Za-z_]//g
+p:s/\%#=1[0-9A-Za-z_]//g
+p:s/\%#=2[0-9A-Za-z_]//g
+p:s/\%#=0\W//g
+p:s/\%#=1\W//g
+p:s/\%#=2\W//g
+p:s/\%#=0[^0-9A-Za-z_]//g
+p:s/\%#=1[^0-9A-Za-z_]//g
+p:s/\%#=2[^0-9A-Za-z_]//g
+p:s/\%#=0\h//g
+p:s/\%#=1\h//g
+p:s/\%#=2\h//g
+p:s/\%#=0[A-Za-z_]//g
+p:s/\%#=1[A-Za-z_]//g
+p:s/\%#=2[A-Za-z_]//g
+p:s/\%#=0\H//g
+p:s/\%#=1\H//g
+p:s/\%#=2\H//g
+p:s/\%#=0[^A-Za-z_]//g
+p:s/\%#=1[^A-Za-z_]//g
+p:s/\%#=2[^A-Za-z_]//g
+p:s/\%#=0\a//g
+p:s/\%#=1\a//g
+p:s/\%#=2\a//g
+p:s/\%#=0[A-Za-z]//g
+p:s/\%#=1[A-Za-z]//g
+p:s/\%#=2[A-Za-z]//g
+p:s/\%#=0\A//g
+p:s/\%#=1\A//g
+p:s/\%#=2\A//g
+p:s/\%#=0[^A-Za-z]//g
+p:s/\%#=1[^A-Za-z]//g
+p:s/\%#=2[^A-Za-z]//g
+p:s/\%#=0\l//g
+p:s/\%#=1\l//g
+p:s/\%#=2\l//g
+p:s/\%#=0[a-z]//g
+p:s/\%#=1[a-z]//g
+p:s/\%#=2[a-z]//g
+p:s/\%#=0\L//g
+p:s/\%#=1\L//g
+p:s/\%#=2\L//g
+p:s/\%#=0[^a-z]//g
+p:s/\%#=1[^a-z]//g
+p:s/\%#=2[^a-z]//g
+p:s/\%#=0\u//g
+p:s/\%#=1\u//g
+p:s/\%#=2\u//g
+p:s/\%#=0[A-Z]//g
+p:s/\%#=1[A-Z]//g
+p:s/\%#=2[A-Z]//g
+p:s/\%#=0\U//g
+p:s/\%#=1\U//g
+p:s/\%#=2\U//g
+p:s/\%#=0[^A-Z]//g
+p:s/\%#=1[^A-Z]//g
+p:s/\%#=2[^A-Z]//g
+p:s/\%#=0\%204l^\t...//g
+p:s/\%#=1\%205l^\t...//g
+p:s/\%#=2\%206l^\t...//g
+:/^start-here/+1,$wq! test.out
+ENDTEST
+
+start-here
+ !"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
diff --git a/src/testdir/test25.ok b/src/testdir/test25.ok
new file mode 100644
index 0000000..df21ed1
--- /dev/null
+++ b/src/testdir/test25.ok
@@ -0,0 +1,99 @@
+ !"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+0123456789
+0123456789
+0123456789
+0123456789
+0123456789
+0123456789
+ !"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+01234567
+01234567
+01234567
+01234567
+01234567
+01234567
+ !"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~€‚›¦±¼ÇÓé
+0123456789ABCDEFabcdef
+0123456789ABCDEFabcdef
+0123456789ABCDEFabcdef
+0123456789ABCDEFabcdef
+0123456789ABCDEFabcdef
+0123456789ABCDEFabcdef
+ !"#$%&'()#+'-./:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+0123456789ABCDEFGHIXYZ_abcdefghiwxyz
+0123456789ABCDEFGHIXYZ_abcdefghiwxyz
+0123456789ABCDEFGHIXYZ_abcdefghiwxyz
+0123456789ABCDEFGHIXYZ_abcdefghiwxyz
+0123456789ABCDEFGHIXYZ_abcdefghiwxyz
+0123456789ABCDEFGHIXYZ_abcdefghiwxyz
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~€‚›¦±¼ÇÓé
+ABCDEFGHIXYZ_abcdefghiwxyz
+ABCDEFGHIXYZ_abcdefghiwxyz
+ABCDEFGHIXYZ_abcdefghiwxyz
+ABCDEFGHIXYZ_abcdefghiwxyz
+ABCDEFGHIXYZ_abcdefghiwxyz
+ABCDEFGHIXYZ_abcdefghiwxyz
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~€‚›¦±¼ÇÓé
+ABCDEFGHIXYZabcdefghiwxyz
+ABCDEFGHIXYZabcdefghiwxyz
+ABCDEFGHIXYZabcdefghiwxyz
+ABCDEFGHIXYZabcdefghiwxyz
+ABCDEFGHIXYZabcdefghiwxyz
+ABCDEFGHIXYZabcdefghiwxyz
+ !"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~€‚›¦±¼ÇÓé
+abcdefghiwxyz
+abcdefghiwxyz
+abcdefghiwxyz
+abcdefghiwxyz
+abcdefghiwxyz
+abcdefghiwxyz
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ !"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+ABCDEFGHIXYZ
+ABCDEFGHIXYZ
+ABCDEFGHIXYZ
+ABCDEFGHIXYZ
+ABCDEFGHIXYZ
+ABCDEFGHIXYZ
+!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
+!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~€‚›¦±¼ÇÓé
diff --git a/src/testdir/test26.in b/src/testdir/test26.in
new file mode 100644
index 0000000..2d294ca
--- /dev/null
+++ b/src/testdir/test26.in
@@ -0,0 +1,24 @@
+Test for writing and reading a file of over 100 Kbyte
+
+1 line: "This is the start"
+3001 lines: "This is the leader"
+1 line: "This is the middle"
+3001 lines: "This is the trailer"
+1 line: "This is the end"
+
+STARTTEST
+:%d
+aThis is the start
+This is the leader
+This is the middle
+This is the trailer
+This is the endkY3000p2GY3000p
+:w! Xtest
+:%d
+:e! Xtest
+:.w! test.out
+3003G:.w >>test.out
+6005G:.w >>test.out
+:qa!
+ENDTEST
+
diff --git a/src/testdir/test26.ok b/src/testdir/test26.ok
new file mode 100644
index 0000000..988e5f2
--- /dev/null
+++ b/src/testdir/test26.ok
@@ -0,0 +1,3 @@
+This is the start
+This is the middle
+This is the end
diff --git a/src/testdir/test27.in b/src/testdir/test27.in
new file mode 100644
index 0000000..c35569a
--- /dev/null
+++ b/src/testdir/test27.in
Binary files differ
diff --git a/src/testdir/test27.ok b/src/testdir/test27.ok
new file mode 100644
index 0000000..183430d
--- /dev/null
+++ b/src/testdir/test27.ok
Binary files differ
diff --git a/src/testdir/test77a.com b/src/testdir/test77a.com
new file mode 100644
index 0000000..ef74a4c
--- /dev/null
+++ b/src/testdir/test77a.com
@@ -0,0 +1,8 @@
+$! test77a - help file creating checksum on VMS
+$! Created by Zoltan Arpadffy
+$
+$ IF P1 .NES. ""
+$ THEN
+$ checksum 'P1'
+$ show symb CHECKSUM$CHECKSUM
+$ ENDIF
diff --git a/src/testdir/test77a.in b/src/testdir/test77a.in
new file mode 100644
index 0000000..b4f38b5
--- /dev/null
+++ b/src/testdir/test77a.in
@@ -0,0 +1,35 @@
+Inserts 2 million lines with consecutive integers starting from 1
+(essentially, the output of GNU's seq 1 2000000), writes them to Xtest
+and writes its cksum to test.out.
+
+We need 2 million lines to trigger a call to mf_hash_grow(). If it would mess
+up the lines the checksum would differ.
+
+cksum is part of POSIX and so should be available on most Unixes.
+If it isn't available then the test will be skipped.
+
+VMS does not have CKSUM but has a built in CHECKSUM - it should be used
+STARTTEST
+:silent! while 0
+: e! test.ok
+: w! test.out
+: qa!
+:silent! endwhile
+:if !has("vms")
+: e! test.ok
+: w! test.out
+: qa!
+:endif
+:set fileformat=unix undolevels=-1
+ggdG
+:let i = 1
+:while i <= 2000000 | call append(i, range(i, i + 99)) | let i += 100 | endwhile
+ggdd
+:w! Xtest.
+:r !@test77a.com Xtest.
+:s/\s/ /g
+:set fileformat&
+:.w! test.out
+:qa!
+ENDTEST
+
diff --git a/src/testdir/test77a.ok b/src/testdir/test77a.ok
new file mode 100644
index 0000000..41ec157
--- /dev/null
+++ b/src/testdir/test77a.ok
@@ -0,0 +1 @@
+ CHECKSUM$CHECKSUM = "844110470"
diff --git a/src/testdir/test_alot.vim b/src/testdir/test_alot.vim
new file mode 100644
index 0000000..592ca65
--- /dev/null
+++ b/src/testdir/test_alot.vim
@@ -0,0 +1,31 @@
+" A series of tests that can run in one Vim invocation.
+" This makes testing go faster, since Vim doesn't need to restart.
+
+source test_backup.vim
+source test_behave.vim
+source test_compiler.vim
+source test_ex_equal.vim
+source test_ex_undo.vim
+source test_ex_z.vim
+source test_ex_mode.vim
+source test_expand.vim
+source test_expand_dllpath.vim
+source test_expand_func.vim
+source test_file_perm.vim
+source test_fnamemodify.vim
+source test_ga.vim
+source test_glob2regpat.vim
+source test_global.vim
+source test_move.vim
+source test_put.vim
+source test_reltime.vim
+source test_searchpos.vim
+source test_set.vim
+source test_shift.vim
+source test_sha256.vim
+source test_tabline.vim
+source test_tagcase.vim
+source test_tagfunc.vim
+source test_unlet.vim
+source test_version.vim
+source test_wnext.vim
diff --git a/src/testdir/test_alot_latin.vim b/src/testdir/test_alot_latin.vim
new file mode 100644
index 0000000..23a404c
--- /dev/null
+++ b/src/testdir/test_alot_latin.vim
@@ -0,0 +1,7 @@
+" A series of tests that can run in one Vim invocation.
+" This makes testing go faster, since Vim doesn't need to restart.
+
+" These tests use latin1 'encoding'. Setting 'encoding' is in the individual
+" files, so that they can be run by themselves.
+
+source test_regexp_latin.vim
diff --git a/src/testdir/test_alot_utf8.vim b/src/testdir/test_alot_utf8.vim
new file mode 100644
index 0000000..77f5ede
--- /dev/null
+++ b/src/testdir/test_alot_utf8.vim
@@ -0,0 +1,14 @@
+" A series of tests that can run in one Vim invocation.
+" This makes testing go faster, since Vim doesn't need to restart.
+
+" These tests use utf8 'encoding'. Setting 'encoding' is already done in
+" runtest.vim.
+
+source test_charsearch_utf8.vim
+source test_expr_utf8.vim
+source test_mksession_utf8.vim
+source test_regexp_utf8.vim
+source test_source_utf8.vim
+source test_startup_utf8.vim
+source test_utf8.vim
+source test_utf8_comparisons.vim
diff --git a/src/testdir/test_arabic.vim b/src/testdir/test_arabic.vim
new file mode 100644
index 0000000..73b85c4
--- /dev/null
+++ b/src/testdir/test_arabic.vim
@@ -0,0 +1,595 @@
+" Simplistic testing of Arabic mode.
+" NOTE: This just checks if the code works. If you know Arabic please add
+" functional tests that check the shaping works with real text.
+
+source check.vim
+CheckFeature arabic
+
+source view_util.vim
+
+" Return list of Unicode characters at line lnum.
+" Combining characters are treated as a single item.
+func s:get_chars(lnum)
+ call cursor(a:lnum, 1)
+ let chars = []
+ let numchars = strchars(getline('.'), 1)
+ for i in range(1, numchars)
+ exe 'norm ' i . '|'
+ let c = execute('ascii')
+ let c = substitute(c, '\n\?<.\{-}Hex\s*', 'U+', 'g')
+ let c = substitute(c, ',\s*Oct\(al\)\=\s\d*\(, Digr ..\)\=', '', 'g')
+ call add(chars, c)
+ endfor
+ return chars
+endfunc
+
+func Test_arabic_toggle()
+ set arabic
+ call assert_equal(1, &rightleft)
+ call assert_equal(1, &arabicshape)
+ call assert_equal('arabic', &keymap)
+ call assert_equal(1, &delcombine)
+
+ set iminsert=1 imsearch=1
+ set arabic&
+ call assert_equal(0, &rightleft)
+ call assert_equal(1, &arabicshape)
+ call assert_equal('arabic', &keymap)
+ call assert_equal(1, &delcombine)
+ call assert_equal(0, &iminsert)
+ call assert_equal(-1, &imsearch)
+
+ set arabicshape& keymap= delcombine&
+endfunc
+
+func Test_arabic_input()
+ new
+ set arabic
+ " Typing sghl in Arabic insert mode should show the
+ " Arabic word 'Salaam' i.e. 'peace', spelled:
+ " SEEN, LAM, ALEF, MEEM.
+ " See: https://www.mediawiki.org/wiki/VisualEditor/Typing/Right-to-left
+ call feedkeys('isghl!', 'tx')
+ call assert_match("^ *!\uFEE1\uFEFC\uFEB3$", ScreenLines(1, &columns)[0])
+ call assert_equal([
+ \ 'U+0633',
+ \ 'U+0644 U+0627',
+ \ 'U+0645',
+ \ 'U+21'], s:get_chars(1))
+
+ " Without shaping, it should give individual Arabic letters.
+ set noarabicshape
+ call assert_match("^ *!\u0645\u0627\u0644\u0633$", ScreenLines(1, &columns)[0])
+ call assert_equal([
+ \ 'U+0633',
+ \ 'U+0644',
+ \ 'U+0627',
+ \ 'U+0645',
+ \ 'U+21'], s:get_chars(1))
+
+ set arabic& arabicshape&
+ bwipe!
+endfunc
+
+func Test_arabic_toggle_keymap()
+ new
+ set arabic
+ call feedkeys("i12\<C-^>12\<C-^>12", 'tx')
+ call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0])
+ call assert_equal('١٢12١٢', getline('.'))
+ set arabic&
+ bwipe!
+endfunc
+
+func Test_delcombine()
+ new
+ set arabic
+ call feedkeys("isghl\<BS>\<BS>", 'tx')
+ call assert_match("^ *\uFEDE\uFEB3$", ScreenLines(1, &columns)[0])
+ call assert_equal(['U+0633', 'U+0644'], s:get_chars(1))
+
+ " Now the same with 'nodelcombine'
+ set nodelcombine
+ %d
+ call feedkeys("isghl\<BS>\<BS>", 'tx')
+ call assert_match("^ *\uFEB1$", ScreenLines(1, &columns)[0])
+ call assert_equal(['U+0633'], s:get_chars(1))
+ set arabic&
+ bwipe!
+endfunc
+
+" Values from src/arabic.h (not all used yet)
+let s:a_COMMA = "\u060C"
+let s:a_SEMICOLON = "\u061B"
+let s:a_QUESTION = "\u061F"
+let s:a_HAMZA = "\u0621"
+let s:a_ALEF_MADDA = "\u0622"
+let s:a_ALEF_HAMZA_ABOVE = "\u0623"
+let s:a_WAW_HAMZA = "\u0624"
+let s:a_ALEF_HAMZA_BELOW = "\u0625"
+let s:a_YEH_HAMZA = "\u0626"
+let s:a_ALEF = "\u0627"
+let s:a_BEH = "\u0628"
+let s:a_TEH_MARBUTA = "\u0629"
+let s:a_TEH = "\u062a"
+let s:a_THEH = "\u062b"
+let s:a_JEEM = "\u062c"
+let s:a_HAH = "\u062d"
+let s:a_KHAH = "\u062e"
+let s:a_DAL = "\u062f"
+let s:a_THAL = "\u0630"
+let s:a_REH = "\u0631"
+let s:a_ZAIN = "\u0632"
+let s:a_SEEN = "\u0633"
+let s:a_SHEEN = "\u0634"
+let s:a_SAD = "\u0635"
+let s:a_DAD = "\u0636"
+let s:a_TAH = "\u0637"
+let s:a_ZAH = "\u0638"
+let s:a_AIN = "\u0639"
+let s:a_GHAIN = "\u063a"
+let s:a_TATWEEL = "\u0640"
+let s:a_FEH = "\u0641"
+let s:a_QAF = "\u0642"
+let s:a_KAF = "\u0643"
+let s:a_LAM = "\u0644"
+let s:a_MEEM = "\u0645"
+let s:a_NOON = "\u0646"
+let s:a_HEH = "\u0647"
+let s:a_WAW = "\u0648"
+let s:a_ALEF_MAKSURA = "\u0649"
+let s:a_YEH = "\u064a"
+
+let s:a_FATHATAN = "\u064b"
+let s:a_DAMMATAN = "\u064c"
+let s:a_KASRATAN = "\u064d"
+let s:a_FATHA = "\u064e"
+let s:a_DAMMA = "\u064f"
+let s:a_KASRA = "\u0650"
+let s:a_SHADDA = "\u0651"
+let s:a_SUKUN = "\u0652"
+
+let s:a_MADDA_ABOVE = "\u0653"
+let s:a_HAMZA_ABOVE = "\u0654"
+let s:a_HAMZA_BELOW = "\u0655"
+
+let s:a_ZERO = "\u0660"
+let s:a_ONE = "\u0661"
+let s:a_TWO = "\u0662"
+let s:a_THREE = "\u0663"
+let s:a_FOUR = "\u0664"
+let s:a_FIVE = "\u0665"
+let s:a_SIX = "\u0666"
+let s:a_SEVEN = "\u0667"
+let s:a_EIGHT = "\u0668"
+let s:a_NINE = "\u0669"
+let s:a_PERCENT = "\u066a"
+let s:a_DECIMAL = "\u066b"
+let s:a_THOUSANDS = "\u066c"
+let s:a_STAR = "\u066d"
+let s:a_MINI_ALEF = "\u0670"
+
+let s:a_s_FATHATAN = "\ufe70"
+let s:a_m_TATWEEL_FATHATAN = "\ufe71"
+let s:a_s_DAMMATAN = "\ufe72"
+
+let s:a_s_KASRATAN = "\ufe74"
+
+let s:a_s_FATHA = "\ufe76"
+let s:a_m_FATHA = "\ufe77"
+let s:a_s_DAMMA = "\ufe78"
+let s:a_m_DAMMA = "\ufe79"
+let s:a_s_KASRA = "\ufe7a"
+let s:a_m_KASRA = "\ufe7b"
+let s:a_s_SHADDA = "\ufe7c"
+let s:a_m_SHADDA = "\ufe7d"
+let s:a_s_SUKUN = "\ufe7e"
+let s:a_m_SUKUN = "\ufe7f"
+
+let s:a_s_HAMZA = "\ufe80"
+let s:a_s_ALEF_MADDA = "\ufe81"
+let s:a_f_ALEF_MADDA = "\ufe82"
+let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83"
+let s:a_f_ALEF_HAMZA_ABOVE = "\ufe84"
+let s:a_s_WAW_HAMZA = "\ufe85"
+let s:a_f_WAW_HAMZA = "\ufe86"
+let s:a_s_ALEF_HAMZA_BELOW = "\ufe87"
+let s:a_f_ALEF_HAMZA_BELOW = "\ufe88"
+let s:a_s_YEH_HAMZA = "\ufe89"
+let s:a_f_YEH_HAMZA = "\ufe8a"
+let s:a_i_YEH_HAMZA = "\ufe8b"
+let s:a_m_YEH_HAMZA = "\ufe8c"
+let s:a_s_ALEF = "\ufe8d"
+let s:a_f_ALEF = "\ufe8e"
+let s:a_s_BEH = "\ufe8f"
+let s:a_f_BEH = "\ufe90"
+let s:a_i_BEH = "\ufe91"
+let s:a_m_BEH = "\ufe92"
+let s:a_s_TEH_MARBUTA = "\ufe93"
+let s:a_f_TEH_MARBUTA = "\ufe94"
+let s:a_s_TEH = "\ufe95"
+let s:a_f_TEH = "\ufe96"
+let s:a_i_TEH = "\ufe97"
+let s:a_m_TEH = "\ufe98"
+let s:a_s_THEH = "\ufe99"
+let s:a_f_THEH = "\ufe9a"
+let s:a_i_THEH = "\ufe9b"
+let s:a_m_THEH = "\ufe9c"
+let s:a_s_JEEM = "\ufe9d"
+let s:a_f_JEEM = "\ufe9e"
+let s:a_i_JEEM = "\ufe9f"
+let s:a_m_JEEM = "\ufea0"
+let s:a_s_HAH = "\ufea1"
+let s:a_f_HAH = "\ufea2"
+let s:a_i_HAH = "\ufea3"
+let s:a_m_HAH = "\ufea4"
+let s:a_s_KHAH = "\ufea5"
+let s:a_f_KHAH = "\ufea6"
+let s:a_i_KHAH = "\ufea7"
+let s:a_m_KHAH = "\ufea8"
+let s:a_s_DAL = "\ufea9"
+let s:a_f_DAL = "\ufeaa"
+let s:a_s_THAL = "\ufeab"
+let s:a_f_THAL = "\ufeac"
+let s:a_s_REH = "\ufead"
+let s:a_f_REH = "\ufeae"
+let s:a_s_ZAIN = "\ufeaf"
+let s:a_f_ZAIN = "\ufeb0"
+let s:a_s_SEEN = "\ufeb1"
+let s:a_f_SEEN = "\ufeb2"
+let s:a_i_SEEN = "\ufeb3"
+let s:a_m_SEEN = "\ufeb4"
+let s:a_s_SHEEN = "\ufeb5"
+let s:a_f_SHEEN = "\ufeb6"
+let s:a_i_SHEEN = "\ufeb7"
+let s:a_m_SHEEN = "\ufeb8"
+let s:a_s_SAD = "\ufeb9"
+let s:a_f_SAD = "\ufeba"
+let s:a_i_SAD = "\ufebb"
+let s:a_m_SAD = "\ufebc"
+let s:a_s_DAD = "\ufebd"
+let s:a_f_DAD = "\ufebe"
+let s:a_i_DAD = "\ufebf"
+let s:a_m_DAD = "\ufec0"
+let s:a_s_TAH = "\ufec1"
+let s:a_f_TAH = "\ufec2"
+let s:a_i_TAH = "\ufec3"
+let s:a_m_TAH = "\ufec4"
+let s:a_s_ZAH = "\ufec5"
+let s:a_f_ZAH = "\ufec6"
+let s:a_i_ZAH = "\ufec7"
+let s:a_m_ZAH = "\ufec8"
+let s:a_s_AIN = "\ufec9"
+let s:a_f_AIN = "\ufeca"
+let s:a_i_AIN = "\ufecb"
+let s:a_m_AIN = "\ufecc"
+let s:a_s_GHAIN = "\ufecd"
+let s:a_f_GHAIN = "\ufece"
+let s:a_i_GHAIN = "\ufecf"
+let s:a_m_GHAIN = "\ufed0"
+let s:a_s_FEH = "\ufed1"
+let s:a_f_FEH = "\ufed2"
+let s:a_i_FEH = "\ufed3"
+let s:a_m_FEH = "\ufed4"
+let s:a_s_QAF = "\ufed5"
+let s:a_f_QAF = "\ufed6"
+let s:a_i_QAF = "\ufed7"
+let s:a_m_QAF = "\ufed8"
+let s:a_s_KAF = "\ufed9"
+let s:a_f_KAF = "\ufeda"
+let s:a_i_KAF = "\ufedb"
+let s:a_m_KAF = "\ufedc"
+let s:a_s_LAM = "\ufedd"
+let s:a_f_LAM = "\ufede"
+let s:a_i_LAM = "\ufedf"
+let s:a_m_LAM = "\ufee0"
+let s:a_s_MEEM = "\ufee1"
+let s:a_f_MEEM = "\ufee2"
+let s:a_i_MEEM = "\ufee3"
+let s:a_m_MEEM = "\ufee4"
+let s:a_s_NOON = "\ufee5"
+let s:a_f_NOON = "\ufee6"
+let s:a_i_NOON = "\ufee7"
+let s:a_m_NOON = "\ufee8"
+let s:a_s_HEH = "\ufee9"
+let s:a_f_HEH = "\ufeea"
+let s:a_i_HEH = "\ufeeb"
+let s:a_m_HEH = "\ufeec"
+let s:a_s_WAW = "\ufeed"
+let s:a_f_WAW = "\ufeee"
+let s:a_s_ALEF_MAKSURA = "\ufeef"
+let s:a_f_ALEF_MAKSURA = "\ufef0"
+let s:a_s_YEH = "\ufef1"
+let s:a_f_YEH = "\ufef2"
+let s:a_i_YEH = "\ufef3"
+let s:a_m_YEH = "\ufef4"
+let s:a_s_LAM_ALEF_MADDA_ABOVE = "\ufef5"
+let s:a_f_LAM_ALEF_MADDA_ABOVE = "\ufef6"
+let s:a_s_LAM_ALEF_HAMZA_ABOVE = "\ufef7"
+let s:a_f_LAM_ALEF_HAMZA_ABOVE = "\ufef8"
+let s:a_s_LAM_ALEF_HAMZA_BELOW = "\ufef9"
+let s:a_f_LAM_ALEF_HAMZA_BELOW = "\ufefa"
+let s:a_s_LAM_ALEF = "\ufefb"
+let s:a_f_LAM_ALEF = "\ufefc"
+
+let s:a_BYTE_ORDER_MARK = "\ufeff"
+
+func Test_shape_initial()
+ new
+ set arabicshape
+
+ " Shaping arabic {testchar} non-arabic Tests chg_c_a2i().
+ " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result
+ for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA],
+ \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA],
+ \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA],
+ \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE],
+ \ [s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_s_WAW_HAMZA],
+ \ [s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_BELOW],
+ \ [s:a_ALEF, s:a_s_GHAIN, s:a_s_ALEF],
+ \ [s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_s_TEH_MARBUTA],
+ \ [s:a_DAL, s:a_s_GHAIN, s:a_s_DAL],
+ \ [s:a_THAL, s:a_s_GHAIN, s:a_s_THAL],
+ \ [s:a_REH, s:a_s_GHAIN, s:a_s_REH],
+ \ [s:a_ZAIN, s:a_s_GHAIN, s:a_s_ZAIN],
+ \ [s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL],
+ \ [s:a_WAW, s:a_s_GHAIN, s:a_s_WAW],
+ \ [s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_s_ALEF_MAKSURA],
+ \ [s:a_BEH, s:a_f_GHAIN, s:a_i_BEH],
+ \ [s:a_TEH, s:a_f_GHAIN, s:a_i_TEH],
+ \ [s:a_THEH, s:a_f_GHAIN, s:a_i_THEH],
+ \ [s:a_JEEM, s:a_f_GHAIN, s:a_i_JEEM],
+ \ [s:a_HAH, s:a_f_GHAIN, s:a_i_HAH],
+ \ [s:a_KHAH, s:a_f_GHAIN, s:a_i_KHAH],
+ \ [s:a_SEEN, s:a_f_GHAIN, s:a_i_SEEN],
+ \ [s:a_SHEEN, s:a_f_GHAIN, s:a_i_SHEEN],
+ \ [s:a_SAD, s:a_f_GHAIN, s:a_i_SAD],
+ \ [s:a_DAD, s:a_f_GHAIN, s:a_i_DAD],
+ \ [s:a_TAH, s:a_f_GHAIN, s:a_i_TAH],
+ \ [s:a_ZAH, s:a_f_GHAIN, s:a_i_ZAH],
+ \ [s:a_AIN, s:a_f_GHAIN, s:a_i_AIN],
+ \ [s:a_GHAIN, s:a_f_GHAIN, s:a_i_GHAIN],
+ \ [s:a_FEH, s:a_f_GHAIN, s:a_i_FEH],
+ \ [s:a_QAF, s:a_f_GHAIN, s:a_i_QAF],
+ \ [s:a_KAF, s:a_f_GHAIN, s:a_i_KAF],
+ \ [s:a_LAM, s:a_f_GHAIN, s:a_i_LAM],
+ \ [s:a_MEEM, s:a_f_GHAIN, s:a_i_MEEM],
+ \ [s:a_NOON, s:a_f_GHAIN, s:a_i_NOON],
+ \ [s:a_HEH, s:a_f_GHAIN, s:a_i_HEH],
+ \ [s:a_YEH, s:a_f_GHAIN, s:a_i_YEH],
+ \ ]
+ call setline(1, s:a_GHAIN . pair[0] . ' ')
+ call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3))
+ endfor
+
+ set arabicshape&
+ bwipe!
+endfunc
+
+func Test_shape_isolated()
+ new
+ set arabicshape
+
+ " Shaping non-arabic {testchar} non-arabic Tests chg_c_a2s().
+ " pair[0] = testchar, pair[1] = current-result
+ for pair in [[s:a_HAMZA, s:a_s_HAMZA],
+ \ [s:a_ALEF_MADDA, s:a_s_ALEF_MADDA],
+ \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_ALEF_HAMZA_ABOVE],
+ \ [s:a_WAW_HAMZA, s:a_s_WAW_HAMZA],
+ \ [s:a_ALEF_HAMZA_BELOW, s:a_s_ALEF_HAMZA_BELOW],
+ \ [s:a_YEH_HAMZA, s:a_s_YEH_HAMZA],
+ \ [s:a_ALEF, s:a_s_ALEF],
+ \ [s:a_TEH_MARBUTA, s:a_s_TEH_MARBUTA],
+ \ [s:a_DAL, s:a_s_DAL],
+ \ [s:a_THAL, s:a_s_THAL],
+ \ [s:a_REH, s:a_s_REH],
+ \ [s:a_ZAIN, s:a_s_ZAIN],
+ \ [s:a_TATWEEL, s:a_TATWEEL],
+ \ [s:a_WAW, s:a_s_WAW],
+ \ [s:a_ALEF_MAKSURA, s:a_s_ALEF_MAKSURA],
+ \ [s:a_BEH, s:a_s_BEH],
+ \ [s:a_TEH, s:a_s_TEH],
+ \ [s:a_THEH, s:a_s_THEH],
+ \ [s:a_JEEM, s:a_s_JEEM],
+ \ [s:a_HAH, s:a_s_HAH],
+ \ [s:a_KHAH, s:a_s_KHAH],
+ \ [s:a_SEEN, s:a_s_SEEN],
+ \ [s:a_SHEEN, s:a_s_SHEEN],
+ \ [s:a_SAD, s:a_s_SAD],
+ \ [s:a_DAD, s:a_s_DAD],
+ \ [s:a_TAH, s:a_s_TAH],
+ \ [s:a_ZAH, s:a_s_ZAH],
+ \ [s:a_AIN, s:a_s_AIN],
+ \ [s:a_GHAIN, s:a_s_GHAIN],
+ \ [s:a_FEH, s:a_s_FEH],
+ \ [s:a_QAF, s:a_s_QAF],
+ \ [s:a_KAF, s:a_s_KAF],
+ \ [s:a_LAM, s:a_s_LAM],
+ \ [s:a_MEEM, s:a_s_MEEM],
+ \ [s:a_NOON, s:a_s_NOON],
+ \ [s:a_HEH, s:a_s_HEH],
+ \ [s:a_YEH, s:a_s_YEH],
+ \ ]
+ call setline(1, ' ' . pair[0] . ' ')
+ call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3))
+ endfor
+
+ set arabicshape&
+ bwipe!
+endfunc
+
+func Test_shape_iso_to_medial()
+ new
+ set arabicshape
+
+ " Shaping arabic {testchar} arabic Tests chg_c_a2m().
+ " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result,
+ " pair[3] = previous-result
+ for pair in [[s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA, s:a_s_BEH],
+ \[s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_f_ALEF_MADDA, s:a_i_BEH],
+ \[s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH],
+ \[s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_f_WAW_HAMZA, s:a_i_BEH],
+ \[s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH],
+ \[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_m_YEH_HAMZA, s:a_i_BEH],
+ \[s:a_ALEF, s:a_s_GHAIN, s:a_f_ALEF, s:a_i_BEH],
+ \[s:a_BEH, s:a_f_GHAIN, s:a_m_BEH, s:a_i_BEH],
+ \[s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_f_TEH_MARBUTA, s:a_i_BEH],
+ \[s:a_TEH, s:a_f_GHAIN, s:a_m_TEH, s:a_i_BEH],
+ \[s:a_THEH, s:a_f_GHAIN, s:a_m_THEH, s:a_i_BEH],
+ \[s:a_JEEM, s:a_f_GHAIN, s:a_m_JEEM, s:a_i_BEH],
+ \[s:a_HAH, s:a_f_GHAIN, s:a_m_HAH, s:a_i_BEH],
+ \[s:a_KHAH, s:a_f_GHAIN, s:a_m_KHAH, s:a_i_BEH],
+ \[s:a_DAL, s:a_s_GHAIN, s:a_f_DAL, s:a_i_BEH],
+ \[s:a_THAL, s:a_s_GHAIN, s:a_f_THAL, s:a_i_BEH],
+ \[s:a_REH, s:a_s_GHAIN, s:a_f_REH, s:a_i_BEH],
+ \[s:a_ZAIN, s:a_s_GHAIN, s:a_f_ZAIN, s:a_i_BEH],
+ \[s:a_SEEN, s:a_f_GHAIN, s:a_m_SEEN, s:a_i_BEH],
+ \[s:a_SHEEN, s:a_f_GHAIN, s:a_m_SHEEN, s:a_i_BEH],
+ \[s:a_SAD, s:a_f_GHAIN, s:a_m_SAD, s:a_i_BEH],
+ \[s:a_DAD, s:a_f_GHAIN, s:a_m_DAD, s:a_i_BEH],
+ \[s:a_TAH, s:a_f_GHAIN, s:a_m_TAH, s:a_i_BEH],
+ \[s:a_ZAH, s:a_f_GHAIN, s:a_m_ZAH, s:a_i_BEH],
+ \[s:a_AIN, s:a_f_GHAIN, s:a_m_AIN, s:a_i_BEH],
+ \[s:a_GHAIN, s:a_f_GHAIN, s:a_m_GHAIN, s:a_i_BEH],
+ \[s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL, s:a_i_BEH],
+ \[s:a_FEH, s:a_f_GHAIN, s:a_m_FEH, s:a_i_BEH],
+ \[s:a_QAF, s:a_f_GHAIN, s:a_m_QAF, s:a_i_BEH],
+ \[s:a_KAF, s:a_f_GHAIN, s:a_m_KAF, s:a_i_BEH],
+ \[s:a_LAM, s:a_f_GHAIN, s:a_m_LAM, s:a_i_BEH],
+ \[s:a_MEEM, s:a_f_GHAIN, s:a_m_MEEM, s:a_i_BEH],
+ \[s:a_NOON, s:a_f_GHAIN, s:a_m_NOON, s:a_i_BEH],
+ \[s:a_HEH, s:a_f_GHAIN, s:a_m_HEH, s:a_i_BEH],
+ \[s:a_WAW, s:a_s_GHAIN, s:a_f_WAW, s:a_i_BEH],
+ \[s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_f_ALEF_MAKSURA, s:a_i_BEH],
+ \[s:a_YEH, s:a_f_GHAIN, s:a_m_YEH, s:a_i_BEH],
+ \ ]
+ call setline(1, s:a_GHAIN . pair[0] . s:a_BEH)
+ call assert_equal([pair[1] . pair[2] . pair[3]], ScreenLines(1, 3))
+ endfor
+
+ set arabicshape&
+ bwipe!
+endfunc
+
+func Test_shape_final()
+ new
+ set arabicshape
+
+ " Shaping arabic {testchar} arabic Tests chg_c_a2f().
+ " pair[0] = testchar, pair[1] = current-result, pair[2] = previous-result
+ for pair in [[s:a_HAMZA, s:a_s_HAMZA, s:a_s_BEH],
+ \[s:a_ALEF_MADDA, s:a_f_ALEF_MADDA, s:a_i_BEH],
+ \[s:a_ALEF_HAMZA_ABOVE, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH],
+ \[s:a_WAW_HAMZA, s:a_f_WAW_HAMZA, s:a_i_BEH],
+ \[s:a_ALEF_HAMZA_BELOW, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH],
+ \[s:a_YEH_HAMZA, s:a_f_YEH_HAMZA, s:a_i_BEH],
+ \[s:a_ALEF, s:a_f_ALEF, s:a_i_BEH],
+ \[s:a_BEH, s:a_f_BEH, s:a_i_BEH],
+ \[s:a_TEH_MARBUTA, s:a_f_TEH_MARBUTA, s:a_i_BEH],
+ \[s:a_TEH, s:a_f_TEH, s:a_i_BEH],
+ \[s:a_THEH, s:a_f_THEH, s:a_i_BEH],
+ \[s:a_JEEM, s:a_f_JEEM, s:a_i_BEH],
+ \[s:a_HAH, s:a_f_HAH, s:a_i_BEH],
+ \[s:a_KHAH, s:a_f_KHAH, s:a_i_BEH],
+ \[s:a_DAL, s:a_f_DAL, s:a_i_BEH],
+ \[s:a_THAL, s:a_f_THAL, s:a_i_BEH],
+ \[s:a_REH, s:a_f_REH, s:a_i_BEH],
+ \[s:a_ZAIN, s:a_f_ZAIN, s:a_i_BEH],
+ \[s:a_SEEN, s:a_f_SEEN, s:a_i_BEH],
+ \[s:a_SHEEN, s:a_f_SHEEN, s:a_i_BEH],
+ \[s:a_SAD, s:a_f_SAD, s:a_i_BEH],
+ \[s:a_DAD, s:a_f_DAD, s:a_i_BEH],
+ \[s:a_TAH, s:a_f_TAH, s:a_i_BEH],
+ \[s:a_ZAH, s:a_f_ZAH, s:a_i_BEH],
+ \[s:a_AIN, s:a_f_AIN, s:a_i_BEH],
+ \[s:a_GHAIN, s:a_f_GHAIN, s:a_i_BEH],
+ \[s:a_TATWEEL, s:a_TATWEEL, s:a_i_BEH],
+ \[s:a_FEH, s:a_f_FEH, s:a_i_BEH],
+ \[s:a_QAF, s:a_f_QAF, s:a_i_BEH],
+ \[s:a_KAF, s:a_f_KAF, s:a_i_BEH],
+ \[s:a_LAM, s:a_f_LAM, s:a_i_BEH],
+ \[s:a_MEEM, s:a_f_MEEM, s:a_i_BEH],
+ \[s:a_NOON, s:a_f_NOON, s:a_i_BEH],
+ \[s:a_HEH, s:a_f_HEH, s:a_i_BEH],
+ \[s:a_WAW, s:a_f_WAW, s:a_i_BEH],
+ \[s:a_ALEF_MAKSURA, s:a_f_ALEF_MAKSURA, s:a_i_BEH],
+ \[s:a_YEH, s:a_f_YEH, s:a_i_BEH],
+ \ ]
+ call setline(1, ' ' . pair[0] . s:a_BEH)
+ call assert_equal([' ' . pair[1] . pair[2]], ScreenLines(1, 3))
+ endfor
+
+ set arabicshape&
+ bwipe!
+endfunc
+
+func Test_shape_combination_final()
+ new
+ set arabicshape
+
+ " Shaping arabic {testchar} arabic Tests chg_c_laa2f().
+ " pair[0] = testchar, pair[1] = current-result
+ for pair in [[s:a_ALEF_MADDA, s:a_f_LAM_ALEF_MADDA_ABOVE],
+ \ [s:a_ALEF_HAMZA_ABOVE, s:a_f_LAM_ALEF_HAMZA_ABOVE],
+ \ [s:a_ALEF_HAMZA_BELOW, s:a_f_LAM_ALEF_HAMZA_BELOW],
+ \ [s:a_ALEF, s:a_f_LAM_ALEF],
+ \ ]
+ " The test char is a composing char, put on s:a_LAM.
+ call setline(1, ' ' . s:a_LAM . pair[0] . s:a_BEH)
+ call assert_equal([' ' . pair[1] . s:a_i_BEH], ScreenLines(1, 3))
+ endfor
+
+ set arabicshape&
+ bwipe!
+endfunc
+
+func Test_shape_combination_isolated()
+ new
+ set arabicshape
+
+ " Shaping arabic {testchar} arabic Tests chg_c_laa2i().
+ " pair[0] = testchar, pair[1] = current-result
+ for pair in [[s:a_ALEF_MADDA, s:a_s_LAM_ALEF_MADDA_ABOVE],
+ \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_LAM_ALEF_HAMZA_ABOVE],
+ \ [s:a_ALEF_HAMZA_BELOW, s:a_s_LAM_ALEF_HAMZA_BELOW],
+ \ [s:a_ALEF, s:a_s_LAM_ALEF],
+ \ ]
+ " The test char is a composing char, put on s:a_LAM.
+ call setline(1, ' ' . s:a_LAM . pair[0] . ' ')
+ call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3))
+ endfor
+
+ set arabicshape&
+ bwipe!
+endfunc
+
+" Test for entering arabic character in a search command
+func Test_arabic_chars_in_search_cmd()
+ new
+ set arabic
+ call feedkeys("i\nsghl!\<C-^>vim\<C-^>", 'tx')
+ call cursor(1, 1)
+ call feedkeys("/^sghl!\<C-^>vim$\<C-^>\<CR>", 'tx')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ " Try searching in left-to-right mode
+ set rightleftcmd=
+ call cursor(1, 1)
+ call feedkeys("/^sghl!\<C-^>vim$\<CR>", 'tx')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ set rightleftcmd&
+ set rightleft&
+ set arabic&
+ bwipe!
+endfunc
+
+func Test_W17_arabic_requires_utf8()
+ let save_enc = &encoding
+ set encoding=latin1 arabic
+ call assert_match('^W17:', GetMessages()[-1])
+ set arabic&
+ let &encoding = save_enc
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_arglist.vim b/src/testdir/test_arglist.vim
new file mode 100644
index 0000000..edc8b77
--- /dev/null
+++ b/src/testdir/test_arglist.vim
@@ -0,0 +1,747 @@
+" Test argument list commands
+
+source check.vim
+source shared.vim
+source term_util.vim
+
+func Reset_arglist()
+ args a | %argd
+endfunc
+
+func Test_argidx()
+ args a b c
+ last
+ call assert_equal(2, argidx())
+ %argdelete
+ call assert_equal(0, argidx())
+ " doing it again doesn't result in an error
+ %argdelete
+ call assert_equal(0, argidx())
+ call assert_fails('2argdelete', 'E16:')
+
+ args a b c
+ call assert_equal(0, argidx())
+ next
+ call assert_equal(1, argidx())
+ next
+ call assert_equal(2, argidx())
+ 1argdelete
+ call assert_equal(1, argidx())
+ 1argdelete
+ call assert_equal(0, argidx())
+ 1argdelete
+ call assert_equal(0, argidx())
+endfunc
+
+func Test_argadd()
+ call Reset_arglist()
+
+ %argdelete
+ argadd a b c
+ call assert_equal(0, argidx())
+
+ %argdelete
+ argadd a
+ call assert_equal(0, argidx())
+ argadd b c d
+ call assert_equal(0, argidx())
+
+ call Init_abc()
+ argadd x
+ call Assert_argc(['a', 'b', 'x', 'c'])
+ call assert_equal(1, argidx())
+
+ call Init_abc()
+ 0argadd x
+ call Assert_argc(['x', 'a', 'b', 'c'])
+ call assert_equal(2, argidx())
+
+ call Init_abc()
+ 1argadd x
+ call Assert_argc(['a', 'x', 'b', 'c'])
+ call assert_equal(2, argidx())
+
+ call Init_abc()
+ $argadd x
+ call Assert_argc(['a', 'b', 'c', 'x'])
+ call assert_equal(1, argidx())
+
+ call Init_abc()
+ $argadd x
+ +2argadd y
+ call Assert_argc(['a', 'b', 'c', 'x', 'y'])
+ call assert_equal(1, argidx())
+
+ %argd
+ edit d
+ arga
+ call assert_equal(1, len(argv()))
+ call assert_equal('d', get(argv(), 0, ''))
+
+ %argd
+ edit some\ file
+ arga
+ call assert_equal(1, len(argv()))
+ call assert_equal('some file', get(argv(), 0, ''))
+
+ %argd
+ new
+ arga
+ call assert_equal(0, len(argv()))
+
+ if has('unix')
+ call assert_fails('argadd `Xdoes_not_exist`', 'E479:')
+ endif
+endfunc
+
+func Test_argadd_empty_curbuf()
+ new
+ let curbuf = bufnr('%')
+ call writefile(['test', 'Xargadd'], 'Xargadd', 'D')
+ " must not re-use the current buffer.
+ argadd Xargadd
+ call assert_equal(curbuf, bufnr('%'))
+ call assert_equal('', bufname('%'))
+ call assert_equal(1, '$'->line())
+ rew
+ call assert_notequal(curbuf, '%'->bufnr())
+ call assert_equal('Xargadd', '%'->bufname())
+ call assert_equal(2, line('$'))
+
+ %argd
+ bwipe!
+endfunc
+
+func Init_abc()
+ args a b c
+ next
+endfunc
+
+func Assert_argc(l)
+ call assert_equal(len(a:l), argc())
+ let i = 0
+ while i < len(a:l) && i < argc()
+ call assert_equal(a:l[i], argv(i))
+ let i += 1
+ endwhile
+endfunc
+
+" Test for [count]argument and [count]argdelete commands
+" Ported from the test_argument_count.in test script
+func Test_argument()
+ call Reset_arglist()
+
+ let save_hidden = &hidden
+ set hidden
+
+ let g:buffers = []
+ augroup TEST
+ au BufEnter * call add(buffers, expand('%:t'))
+ augroup END
+
+ argadd a b c d
+ $argu
+ $-argu
+ -argu
+ 1argu
+ +2argu
+
+ augroup TEST
+ au!
+ augroup END
+
+ call assert_equal(['d', 'c', 'b', 'a', 'c'], g:buffers)
+
+ call assert_equal("\na b [c] d ", execute(':args'))
+
+ .argd
+ call assert_equal(['a', 'b', 'd'], argv())
+
+ -argd
+ call assert_equal(['a', 'd'], argv())
+
+ $argd
+ call assert_equal(['a'], argv())
+
+ 1arga c
+ 1arga b
+ $argu
+ $arga x
+ call assert_equal(['a', 'b', 'c', 'x'], argv())
+
+ 0arga y
+ call assert_equal(['y', 'a', 'b', 'c', 'x'], argv())
+
+ %argd
+ call assert_equal([], argv())
+
+ arga a b c d e f
+ 2,$-argd
+ call assert_equal(['a', 'f'], argv())
+
+ let &hidden = save_hidden
+
+ let save_columns = &columns
+ let &columns = 79
+ try
+ exe 'args ' .. join(range(1, 81))
+ call assert_equal(join([
+ \ '',
+ \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ',
+ \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ',
+ \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ',
+ \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ',
+ \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ',
+ \ ], "\n"),
+ \ execute('args'))
+
+ " No trailing newline with one item per row.
+ let long_arg = repeat('X', 81)
+ exe 'args ' .. long_arg
+ call assert_equal("\n[".long_arg.']', execute('args'))
+ finally
+ let &columns = save_columns
+ endtry
+
+ " Setting argument list should fail when the current buffer has unsaved
+ " changes
+ %argd
+ enew!
+ set modified
+ call assert_fails('args x y z', 'E37:')
+ args! x y z
+ call assert_equal(['x', 'y', 'z'], argv())
+ call assert_equal('x', expand('%:t'))
+
+ last | enew | argu
+ call assert_equal('z', expand('%:t'))
+
+ %argdelete
+ call assert_fails('argument', 'E163:')
+endfunc
+
+func Test_list_arguments()
+ call Reset_arglist()
+
+ " four args half the screen width makes two lines with two columns
+ let aarg = repeat('a', &columns / 2 - 4)
+ let barg = repeat('b', &columns / 2 - 4)
+ let carg = repeat('c', &columns / 2 - 4)
+ let darg = repeat('d', &columns / 2 - 4)
+ exe 'argadd ' aarg barg carg darg
+
+ redir => result
+ args
+ redir END
+ call assert_match('\[' . aarg . '] \+' . carg . '\n' . barg . ' \+' . darg, trim(result))
+
+ " if one arg is longer than half the screen make one column
+ exe 'argdel' aarg
+ let aarg = repeat('a', &columns / 2 + 2)
+ exe '0argadd' aarg
+ redir => result
+ args
+ redir END
+ call assert_match(aarg . '\n\[' . barg . ']\n' . carg . '\n' . darg, trim(result))
+
+ %argdelete
+endfunc
+
+func Test_args_with_quote()
+ " Only on Unix can a file name include a double quote.
+ CheckUnix
+
+ args \"foobar
+ call assert_equal('"foobar', argv(0))
+ %argdelete
+endfunc
+
+" Test for 0argadd and 0argedit
+" Ported from the test_argument_0count.in test script
+func Test_zero_argadd()
+ call Reset_arglist()
+
+ arga a b c d
+ 2argu
+ 0arga added
+ call assert_equal(['added', 'a', 'b', 'c', 'd'], argv())
+
+ 2argu
+ arga third
+ call assert_equal(['added', 'a', 'third', 'b', 'c', 'd'], argv())
+
+ %argd
+ arga a b c d
+ 2argu
+ 0arge edited
+ call assert_equal(['edited', 'a', 'b', 'c', 'd'], argv())
+
+ 2argu
+ arga third
+ call assert_equal(['edited', 'a', 'third', 'b', 'c', 'd'], argv())
+
+ 2argu
+ argedit file\ with\ spaces another file
+ call assert_equal(['edited', 'a', 'file with spaces', 'another', 'file', 'third', 'b', 'c', 'd'], argv())
+ call assert_equal('file with spaces', expand('%'))
+endfunc
+
+" Test for argc()
+func Test_argc()
+ call Reset_arglist()
+ call assert_equal(0, argc())
+ argadd a b
+ call assert_equal(2, argc())
+endfunc
+
+" Test for arglistid()
+func Test_arglistid()
+ call Reset_arglist()
+ arga a b
+ call assert_equal(0, arglistid())
+ split
+ arglocal
+ call assert_equal(1, arglistid())
+ tabnew | tabfirst
+ call assert_equal(0, arglistid(2))
+ call assert_equal(1, arglistid(1, 1))
+ call assert_equal(0, arglistid(2, 1))
+ call assert_equal(1, arglistid(1, 2))
+ tabonly | only | enew!
+ argglobal
+ call assert_equal(0, arglistid())
+endfunc
+
+" Tests for argv() and argc()
+func Test_argv()
+ call Reset_arglist()
+ call assert_equal([], argv())
+ call assert_equal("", argv(2))
+ call assert_equal(0, argc())
+ argadd a b c d
+ call assert_equal(4, argc())
+ call assert_equal('c', argv(2))
+
+ let w1_id = win_getid()
+ split
+ let w2_id = win_getid()
+ arglocal
+ args e f g
+ tabnew
+ let w3_id = win_getid()
+ split
+ let w4_id = win_getid()
+ argglobal
+ tabfirst
+ call assert_equal(4, argc(w1_id))
+ call assert_equal('b', argv(1, w1_id))
+ call assert_equal(['a', 'b', 'c', 'd'], argv(-1, w1_id))
+
+ call assert_equal(3, argc(w2_id))
+ call assert_equal('f', argv(1, w2_id))
+ call assert_equal(['e', 'f', 'g'], argv(-1, w2_id))
+
+ call assert_equal(3, argc(w3_id))
+ call assert_equal('e', argv(0, w3_id))
+ call assert_equal(['e', 'f', 'g'], argv(-1, w3_id))
+
+ call assert_equal(4, argc(w4_id))
+ call assert_equal('c', argv(2, w4_id))
+ call assert_equal(['a', 'b', 'c', 'd'], argv(-1, w4_id))
+
+ call assert_equal(4, argc(-1))
+ call assert_equal(3, argc())
+ call assert_equal('d', argv(3, -1))
+ call assert_equal(['a', 'b', 'c', 'd'], argv(-1, -1))
+ tabonly | only | enew!
+ " Negative test cases
+ call assert_equal(-1, argc(100))
+ call assert_equal('', argv(1, 100))
+ call assert_equal([], argv(-1, 100))
+ call assert_equal('', argv(10, -1))
+endfunc
+
+" Test for the :argedit command
+func Test_argedit()
+ call Reset_arglist()
+ argedit a
+ call assert_equal(['a'], argv())
+ call assert_equal('a', expand('%:t'))
+ argedit b
+ call assert_equal(['a', 'b'], argv())
+ call assert_equal('b', expand('%:t'))
+ argedit a
+ call assert_equal(['a', 'b', 'a'], argv())
+ call assert_equal('a', expand('%:t'))
+ " When file name case is ignored, an existing buffer with only case
+ " difference is re-used.
+ argedit C D
+ call assert_equal('C', expand('%:t'))
+ call assert_equal(['a', 'b', 'a', 'C', 'D'], argv())
+ argedit c
+ if has('fname_case')
+ call assert_equal(['a', 'b', 'a', 'C', 'c', 'D'], argv())
+ else
+ call assert_equal(['a', 'b', 'a', 'C', 'C', 'D'], argv())
+ endif
+ 0argedit x
+ if has('fname_case')
+ call assert_equal(['x', 'a', 'b', 'a', 'C', 'c', 'D'], argv())
+ else
+ call assert_equal(['x', 'a', 'b', 'a', 'C', 'C', 'D'], argv())
+ endif
+ enew! | set modified
+ call assert_fails('argedit y', 'E37:')
+ argedit! y
+ if has('fname_case')
+ call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'c', 'D'], argv())
+ else
+ call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'C', 'D'], argv())
+ endif
+ %argd
+ bwipe! C
+ bwipe! D
+
+ " :argedit reuses the current buffer if it is empty
+ %argd
+ " make sure to use a new buffer number for x when it is loaded
+ bw! x
+ new
+ let a = bufnr()
+ argedit x
+ call assert_equal(a, bufnr())
+ call assert_equal('x', bufname())
+ %argd
+ bw! x
+endfunc
+
+" Test for the :argdedupe command
+func Test_argdedupe()
+ call Reset_arglist()
+ argdedupe
+ call assert_equal([], argv())
+
+ args a a a aa b b a b aa
+ argdedupe
+ call assert_equal(['a', 'aa', 'b'], argv())
+
+ args a b c
+ argdedupe
+ call assert_equal(['a', 'b', 'c'], argv())
+
+ args a
+ argdedupe
+ call assert_equal(['a'], argv())
+
+ args a A b B
+ argdedupe
+ if has('fname_case')
+ call assert_equal(['a', 'A', 'b', 'B'], argv())
+ else
+ call assert_equal(['a', 'b'], argv())
+ endif
+
+ args a b a c a b
+ last
+ argdedupe
+ next
+ call assert_equal('c', expand('%:t'))
+
+ args a ./a
+ argdedupe
+ call assert_equal(['a'], argv())
+
+ %argd
+endfunc
+
+" Test for the :argdelete command
+func Test_argdelete()
+ call Reset_arglist()
+ args aa a aaa b bb
+ argdelete a*
+ call assert_equal(['b', 'bb'], argv())
+ call assert_equal('aa', expand('%:t'))
+ last
+ argdelete %
+ call assert_equal(['b'], argv())
+ call assert_fails('argdelete', 'E610:')
+ call assert_fails('1,100argdelete', 'E16:')
+ call assert_fails('argdel /\)/', 'E55:')
+ call assert_fails('1argdel 1', 'E474:')
+
+ call Reset_arglist()
+ args a b c d
+ next
+ argdel
+ call Assert_argc(['a', 'c', 'd'])
+ %argdel
+
+ call assert_fails('argdel does_not_exist', 'E480:')
+endfunc
+
+func Test_argdelete_completion()
+ args foo bar
+
+ call feedkeys(":argdelete \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"argdelete bar foo', @:)
+
+ call feedkeys(":argdelete x \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"argdelete x bar foo', @:)
+
+ %argd
+endfunc
+
+" Tests for the :next, :prev, :first, :last, :rewind commands
+func Test_argpos()
+ call Reset_arglist()
+ args a b c d
+ last
+ call assert_equal(3, argidx())
+ call assert_fails('next', 'E165:')
+ prev
+ call assert_equal(2, argidx())
+ Next
+ call assert_equal(1, argidx())
+ first
+ call assert_equal(0, argidx())
+ call assert_fails('prev', 'E164:')
+ 3next
+ call assert_equal(3, argidx())
+ rewind
+ call assert_equal(0, argidx())
+ %argd
+endfunc
+
+" Test for autocommand that redefines the argument list, when doing ":all".
+func Test_arglist_autocmd()
+ autocmd BufReadPost Xxx2 next Xxx2 Xxx1
+ call writefile(['test file Xxx1'], 'Xxx1', 'D')
+ call writefile(['test file Xxx2'], 'Xxx2', 'D')
+ call writefile(['test file Xxx3'], 'Xxx3', 'D')
+
+ new
+ " redefine arglist; go to Xxx1
+ next! Xxx1 Xxx2 Xxx3
+ " open window for all args; Reading Xxx2 will try to change the arglist and
+ " that will fail
+ call assert_fails("all", "E1156:")
+ call assert_equal('test file Xxx1', getline(1))
+ wincmd w
+ call assert_equal('test file Xxx2', getline(1))
+ wincmd w
+ call assert_equal('test file Xxx3', getline(1))
+
+ autocmd! BufReadPost Xxx2
+ enew! | only
+ argdelete Xxx*
+ bwipe! Xxx1 Xxx2 Xxx3
+endfunc
+
+func Test_arg_all_expand()
+ call writefile(['test file Xxx1'], 'Xx x', 'D')
+ next notexist Xx\ x runtest.vim
+ call assert_equal('notexist Xx\ x runtest.vim', expand('##'))
+endfunc
+
+func Test_large_arg()
+ " Argument longer or equal to the number of columns used to cause
+ " access to invalid memory.
+ exe 'argadd ' .repeat('x', &columns)
+ args
+endfunc
+
+func Test_argdo()
+ next! Xa.c Xb.c Xc.c
+ new
+ let l = []
+ argdo call add(l, expand('%'))
+ call assert_equal(['Xa.c', 'Xb.c', 'Xc.c'], l)
+ bwipe Xa.c Xb.c Xc.c
+endfunc
+
+" Test for quitting Vim with unedited files in the argument list
+func Test_quit_with_arglist()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', {'rows': 6})
+ call term_sendkeys(buf, ":set nomore\n")
+ call term_sendkeys(buf, ":args a b c\n")
+ call term_sendkeys(buf, ":quit\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^E173:', term_getline(buf, 6))})
+ call StopVimInTerminal(buf)
+
+ " Try :confirm quit with unedited files in arglist
+ let buf = RunVimInTerminal('', {'rows': 6})
+ call term_sendkeys(buf, ":set nomore\n")
+ call term_sendkeys(buf, ":args a b c\n")
+ call term_sendkeys(buf, ":confirm quit\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$',
+ \ term_getline(buf, 6))})
+ call term_sendkeys(buf, "N")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":confirm quit\n")
+ call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$',
+ \ term_getline(buf, 6))})
+ call term_sendkeys(buf, "Y")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))})
+ only!
+ " When this test fails, swap files are left behind which breaks subsequent
+ " tests
+ call delete('.a.swp')
+ call delete('.b.swp')
+ call delete('.c.swp')
+endfunc
+
+" Test for ":all" not working when in the cmdline window
+func Test_all_not_allowed_from_cmdwin()
+ au BufEnter * all
+ next x
+ " Use try/catch here, somehow assert_fails() doesn't work on MS-Windows
+ " console.
+ let caught = 'no'
+ try
+ exe ":norm! 7q?apat\<CR>"
+ catch /E11:/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+ au! BufEnter
+endfunc
+
+func Test_clear_arglist_in_all()
+ n 0 00 000 0000 00000 000000
+ au WinNew 0 n 0
+ call assert_fails("all", "E1156")
+ au! *
+endfunc
+
+" Test for the :all command
+func Test_all_command()
+ %argdelete
+
+ " :all command should not close windows with files in the argument list,
+ " but can rearrange the windows.
+ args Xargnew1 Xargnew2
+ %bw!
+ edit Xargold1
+ split Xargnew1
+ let Xargnew1_winid = win_getid()
+ split Xargold2
+ split Xargnew2
+ let Xargnew2_winid = win_getid()
+ split Xargold3
+ all
+ call assert_equal(2, winnr('$'))
+ call assert_equal([Xargnew1_winid, Xargnew2_winid],
+ \ [win_getid(1), win_getid(2)])
+ call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')],
+ \ [winbufnr(1), winbufnr(2)])
+
+ " :all command should close windows for files which are not in the
+ " argument list in the current tab page.
+ %bw!
+ edit Xargold1
+ split Xargold2
+ tabedit Xargold3
+ split Xargold4
+ tabedit Xargold5
+ tabfirst
+ all
+ call assert_equal(3, tabpagenr('$'))
+ call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')], tabpagebuflist(1))
+ call assert_equal([bufnr('Xargold4'), bufnr('Xargold3')], tabpagebuflist(2))
+ call assert_equal([bufnr('Xargold5')], tabpagebuflist(3))
+
+ " :tab all command should close windows for files which are not in the
+ " argument list across all the tab pages.
+ %bw!
+ edit Xargold1
+ split Xargold2
+ tabedit Xargold3
+ split Xargold4
+ tabedit Xargold5
+ tabfirst
+ args Xargnew1 Xargnew2
+ tab all
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal([bufnr('Xargnew1')], tabpagebuflist(1))
+ call assert_equal([bufnr('Xargnew2')], tabpagebuflist(2))
+
+ " If a count is specified, then :all should open only that many windows.
+ %bw!
+ args Xargnew1 Xargnew2 Xargnew3 Xargnew4 Xargnew5
+ all 3
+ call assert_equal(3, winnr('$'))
+ call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2'), bufnr('Xargnew3')],
+ \ [winbufnr(1), winbufnr(2), winbufnr(3)])
+
+ " The :all command should not open more than 'tabpagemax' tab pages.
+ " If there are more files, then they should be opened in the last tab page.
+ %bw!
+ set tabpagemax=3
+ tab all
+ call assert_equal(3, tabpagenr('$'))
+ call assert_equal([bufnr('Xargnew1')], tabpagebuflist(1))
+ call assert_equal([bufnr('Xargnew2')], tabpagebuflist(2))
+ call assert_equal([bufnr('Xargnew3'), bufnr('Xargnew4'), bufnr('Xargnew5')],
+ \ tabpagebuflist(3))
+ set tabpagemax&
+
+ " Without the 'hidden' option, modified buffers should not be closed.
+ args Xargnew1 Xargnew2
+ %bw!
+ edit Xargtemp1
+ call setline(1, 'temp buffer 1')
+ split Xargtemp2
+ call setline(1, 'temp buffer 2')
+ all
+ call assert_equal(4, winnr('$'))
+ call assert_equal([bufnr('Xargtemp2'), bufnr('Xargtemp1'), bufnr('Xargnew1'),
+ \ bufnr('Xargnew2')],
+ \ [winbufnr(1), winbufnr(2), winbufnr(3), winbufnr(4)])
+
+ " With the 'hidden' option set, both modified and unmodified buffers in
+ " closed windows should be hidden.
+ set hidden
+ all
+ call assert_equal(2, winnr('$'))
+ call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')],
+ \ [winbufnr(1), winbufnr(2)])
+ call assert_equal([1, 1, 0, 0], [getbufinfo('Xargtemp1')[0].hidden,
+ \ getbufinfo('Xargtemp2')[0].hidden,
+ \ getbufinfo('Xargnew1')[0].hidden,
+ \ getbufinfo('Xargnew2')[0].hidden])
+ set nohidden
+
+ " When 'winheight' is set to a large value, :all should open only one
+ " window.
+ args Xargnew1 Xargnew2 Xargnew3 Xargnew4 Xargnew5
+ %bw!
+ set winheight=9999
+ call assert_fails('all', 'E36:')
+ call assert_equal([1, bufnr('Xargnew1')], [winnr('$'), winbufnr(1)])
+ set winheight&
+
+ " When 'winwidth' is set to a large value, :vert all should open only one
+ " window.
+ %bw!
+ set winwidth=9999
+ call assert_fails('vert all', 'E36:')
+ call assert_equal([1, bufnr('Xargnew1')], [winnr('$'), winbufnr(1)])
+ set winwidth&
+
+ " empty argument list tests
+ %bw!
+ %argdelete
+ call assert_equal('', execute('args'))
+ all
+ call assert_equal(1, winnr('$'))
+
+ %argdelete
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_assert.vim b/src/testdir/test_assert.vim
new file mode 100644
index 0000000..ecf5024
--- /dev/null
+++ b/src/testdir/test_assert.vim
@@ -0,0 +1,488 @@
+" Test that the methods used for testing work.
+
+source check.vim
+source term_util.vim
+
+func Test_assert_false()
+ call assert_equal(0, assert_false(0))
+ call assert_equal(0, assert_false(v:false))
+ call assert_equal(0, v:false->assert_false())
+
+ call assert_equal(1, assert_false(123))
+ call assert_match("Expected 'False' but got 123", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 123->assert_false())
+ call assert_match("Expected 'False' but got 123", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_true()
+ call assert_equal(0, assert_true(1))
+ call assert_equal(0, assert_true(123))
+ call assert_equal(0, assert_true(v:true))
+ call assert_equal(0, v:true->assert_true())
+
+ call assert_equal(1, assert_true(0))
+ call assert_match("Expected 'True' but got 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 0->assert_true())
+ call assert_match("Expected 'True' but got 0", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_equal()
+ let s = 'foo'
+ call assert_equal(0, assert_equal('foo', s))
+ let n = 4
+ call assert_equal(0, assert_equal(4, n))
+ let l = [1, 2, 3]
+ call assert_equal(0, assert_equal([1, 2, 3], l))
+ call assert_equal(test_null_list(), test_null_list())
+ call assert_equal(test_null_list(), [])
+ call assert_equal([], test_null_list())
+
+ let s = 'foo'
+ call assert_equal(1, assert_equal('bar', s))
+ call assert_match("Expected 'bar' but got 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX')
+ call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0])
+ call remove(v:errors, 0)
+
+ " special characters are escaped
+ call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x')
+ call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0])
+ call remove(v:errors, 0)
+
+ " many composing characters are handled properly
+ call setline(1, ' ')
+ norm 100gr݀
+ call assert_equal(1, getline(1))
+ call assert_match("Expected 1 but got '.* occurs 100 times]'", v:errors[0])
+ call remove(v:errors, 0)
+ bwipe!
+endfunc
+
+func Test_assert_equal_dict()
+ call assert_equal(0, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 1}))
+
+ call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 3}))
+ call assert_match("Expected {'one': 1} but got {'one': 3} - 1 equal item omitted", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 22, one: 11}))
+ call assert_match("Expected {'one': 1, 'two': 2} but got {'one': 11, 'two': 22}", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_equal(#{}, #{two: 2, one: 1}))
+ call assert_match("Expected {} but got {'one': 1, 'two': 2}", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_equal(#{two: 2, one: 1}, #{}))
+ call assert_match("Expected {'one': 1, 'two': 2} but got {}", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_equalfile()
+ call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz'))
+ call assert_match("E485: Can't read file abcabc", v:errors[0])
+ call remove(v:errors, 0)
+
+ let goodtext = ["one", "two", "three"]
+ call writefile(goodtext, 'Xone', 'D')
+ call assert_equal(1, 'Xone'->assert_equalfile('xyzxyz'))
+ call assert_match("E485: Can't read file xyzxyz", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(goodtext, 'Xtwo', 'D')
+ call assert_equal(0, assert_equalfile('Xone', 'Xtwo'))
+
+ call writefile([goodtext[0]], 'Xone')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match("first file is shorter", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(goodtext, 'Xone')
+ call writefile([goodtext[0]], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match("second file is shorter", v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile(['1234X89'], 'Xone')
+ call writefile(['1234Y89'], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ call assert_match('difference at byte 4, line 1 after "1234X" vs "1234Y"', v:errors[0])
+ call remove(v:errors, 0)
+
+ call writefile([repeat('x', 234) .. 'X'], 'Xone')
+ call writefile([repeat('x', 234) .. 'Y'], 'Xtwo')
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo'))
+ let xes = repeat('x', 134)
+ call assert_match('difference at byte 234, line 1 after "' .. xes .. 'X" vs "' .. xes .. 'Y"', v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_equalfile('Xone', 'Xtwo', 'a message'))
+ call assert_match("a message: difference at byte 234, line 1 after", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_notequal()
+ let n = 4
+ call assert_equal(0, assert_notequal('foo', n))
+ let s = 'foo'
+ call assert_equal(0, assert_notequal([1, 2, 3], s))
+
+ call assert_equal(1, assert_notequal('foo', s))
+ call assert_match("Expected not equal to 'foo'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_report()
+ call assert_equal(1, assert_report('something is wrong'))
+ call assert_match('something is wrong', v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, 'also wrong'->assert_report())
+ call assert_match('also wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_exception()
+ try
+ nocommand
+ catch
+ call assert_equal(0, assert_exception('E492:'))
+ endtry
+
+ try
+ nocommand
+ catch
+ call assert_equal(1, assert_exception('E12345:'))
+ endtry
+ call assert_match("Expected 'E12345:' but got 'Vim:E492: ", v:errors[0])
+ call remove(v:errors, 0)
+
+ try
+ nocommand
+ catch
+ try
+ " illegal argument, get NULL for error
+ call assert_equal(1, assert_exception([]))
+ catch
+ call assert_equal(0, assert_exception('E730:'))
+ endtry
+ endtry
+
+ call assert_equal(1, assert_exception('E492:'))
+ call assert_match('v:exception is not set', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_wrong_error_type()
+ let save_verrors = v:errors
+ let v:['errors'] = {'foo': 3}
+ call assert_equal('yes', 'no')
+ let verrors = v:errors
+ let v:errors = save_verrors
+ call assert_equal(type([]), type(verrors))
+endfunc
+
+func Test_compare_fail()
+ let s:v = {}
+ let s:x = {"a": s:v}
+ let s:v["b"] = s:x
+ let s:w = {"c": s:x, "d": ''}
+ try
+ call assert_equal(s:w, '')
+ catch
+ call assert_equal(0, assert_exception('E724:'))
+ call assert_match("Expected NULL but got ''", v:errors[0])
+ call remove(v:errors, 0)
+ endtry
+endfunc
+
+func Test_match()
+ call assert_equal(0, assert_match('^f.*b.*r$', 'foobar'))
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar'))
+ call assert_match("Pattern 'bar.*foo' does not match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_match('bar.*foo', 'foobar', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong'))
+ call assert_match('wrong', v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_notmatch()
+ call assert_equal(0, assert_notmatch('foo', 'bar'))
+ call assert_equal(0, assert_notmatch('^foobar$', 'foobars'))
+
+ call assert_equal(1, assert_notmatch('foo', 'foobar'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'foobar'->assert_notmatch('foo'))
+ call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_fail_fails()
+ call assert_equal(1, assert_fails('xxx', 'E12345'))
+ call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('xxx', 'E9876', 'stupid'))
+ call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('xxx', ['E9876']))
+ call assert_match("Expected 'E9876' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('xxx', ['E492:', 'E9876']))
+ call assert_match("Expected 'E9876' but got 'E492:", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, assert_fails('echo', '', 'echo command'))
+ call assert_match("command did not fail: echo command", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(1, 'echo'->assert_fails('', 'echo command'))
+ call assert_match("command did not fail: echo command", v:errors[0])
+ call remove(v:errors, 0)
+
+ try
+ call assert_equal(1, assert_fails('xxx', []))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', ['1', '2', '3']))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', test_null_list()))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', []))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', #{one: 1}))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E1222: String or List required for argument 2", exp)
+
+ try
+ call assert_equal(0, assert_fails('xxx', [#{one: 1}]))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E731: Using a Dictionary as a String", exp)
+
+ let exp = ''
+ try
+ call assert_equal(0, assert_fails('xxx', ['E492', #{one: 1}]))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E731: Using a Dictionary as a String", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', 'E492', '', 'burp'))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E1210: Number required for argument 4", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E1174: String required for argument 5", exp)
+
+ call assert_equal(1, assert_fails('c0', ['', '\(.\)\1']))
+ call assert_match("Expected '\\\\\\\\(.\\\\\\\\)\\\\\\\\1' but got 'E939: Positive count required: c0': c0", v:errors[0])
+ call remove(v:errors, 0)
+
+ " Test for matching the line number and the script name in an error message
+ call writefile(['', 'call Xnonexisting()'], 'Xassertfails.vim', 'D')
+ call assert_fails('source Xassertfails.vim', 'E117:', '', 10)
+ call assert_match("Expected 10 but got 2", v:errors[0])
+ call remove(v:errors, 0)
+ call assert_fails('source Xassertfails.vim', 'E117:', '', 2, 'Xabc')
+ call assert_match("Expected 'Xabc' but got .*Xassertfails.vim", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_fails_in_try_block()
+ try
+ call assert_equal(0, assert_fails('throw "error"'))
+ endtry
+endfunc
+
+" Test that assert_fails() in a timer does not cause a hit-enter prompt.
+" Requires using a terminal, in regular tests the hit-enter prompt won't be
+" triggered.
+func Test_assert_fails_in_timer()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', {'rows': 6})
+ let cmd = ":call timer_start(0, {-> assert_fails('call', 'E471:')})"
+ call term_sendkeys(buf, cmd)
+ call WaitForAssert({-> assert_equal(cmd, term_getline(buf, 6))})
+ call term_sendkeys(buf, "\<CR>")
+ call TermWait(buf, 100)
+ call assert_match('E471: Argument required', term_getline(buf, 6))
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_assert_beeps()
+ new
+ call assert_equal(0, assert_beeps('normal h'))
+
+ call assert_equal(1, assert_beeps('normal 0'))
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(0, 'normal h'->assert_beeps())
+ call assert_equal(1, 'normal 0'->assert_beeps())
+ call assert_match("command did not beep: normal 0", v:errors[0])
+ call remove(v:errors, 0)
+
+ bwipe
+endfunc
+
+func Test_assert_nobeep()
+ call assert_equal(1, assert_nobeep('normal! cr'))
+ call assert_match("command did beep: normal! cr", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_inrange()
+ call assert_equal(0, assert_inrange(7, 7, 7))
+ call assert_equal(0, assert_inrange(5, 7, 5))
+ call assert_equal(0, assert_inrange(5, 7, 6))
+ call assert_equal(0, assert_inrange(5, 7, 7))
+
+ call assert_equal(1, assert_inrange(5, 7, 4))
+ call assert_match("Expected range 5 - 7, but got 4", v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, assert_inrange(5, 7, 8))
+ call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_equal(0, 5->assert_inrange(5, 7))
+ call assert_equal(0, 7->assert_inrange(5, 7))
+ call assert_equal(1, 8->assert_inrange(5, 7))
+ call assert_match("Expected range 5 - 7, but got 8", v:errors[0])
+ call remove(v:errors, 0)
+
+ call assert_fails('call assert_inrange(1, 1)', 'E119:')
+
+ call assert_equal(0, assert_inrange(7.0, 7, 7))
+ call assert_equal(0, assert_inrange(7, 7.0, 7))
+ call assert_equal(0, assert_inrange(7, 7, 7.0))
+ call assert_equal(0, assert_inrange(5, 7, 5.0))
+ call assert_equal(0, assert_inrange(5, 7, 6.0))
+ call assert_equal(0, assert_inrange(5, 7, 7.0))
+
+ call assert_equal(1, assert_inrange(5, 7, 4.0))
+ call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, assert_inrange(5, 7, 8.0))
+ call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0])
+ call remove(v:errors, 0)
+
+ " Use a custom message
+ call assert_equal(1, assert_inrange(5, 7, 8.0, "Higher"))
+ call assert_match("Higher", v:errors[0])
+ call remove(v:errors, 0)
+
+ " Invalid arguments
+ call assert_fails("call assert_inrange([], 2, 3)", 'E1219:')
+ call assert_fails("call assert_inrange(1, [], 3)", 'E1219:')
+ call assert_fails("call assert_inrange(1, 2, [])", 'E1219:')
+endfunc
+
+func Test_assert_with_msg()
+ call assert_equal('foo', 'bar', 'testing')
+ call assert_match("testing: Expected 'foo' but got 'bar'", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_override()
+ call test_override('char_avail', 1)
+ eval 1->test_override('redraw')
+ call test_override('ALL', 0)
+ call assert_fails("call test_override('xxx', 1)", 'E475:')
+ call assert_fails("call test_override('redraw', 'yes')", 'E1210:')
+endfunc
+
+func Test_mouse_position()
+ let save_mouse = &mouse
+ set mouse=a
+ new
+ call setline(1, ['line one', 'line two'])
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ call test_setmouse(1, 5)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 5, 0], getpos('.'))
+ call test_setmouse(2, 20)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 8, 0], getpos('.'))
+ call test_setmouse(5, 1)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 1, 0], getpos('.'))
+ call assert_fails('call test_setmouse("", 2)', 'E474:')
+ call assert_fails('call test_setmouse(1, "")', 'E474:')
+ bwipe!
+ let &mouse = save_mouse
+endfunc
+
+func Test_user_is_happy()
+ smile
+ sleep 300m
+endfunc
+
+" Test for the test_alloc_fail() function
+func Test_test_alloc_fail()
+ call assert_fails('call test_alloc_fail([], 1, 1)', 'E474:')
+ call assert_fails('call test_alloc_fail(10, [], 1)', 'E474:')
+ call assert_fails('call test_alloc_fail(10, 1, [])', 'E474:')
+ call assert_fails('call test_alloc_fail(999999, 1, 1)', 'E474:')
+endfunc
+
+" Test for the test_option_not_set() function
+func Test_test_option_not_set()
+ call assert_fails('call test_option_not_set("Xinvalidopt")', 'E475:')
+endfunc
+
+" Must be last.
+func Test_zz_quit_detected()
+ " Verify that if a test function ends Vim the test script detects this.
+ quit
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_autochdir.vim b/src/testdir/test_autochdir.vim
new file mode 100644
index 0000000..eb40253
--- /dev/null
+++ b/src/testdir/test_autochdir.vim
@@ -0,0 +1,121 @@
+" Test 'autochdir' behavior
+
+source check.vim
+CheckOption autochdir
+
+func Test_set_filename()
+ let cwd = getcwd()
+ call test_autochdir()
+ set acd
+
+ let s:li = []
+ autocmd DirChanged auto call add(s:li, "autocd")
+ autocmd DirChanged auto call add(s:li, expand("<afile>"))
+
+ new
+ w samples/Xtest
+ call assert_equal("Xtest", expand('%'))
+ call assert_equal("samples", substitute(getcwd(), '.*/\(\k*\)', '\1', ''))
+ call assert_equal(["autocd", getcwd()], s:li)
+
+ bwipe!
+ au! DirChanged
+ set noacd
+ call chdir(cwd)
+ call delete('samples/Xtest')
+endfunc
+
+func Test_set_filename_other_window()
+ let cwd = getcwd()
+ call test_autochdir()
+ call mkdir('Xa', 'R')
+ call mkdir('Xb', 'R')
+ call mkdir('Xc', 'R')
+ try
+ args Xa/aaa.txt Xb/bbb.txt
+ set acd
+ let winid = win_getid()
+ snext
+ call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', ''))
+ call win_execute(winid, 'file ' .. cwd .. '/Xc/ccc.txt')
+ call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', ''))
+ finally
+ set noacd
+ call chdir(cwd)
+ bwipe! aaa.txt
+ bwipe! bbb.txt
+ bwipe! ccc.txt
+ endtry
+endfunc
+
+func Test_acd_win_execute()
+ let cwd = getcwd()
+ set acd
+ call test_autochdir()
+
+ call mkdir('XacdDir', 'R')
+ let winid = win_getid()
+ new XacdDir/file
+ call assert_match('testdir.XacdDir$', getcwd())
+ cd ..
+ call assert_match('testdir$', getcwd())
+ call win_execute(winid, 'echo')
+ call assert_match('testdir$', getcwd())
+
+ bwipe!
+ set noacd
+ call chdir(cwd)
+endfunc
+
+func Test_verbose_pwd()
+ let cwd = getcwd()
+ call test_autochdir()
+
+ edit global.txt
+ call assert_match('\[global\].*testdir$', execute('verbose pwd'))
+
+ call mkdir('Xautodir', 'R')
+ split Xautodir/local.txt
+ lcd Xautodir
+ call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
+
+ set acd
+ wincmd w
+ call assert_match('\[autochdir\].*testdir$', execute('verbose pwd'))
+ execute 'tcd' cwd
+ call assert_match('\[tabpage\].*testdir$', execute('verbose pwd'))
+ execute 'cd' cwd
+ call assert_match('\[global\].*testdir$', execute('verbose pwd'))
+ execute 'lcd' cwd
+ call assert_match('\[window\].*testdir$', execute('verbose pwd'))
+ edit
+ call assert_match('\[autochdir\].*testdir$', execute('verbose pwd'))
+ enew
+ wincmd w
+ call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd'))
+ wincmd w
+ call assert_match('\[window\].*testdir$', execute('verbose pwd'))
+ wincmd w
+ call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd'))
+ set noacd
+ call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd'))
+ wincmd w
+ call assert_match('\[window\].*testdir$', execute('verbose pwd'))
+ execute 'cd' cwd
+ call assert_match('\[global\].*testdir$', execute('verbose pwd'))
+ wincmd w
+ call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
+
+ bwipe!
+ call chdir(cwd)
+endfunc
+
+func Test_multibyte()
+ " using an invalid character should not cause a crash
+ set wic
+ call assert_fails('tc û¦*', has('win32') ? 'E480:' : 'E344:')
+ set nowic
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
new file mode 100644
index 0000000..f2a763a
--- /dev/null
+++ b/src/testdir/test_autocmd.vim
@@ -0,0 +1,4270 @@
+" Tests for autocommands
+
+source shared.vim
+source check.vim
+source term_util.vim
+source screendump.vim
+import './vim9.vim' as v9
+
+func s:cleanup_buffers() abort
+ for bnr in range(1, bufnr('$'))
+ if bufloaded(bnr) && bufnr('%') != bnr
+ execute 'bd! ' . bnr
+ endif
+ endfor
+endfunc
+
+func Test_vim_did_enter()
+ call assert_false(v:vim_did_enter)
+
+ " This script will never reach the main loop, can't check if v:vim_did_enter
+ " becomes one.
+endfunc
+
+" Test for the CursorHold autocmd
+func Test_CursorHold_autocmd()
+ CheckRunVimInTerminal
+ call writefile(['one', 'two', 'three'], 'XoneTwoThree', 'D')
+ let before =<< trim END
+ set updatetime=10
+ au CursorHold * call writefile([line('.')], 'XCHoutput', 'a')
+ END
+ call writefile(before, 'XCHinit', 'D')
+ let buf = RunVimInTerminal('-S XCHinit XoneTwoThree', {})
+ call term_sendkeys(buf, "G")
+ call term_wait(buf, 50)
+ call term_sendkeys(buf, "gg")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_equal(['1'], readfile('XCHoutput')[-1:-1])})
+ call term_sendkeys(buf, "j")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_equal(['1', '2'], readfile('XCHoutput')[-2:-1])})
+ call term_sendkeys(buf, "j")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_equal(['1', '2', '3'], readfile('XCHoutput')[-3:-1])})
+ call StopVimInTerminal(buf)
+
+ call delete('XCHoutput')
+endfunc
+
+if has('timers')
+
+ func ExitInsertMode(id)
+ call feedkeys("\<Esc>")
+ endfunc
+
+ func Test_cursorhold_insert()
+ " Need to move the cursor.
+ call feedkeys("ggG", "xt")
+
+ let g:triggered = 0
+ au CursorHoldI * let g:triggered += 1
+ set updatetime=20
+ call timer_start(200, 'ExitInsertMode')
+ call feedkeys('a', 'x!')
+ sleep 30m
+ call assert_equal(1, g:triggered)
+ unlet g:triggered
+ au! CursorHoldI
+ set updatetime&
+ endfunc
+
+ func Test_cursorhold_insert_with_timer_interrupt()
+ CheckFeature job
+ " Need to move the cursor.
+ call feedkeys("ggG", "xt")
+
+ " Confirm the timer invoked in exit_cb of the job doesn't disturb
+ " CursorHoldI event.
+ let g:triggered = 0
+ au CursorHoldI * let g:triggered += 1
+ set updatetime=100
+ call job_start(has('win32') ? 'cmd /c echo:' : 'echo',
+ \ {'exit_cb': {-> timer_start(200, 'ExitInsertMode')}})
+ call feedkeys('a', 'x!')
+ call assert_equal(1, g:triggered)
+ unlet g:triggered
+ au! CursorHoldI
+ set updatetime&
+ endfunc
+
+ func Test_cursorhold_insert_ctrl_x()
+ let g:triggered = 0
+ au CursorHoldI * let g:triggered += 1
+ set updatetime=20
+ call timer_start(100, 'ExitInsertMode')
+ " CursorHoldI does not trigger after CTRL-X
+ call feedkeys("a\<C-X>", 'x!')
+ call assert_equal(0, g:triggered)
+ unlet g:triggered
+ au! CursorHoldI
+ set updatetime&
+ endfunc
+
+ func Test_cursorhold_insert_ctrl_g_U()
+ au CursorHoldI * :
+ set updatetime=20
+ new
+ call timer_start(100, { -> feedkeys("\<Left>foo\<Esc>", 't') })
+ call feedkeys("i()\<C-g>U", 'tx!')
+ sleep 200m
+ call assert_equal('(foo)', getline(1))
+ undo
+ call assert_equal('', getline(1))
+
+ bwipe!
+ au! CursorHoldI
+ set updatetime&
+ endfunc
+
+ func Test_OptionSet_modeline()
+ call test_override('starting', 1)
+ au! OptionSet
+ augroup set_tabstop
+ au OptionSet tabstop call timer_start(1, {-> execute("echo 'Handler called'", "")})
+ augroup END
+ call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline', 'D')
+ set modeline
+ let v:errmsg = ''
+ call assert_fails('split XoptionsetModeline', 'E12:')
+ call assert_equal(7, &ts)
+ call assert_equal('', v:errmsg)
+
+ augroup set_tabstop
+ au!
+ augroup END
+ bwipe!
+ set ts&
+ call test_override('starting', 0)
+ endfunc
+
+endif "has('timers')
+
+func Test_bufunload()
+ augroup test_bufunload_group
+ autocmd!
+ autocmd BufUnload * call add(s:li, "bufunload")
+ autocmd BufDelete * call add(s:li, "bufdelete")
+ autocmd BufWipeout * call add(s:li, "bufwipeout")
+ augroup END
+
+ let s:li = []
+ new
+ setlocal bufhidden=
+ bunload
+ call assert_equal(["bufunload", "bufdelete"], s:li)
+
+ let s:li = []
+ new
+ setlocal bufhidden=delete
+ bunload
+ call assert_equal(["bufunload", "bufdelete"], s:li)
+
+ let s:li = []
+ new
+ setlocal bufhidden=unload
+ bwipeout
+ call assert_equal(["bufunload", "bufdelete", "bufwipeout"], s:li)
+
+ au! test_bufunload_group
+ augroup! test_bufunload_group
+endfunc
+
+" SEGV occurs in older versions. (At least 7.4.2005 or older)
+func Test_autocmd_bufunload_with_tabnext()
+ tabedit
+ tabfirst
+
+ augroup test_autocmd_bufunload_with_tabnext_group
+ autocmd!
+ autocmd BufUnload <buffer> tabnext
+ augroup END
+
+ quit
+ call assert_equal(2, tabpagenr('$'))
+
+ autocmd! test_autocmd_bufunload_with_tabnext_group
+ augroup! test_autocmd_bufunload_with_tabnext_group
+ tablast
+ quit
+endfunc
+
+func Test_argdelete_in_next()
+ au BufNew,BufEnter,BufLeave,BufWinEnter * argdel
+ call assert_fails('next a b', 'E1156:')
+ au! BufNew,BufEnter,BufLeave,BufWinEnter *
+endfunc
+
+func Test_autocmd_bufwinleave_with_tabfirst()
+ tabedit
+ augroup sample
+ autocmd!
+ autocmd BufWinLeave <buffer> tabfirst
+ augroup END
+ call setline(1, ['a', 'b', 'c'])
+ edit! a.txt
+ tabclose
+endfunc
+
+" SEGV occurs in older versions. (At least 7.4.2321 or older)
+func Test_autocmd_bufunload_avoiding_SEGV_01()
+ split aa.txt
+ let lastbuf = bufnr('$')
+
+ augroup test_autocmd_bufunload
+ autocmd!
+ exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!'
+ augroup END
+
+ call assert_fails('edit bb.txt', 'E937:')
+
+ autocmd! test_autocmd_bufunload
+ augroup! test_autocmd_bufunload
+ bwipe! aa.txt
+ bwipe! bb.txt
+endfunc
+
+" SEGV occurs in older versions. (At least 7.4.2321 or older)
+func Test_autocmd_bufunload_avoiding_SEGV_02()
+ setlocal buftype=nowrite
+ let lastbuf = bufnr('$')
+
+ augroup test_autocmd_bufunload
+ autocmd!
+ exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!'
+ augroup END
+
+ normal! i1
+ call assert_fails('edit a.txt', 'E517:')
+
+ autocmd! test_autocmd_bufunload
+ augroup! test_autocmd_bufunload
+ bwipe! a.txt
+endfunc
+
+func Test_autocmd_dummy_wipeout()
+ " prepare files
+ call writefile([''], 'Xdummywipetest1.txt', 'D')
+ call writefile([''], 'Xdummywipetest2.txt', 'D')
+ augroup test_bufunload_group
+ autocmd!
+ autocmd BufUnload * call add(s:li, "bufunload")
+ autocmd BufDelete * call add(s:li, "bufdelete")
+ autocmd BufWipeout * call add(s:li, "bufwipeout")
+ augroup END
+
+ let s:li = []
+ split Xdummywipetest1.txt
+ silent! vimgrep /notmatched/ Xdummywipetest*
+ call assert_equal(["bufunload", "bufwipeout"], s:li)
+
+ bwipeout
+ au! test_bufunload_group
+ augroup! test_bufunload_group
+endfunc
+
+func Test_win_tab_autocmd()
+ let g:record = []
+
+ augroup testing
+ au WinNew * call add(g:record, 'WinNew')
+ au WinClosed * call add(g:record, 'WinClosed')
+ au WinEnter * call add(g:record, 'WinEnter')
+ au WinLeave * call add(g:record, 'WinLeave')
+ au TabNew * call add(g:record, 'TabNew')
+ au TabClosed * call add(g:record, 'TabClosed')
+ au TabEnter * call add(g:record, 'TabEnter')
+ au TabLeave * call add(g:record, 'TabLeave')
+ augroup END
+
+ split
+ tabnew
+ close
+ close
+
+ call assert_equal([
+ \ 'WinLeave', 'WinNew', 'WinEnter',
+ \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
+ \ 'WinLeave', 'TabLeave', 'WinClosed', 'TabClosed', 'WinEnter', 'TabEnter',
+ \ 'WinLeave', 'WinClosed', 'WinEnter'
+ \ ], g:record)
+
+ let g:record = []
+ tabnew somefile
+ tabnext
+ bwipe somefile
+
+ call assert_equal([
+ \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
+ \ 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter',
+ \ 'WinClosed', 'TabClosed'
+ \ ], g:record)
+
+ augroup testing
+ au!
+ augroup END
+ unlet g:record
+endfunc
+
+func Test_WinResized()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set scrolloff=0
+ call setline(1, ['111', '222'])
+ vnew
+ call setline(1, ['aaa', 'bbb'])
+ new
+ call setline(1, ['foo', 'bar'])
+
+ let g:resized = 0
+ au WinResized * let g:resized += 1
+
+ func WriteResizedEvent()
+ call writefile([json_encode(v:event)], 'XresizeEvent')
+ endfunc
+ au WinResized * call WriteResizedEvent()
+ END
+ call writefile(lines, 'Xtest_winresized', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winresized', {'rows': 10})
+
+ " redraw now to avoid a redraw after the :echo command
+ call term_sendkeys(buf, ":redraw!\<CR>")
+ call TermWait(buf)
+
+ call term_sendkeys(buf, ":echo g:resized\<CR>")
+ call WaitForAssert({-> assert_match('^0$', term_getline(buf, 10))}, 1000)
+
+ " increase window height, two windows will be reported
+ call term_sendkeys(buf, "\<C-W>+")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":echo g:resized\<CR>")
+ call WaitForAssert({-> assert_match('^1$', term_getline(buf, 10))}, 1000)
+
+ let event = readfile('XresizeEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'windows': [1002, 1001],
+ \ }, event)
+
+ " increase window width, three windows will be reported
+ call term_sendkeys(buf, "\<C-W>>")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":echo g:resized\<CR>")
+ call WaitForAssert({-> assert_match('^2$', term_getline(buf, 10))}, 1000)
+
+ let event = readfile('XresizeEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'windows': [1002, 1001, 1000],
+ \ }, event)
+
+ call delete('XresizeEvent')
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_WinScrolled()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nowrap scrolloff=0
+ for ii in range(1, 18)
+ call setline(ii, repeat(nr2char(96 + ii), ii * 2))
+ endfor
+ let win_id = win_getid()
+ let g:matched = v:false
+ func WriteScrollEvent()
+ call writefile([json_encode(v:event)], 'XscrollEvent')
+ endfunc
+ execute 'au WinScrolled' win_id 'let g:matched = v:true'
+ let g:scrolled = 0
+ au WinScrolled * let g:scrolled += 1
+ au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
+ au WinScrolled * let g:afile = str2nr(expand('<afile>'))
+ au WinScrolled * call WriteScrollEvent()
+ END
+ call writefile(lines, 'Xtest_winscrolled', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6})
+
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000)
+
+ " Scroll left/right in Normal mode.
+ call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': -1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ " Scroll up/down in Normal mode.
+ call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ " Scroll up/down in Insert mode.
+ call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>")
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ " Scroll the window horizontally to focus the last letter of the third line
+ " containing only six characters. Moving to the previous and shorter lines
+ " should trigger another autocommand as Vim has to make them visible.
+ call term_sendkeys(buf, "5zl2k")
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': -5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ " Ensure the command was triggered for the specified window ID.
+ call term_sendkeys(buf, ":echo g:matched\<CR>")
+ call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
+
+ " Ensure the expansion of <amatch> and <afile> matches the window ID.
+ call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>")
+ call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000)
+
+ call delete('XscrollEvent')
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_WinScrolled_mouse()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nowrap scrolloff=0
+ set mouse=a term=xterm ttymouse=sgr mousetime=200 clipboard=
+ call setline(1, ['foo']->repeat(32))
+ split
+ let g:scrolled = 0
+ au WinScrolled * let g:scrolled += 1
+ END
+ call writefile(lines, 'Xtest_winscrolled_mouse', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winscrolled_mouse', {'rows': 10})
+
+ " With the upper split focused, send a scroll-down event to the unfocused one.
+ call test_setmouse(7, 1)
+ call term_sendkeys(buf, "\<ScrollWheelDown>")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^1', term_getline(buf, 10))}, 1000)
+
+ " Again, but this time while we're in insert mode.
+ call term_sendkeys(buf, "i\<ScrollWheelDown>\<Esc>")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^2', term_getline(buf, 10))}, 1000)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_WinScrolled_close_curwin()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nowrap scrolloff=0
+ call setline(1, ['aaa', 'bbb'])
+ vsplit
+ au WinScrolled * close
+ au VimLeave * call writefile(['123456'], 'Xtestout')
+ END
+ call writefile(lines, 'Xtest_winscrolled_close_curwin', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6})
+
+ " This was using freed memory
+ call term_sendkeys(buf, "\<C-E>")
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+
+ " check the startup script finished to the end
+ call assert_equal(['123456'], readfile('Xtestout'))
+ call delete('Xtestout')
+endfunc
+
+func Test_WinScrolled_once_only()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set cmdheight=2
+ call setline(1, ['aaa', 'bbb'])
+ let trigger_count = 0
+ func ShowInfo(id)
+ echo g:trigger_count g:winid winlayout()
+ endfunc
+
+ vsplit
+ split
+ " use a timer to show the info after a redraw
+ au WinScrolled * let trigger_count += 1 | let winid = expand('<amatch>') | call timer_start(100, 'ShowInfo')
+ wincmd j
+ wincmd l
+ END
+ call writefile(lines, 'Xtest_winscrolled_once', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winscrolled_once', #{rows: 10, cols: 60, statusoff: 2})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_winscrolled_once_only_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Check that WinScrolled is not triggered immediately when defined and there
+" are split windows.
+func Test_WinScrolled_not_when_defined()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['aaa', 'bbb'])
+ echo 'nothing happened'
+ func ShowTriggered(id)
+ echo 'triggered'
+ endfunc
+ END
+ call writefile(lines, 'Xtest_winscrolled_not', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winscrolled_not', #{rows: 10, cols: 60, statusoff: 2})
+ call term_sendkeys(buf, ":split\<CR>")
+ call TermWait(buf)
+ " use a timer to show the message after redrawing
+ call term_sendkeys(buf, ":au WinScrolled * call timer_start(100, 'ShowTriggered')\<CR>")
+ call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_1', {})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_WinScrolled_long_wrapped()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set scrolloff=0
+ let height = winheight(0)
+ let width = winwidth(0)
+ let g:scrolled = 0
+ au WinScrolled * let g:scrolled += 1
+ call setline(1, repeat('foo', height * width))
+ call cursor(1, height * width)
+ END
+ call writefile(lines, 'Xtest_winscrolled_long_wrapped', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6})
+
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000)
+
+ call term_sendkeys(buf, 'gj')
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^1 ', term_getline(buf, 6))}, 1000)
+
+ call term_sendkeys(buf, '0')
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000)
+
+ call term_sendkeys(buf, '$')
+ call term_sendkeys(buf, ":echo g:scrolled\<CR>")
+ call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_WinScrolled_diff()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set diffopt+=foldcolumn:0
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
+ vnew
+ call setline(1, ['d', 'e', 'f', 'g', 'h', 'i'])
+ windo diffthis
+ func WriteScrollEvent()
+ call writefile([json_encode(v:event)], 'XscrollEvent')
+ endfunc
+ au WinScrolled * call WriteScrollEvent()
+ END
+ call writefile(lines, 'Xtest_winscrolled_diff', 'D')
+ let buf = RunVimInTerminal('-S Xtest_winscrolled_diff', {'rows': 8})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call WaitForAssert({-> assert_match('^d', term_getline(buf, 3))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -1, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ call term_sendkeys(buf, "2\<C-E>")
+ call WaitForAssert({-> assert_match('^f', term_getline(buf, 3))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 2, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -2, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ call term_sendkeys(buf, "\<C-E>")
+ call WaitForAssert({-> assert_match('^g', term_getline(buf, 3))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1001': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ call term_sendkeys(buf, "2\<C-Y>")
+ call WaitForAssert({-> assert_match('^e', term_getline(buf, 3))}, 1000)
+
+ let event = readfile('XscrollEvent')[0]->json_decode()
+ call assert_equal({
+ \ 'all': {'leftcol': 0, 'topline': 3, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1000': {'leftcol': 0, 'topline': -2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0},
+ \ '1001': {'leftcol': 0, 'topline': -1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0}
+ \ }, event)
+
+ call StopVimInTerminal(buf)
+ call delete('XscrollEvent')
+endfunc
+
+func Test_WinClosed()
+ " Test that the pattern is matched against the closed window's ID, and both
+ " <amatch> and <afile> are set to it.
+ new
+ let winid = win_getid()
+ let g:matched = v:false
+ augroup test-WinClosed
+ autocmd!
+ execute 'autocmd WinClosed' winid 'let g:matched = v:true'
+ autocmd WinClosed * let g:amatch = str2nr(expand('<amatch>'))
+ autocmd WinClosed * let g:afile = str2nr(expand('<afile>'))
+ augroup END
+ close
+ call assert_true(g:matched)
+ call assert_equal(winid, g:amatch)
+ call assert_equal(winid, g:afile)
+
+ " Test that WinClosed is non-recursive.
+ new
+ new
+ call assert_equal(3, winnr('$'))
+ let g:triggered = 0
+ augroup test-WinClosed
+ autocmd!
+ autocmd WinClosed * let g:triggered += 1
+ autocmd WinClosed * 2 wincmd c
+ augroup END
+ close
+ call assert_equal(1, winnr('$'))
+ call assert_equal(1, g:triggered)
+
+ autocmd! test-WinClosed
+ augroup! test-WinClosed
+ unlet g:matched
+ unlet g:amatch
+ unlet g:afile
+ unlet g:triggered
+endfunc
+
+func Test_WinClosed_throws()
+ vnew
+ let bnr = bufnr()
+ call assert_equal(1, bufloaded(bnr))
+ augroup test-WinClosed
+ autocmd WinClosed * throw 'foo'
+ augroup END
+ try
+ close
+ catch /.*/
+ endtry
+ call assert_equal(0, bufloaded(bnr))
+
+ autocmd! test-WinClosed
+ augroup! test-WinClosed
+endfunc
+
+func Test_WinClosed_throws_with_tabs()
+ tabnew
+ let bnr = bufnr()
+ call assert_equal(1, bufloaded(bnr))
+ augroup test-WinClosed
+ autocmd WinClosed * throw 'foo'
+ augroup END
+ try
+ close
+ catch /.*/
+ endtry
+ call assert_equal(0, bufloaded(bnr))
+
+ autocmd! test-WinClosed
+ augroup! test-WinClosed
+endfunc
+
+" This used to trigger WinClosed twice for the same window, and the window's
+" buffer was NULL in the second autocommand.
+func Test_WinClosed_switch_tab()
+ edit Xa
+ split Xb
+ split Xc
+ tab split
+ new
+ augroup test-WinClosed
+ autocmd WinClosed * tabprev | bwipe!
+ augroup END
+ close
+ " Check that the tabline has been fully removed
+ call assert_equal([1, 1], win_screenpos(0))
+
+ autocmd! test-WinClosed
+ augroup! test-WinClosed
+ %bwipe!
+endfunc
+
+func s:AddAnAutocmd()
+ augroup vimBarTest
+ au BufReadCmd * echo 'hello'
+ augroup END
+ call assert_equal(3, len(split(execute('au vimBarTest'), "\n")))
+endfunc
+
+func Test_early_bar()
+ " test that a bar is recognized before the {event}
+ call s:AddAnAutocmd()
+ augroup vimBarTest | au! | let done = 77 | augroup END
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+ call assert_equal(77, done)
+
+ call s:AddAnAutocmd()
+ augroup vimBarTest| au!| let done = 88 | augroup END
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+ call assert_equal(88, done)
+
+ " test that a bar is recognized after the {event}
+ call s:AddAnAutocmd()
+ augroup vimBarTest| au!BufReadCmd| let done = 99 | augroup END
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+ call assert_equal(99, done)
+
+ " test that a bar is recognized after the {group}
+ call s:AddAnAutocmd()
+ au! vimBarTest|echo 'hello'
+ call assert_equal(1, len(split(execute('au vimBarTest'), "\n")))
+endfunc
+
+func RemoveGroup()
+ autocmd! StartOK
+ augroup! StartOK
+endfunc
+
+func Test_augroup_warning()
+ augroup TheWarning
+ au VimEnter * echo 'entering'
+ augroup END
+ call assert_match("TheWarning.*VimEnter", execute('au VimEnter'))
+ redir => res
+ augroup! TheWarning
+ redir END
+ call assert_match("W19:", res)
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
+
+ " check "Another" does not take the pace of the deleted entry
+ augroup Another
+ augroup END
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
+ augroup! Another
+
+ " no warning for postpone aucmd delete
+ augroup StartOK
+ au VimEnter * call RemoveGroup()
+ augroup END
+ call assert_match("StartOK.*VimEnter", execute('au VimEnter'))
+ redir => res
+ doautocmd VimEnter
+ redir END
+ call assert_notmatch("W19:", res)
+ au! VimEnter
+
+ call assert_fails('augroup!', 'E471:')
+endfunc
+
+func Test_BufReadCmdHelp()
+ " This used to cause access to free memory
+ au BufReadCmd * e +h
+ help
+
+ au! BufReadCmd
+endfunc
+
+func Test_BufReadCmdHelpJump()
+ " This used to cause access to free memory
+ au BufReadCmd * e +h{
+ " } to fix highlighting
+ call assert_fails('help', 'E434:')
+
+ au! BufReadCmd
+endfunc
+
+" BufReadCmd is triggered for a "nofile" buffer. Check all values.
+func Test_BufReadCmdNofile()
+ for val in ['nofile',
+ \ 'nowrite',
+ \ 'acwrite',
+ \ 'quickfix',
+ \ 'help',
+ \ 'terminal',
+ \ 'prompt',
+ \ 'popup',
+ \ ]
+ new somefile
+ exe 'set buftype=' .. val
+ au BufReadCmd somefile call setline(1, 'triggered')
+ edit
+ call assert_equal('triggered', getline(1))
+
+ au! BufReadCmd
+ bwipe!
+ endfor
+endfunc
+
+func Test_augroup_deleted()
+ " This caused a crash before E936 was introduced
+ augroup x
+ call assert_fails('augroup! x', 'E936:')
+ au VimEnter * echo
+ augroup end
+ augroup! x
+ call assert_match("-Deleted-.*VimEnter", execute('au VimEnter'))
+ au! VimEnter
+endfunc
+
+" Tests for autocommands on :close command.
+" This used to be in test13.
+func Test_three_windows()
+ " Clean up buffers, because in some cases this function fails.
+ call s:cleanup_buffers()
+
+ " Write three files and open them, each in a window.
+ " Then go to next window, with autocommand that deletes the previous one.
+ " Do this twice, writing the file.
+ e! Xtestje1
+ call setline(1, 'testje1')
+ w
+ sp Xtestje2
+ call setline(1, 'testje2')
+ w
+ sp Xtestje3
+ call setline(1, 'testje3')
+ w
+ wincmd w
+ au WinLeave Xtestje2 bwipe
+ wincmd w
+ call assert_equal('Xtestje1', expand('%'))
+
+ au WinLeave Xtestje1 bwipe Xtestje3
+ close
+ call assert_equal('Xtestje1', expand('%'))
+
+ " Test deleting the buffer on a Unload event. If this goes wrong there
+ " will be the ATTENTION prompt.
+ e Xtestje1
+ au!
+ au! BufUnload Xtestje1 bwipe
+ call assert_fails('e Xtestje3', 'E937:')
+ call assert_equal('Xtestje3', expand('%'))
+
+ e Xtestje2
+ sp Xtestje1
+ call assert_fails('e', 'E937:')
+ call assert_equal('Xtestje1', expand('%'))
+
+ " Test changing buffers in a BufWipeout autocommand. If this goes wrong
+ " there are ml_line errors and/or a Crash.
+ au!
+ only
+ e Xanother
+ e Xtestje1
+ bwipe Xtestje2
+ bwipe Xtestje3
+ au BufWipeout Xtestje1 buf Xtestje1
+ bwipe
+ call assert_equal('Xanother', expand('%'))
+
+ only
+ help
+ wincmd w
+ 1quit
+ call assert_equal('Xanother', expand('%'))
+
+ au!
+ enew
+ call delete('Xtestje1')
+ call delete('Xtestje2')
+ call delete('Xtestje3')
+endfunc
+
+func Test_BufEnter()
+ au! BufEnter
+ au Bufenter * let val = val . '+'
+ let g:val = ''
+ split NewFile
+ call assert_equal('+', g:val)
+ bwipe!
+ call assert_equal('++', g:val)
+
+ " Also get BufEnter when editing a directory
+ call mkdir('Xbufenterdir', 'D')
+ split Xbufenterdir
+ call assert_equal('+++', g:val)
+
+ " On MS-Windows we can't edit the directory, make sure we wipe the right
+ " buffer.
+ bwipe! Xbufenterdir
+ au! BufEnter
+
+ " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
+ " for historic reasons. Also test other 'buftype' values.
+ for val in ['nofile',
+ \ 'nowrite',
+ \ 'acwrite',
+ \ 'quickfix',
+ \ 'help',
+ \ 'terminal',
+ \ 'prompt',
+ \ 'popup',
+ \ ]
+ new somefile
+ exe 'set buftype=' .. val
+ au BufEnter somefile call setline(1, 'some text')
+ edit
+ call assert_equal('some text', getline(1))
+ bwipe!
+ au! BufEnter
+ endfor
+
+ new
+ new
+ autocmd BufEnter * ++once close
+ call assert_fails('close', 'E1312:')
+
+ au! BufEnter
+ only
+endfunc
+
+" Closing a window might cause an endless loop
+" E814 for older Vims
+func Test_autocmd_bufwipe_in_SessLoadPost()
+ edit Xtest
+ tabnew
+ file Xsomething
+ set noswapfile
+ mksession!
+
+ let content =<< trim [CODE]
+ call test_override('ui_delay', 10)
+ set nocp noswapfile
+ let v:swapchoice = "e"
+ augroup test_autocmd_sessionload
+ autocmd!
+ autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"
+ augroup END
+
+ func WriteErrors()
+ call writefile([execute("messages")], "XerrorsBwipe")
+ endfunc
+ au VimLeave * call WriteErrors()
+ [CODE]
+
+ call writefile(content, 'Xvimrc', 'D')
+ call system(GetVimCommand('Xvimrc') .. ' --not-a-term --noplugins -S Session.vim -c cq')
+ sleep 100m
+ let errors = join(readfile('XerrorsBwipe'))
+ call assert_match('E814:', errors)
+
+ set swapfile
+ for file in ['Session.vim', 'XerrorsBwipe']
+ call delete(file)
+ endfor
+endfunc
+
+" Using :blast and :ball for many events caused a crash, because b_nwindows was
+" not incremented correctly.
+func Test_autocmd_blast_badd()
+ let content =<< trim [CODE]
+ au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast
+ edit foo1
+ au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball
+ edit foo2
+ call writefile(['OK'], 'XerrorsBlast')
+ qall
+ [CODE]
+
+ call writefile(content, 'XblastBall', 'D')
+ call system(GetVimCommand() .. ' --clean -S XblastBall')
+ sleep 100m
+ call assert_match('OK', readfile('XerrorsBlast')->join())
+
+ call delete('XerrorsBlast')
+endfunc
+
+" SEGV occurs in older versions.
+func Test_autocmd_bufwipe_in_SessLoadPost2()
+ tabnew
+ set noswapfile
+ mksession!
+
+ let content =<< trim [CODE]
+ set nocp noswapfile
+ function! DeleteInactiveBufs()
+ tabfirst
+ let tabblist = []
+ for i in range(1, tabpagenr(''$''))
+ call extend(tabblist, tabpagebuflist(i))
+ endfor
+ for b in range(1, bufnr(''$''))
+ if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'')
+ exec ''bwipeout '' . b
+ endif
+ endfor
+ echomsg "SessionLoadPost DONE"
+ endfunction
+ au SessionLoadPost * call DeleteInactiveBufs()
+
+ func WriteErrors()
+ call writefile([execute("messages")], "XerrorsPost")
+ endfunc
+ au VimLeave * call WriteErrors()
+ [CODE]
+
+ call writefile(content, 'Xvimrc', 'D')
+ call system(GetVimCommand('Xvimrc') .. ' --not-a-term --noplugins -S Session.vim -c cq')
+ sleep 100m
+ let errors = join(readfile('XerrorsPost'))
+ " This probably only ever matches on unix.
+ call assert_notmatch('Caught deadly signal SEGV', errors)
+ call assert_match('SessionLoadPost DONE', errors)
+
+ set swapfile
+ for file in ['Session.vim', 'XerrorsPost']
+ call delete(file)
+ endfor
+endfunc
+
+func Test_empty_doau()
+ doau \|
+endfunc
+
+func s:AutoCommandOptionSet(match)
+ let template = "Option: <%s>, OldVal: <%s>, OldValLocal: <%s>, OldValGlobal: <%s>, NewVal: <%s>, Scope: <%s>, Command: <%s>\n"
+ let item = remove(g:options, 0)
+ let expected = printf(template, item[0], item[1], item[2], item[3], item[4], item[5], item[6])
+ let actual = printf(template, a:match, v:option_old, v:option_oldlocal, v:option_oldglobal, v:option_new, v:option_type, v:option_command)
+ let g:opt = [expected, actual]
+ "call assert_equal(expected, actual)
+endfunc
+
+func Test_OptionSet()
+ CheckOption autochdir
+
+ badd test_autocmd.vim
+
+ call test_override('starting', 1)
+ set nocp
+ au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>"))
+
+ " 1: Setting number option"
+ let g:options = [['number', 0, 0, 0, 1, 'global', 'set']]
+ set nu
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 2: Setting local number option"
+ let g:options = [['number', 1, 1, '', 0, 'local', 'setlocal']]
+ setlocal nonu
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 3: Setting global number option"
+ let g:options = [['number', 1, '', 1, 0, 'global', 'setglobal']]
+ setglobal nonu
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 4: Setting local autoindent option"
+ let g:options = [['autoindent', 0, 0, '', 1, 'local', 'setlocal']]
+ setlocal ai
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 5: Setting global autoindent option"
+ let g:options = [['autoindent', 0, '', 0, 1, 'global', 'setglobal']]
+ setglobal ai
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 6: Setting global autoindent option"
+ let g:options = [['autoindent', 1, 1, 1, 0, 'global', 'set']]
+ set ai!
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 6a: Setting global autoindent option"
+ let g:options = [['autoindent', 1, 1, 0, 0, 'global', 'set']]
+ noa setlocal ai
+ noa setglobal noai
+ set ai!
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " Should not print anything, use :noa
+ " 7: don't trigger OptionSet"
+ let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']]
+ noa set nonu
+ call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 8: Setting several global list and number option"
+ let g:options = [['list', 0, 0, 0, 1, 'global', 'set'], ['number', 0, 0, 0, 1, 'global', 'set']]
+ set list nu
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 9: don't trigger OptionSet"
+ let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']]
+ noa set nolist nonu
+ call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 10: Setting global acd"
+ let g:options = [['autochdir', 0, 0, '', 1, 'local', 'setlocal']]
+ setlocal acd
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 11: Setting global autoread (also sets local value)"
+ let g:options = [['autoread', 0, 0, 0, 1, 'global', 'set']]
+ set ar
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 12: Setting local autoread"
+ let g:options = [['autoread', 1, 1, '', 1, 'local', 'setlocal']]
+ setlocal ar
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 13: Setting global autoread"
+ let g:options = [['autoread', 1, '', 1, 0, 'global', 'setglobal']]
+ setglobal invar
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 14: Setting option backspace through :let"
+ let g:options = [['backspace', '', '', '', 'eol,indent,start', 'global', 'set']]
+ let &bs = "eol,indent,start"
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 15: Setting option backspace through setbufvar()"
+ let g:options = [['backup', 0, 0, '', 1, 'local', 'setlocal']]
+ " try twice, first time, shouldn't trigger because option name is invalid,
+ " second time, it should trigger
+ let bnum = bufnr('%')
+ call assert_fails("call setbufvar(bnum, '&l:bk', 1)", 'E355:')
+ " should trigger, use correct option name
+ call setbufvar(bnum, '&backup', 1)
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 16: Setting number option using setwinvar"
+ let g:options = [['number', 0, 0, '', 1, 'local', 'setlocal']]
+ call setwinvar(0, '&number', 1)
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 17: Setting key option, shouldn't trigger"
+ let g:options = [['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']]
+ setlocal key=blah
+ setlocal key=
+ call assert_equal([['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 18a: Setting string global option"
+ let oldval = &backupext
+ let g:options = [['backupext', oldval, oldval, oldval, 'foo', 'global', 'set']]
+ set backupext=foo
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 18b: Resetting string global option"
+ let g:options = [['backupext', 'foo', 'foo', 'foo', oldval, 'global', 'set']]
+ set backupext&
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 18c: Setting global string global option"
+ let g:options = [['backupext', oldval, '', oldval, 'bar', 'global', 'setglobal']]
+ setglobal backupext=bar
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 18d: Setting local string global option"
+ " As this is a global option this sets the global value even though
+ " :setlocal is used!
+ noa set backupext& " Reset global and local value (without triggering autocmd)
+ let g:options = [['backupext', oldval, oldval, '', 'baz', 'local', 'setlocal']]
+ setlocal backupext=baz
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 18e: Setting again string global option"
+ noa setglobal backupext=ext_global " Reset global and local value (without triggering autocmd)
+ noa setlocal backupext=ext_local " Sets the global(!) value!
+ let g:options = [['backupext', 'ext_local', 'ext_local', 'ext_local', 'fuu', 'global', 'set']]
+ set backupext=fuu
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 19a: Setting string global-local (to buffer) option"
+ let oldval = &tags
+ let g:options = [['tags', oldval, oldval, oldval, 'tagpath', 'global', 'set']]
+ set tags=tagpath
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 19b: Resetting string global-local (to buffer) option"
+ let g:options = [['tags', 'tagpath', 'tagpath', 'tagpath', oldval, 'global', 'set']]
+ set tags&
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 19c: Setting global string global-local (to buffer) option "
+ let g:options = [['tags', oldval, '', oldval, 'tagpath1', 'global', 'setglobal']]
+ setglobal tags=tagpath1
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 19d: Setting local string global-local (to buffer) option"
+ let g:options = [['tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal']]
+ setlocal tags=tagpath2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 19e: Setting again string global-local (to buffer) option"
+ " Note: v:option_old is the old global value for global-local string options
+ " but the old local value for all other kinds of options.
+ noa setglobal tags=tag_global " Reset global and local value (without triggering autocmd)
+ noa setlocal tags=tag_local
+ let g:options = [['tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set']]
+ set tags=tagpath
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 19f: Setting string global-local (to buffer) option to an empty string"
+ " Note: v:option_old is the old global value for global-local string options
+ " but the old local value for all other kinds of options.
+ noa set tags=tag_global " Reset global and local value (without triggering autocmd)
+ noa setlocal tags= " empty string
+ let g:options = [['tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set']]
+ set tags=tagpath
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 20a: Setting string local (to buffer) option"
+ let oldval = &spelllang
+ let g:options = [['spelllang', oldval, oldval, oldval, 'elvish,klingon', 'global', 'set']]
+ set spelllang=elvish,klingon
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 20b: Resetting string local (to buffer) option"
+ let g:options = [['spelllang', 'elvish,klingon', 'elvish,klingon', 'elvish,klingon', oldval, 'global', 'set']]
+ set spelllang&
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 20c: Setting global string local (to buffer) option"
+ let g:options = [['spelllang', oldval, '', oldval, 'elvish', 'global', 'setglobal']]
+ setglobal spelllang=elvish
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 20d: Setting local string local (to buffer) option"
+ noa set spelllang& " Reset global and local value (without triggering autocmd)
+ let g:options = [['spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal']]
+ setlocal spelllang=klingon
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 20e: Setting again string local (to buffer) option"
+ " Note: v:option_old is the old global value for global-local string options
+ " but the old local value for all other kinds of options.
+ noa setglobal spelllang=spellglobal " Reset global and local value (without triggering autocmd)
+ noa setlocal spelllang=spelllocal
+ let g:options = [['spelllang', 'spelllocal', 'spelllocal', 'spellglobal', 'foo', 'global', 'set']]
+ set spelllang=foo
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 21a: Setting string global-local (to window) option"
+ let oldval = &statusline
+ let g:options = [['statusline', oldval, oldval, oldval, 'foo', 'global', 'set']]
+ set statusline=foo
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 21b: Resetting string global-local (to window) option"
+ " Note: v:option_old is the old global value for global-local string options
+ " but the old local value for all other kinds of options.
+ let g:options = [['statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set']]
+ set statusline&
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 21c: Setting global string global-local (to window) option"
+ let g:options = [['statusline', oldval, '', oldval, 'bar', 'global', 'setglobal']]
+ setglobal statusline=bar
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 21d: Setting local string global-local (to window) option"
+ noa set statusline& " Reset global and local value (without triggering autocmd)
+ let g:options = [['statusline', oldval, oldval, '', 'baz', 'local', 'setlocal']]
+ setlocal statusline=baz
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 21e: Setting again string global-local (to window) option"
+ " Note: v:option_old is the old global value for global-local string options
+ " but the old local value for all other kinds of options.
+ noa setglobal statusline=bar " Reset global and local value (without triggering autocmd)
+ noa setlocal statusline=baz
+ let g:options = [['statusline', 'bar', 'baz', 'bar', 'foo', 'global', 'set']]
+ set statusline=foo
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 22a: Setting string local (to window) option"
+ let oldval = &foldignore
+ let g:options = [['foldignore', oldval, oldval, oldval, 'fo', 'global', 'set']]
+ set foldignore=fo
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 22b: Resetting string local (to window) option"
+ let g:options = [['foldignore', 'fo', 'fo', 'fo', oldval, 'global', 'set']]
+ set foldignore&
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 22c: Setting global string local (to window) option"
+ let g:options = [['foldignore', oldval, '', oldval, 'bar', 'global', 'setglobal']]
+ setglobal foldignore=bar
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 22d: Setting local string local (to window) option"
+ noa set foldignore& " Reset global and local value (without triggering autocmd)
+ let g:options = [['foldignore', oldval, oldval, '', 'baz', 'local', 'setlocal']]
+ setlocal foldignore=baz
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 22e: Setting again string local (to window) option"
+ noa setglobal foldignore=glob " Reset global and local value (without triggering autocmd)
+ noa setlocal foldignore=loc
+ let g:options = [['foldignore', 'loc', 'loc', 'glob', 'fo', 'global', 'set']]
+ set foldignore=fo
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 23a: Setting global number global option"
+ noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal cmdheight=1 " Sets the global(!) value!
+ let g:options = [['cmdheight', '1', '', '1', '2', 'global', 'setglobal']]
+ setglobal cmdheight=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 23b: Setting local number global option"
+ noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal cmdheight=1 " Sets the global(!) value!
+ let g:options = [['cmdheight', '1', '1', '', '2', 'local', 'setlocal']]
+ setlocal cmdheight=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 23c: Setting again number global option"
+ noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal cmdheight=1 " Sets the global(!) value!
+ let g:options = [['cmdheight', '1', '1', '1', '2', 'global', 'set']]
+ set cmdheight=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 23d: Setting again number global option"
+ noa set cmdheight=8 " Reset global and local value (without triggering autocmd)
+ let g:options = [['cmdheight', '8', '8', '8', '2', 'global', 'set']]
+ set cmdheight=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 24a: Setting global number global-local (to buffer) option"
+ noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal undolevels=1
+ let g:options = [['undolevels', '8', '', '8', '2', 'global', 'setglobal']]
+ setglobal undolevels=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 24b: Setting local number global-local (to buffer) option"
+ noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal undolevels=1
+ let g:options = [['undolevels', '1', '1', '', '2', 'local', 'setlocal']]
+ setlocal undolevels=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 24c: Setting again number global-local (to buffer) option"
+ noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal undolevels=1
+ let g:options = [['undolevels', '1', '1', '8', '2', 'global', 'set']]
+ set undolevels=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 24d: Setting again global number global-local (to buffer) option"
+ noa set undolevels=8 " Reset global and local value (without triggering autocmd)
+ let g:options = [['undolevels', '8', '8', '8', '2', 'global', 'set']]
+ set undolevels=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 25a: Setting global number local (to buffer) option"
+ noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal wrapmargin=1
+ let g:options = [['wrapmargin', '8', '', '8', '2', 'global', 'setglobal']]
+ setglobal wrapmargin=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 25b: Setting local number local (to buffer) option"
+ noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal wrapmargin=1
+ let g:options = [['wrapmargin', '1', '1', '', '2', 'local', 'setlocal']]
+ setlocal wrapmargin=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 25c: Setting again number local (to buffer) option"
+ noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal wrapmargin=1
+ let g:options = [['wrapmargin', '1', '1', '8', '2', 'global', 'set']]
+ set wrapmargin=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 25d: Setting again global number local (to buffer) option"
+ noa set wrapmargin=8 " Reset global and local value (without triggering autocmd)
+ let g:options = [['wrapmargin', '8', '8', '8', '2', 'global', 'set']]
+ set wrapmargin=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 26: Setting number global-local (to window) option.
+ " Such option does currently not exist.
+
+
+ " 27a: Setting global number local (to window) option"
+ noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal foldcolumn=1
+ let g:options = [['foldcolumn', '8', '', '8', '2', 'global', 'setglobal']]
+ setglobal foldcolumn=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 27b: Setting local number local (to window) option"
+ noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal foldcolumn=1
+ let g:options = [['foldcolumn', '1', '1', '', '2', 'local', 'setlocal']]
+ setlocal foldcolumn=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 27c: Setting again number local (to window) option"
+ noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd)
+ noa setlocal foldcolumn=1
+ let g:options = [['foldcolumn', '1', '1', '8', '2', 'global', 'set']]
+ set foldcolumn=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 27d: Setting again global number local (to window) option"
+ noa set foldcolumn=8 " Reset global and local value (without triggering autocmd)
+ let g:options = [['foldcolumn', '8', '8', '8', '2', 'global', 'set']]
+ set foldcolumn=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 28a: Setting global boolean global option"
+ noa setglobal nowrapscan " Reset global and local value (without triggering autocmd)
+ noa setlocal wrapscan " Sets the global(!) value!
+ let g:options = [['wrapscan', '1', '', '1', '0', 'global', 'setglobal']]
+ setglobal nowrapscan
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 28b: Setting local boolean global option"
+ noa setglobal nowrapscan " Reset global and local value (without triggering autocmd)
+ noa setlocal wrapscan " Sets the global(!) value!
+ let g:options = [['wrapscan', '1', '1', '', '0', 'local', 'setlocal']]
+ setlocal nowrapscan
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 28c: Setting again boolean global option"
+ noa setglobal nowrapscan " Reset global and local value (without triggering autocmd)
+ noa setlocal wrapscan " Sets the global(!) value!
+ let g:options = [['wrapscan', '1', '1', '1', '0', 'global', 'set']]
+ set nowrapscan
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 28d: Setting again global boolean global option"
+ noa set nowrapscan " Reset global and local value (without triggering autocmd)
+ let g:options = [['wrapscan', '0', '0', '0', '1', 'global', 'set']]
+ set wrapscan
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 29a: Setting global boolean global-local (to buffer) option"
+ noa setglobal noautoread " Reset global and local value (without triggering autocmd)
+ noa setlocal autoread
+ let g:options = [['autoread', '0', '', '0', '1', 'global', 'setglobal']]
+ setglobal autoread
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 29b: Setting local boolean global-local (to buffer) option"
+ noa setglobal noautoread " Reset global and local value (without triggering autocmd)
+ noa setlocal autoread
+ let g:options = [['autoread', '1', '1', '', '0', 'local', 'setlocal']]
+ setlocal noautoread
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 29c: Setting again boolean global-local (to buffer) option"
+ noa setglobal noautoread " Reset global and local value (without triggering autocmd)
+ noa setlocal autoread
+ let g:options = [['autoread', '1', '1', '0', '1', 'global', 'set']]
+ set autoread
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 29d: Setting again global boolean global-local (to buffer) option"
+ noa set noautoread " Reset global and local value (without triggering autocmd)
+ let g:options = [['autoread', '0', '0', '0', '1', 'global', 'set']]
+ set autoread
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 30a: Setting global boolean local (to buffer) option"
+ noa setglobal nocindent " Reset global and local value (without triggering autocmd)
+ noa setlocal cindent
+ let g:options = [['cindent', '0', '', '0', '1', 'global', 'setglobal']]
+ setglobal cindent
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 30b: Setting local boolean local (to buffer) option"
+ noa setglobal nocindent " Reset global and local value (without triggering autocmd)
+ noa setlocal cindent
+ let g:options = [['cindent', '1', '1', '', '0', 'local', 'setlocal']]
+ setlocal nocindent
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 30c: Setting again boolean local (to buffer) option"
+ noa setglobal nocindent " Reset global and local value (without triggering autocmd)
+ noa setlocal cindent
+ let g:options = [['cindent', '1', '1', '0', '1', 'global', 'set']]
+ set cindent
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 30d: Setting again global boolean local (to buffer) option"
+ noa set nocindent " Reset global and local value (without triggering autocmd)
+ let g:options = [['cindent', '0', '0', '0', '1', 'global', 'set']]
+ set cindent
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 31: Setting boolean global-local (to window) option
+ " Currently no such option exists.
+
+
+ " 32a: Setting global boolean local (to window) option"
+ noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd)
+ noa setlocal cursorcolumn
+ let g:options = [['cursorcolumn', '0', '', '0', '1', 'global', 'setglobal']]
+ setglobal cursorcolumn
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 32b: Setting local boolean local (to window) option"
+ noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd)
+ noa setlocal cursorcolumn
+ let g:options = [['cursorcolumn', '1', '1', '', '0', 'local', 'setlocal']]
+ setlocal nocursorcolumn
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 32c: Setting again boolean local (to window) option"
+ noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd)
+ noa setlocal cursorcolumn
+ let g:options = [['cursorcolumn', '1', '1', '0', '1', 'global', 'set']]
+ set cursorcolumn
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+ " 32d: Setting again global boolean local (to window) option"
+ noa set nocursorcolumn " Reset global and local value (without triggering autocmd)
+ let g:options = [['cursorcolumn', '0', '0', '0', '1', 'global', 'set']]
+ set cursorcolumn
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " 33: Test autocommands when an option value is converted internally.
+ noa set backspace=1 " Reset global and local value (without triggering autocmd)
+ let g:options = [['backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set']]
+ set backspace=2
+ call assert_equal([], g:options)
+ call assert_equal(g:opt[0], g:opt[1])
+
+
+ " Cleanup
+ au! OptionSet
+ " set tags&
+ for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'backupext', 'tags', 'spelllang', 'statusline', 'foldignore', 'cmdheight', 'undolevels', 'wrapmargin', 'foldcolumn', 'wrapscan', 'autoread', 'cindent', 'cursorcolumn']
+ exe printf(":set %s&vim", opt)
+ endfor
+ call test_override('starting', 0)
+ delfunc! AutoCommandOptionSet
+endfunc
+
+func Test_OptionSet_diffmode()
+ call test_override('starting', 1)
+ " 18: Changing an option when entering diff mode
+ new
+ au OptionSet diff :let &l:cul = v:option_new
+
+ call setline(1, ['buffer 1', 'line2', 'line3', 'line4'])
+ call assert_equal(0, &l:cul)
+ diffthis
+ call assert_equal(1, &l:cul)
+
+ vnew
+ call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4'])
+ call assert_equal(0, &l:cul)
+ diffthis
+ call assert_equal(1, &l:cul)
+
+ diffoff
+ call assert_equal(0, &l:cul)
+ call assert_equal(1, getwinvar(2, '&l:cul'))
+ bw!
+
+ call assert_equal(1, &l:cul)
+ diffoff!
+ call assert_equal(0, &l:cul)
+ call assert_equal(0, getwinvar(1, '&l:cul'))
+ bw!
+
+ " Cleanup
+ au! OptionSet
+ call test_override('starting', 0)
+endfunc
+
+func Test_OptionSet_diffmode_close()
+ call test_override('starting', 1)
+ " 19: Try to close the current window when entering diff mode
+ " should not segfault
+ new
+ au OptionSet diff close
+
+ call setline(1, ['buffer 1', 'line2', 'line3', 'line4'])
+ call assert_fails(':diffthis', 'E788:')
+ call assert_equal(1, &diff)
+ vnew
+ call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4'])
+ call assert_fails(':diffthis', 'E788:')
+ call assert_equal(1, &diff)
+ set diffopt-=closeoff
+ bw!
+ call assert_fails(':diffoff!', 'E788:')
+ bw!
+
+ " Cleanup
+ au! OptionSet
+ call test_override('starting', 0)
+ "delfunc! AutoCommandOptionSet
+endfunc
+
+" Test for Bufleave autocommand that deletes the buffer we are about to edit.
+func Test_BufleaveWithDelete()
+ new | edit XbufLeave1
+
+ augroup test_bufleavewithdelete
+ autocmd!
+ autocmd BufLeave XbufLeave1 bwipe XbufLeave2
+ augroup END
+
+ call assert_fails('edit XbufLeave2', 'E143:')
+ call assert_equal('XbufLeave1', bufname('%'))
+
+ autocmd! test_bufleavewithdelete BufLeave XbufLeave1
+ augroup! test_bufleavewithdelete
+
+ new
+ bwipe! XbufLeave1
+endfunc
+
+" Test for autocommand that changes the buffer list, when doing ":ball".
+func Test_Acmd_BufAll()
+ enew!
+ %bwipe!
+ call writefile(['Test file Xxx1'], 'Xxx1', 'D')
+ call writefile(['Test file Xxx2'], 'Xxx2', 'D')
+ call writefile(['Test file Xxx3'], 'Xxx3', 'D')
+
+ " Add three files to the buffer list
+ split Xxx1
+ close
+ split Xxx2
+ close
+ split Xxx3
+ close
+
+ " Wipe the buffer when the buffer is opened
+ au BufReadPost Xxx2 bwipe
+
+ call append(0, 'Test file Xxx4')
+ ball
+
+ call assert_equal(2, winnr('$'))
+ call assert_equal('Xxx1', bufname(winbufnr(winnr('$'))))
+ wincmd t
+
+ au! BufReadPost
+ %bwipe!
+ enew! | only
+endfunc
+
+" Test for autocommand that changes current buffer on BufEnter event.
+" Check if modelines are interpreted for the correct buffer.
+func Test_Acmd_BufEnter()
+ %bwipe!
+ call writefile(['start of test file Xxx1',
+ \ "\<Tab>this is a test",
+ \ 'end of test file Xxx1'], 'Xxx1', 'D')
+ call writefile(['start of test file Xxx2',
+ \ 'vim: set noai :',
+ \ "\<Tab>this is a test",
+ \ 'end of test file Xxx2'], 'Xxx2', 'D')
+
+ au BufEnter Xxx2 brew
+ set ai modeline modelines=3
+ edit Xxx1
+ " edit Xxx2, autocmd will do :brew
+ edit Xxx2
+ exe "normal G?this is a\<CR>"
+ " Append text with autoindent to this file
+ normal othis should be auto-indented
+ call assert_equal("\<Tab>this should be auto-indented", getline('.'))
+ call assert_equal(3, line('.'))
+ " Remove autocmd and edit Xxx2 again
+ au! BufEnter Xxx2
+ buf! Xxx2
+ exe "normal G?this is a\<CR>"
+ " append text without autoindent to Xxx
+ normal othis should be in column 1
+ call assert_equal("this should be in column 1", getline('.'))
+ call assert_equal(4, line('.'))
+
+ %bwipe!
+ set ai&vim modeline&vim modelines&vim
+endfunc
+
+" Test for issue #57
+" do not move cursor on <c-o> when autoindent is set
+func Test_ai_CTRL_O()
+ enew!
+ set ai
+ let save_fo = &fo
+ set fo+=r
+ exe "normal o# abcdef\<Esc>2hi\<CR>\<C-O>d0\<Esc>"
+ exe "normal o# abcdef\<Esc>2hi\<C-O>d0\<Esc>"
+ call assert_equal(['# abc', 'def', 'def'], getline(2, 4))
+
+ set ai&vim
+ let &fo = save_fo
+ enew!
+endfunc
+
+" Test for autocommand that deletes the current buffer on BufLeave event.
+" Also test deleting the last buffer, should give a new, empty buffer.
+func Test_BufLeave_Wipe()
+ %bwipe!
+ let content = ['start of test file Xxx',
+ \ 'this is a test',
+ \ 'end of test file Xxx']
+ call writefile(content, 'Xxx1', 'D')
+ call writefile(content, 'Xxx2', 'D')
+
+ au BufLeave Xxx2 bwipe
+ edit Xxx1
+ split Xxx2
+ " delete buffer Xxx2, we should be back to Xxx1
+ bwipe
+ call assert_equal('Xxx1', bufname('%'))
+ call assert_equal(1, winnr('$'))
+
+ " Create an alternate buffer
+ %write! test.out
+ call assert_equal('test.out', bufname('#'))
+ " delete alternate buffer
+ bwipe test.out
+ call assert_equal('Xxx1', bufname('%'))
+ call assert_equal('', bufname('#'))
+
+ au BufLeave Xxx1 bwipe
+ " delete current buffer, get an empty one
+ bwipe!
+ call assert_equal(1, line('$'))
+ call assert_equal('', bufname('%'))
+ let g:bufinfo = getbufinfo()
+ call assert_equal(1, len(g:bufinfo))
+
+ call delete('test.out')
+ %bwipe
+ au! BufLeave
+
+ " check that bufinfo doesn't contain a pointer to freed memory
+ call test_garbagecollect_now()
+endfunc
+
+func Test_QuitPre()
+ edit Xfoo
+ let winid = win_getid(winnr())
+ split Xbar
+ au! QuitPre * let g:afile = expand('<afile>')
+ " Close the other window, <afile> should be correct.
+ exe win_id2win(winid) . 'q'
+ call assert_equal('Xfoo', g:afile)
+
+ unlet g:afile
+ bwipe Xfoo
+ bwipe Xbar
+endfunc
+
+func Test_Cmdline()
+ au! CmdlineChanged : let g:text = getcmdline()
+ let g:text = 0
+ call feedkeys(":echom 'hello'\<CR>", 'xt')
+ call assert_equal("echom 'hello'", g:text)
+ au! CmdlineChanged
+
+ au! CmdlineChanged : let g:entered = expand('<afile>')
+ let g:entered = 0
+ call feedkeys(":echom 'hello'\<CR>", 'xt')
+ call assert_equal(':', g:entered)
+ au! CmdlineChanged
+
+ autocmd CmdlineChanged : let g:log += [getcmdline()]
+
+ let g:log = []
+ cnoremap <F1> <Cmd>call setcmdline('ls')<CR>
+ call feedkeys(":\<F1>", 'xt')
+ call assert_equal(['ls'], g:log)
+ cunmap <F1>
+
+ let g:log = []
+ call feedkeys(":sign \<Tab>\<Tab>\<C-N>\<C-P>\<S-Tab>\<S-Tab>\<Esc>", 'xt')
+ call assert_equal([
+ \ 's',
+ \ 'si',
+ \ 'sig',
+ \ 'sign',
+ \ 'sign ',
+ \ 'sign define',
+ \ 'sign jump',
+ \ 'sign list',
+ \ 'sign jump',
+ \ 'sign define',
+ \ 'sign ',
+ \ ], g:log)
+ let g:log = []
+ set wildmenu wildoptions+=pum
+ call feedkeys(":sign \<S-Tab>\<PageUp>\<kPageUp>\<kPageDown>\<PageDown>\<Esc>", 'xt')
+ call assert_equal([
+ \ 's',
+ \ 'si',
+ \ 'sig',
+ \ 'sign',
+ \ 'sign ',
+ \ 'sign unplace',
+ \ 'sign jump',
+ \ 'sign define',
+ \ 'sign undefine',
+ \ 'sign unplace',
+ \ ], g:log)
+ set wildmenu& wildoptions&
+
+ let g:log = []
+ let @r = 'abc'
+ call feedkeys(":0\<C-R>r1\<C-R>\<C-O>r2\<C-R>\<C-R>r3\<Esc>", 'xt')
+ call assert_equal([
+ \ '0',
+ \ '0a',
+ \ '0ab',
+ \ '0abc',
+ \ '0abc1',
+ \ '0abc1abc',
+ \ '0abc1abc2',
+ \ '0abc1abc2abc',
+ \ '0abc1abc2abc3',
+ \ ], g:log)
+
+ unlet g:log
+ au! CmdlineChanged
+
+ au! CmdlineEnter : let g:entered = expand('<afile>')
+ au! CmdlineLeave : let g:left = expand('<afile>')
+ let g:entered = 0
+ let g:left = 0
+ call feedkeys(":echo 'hello'\<CR>", 'xt')
+ call assert_equal(':', g:entered)
+ call assert_equal(':', g:left)
+ au! CmdlineEnter
+ au! CmdlineLeave
+
+ let save_shellslash = &shellslash
+ set noshellslash
+ au! CmdlineEnter / let g:entered = expand('<afile>')
+ au! CmdlineLeave / let g:left = expand('<afile>')
+ let g:entered = 0
+ let g:left = 0
+ new
+ call setline(1, 'hello')
+ call feedkeys("/hello\<CR>", 'xt')
+ call assert_equal('/', g:entered)
+ call assert_equal('/', g:left)
+ bwipe!
+ au! CmdlineEnter
+ au! CmdlineLeave
+ let &shellslash = save_shellslash
+endfunc
+
+" Test for BufWritePre autocommand that deletes or unloads the buffer.
+func Test_BufWritePre()
+ %bwipe
+ au BufWritePre Xxx1 bunload
+ au BufWritePre Xxx2 bwipe
+
+ call writefile(['start of Xxx1', 'test', 'end of Xxx1'], 'Xxx1', 'D')
+ call writefile(['start of Xxx2', 'test', 'end of Xxx2'], 'Xxx2', 'D')
+
+ edit Xtest
+ e! Xxx2
+ bdel Xtest
+ e Xxx1
+ " write it, will unload it and give an error msg
+ call assert_fails('w', 'E203:')
+ call assert_equal('Xxx2', bufname('%'))
+ edit Xtest
+ e! Xxx2
+ bwipe Xtest
+ " write it, will delete the buffer and give an error msg
+ call assert_fails('w', 'E203:')
+ call assert_equal('Xxx1', bufname('%'))
+ au! BufWritePre
+endfunc
+
+" Test for BufUnload autocommand that unloads all the other buffers
+func Test_bufunload_all()
+ let g:test_is_flaky = 1
+ call writefile(['Test file Xxx1'], 'Xxx1', 'D')"
+ call writefile(['Test file Xxx2'], 'Xxx2', 'D')"
+
+ let content =<< trim [CODE]
+ func UnloadAllBufs()
+ let i = 1
+ while i <= bufnr('$')
+ if i != bufnr('%') && bufloaded(i)
+ exe i . 'bunload'
+ endif
+ let i += 1
+ endwhile
+ endfunc
+ au BufUnload * call UnloadAllBufs()
+ au VimLeave * call writefile(['Test Finished'], 'Xout')
+ edit Xxx1
+ split Xxx2
+ q
+ [CODE]
+
+ call writefile(content, 'Xbunloadtest', 'D')
+
+ call delete('Xout')
+ call system(GetVimCommandClean() .. ' -N --not-a-term -S Xbunloadtest')
+ call assert_true(filereadable('Xout'))
+
+ call delete('Xout')
+endfunc
+
+" Some tests for buffer-local autocommands
+func Test_buflocal_autocmd()
+ let g:bname = ''
+ edit xx
+ au BufLeave <buffer> let g:bname = expand("%")
+ " here, autocommand for xx should trigger.
+ " but autocommand shall not apply to buffer named <buffer>.
+ edit somefile
+ call assert_equal('xx', g:bname)
+ let g:bname = ''
+ " here, autocommand shall be auto-deleted
+ bwipe xx
+ " autocmd should not trigger
+ edit xx
+ call assert_equal('', g:bname)
+ " autocmd should not trigger
+ edit somefile
+ call assert_equal('', g:bname)
+ enew
+ unlet g:bname
+endfunc
+
+" Test for "*Cmd" autocommands
+func Test_Cmd_Autocmds()
+ call writefile(['start of Xxx', "\tabc2", 'end of Xxx'], 'Xxx', 'D')
+
+ enew!
+ au BufReadCmd XtestA 0r Xxx|$del
+ edit XtestA " will read text of Xxd instead
+ call assert_equal('start of Xxx', getline(1))
+
+ au BufWriteCmd XtestA call append(line("$"), "write")
+ write " will append a line to the file
+ call assert_equal('write', getline('$'))
+ call assert_fails('read XtestA', 'E484:') " should not read anything
+ call assert_equal('write', getline(4))
+
+ " now we have:
+ " 1 start of Xxx
+ " 2 abc2
+ " 3 end of Xxx
+ " 4 write
+
+ au FileReadCmd XtestB '[r Xxx
+ 2r XtestB " will read Xxx below line 2 instead
+ call assert_equal('start of Xxx', getline(3))
+
+ " now we have:
+ " 1 start of Xxx
+ " 2 abc2
+ " 3 start of Xxx
+ " 4 abc2
+ " 5 end of Xxx
+ " 6 end of Xxx
+ " 7 write
+
+ au FileWriteCmd XtestC '[,']copy $
+ normal 4GA1
+ 4,5w XtestC " will copy lines 4 and 5 to the end
+ call assert_equal("\tabc21", getline(8))
+ call assert_fails('r XtestC', 'E484:') " should not read anything
+ call assert_equal("end of Xxx", getline(9))
+
+ " now we have:
+ " 1 start of Xxx
+ " 2 abc2
+ " 3 start of Xxx
+ " 4 abc21
+ " 5 end of Xxx
+ " 6 end of Xxx
+ " 7 write
+ " 8 abc21
+ " 9 end of Xxx
+
+ let g:lines = []
+ au FileAppendCmd XtestD call extend(g:lines, getline(line("'["), line("']")))
+ w >>XtestD " will add lines to 'lines'
+ call assert_equal(9, len(g:lines))
+ call assert_fails('$r XtestD', 'E484:') " should not read anything
+ call assert_equal(9, line('$'))
+ call assert_equal('end of Xxx', getline('$'))
+
+ au BufReadCmd XtestE 0r Xxx|$del
+ sp XtestE " split window with test.out
+ call assert_equal('end of Xxx', getline(3))
+
+ let g:lines = []
+ exe "normal 2Goasdf\<Esc>\<C-W>\<C-W>"
+ au BufWriteCmd XtestE call extend(g:lines, getline(0, '$'))
+ wall " will write other window to 'lines'
+ call assert_equal(4, len(g:lines), g:lines)
+ call assert_equal('asdf', g:lines[2])
+
+ au! BufReadCmd
+ au! BufWriteCmd
+ au! FileReadCmd
+ au! FileWriteCmd
+ au! FileAppendCmd
+ %bwipe!
+ enew!
+endfunc
+
+func s:ReadFile()
+ setl noswapfile nomodified
+ let filename = resolve(expand("<afile>:p"))
+ execute 'read' fnameescape(filename)
+ 1d_
+ exe 'file' fnameescape(filename)
+ setl buftype=acwrite
+endfunc
+
+func s:WriteFile()
+ let filename = resolve(expand("<afile>:p"))
+ setl buftype=
+ noautocmd execute 'write' fnameescape(filename)
+ setl buftype=acwrite
+ setl nomodified
+endfunc
+
+func Test_BufReadCmd()
+ autocmd BufReadCmd *.test call s:ReadFile()
+ autocmd BufWriteCmd *.test call s:WriteFile()
+
+ call writefile(['one', 'two', 'three'], 'Xcmd.test', 'D')
+ edit Xcmd.test
+ call assert_match('Xcmd.test" line 1 of 3', execute('file'))
+ normal! Gofour
+ write
+ call assert_equal(['one', 'two', 'three', 'four'], readfile('Xcmd.test'))
+
+ bwipe!
+ au! BufReadCmd
+ au! BufWriteCmd
+endfunc
+
+func Test_BufWriteCmd()
+ autocmd BufWriteCmd Xbufwritecmd let g:written = 1
+ new
+ file Xbufwritecmd
+ set buftype=acwrite
+ call mkdir('Xbufwritecmd', 'D')
+ write
+ " BufWriteCmd should be triggered even if a directory has the same name
+ call assert_equal(1, g:written)
+ unlet g:written
+ au! BufWriteCmd
+ bwipe!
+endfunc
+
+func SetChangeMarks(start, end)
+ exe a:start .. 'mark ['
+ exe a:end .. 'mark ]'
+endfunc
+
+" Verify the effects of autocmds on '[ and ']
+func Test_change_mark_in_autocmds()
+ edit! Xtest
+ call feedkeys("ia\<CR>b\<CR>c\<CR>d\<C-g>u\<Esc>", 'xtn')
+
+ call SetChangeMarks(2, 3)
+ write
+ call assert_equal([1, 4], [line("'["), line("']")])
+
+ call SetChangeMarks(2, 3)
+ au BufWritePre * call assert_equal([1, 4], [line("'["), line("']")])
+ write
+ au! BufWritePre
+
+ if has('unix')
+ write XtestFilter
+ write >> XtestFilter
+
+ call SetChangeMarks(2, 3)
+ " Marks are set to the entire range of the write
+ au FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")])
+ " '[ is adjusted to just before the line that will receive the filtered
+ " data
+ au FilterReadPre * call assert_equal([4, 4], [line("'["), line("']")])
+ " The filtered data is read into the buffer, and the source lines are
+ " still present, so the range is after the source lines
+ au FilterReadPost * call assert_equal([5, 12], [line("'["), line("']")])
+ %!cat XtestFilter
+ " After the filtered data is read, the original lines are deleted
+ call assert_equal([1, 8], [line("'["), line("']")])
+ au! FilterWritePre,FilterReadPre,FilterReadPost
+ undo
+
+ call SetChangeMarks(1, 4)
+ au FilterWritePre * call assert_equal([2, 3], [line("'["), line("']")])
+ au FilterReadPre * call assert_equal([3, 3], [line("'["), line("']")])
+ au FilterReadPost * call assert_equal([4, 11], [line("'["), line("']")])
+ 2,3!cat XtestFilter
+ call assert_equal([2, 9], [line("'["), line("']")])
+ au! FilterWritePre,FilterReadPre,FilterReadPost
+ undo
+
+ call delete('XtestFilter')
+ endif
+
+ call SetChangeMarks(1, 4)
+ au FileWritePre * call assert_equal([2, 3], [line("'["), line("']")])
+ 2,3write Xtest2
+ au! FileWritePre
+
+ call SetChangeMarks(2, 3)
+ au FileAppendPre * call assert_equal([1, 4], [line("'["), line("']")])
+ write >> Xtest2
+ au! FileAppendPre
+
+ call SetChangeMarks(1, 4)
+ au FileAppendPre * call assert_equal([2, 3], [line("'["), line("']")])
+ 2,3write >> Xtest2
+ au! FileAppendPre
+
+ call SetChangeMarks(1, 1)
+ au FileReadPre * call assert_equal([3, 1], [line("'["), line("']")])
+ au FileReadPost * call assert_equal([4, 11], [line("'["), line("']")])
+ 3read Xtest2
+ au! FileReadPre,FileReadPost
+ undo
+
+ call SetChangeMarks(4, 4)
+ " When the line is 0, it's adjusted to 1
+ au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")])
+ au FileReadPost * call assert_equal([1, 8], [line("'["), line("']")])
+ 0read Xtest2
+ au! FileReadPre,FileReadPost
+ undo
+
+ call SetChangeMarks(4, 4)
+ " When the line is 0, it's adjusted to 1
+ au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")])
+ au FileReadPost * call assert_equal([2, 9], [line("'["), line("']")])
+ 1read Xtest2
+ au! FileReadPre,FileReadPost
+ undo
+
+ bwipe!
+ call delete('Xtest')
+ call delete('Xtest2')
+endfunc
+
+func Test_Filter_noshelltemp()
+ CheckExecutable cat
+
+ enew!
+ call setline(1, ['a', 'b', 'c', 'd'])
+
+ let shelltemp = &shelltemp
+ set shelltemp
+
+ let g:filter_au = 0
+ au FilterWritePre * let g:filter_au += 1
+ au FilterReadPre * let g:filter_au += 1
+ au FilterReadPost * let g:filter_au += 1
+ %!cat
+ call assert_equal(3, g:filter_au)
+
+ if has('filterpipe')
+ set noshelltemp
+
+ let g:filter_au = 0
+ au FilterWritePre * let g:filter_au += 1
+ au FilterReadPre * let g:filter_au += 1
+ au FilterReadPost * let g:filter_au += 1
+ %!cat
+ call assert_equal(0, g:filter_au)
+ endif
+
+ au! FilterWritePre,FilterReadPre,FilterReadPost
+ let &shelltemp = shelltemp
+ bwipe!
+endfunc
+
+func Test_TextYankPost()
+ enew!
+ call setline(1, ['foo'])
+
+ let g:event = []
+ au TextYankPost * let g:event = copy(v:event)
+
+ call assert_equal({}, v:event)
+ call assert_fails('let v:event = {}', 'E46:')
+ call assert_fails('let v:event.mykey = 0', 'E742:')
+
+ norm "ayiw
+ call assert_equal(
+ \ #{regcontents: ['foo'], regname: 'a', operator: 'y',
+ \ regtype: 'v', visual: v:false, inclusive: v:true},
+ \ g:event)
+ norm y_
+ call assert_equal(
+ \ #{regcontents: ['foo'], regname: '', operator: 'y', regtype: 'V',
+ \ visual: v:false, inclusive: v:false},
+ \ g:event)
+ norm Vy
+ call assert_equal(
+ \ #{regcontents: ['foo'], regname: '', operator: 'y', regtype: 'V',
+ \ visual: v:true, inclusive: v:true},
+ \ g:event)
+ call feedkeys("\<C-V>y", 'x')
+ call assert_equal(
+ \ #{regcontents: ['f'], regname: '', operator: 'y', regtype: "\x161",
+ \ visual: v:true, inclusive: v:true},
+ \ g:event)
+ norm "xciwbar
+ call assert_equal(
+ \ #{regcontents: ['foo'], regname: 'x', operator: 'c', regtype: 'v',
+ \ visual: v:false, inclusive: v:true},
+ \ g:event)
+ norm "bdiw
+ call assert_equal(
+ \ #{regcontents: ['bar'], regname: 'b', operator: 'd', regtype: 'v',
+ \ visual: v:false, inclusive: v:true},
+ \ g:event)
+
+ call setline(1, 'foobar')
+ " exclusive motion
+ norm $"ay0
+ call assert_equal(
+ \ #{regcontents: ['fooba'], regname: 'a', operator: 'y', regtype: 'v',
+ \ visual: v:false, inclusive: v:false},
+ \ g:event)
+ " inclusive motion
+ norm 0"ay$
+ call assert_equal(
+ \ #{regcontents: ['foobar'], regname: 'a', operator: 'y', regtype: 'v',
+ \ visual: v:false, inclusive: v:true},
+ \ g:event)
+
+ call assert_equal({}, v:event)
+
+ if has('clipboard_working') && !has('gui_running')
+ " Test that when the visual selection is automatically copied to clipboard
+ " register a TextYankPost is emitted
+ call setline(1, ['foobar'])
+
+ let @* = ''
+ set clipboard=autoselect
+ exe "norm! ggviw\<Esc>"
+ call assert_equal(
+ \ #{regcontents: ['foobar'], regname: '*', operator: 'y',
+ \ regtype: 'v', visual: v:true, inclusive: v:false},
+ \ g:event)
+
+ let @+ = ''
+ set clipboard=autoselectplus
+ exe "norm! ggviw\<Esc>"
+ call assert_equal(
+ \ #{regcontents: ['foobar'], regname: '+', operator: 'y',
+ \ regtype: 'v', visual: v:true, inclusive: v:false},
+ \ g:event)
+
+ set clipboard&vim
+ endif
+
+ au! TextYankPost
+ unlet g:event
+ bwipe!
+endfunc
+
+func Test_autocommand_all_events()
+ call assert_fails('au * * bwipe', 'E1155:')
+ call assert_fails('au * x bwipe', 'E1155:')
+ call assert_fails('au! * x bwipe', 'E1155:')
+endfunc
+
+func Test_autocmd_user()
+ au User MyEvent let s:res = [expand("<afile>"), expand("<amatch>")]
+ doautocmd User MyEvent
+ call assert_equal(['MyEvent', 'MyEvent'], s:res)
+ au! User
+ unlet s:res
+endfunc
+
+func Test_autocmd_user_clear_group()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ autocmd! User
+ for i in range(1, 999)
+ exe 'autocmd User ' .. 'Foo' .. i .. ' bar'
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('autocmd! User')})
+ END
+ call writefile(lines, 'XautoUser', 'D')
+ let buf = RunVimInTerminal('-S XautoUser', {'rows': 10})
+
+ " this was using freed memory
+ call term_sendkeys(buf, ":autocmd User\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "G")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_autocmd_CmdlineLeave_unlet()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ for i in range(1, 999)
+ exe 'let g:var' .. i '=' i
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('unlet g:var990')})
+ END
+ call writefile(lines, 'XleaveUnlet', 'D')
+ let buf = RunVimInTerminal('-S XleaveUnlet', {'rows': 10})
+
+ " this was using freed memory
+ call term_sendkeys(buf, ":let g:\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "G")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>") " for the hit-enter prompt
+
+ call StopVimInTerminal(buf)
+endfunc
+
+function s:Before_test_dirchanged()
+ augroup test_dirchanged
+ autocmd!
+ augroup END
+ let s:li = []
+ let s:dir_this = getcwd()
+ let s:dir_foo = s:dir_this . '/Xfoo'
+ call mkdir(s:dir_foo)
+ let s:dir_bar = s:dir_this . '/Xbar'
+ call mkdir(s:dir_bar)
+endfunc
+
+function s:After_test_dirchanged()
+ call chdir(s:dir_this)
+ call delete(s:dir_foo, 'd')
+ call delete(s:dir_bar, 'd')
+ augroup test_dirchanged
+ autocmd!
+ augroup END
+endfunc
+
+function Test_dirchanged_global()
+ call s:Before_test_dirchanged()
+ autocmd test_dirchanged DirChangedPre global call add(s:li, expand("<amatch>") .. " pre cd " .. v:event.directory)
+ autocmd test_dirchanged DirChanged global call add(s:li, "cd:")
+ autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>"))
+ call chdir(s:dir_foo)
+ let expected = ["global pre cd " .. s:dir_foo, "cd:", s:dir_foo]
+ call assert_equal(expected, s:li)
+ call chdir(s:dir_foo)
+ call assert_equal(expected, s:li)
+ exe 'lcd ' .. fnameescape(s:dir_bar)
+ call assert_equal(expected, s:li)
+
+ exe 'cd ' .. s:dir_foo
+ exe 'cd ' .. s:dir_bar
+ autocmd! test_dirchanged DirChanged global let g:result = expand("<afile>")
+ cd -
+ call assert_equal(s:dir_foo, substitute(g:result, '\\', '/', 'g'))
+
+ call s:After_test_dirchanged()
+endfunc
+
+function Test_dirchanged_local()
+ call s:Before_test_dirchanged()
+ autocmd test_dirchanged DirChanged window call add(s:li, "lcd:")
+ autocmd test_dirchanged DirChanged window call add(s:li, expand("<afile>"))
+ call chdir(s:dir_foo)
+ call assert_equal([], s:li)
+ exe 'lcd ' .. fnameescape(s:dir_bar)
+ call assert_equal(["lcd:", s:dir_bar], s:li)
+ exe 'lcd ' .. fnameescape(s:dir_bar)
+ call assert_equal(["lcd:", s:dir_bar], s:li)
+ call s:After_test_dirchanged()
+endfunc
+
+function Test_dirchanged_auto()
+ CheckOption autochdir
+ call s:Before_test_dirchanged()
+ call test_autochdir()
+ autocmd test_dirchanged DirChangedPre auto call add(s:li, "pre cd " .. v:event.directory)
+ autocmd test_dirchanged DirChanged auto call add(s:li, "auto:")
+ autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>"))
+ set acd
+ cd ..
+ call assert_equal([], s:li)
+ exe 'edit ' . s:dir_foo . '/Xautofile'
+ call assert_equal(s:dir_foo, getcwd())
+ let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo]
+ call assert_equal(expected, s:li)
+ set noacd
+ bwipe!
+ call s:After_test_dirchanged()
+endfunc
+
+" Test TextChangedI and TextChangedP
+func Test_ChangedP()
+ new
+ call setline(1, ['foo', 'bar', 'foobar'])
+ call test_override("char_avail", 1)
+ set complete=. completeopt=menuone
+
+ func! TextChangedAutocmd(char)
+ let g:autocmd .= a:char
+ endfunc
+
+ " TextChanged will not be triggered, only check that it isn't.
+ au! TextChanged <buffer> :call TextChangedAutocmd('N')
+ au! TextChangedI <buffer> :call TextChangedAutocmd('I')
+ au! TextChangedP <buffer> :call TextChangedAutocmd('P')
+
+ call cursor(3, 1)
+ let g:autocmd = ''
+ call feedkeys("o\<esc>", 'tnix')
+ call assert_equal('I', g:autocmd)
+
+ let g:autocmd = ''
+ call feedkeys("Sf", 'tnix')
+ call assert_equal('II', g:autocmd)
+
+ let g:autocmd = ''
+ call feedkeys("Sf\<C-N>", 'tnix')
+ call assert_equal('IIP', g:autocmd)
+
+ let g:autocmd = ''
+ call feedkeys("Sf\<C-N>\<C-N>", 'tnix')
+ call assert_equal('IIPP', g:autocmd)
+
+ let g:autocmd = ''
+ call feedkeys("Sf\<C-N>\<C-N>\<C-N>", 'tnix')
+ call assert_equal('IIPPP', g:autocmd)
+
+ let g:autocmd = ''
+ call feedkeys("Sf\<C-N>\<C-N>\<C-N>\<C-N>", 'tnix')
+ call assert_equal('IIPPPP', g:autocmd)
+
+ call assert_equal(['foo', 'bar', 'foobar', 'foo'], getline(1, '$'))
+ " TODO: how should it handle completeopt=noinsert,noselect?
+
+ " CleanUp
+ call test_override("char_avail", 0)
+ au! TextChanged
+ au! TextChangedI
+ au! TextChangedP
+ delfu TextChangedAutocmd
+ unlet! g:autocmd
+ set complete&vim completeopt&vim
+
+ bw!
+endfunc
+
+let g:setline_handled = v:false
+func SetLineOne()
+ if !g:setline_handled
+ call setline(1, "(x)")
+ let g:setline_handled = v:true
+ endif
+endfunc
+
+func Test_TextChangedI_with_setline()
+ new
+ call test_override('char_avail', 1)
+ autocmd TextChangedI <buffer> call SetLineOne()
+ call feedkeys("i(\<CR>\<Esc>", 'tx')
+ call assert_equal('(', getline(1))
+ call assert_equal('x)', getline(2))
+ undo
+ call assert_equal('', getline(1))
+ call assert_equal('', getline(2))
+
+ call test_override('char_avail', 0)
+ bwipe!
+endfunc
+
+func Test_Changed_FirstTime()
+ CheckFeature terminal
+ CheckNotGui
+ " Starting a terminal to run Vim is always considered flaky.
+ let g:test_is_flaky = 1
+
+ " Prepare file for TextChanged event.
+ call writefile([''], 'Xchanged.txt', 'D')
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3})
+ call assert_equal('running', term_getstatus(buf))
+ " Wait for the ruler (in the status line) to be shown.
+ " In ConPTY, there is additional character which is drawn up to the width of
+ " the screen.
+ if has('conpty')
+ call WaitForAssert({-> assert_match('\<All.*$', term_getline(buf, 3))})
+ else
+ call WaitForAssert({-> assert_match('\<All$', term_getline(buf, 3))})
+ endif
+ " It's only adding autocmd, so that no event occurs.
+ call term_sendkeys(buf, ":au! TextChanged <buffer> call writefile(['No'], 'Xchanged.txt')\<cr>")
+ call term_sendkeys(buf, "\<C-\\>\<C-N>:qa!\<cr>")
+ call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
+ call assert_equal([''], readfile('Xchanged.txt'))
+
+ " clean up
+ bwipe!
+endfunc
+
+func Test_autocmd_nested()
+ let g:did_nested = 0
+ augroup Testing
+ au WinNew * edit somefile
+ au BufNew * let g:did_nested = 1
+ augroup END
+ split
+ call assert_equal(0, g:did_nested)
+ close
+ bwipe! somefile
+
+ " old nested argument still works
+ augroup Testing
+ au!
+ au WinNew * nested edit somefile
+ au BufNew * let g:did_nested = 1
+ augroup END
+ split
+ call assert_equal(1, g:did_nested)
+ close
+ bwipe! somefile
+
+ " New ++nested argument works
+ augroup Testing
+ au!
+ au WinNew * ++nested edit somefile
+ au BufNew * let g:did_nested = 1
+ augroup END
+ split
+ call assert_equal(1, g:did_nested)
+ close
+ bwipe! somefile
+
+ " nested without ++ does not work in Vim9 script
+ call assert_fails('vim9cmd au WinNew * nested echo fails', 'E1078:')
+
+ augroup Testing
+ au!
+ augroup END
+
+ call assert_fails('au WinNew * ++nested ++nested echo bad', 'E983:')
+ call assert_fails('au WinNew * nested nested echo bad', 'E983:')
+endfunc
+
+func Test_autocmd_nested_cursor_invalid()
+ set laststatus=0
+ copen
+ cclose
+ call setline(1, ['foo', 'bar', 'baz'])
+ 3
+ augroup nested_inv
+ autocmd User foo ++nested copen
+ autocmd BufAdd * let &laststatus = 2 - &laststatus
+ augroup END
+ doautocmd User foo
+
+ augroup nested_inv
+ au!
+ augroup END
+ set laststatus&
+ cclose
+ bwipe!
+endfunc
+
+func Test_autocmd_nested_keeps_cursor_pos()
+ enew
+ call setline(1, 'foo')
+ autocmd User foo ++nested normal! $a
+ autocmd InsertLeave * :
+ doautocmd User foo
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ bwipe!
+endfunc
+
+func Test_autocmd_nested_switch_window()
+ " run this in a separate Vim so that SafeState works
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ ['()']->writefile('Xautofile')
+ autocmd VimEnter * ++nested edit Xautofile | split
+ autocmd BufReadPost * autocmd SafeState * ++once foldclosed('.')
+ autocmd WinEnter * matchadd('ErrorMsg', 'pat')
+ END
+ call writefile(lines, 'Xautoscript', 'D')
+ let buf = RunVimInTerminal('-S Xautoscript', {'rows': 10})
+ call VerifyScreenDump(buf, 'Test_autocmd_nested_switch', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xautofile')
+endfunc
+
+func Test_autocmd_once()
+ " Without ++once WinNew triggers twice
+ let g:did_split = 0
+ augroup Testing
+ au WinNew * let g:did_split += 1
+ augroup END
+ split
+ split
+ call assert_equal(2, g:did_split)
+ call assert_true(exists('#WinNew'))
+ close
+ close
+
+ " With ++once WinNew triggers once
+ let g:did_split = 0
+ augroup Testing
+ au!
+ au WinNew * ++once let g:did_split += 1
+ augroup END
+ split
+ split
+ call assert_equal(1, g:did_split)
+ call assert_false(exists('#WinNew'))
+ close
+ close
+
+ call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
+endfunc
+
+func Test_autocmd_bufreadpre()
+ new
+ let b:bufreadpre = 1
+ call append(0, range(1000))
+ w! XAutocmdBufReadPre.txt
+ autocmd BufReadPre <buffer> :let b:bufreadpre += 1
+ norm! 500gg
+ sp
+ norm! 1000gg
+ wincmd p
+ let g:wsv1 = winsaveview()
+ wincmd p
+ let g:wsv2 = winsaveview()
+ " triggers BufReadPre, should not move the cursor in either window
+ " The topline may change one line in a large window.
+ edit
+ call assert_inrange(g:wsv2.topline - 1, g:wsv2.topline + 1, winsaveview().topline)
+ call assert_equal(g:wsv2.lnum, winsaveview().lnum)
+ call assert_equal(2, b:bufreadpre)
+ wincmd p
+ call assert_equal(g:wsv1.topline, winsaveview().topline)
+ call assert_equal(g:wsv1.lnum, winsaveview().lnum)
+ call assert_equal(2, b:bufreadpre)
+ " Now set the cursor position in an BufReadPre autocommand
+ " (even though the position will be invalid, this should make Vim reset the
+ " cursor position in the other window.
+ wincmd p
+ set cpo+=g
+ " won't do anything, but try to set the cursor on an invalid lnum
+ autocmd BufReadPre <buffer> :norm! 70gg
+ " triggers BufReadPre, should not move the cursor in either window
+ e
+ call assert_equal(1, winsaveview().topline)
+ call assert_equal(1, winsaveview().lnum)
+ call assert_equal(3, b:bufreadpre)
+ wincmd p
+ call assert_equal(g:wsv1.topline, winsaveview().topline)
+ call assert_equal(g:wsv1.lnum, winsaveview().lnum)
+ call assert_equal(3, b:bufreadpre)
+ close
+ close
+ call delete('XAutocmdBufReadPre.txt')
+ set cpo-=g
+endfunc
+
+" FileChangedShell tested in test_filechanged.vim
+
+" Tests for the following autocommands:
+" - FileWritePre writing a compressed file
+" - FileReadPost reading a compressed file
+" - BufNewFile reading a file template
+" - BufReadPre decompressing the file to be read
+" - FilterReadPre substituting characters in the temp file
+" - FilterReadPost substituting characters after filtering
+" - FileReadPre set options for decompression
+" - FileReadPost decompress the file
+func Test_ReadWrite_Autocmds()
+ " Run this test only on Unix-like systems and if gzip is available
+ CheckUnix
+ CheckExecutable gzip
+
+ " Make $GZIP empty, "-v" would cause trouble.
+ let $GZIP = ""
+
+ " Use a FileChangedShell autocommand to avoid a prompt for 'Xtestfile.gz'
+ " being modified outside of Vim (noticed on Solaris).
+ au FileChangedShell * echo 'caught FileChangedShell'
+
+ " Test for the FileReadPost, FileWritePre and FileWritePost autocmds
+ augroup Test1
+ au!
+ au FileWritePre *.gz '[,']!gzip
+ au FileWritePost *.gz undo
+ au FileReadPost *.gz '[,']!gzip -d
+ augroup END
+
+ new
+ set bin
+ call append(0, [
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ])
+ 1,9write! Xtestfile.gz
+ enew! | close
+
+ new
+ " Read and decompress the testfile
+ 0read Xtestfile.gz
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+ enew! | close
+
+ augroup Test1
+ au!
+ augroup END
+
+ " Test for the FileAppendPre and FileAppendPost autocmds
+ augroup Test2
+ au!
+ au BufNewFile *.c read Xtest.c
+ au FileAppendPre *.out '[,']s/new/NEW/
+ au FileAppendPost *.out !cat Xtest.c >> test.out
+ augroup END
+
+ call writefile(['/*', ' * Here is a new .c file', ' */'], 'Xtest.c', 'D')
+ new foo.c " should load Xtest.c
+ call assert_equal(['/*', ' * Here is a new .c file', ' */'], getline(2, 4))
+ w! >> test.out " append it to the output file
+
+ let contents = readfile('test.out')
+ call assert_equal(' * Here is a NEW .c file', contents[2])
+ call assert_equal(' * Here is a new .c file', contents[5])
+
+ call delete('test.out')
+ enew! | close
+ augroup Test2
+ au!
+ augroup END
+
+ " Test for the BufReadPre and BufReadPost autocmds
+ augroup Test3
+ au!
+ " setup autocommands to decompress before reading and re-compress
+ " afterwards
+ au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand("<afile>"))
+ au BufReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
+ au BufReadPost *.gz call rename(expand("<afile>"), expand("<afile>:r"))
+ au BufReadPost *.gz exe '!gzip ' . shellescape(expand("<afile>:r"))
+ augroup END
+
+ e! Xtestfile.gz " Edit compressed file
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+
+ w! >> test.out " Append it to the output file
+
+ augroup Test3
+ au!
+ augroup END
+
+ " Test for the FilterReadPre and FilterReadPost autocmds.
+ set shelltemp " need temp files here
+ augroup Test4
+ au!
+ au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t")
+ au FilterReadPre *.out exe 'silent !sed s/e/E/ ' . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>"))
+ au FilterReadPre *.out exe 'silent !rm ' . shellescape(expand("<afile>")) . '.t'
+ au FilterReadPost *.out '[,']s/x/X/g
+ augroup END
+
+ e! test.out " Edit the output file
+ 1,$!cat
+ call assert_equal([
+ \ 'linE 2 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 4 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 6 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 8 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 10 AbcdefghijklmnopqrstuvwXyz'
+ \ ], getline(1, 9))
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], readfile('test.out'))
+
+ augroup Test4
+ au!
+ augroup END
+ set shelltemp&vim
+
+ " Test for the FileReadPre and FileReadPost autocmds.
+ augroup Test5
+ au!
+ au FileReadPre *.gz exe 'silent !gzip -d ' . shellescape(expand("<afile>"))
+ au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
+ au FileReadPost *.gz '[,']s/l/L/
+ augroup END
+
+ new
+ 0r Xtestfile.gz " Read compressed file
+ call assert_equal([
+ \ 'Line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], readfile('Xtestfile.gz'))
+
+ augroup Test5
+ au!
+ augroup END
+
+ au! FileChangedShell
+ call delete('Xtestfile.gz')
+ call delete('test.out')
+endfunc
+
+func Test_throw_in_BufWritePre()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ call assert_false(filereadable('Xthefile'))
+ augroup throwing
+ au BufWritePre X* throw 'do not write'
+ augroup END
+ try
+ w Xthefile
+ catch
+ let caught = 1
+ endtry
+ call assert_equal(1, caught)
+ call assert_false(filereadable('Xthefile'))
+
+ bwipe!
+ au! throwing
+endfunc
+
+func Test_autocmd_in_try_block()
+ call mkdir('Xintrydir', 'R')
+ au BufEnter * let g:fname = expand('%')
+ try
+ edit Xintrydir/
+ endtry
+ call assert_match('Xintrydir', g:fname)
+
+ unlet g:fname
+ au! BufEnter
+endfunc
+
+func Test_autocmd_SafeState()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ let g:safe = 0
+ let g:again = ''
+ au SafeState * let g:safe += 1
+ au SafeStateAgain * let g:again ..= 'x'
+ func CallTimer()
+ call timer_start(10, {id -> execute('let g:again ..= "t"')})
+ endfunc
+ END
+ call writefile(lines, 'XSafeState', 'D')
+ let buf = RunVimInTerminal('-S XSafeState', #{rows: 6})
+
+ " Sometimes we loop to handle a K_IGNORE, SafeState may be triggered once or
+ " more often.
+ call term_sendkeys(buf, ":echo g:safe\<CR>")
+ call WaitForAssert({-> assert_match('^\d ', term_getline(buf, 6))}, 1000)
+
+ " SafeStateAgain should be invoked at least three times
+ call term_sendkeys(buf, ":echo g:again\<CR>")
+ call WaitForAssert({-> assert_match('^xxx', term_getline(buf, 6))}, 1000)
+
+ call term_sendkeys(buf, ":let g:again = ''\<CR>:call CallTimer()\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":echo g:again\<CR>")
+ call WaitForAssert({-> assert_match('xtx', term_getline(buf, 6))}, 1000)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_autocmd_CmdWinEnter()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ augroup vimHints | au! | augroup END
+ let b:dummy_var = 'This is a dummy'
+ autocmd CmdWinEnter * quit
+ let winnr = winnr('$')
+ END
+ let filename = 'XCmdWinEnter'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 6})
+
+ call term_sendkeys(buf, "q:")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":echo b:dummy_var\<cr>")
+ call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 2000)
+ call term_sendkeys(buf, ":echo &buftype\<cr>")
+ call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, ":echo winnr\<cr>")
+ call WaitForAssert({-> assert_match('^1', term_getline(buf, 6))}, 1000)
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
+
+func Test_autocmd_was_using_freed_memory()
+ CheckFeature quickfix
+
+ pedit xx
+ n x
+ augroup winenter
+ au WinEnter * if winnr('$') > 2 | quit | endif
+ augroup END
+ split
+
+ augroup winenter
+ au! WinEnter
+ augroup END
+
+ bwipe xx
+ bwipe x
+ pclose
+endfunc
+
+func Test_BufWrite_lockmarks()
+ let g:test_is_flaky = 1
+ edit! Xtest
+ call setline(1, ['a', 'b', 'c', 'd'])
+
+ " :lockmarks preserves the marks
+ call SetChangeMarks(2, 3)
+ lockmarks write
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ " *WritePre autocmds get the correct line range, but lockmarks preserves the
+ " original values for the user
+ augroup lockmarks
+ au!
+ au BufWritePre,FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")])
+ au FileWritePre * call assert_equal([3, 4], [line("'["), line("']")])
+ augroup END
+
+ lockmarks write
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ if executable('cat')
+ lockmarks %!cat
+ call assert_equal([2, 3], [line("'["), line("']")])
+ endif
+
+ lockmarks 3,4write Xtest2
+ call assert_equal([2, 3], [line("'["), line("']")])
+
+ au! lockmarks
+ augroup! lockmarks
+ call delete('Xtest')
+ call delete('Xtest2')
+endfunc
+
+func Test_FileType_spell()
+ if !isdirectory('/tmp')
+ throw "Skipped: requires /tmp directory"
+ endif
+
+ " this was crashing with an invalid free()
+ setglobal spellfile=/tmp/en.utf-8.add
+ augroup crash
+ autocmd!
+ autocmd BufNewFile,BufReadPost crashfile setf somefiletype
+ autocmd BufNewFile,BufReadPost crashfile set ft=anotherfiletype
+ autocmd FileType anotherfiletype setlocal spell
+ augroup END
+ func! NoCrash() abort
+ edit /tmp/crashfile
+ endfunc
+ call NoCrash()
+
+ au! crash
+ setglobal spellfile=
+endfunc
+
+" this was wiping out the current buffer and using freed memory
+func Test_SpellFileMissing_bwipe()
+ next 0
+ au SpellFileMissing 0 bwipe
+ call assert_fails('set spell spelllang=0', 'E937:')
+
+ au! SpellFileMissing
+ set nospell spelllang=en
+ bwipe
+endfunc
+
+" Test closing a window or editing another buffer from a FileChangedRO handler
+" in a readonly buffer
+func Test_FileChangedRO_winclose()
+ call test_override('ui_delay', 10)
+
+ augroup FileChangedROTest
+ au!
+ autocmd FileChangedRO * quit
+ augroup END
+ new
+ set readonly
+ call assert_fails('normal i', 'E788:')
+ close
+ augroup! FileChangedROTest
+
+ augroup FileChangedROTest
+ au!
+ autocmd FileChangedRO * edit Xrofile
+ augroup END
+ new
+ set readonly
+ call assert_fails('normal i', 'E788:')
+ close
+ augroup! FileChangedROTest
+ call test_override('ALL', 0)
+endfunc
+
+func LogACmd()
+ call add(g:logged, line('$'))
+endfunc
+
+func Test_TermChanged()
+ CheckNotGui
+
+ enew!
+ tabnew
+ call setline(1, ['a', 'b', 'c', 'd'])
+ $
+ au TermChanged * call LogACmd()
+ let g:logged = []
+ let term_save = &term
+ set term=xterm
+ call assert_equal([1, 4], g:logged)
+
+ au! TermChanged
+ let &term = term_save
+ bwipe!
+endfunc
+
+" Test for FileReadCmd autocmd
+func Test_autocmd_FileReadCmd()
+ func ReadFileCmd()
+ call append(line('$'), "v:cmdarg = " .. v:cmdarg)
+ endfunc
+ augroup FileReadCmdTest
+ au!
+ au FileReadCmd Xtest call ReadFileCmd()
+ augroup END
+
+ new
+ read ++bin Xtest
+ read ++nobin Xtest
+ read ++edit Xtest
+ read ++bad=keep Xtest
+ read ++bad=drop Xtest
+ read ++bad=- Xtest
+ read ++ff=unix Xtest
+ read ++ff=dos Xtest
+ read ++ff=mac Xtest
+ read ++enc=utf-8 Xtest
+
+ call assert_equal(['',
+ \ 'v:cmdarg = ++bin',
+ \ 'v:cmdarg = ++nobin',
+ \ 'v:cmdarg = ++edit',
+ \ 'v:cmdarg = ++bad=keep',
+ \ 'v:cmdarg = ++bad=drop',
+ \ 'v:cmdarg = ++bad=-',
+ \ 'v:cmdarg = ++ff=unix',
+ \ 'v:cmdarg = ++ff=dos',
+ \ 'v:cmdarg = ++ff=mac',
+ \ 'v:cmdarg = ++enc=utf-8'], getline(1, '$'))
+
+ bwipe!
+ augroup FileReadCmdTest
+ au!
+ augroup END
+ delfunc ReadFileCmd
+endfunc
+
+" Test for passing invalid arguments to autocmd
+func Test_autocmd_invalid_args()
+ " Additional character after * for event
+ call assert_fails('autocmd *a Xinvfile set ff=unix', 'E215:')
+ augroup Test
+ augroup END
+ " Invalid autocmd event
+ call assert_fails('autocmd Bufabc Xinvfile set ft=vim', 'E216:')
+ " Invalid autocmd event in a autocmd group
+ call assert_fails('autocmd Test Bufabc Xinvfile set ft=vim', 'E216:')
+ augroup! Test
+ " Execute all autocmds
+ call assert_fails('doautocmd * BufEnter', 'E217:')
+ call assert_fails('augroup! x1a2b3', 'E367:')
+ call assert_fails('autocmd BufNew <buffer=999> pwd', 'E680:')
+ call assert_fails('autocmd BufNew \) set ff=unix', 'E55:')
+endfunc
+
+" Test for deep nesting of autocmds
+func Test_autocmd_deep_nesting()
+ autocmd BufEnter Xdeepfile doautocmd BufEnter Xdeepfile
+ call assert_fails('doautocmd BufEnter Xdeepfile', 'E218:')
+ autocmd! BufEnter Xdeepfile
+endfunc
+
+" Tests for SigUSR1 autocmd event, which is only available on posix systems.
+func Test_autocmd_sigusr1()
+ CheckUnix
+ " FIXME: should this work on MacOS M1?
+ CheckNotMacM1
+ CheckExecutable /bin/kill
+
+ let g:sigusr1_passed = 0
+ au SigUSR1 * let g:sigusr1_passed = 1
+ call system('/bin/kill -s usr1 ' . getpid())
+ call WaitForAssert({-> assert_true(g:sigusr1_passed)})
+
+ au! SigUSR1
+ unlet g:sigusr1_passed
+endfunc
+
+" Test for BufReadPre autocmd deleting the file
+func Test_BufReadPre_delfile()
+ augroup TestAuCmd
+ au!
+ autocmd BufReadPre XbufreadPre call delete('XbufreadPre')
+ augroup END
+ call writefile([], 'XbufreadPre', 'D')
+ call assert_fails('new XbufreadPre', 'E200:')
+ call assert_equal('XbufreadPre', @%)
+ call assert_equal(1, &readonly)
+
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
+" Test for BufReadPre autocmd changing the current buffer
+func Test_BufReadPre_changebuf()
+ augroup TestAuCmd
+ au!
+ autocmd BufReadPre Xchangebuf edit Xsomeotherfile
+ augroup END
+ call writefile([], 'Xchangebuf', 'D')
+ call assert_fails('new Xchangebuf', 'E201:')
+ call assert_equal('Xsomeotherfile', @%)
+ call assert_equal(1, &readonly)
+
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
+" Test for BufWipeouti autocmd changing the current buffer when reading a file
+" in an empty buffer with 'f' flag in 'cpo'
+func Test_BufDelete_changebuf()
+ new
+ augroup TestAuCmd
+ au!
+ autocmd BufWipeout * let bufnr = bufadd('somefile') | exe "b " .. bufnr
+ augroup END
+ let save_cpo = &cpo
+ set cpo+=f
+ call assert_fails('r Xchangebuf', ['E812:', 'E484:'])
+ call assert_equal('somefile', @%)
+ let &cpo = save_cpo
+ augroup TestAuCmd
+ au!
+ augroup END
+ close!
+endfunc
+
+" Test for the temporary internal window used to execute autocmds
+func Test_autocmd_window()
+ %bw!
+ edit one.txt
+ tabnew two.txt
+ vnew three.txt
+ tabnew four.txt
+ tabprevious
+ let g:blist = []
+ augroup aucmd_win_test1
+ au!
+ au BufEnter * call add(g:blist, [expand('<afile>'),
+ \ win_gettype(bufwinnr(expand('<afile>')))])
+ augroup END
+
+ doautoall BufEnter
+ call assert_equal([
+ \ ['one.txt', 'autocmd'],
+ \ ['two.txt', ''],
+ \ ['four.txt', 'autocmd'],
+ \ ['three.txt', ''],
+ \ ], g:blist)
+
+ augroup aucmd_win_test1
+ au!
+ augroup END
+ augroup! aucmd_win_test1
+ %bw!
+endfunc
+
+" Test for trying to close the temporary window used for executing an autocmd
+func Test_close_autocmd_window()
+ %bw!
+ edit one.txt
+ tabnew two.txt
+ augroup aucmd_win_test2
+ au!
+ au BufEnter * if expand('<afile>') == 'one.txt' | 1close | endif
+ augroup END
+
+ call assert_fails('doautoall BufEnter', 'E813:')
+
+ augroup aucmd_win_test2
+ au!
+ augroup END
+ augroup! aucmd_win_test2
+ %bwipe!
+endfunc
+
+" Test for trying to close the tab that has the temporary window for exeucing
+" an autocmd.
+func Test_close_autocmd_tab()
+ edit one.txt
+ tabnew two.txt
+ augroup aucmd_win_test
+ au!
+ au BufEnter * if expand('<afile>') == 'one.txt' | tabfirst | tabonly | endif
+ augroup END
+
+ call assert_fails('doautoall BufEnter', 'E813:')
+
+ tabonly
+ augroup aucmd_win_test
+ au!
+ augroup END
+ augroup! aucmd_win_test
+ %bwipe!
+endfunc
+
+func Test_Visual_doautoall_redraw()
+ call setline(1, ['a', 'b'])
+ new
+ wincmd p
+ call feedkeys("G\<C-V>", 'txn')
+ autocmd User Explode ++once redraw
+ doautoall User Explode
+ %bwipe!
+endfunc
+
+" This was using freed memory.
+func Test_BufNew_arglocal()
+ arglocal
+ au BufNew * arglocal
+ call assert_fails('drop xx', 'E1156:')
+
+ au! BufNew
+endfunc
+
+func Test_autocmd_closes_window()
+ au BufNew,BufWinLeave * e %e
+ file yyy
+ au BufNew,BufWinLeave * ball
+ n xxx
+
+ %bwipe
+ au! BufNew
+ au! BufWinLeave
+endfunc
+
+func Test_autocmd_quit_psearch()
+ sn aa bb
+ augroup aucmd_win_test
+ au!
+ au BufEnter,BufLeave,BufNew,WinEnter,WinLeave,WinNew * if winnr('$') > 1 | q | endif
+ augroup END
+ ps /
+
+ augroup aucmd_win_test
+ au!
+ augroup END
+ new
+ pclose
+endfunc
+
+" Fuzzer found some strange combination that caused a crash.
+func Test_autocmd_normal_mess()
+ " For unknown reason this hangs on MS-Windows
+ CheckNotMSWindows
+
+ augroup aucmd_normal_test
+ au BufLeave,BufWinLeave,BufHidden,BufUnload,BufDelete,BufWipeout * norm 7q/qc
+ augroup END
+ call assert_fails('o4', 'E1159')
+ silent! H
+ call assert_fails('e xx', 'E1159')
+ normal G
+
+ augroup aucmd_normal_test
+ au!
+ augroup END
+endfunc
+
+func Test_autocmd_closing_cmdwin()
+ " For unknown reason this hangs on MS-Windows
+ CheckNotMSWindows
+
+ au BufWinLeave * nested q
+ call assert_fails("norm 7q?\n", 'E855:')
+
+ au! BufWinLeave
+ new
+ only
+endfunc
+
+func Test_autocmd_vimgrep()
+ augroup aucmd_vimgrep
+ au QuickfixCmdPre,BufNew,BufReadCmd * sb
+ au QuickfixCmdPre,BufNew,BufReadCmd * q9
+ augroup END
+ call assert_fails('lv ?a? foo', 'E926:')
+
+ augroup aucmd_vimgrep
+ au!
+ augroup END
+endfunc
+
+func Test_autocmd_with_block()
+ augroup block_testing
+ au BufReadPost *.xml {
+ setlocal matchpairs+=<:>
+ /<start
+ }
+ au CursorHold * {
+ autocmd BufReadPre * ++once echo 'one' | echo 'two'
+ g:gotSafeState = 77
+ }
+ augroup END
+
+ let expected = "\n--- Autocommands ---\nblock_testing BufRead\n *.xml {^@ setlocal matchpairs+=<:>^@ /<start^@ }"
+ call assert_equal(expected, execute('au BufReadPost *.xml'))
+
+ doautocmd CursorHold
+ call assert_equal(77, g:gotSafeState)
+ unlet g:gotSafeState
+
+ augroup block_testing
+ au!
+ autocmd CursorHold * {
+ if true
+ # comment
+ && true
+
+ && true
+ g:done = 'yes'
+ endif
+ }
+ augroup END
+ doautocmd CursorHold
+ call assert_equal('yes', g:done)
+
+ unlet g:done
+ augroup block_testing
+ au!
+ augroup END
+endfunc
+
+" Test TextChangedI and TextChanged
+func Test_Changed_ChangedI()
+ new
+ call test_override("char_avail", 1)
+ let [g:autocmd_i, g:autocmd_n] = ['','']
+
+ func! TextChangedAutocmdI(char)
+ let g:autocmd_{tolower(a:char)} = a:char .. b:changedtick
+ endfunc
+
+ augroup Test_TextChanged
+ au!
+ au TextChanged <buffer> :call TextChangedAutocmdI('N')
+ au TextChangedI <buffer> :call TextChangedAutocmdI('I')
+ augroup END
+
+ call feedkeys("ifoo\<esc>", 'tnix')
+ " TODO: Test test does not seem to trigger TextChanged autocommand, this
+ " requires running Vim in a terminal window.
+ " call assert_equal('N3', g:autocmd_n)
+ call assert_equal('I3', g:autocmd_i)
+
+ call feedkeys("yyp", 'tnix')
+ " TODO: Test test does not seem to trigger TextChanged autocommand.
+ " call assert_equal('N4', g:autocmd_n)
+ call assert_equal('I3', g:autocmd_i)
+
+ " CleanUp
+ call test_override("char_avail", 0)
+ au! TextChanged <buffer>
+ au! TextChangedI <buffer>
+ augroup! Test_TextChanged
+ delfu TextChangedAutocmdI
+ unlet! g:autocmd_i g:autocmd_n
+
+ bw!
+endfunc
+
+func Test_closing_autocmd_window()
+ let lines =<< trim END
+ edit Xa.txt
+ tabnew Xb.txt
+ autocmd BufEnter Xa.txt unhide 1
+ doautoall BufEnter
+ END
+ call v9.CheckScriptFailure(lines, 'E814:')
+ au! BufEnter
+ only!
+ bwipe Xa.txt
+ bwipe Xb.txt
+endfunc
+
+func Test_bufwipeout_changes_window()
+ " This should not crash, but we don't have any expectations about what
+ " happens, changing window in BufWipeout has unpredictable results.
+ tabedit
+ let g:window_id = win_getid()
+ topleft new
+ setlocal bufhidden=wipe
+ autocmd BufWipeout <buffer> call win_gotoid(g:window_id)
+ tabprevious
+ +tabclose
+
+ unlet g:window_id
+ au! BufWipeout
+ %bwipe!
+endfunc
+
+func Test_v_event_readonly()
+ autocmd CompleteChanged * let v:event.width = 0
+ call assert_fails("normal! i\<C-X>\<C-V>", 'E46:')
+ au! CompleteChanged
+
+ autocmd DirChangedPre * let v:event.directory = ''
+ call assert_fails('cd .', 'E46:')
+ au! DirChangedPre
+
+ autocmd ModeChanged * let v:event.new_mode = ''
+ call assert_fails('normal! cc', 'E46:')
+ au! ModeChanged
+
+ autocmd TextYankPost * let v:event.operator = ''
+ call assert_fails('normal! yy', 'E46:')
+ au! TextYankPost
+endfunc
+
+" Test for ModeChanged pattern
+func Test_mode_changes()
+ let g:index = 0
+ let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'n', 'V', 'v', 's', 'n']
+ func! TestMode()
+ call assert_equal(g:mode_seq[g:index], get(v:event, "old_mode"))
+ call assert_equal(g:mode_seq[g:index + 1], get(v:event, "new_mode"))
+ call assert_equal(mode(1), get(v:event, "new_mode"))
+ let g:index += 1
+ endfunc
+
+ au ModeChanged * :call TestMode()
+ let g:n_to_any = 0
+ au ModeChanged n:* let g:n_to_any += 1
+ call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdG", 'tnix')
+
+ let g:V_to_v = 0
+ au ModeChanged V:v let g:V_to_v += 1
+ call feedkeys("Vv\<C-G>\<esc>", 'tnix')
+ call assert_equal(len(filter(g:mode_seq[1:], {idx, val -> val == 'n'})), g:n_to_any)
+ call assert_equal(1, g:V_to_v)
+ call assert_equal(len(g:mode_seq) - 1, g:index)
+
+ let g:n_to_i = 0
+ au ModeChanged n:i let g:n_to_i += 1
+ let g:n_to_niI = 0
+ au ModeChanged i:niI let g:n_to_niI += 1
+ let g:niI_to_i = 0
+ au ModeChanged niI:i let g:niI_to_i += 1
+ let g:nany_to_i = 0
+ au ModeChanged n*:i let g:nany_to_i += 1
+ let g:i_to_n = 0
+ au ModeChanged i:n let g:i_to_n += 1
+ let g:nori_to_any = 0
+ au ModeChanged [ni]:* let g:nori_to_any += 1
+ let g:i_to_any = 0
+ au ModeChanged i:* let g:i_to_any += 1
+ let g:index = 0
+ let g:mode_seq = ['n', 'i', 'niI', 'i', 'n']
+ call feedkeys("a\<C-O>l\<esc>", 'tnix')
+ call assert_equal(len(g:mode_seq) - 1, g:index)
+ call assert_equal(1, g:n_to_i)
+ call assert_equal(1, g:n_to_niI)
+ call assert_equal(1, g:niI_to_i)
+ call assert_equal(2, g:nany_to_i)
+ call assert_equal(1, g:i_to_n)
+ call assert_equal(2, g:i_to_any)
+ call assert_equal(3, g:nori_to_any)
+
+ if has('terminal')
+ let g:mode_seq += ['c', 'n', 't', 'nt', 'c', 'nt', 'n']
+ call feedkeys(":term\<CR>\<C-W>N:bd!\<CR>", 'tnix')
+ call assert_equal(len(g:mode_seq) - 1, g:index)
+ call assert_equal(1, g:n_to_i)
+ call assert_equal(1, g:n_to_niI)
+ call assert_equal(1, g:niI_to_i)
+ call assert_equal(2, g:nany_to_i)
+ call assert_equal(1, g:i_to_n)
+ call assert_equal(2, g:i_to_any)
+ call assert_equal(5, g:nori_to_any)
+ endif
+
+ let g:n_to_c = 0
+ au ModeChanged n:c let g:n_to_c += 1
+ let g:c_to_n = 0
+ au ModeChanged c:n let g:c_to_n += 1
+ let g:mode_seq += ['c', 'n', 'c', 'n']
+ call feedkeys("q:\<C-C>\<Esc>", 'tnix')
+ call assert_equal(len(g:mode_seq) - 1, g:index)
+ call assert_equal(2, g:n_to_c)
+ call assert_equal(2, g:c_to_n)
+ unlet g:n_to_c
+ unlet g:c_to_n
+
+ let g:n_to_v = 0
+ au ModeChanged n:v let g:n_to_v += 1
+ let g:v_to_n = 0
+ au ModeChanged v:n let g:v_to_n += 1
+ let g:mode_seq += ['v', 'n']
+ call feedkeys("v\<C-C>", 'tnix')
+ call assert_equal(len(g:mode_seq) - 1, g:index)
+ call assert_equal(1, g:n_to_v)
+ call assert_equal(1, g:v_to_n)
+ unlet g:n_to_v
+ unlet g:v_to_n
+
+ au! ModeChanged
+ delfunc TestMode
+ unlet! g:mode_seq
+ unlet! g:index
+ unlet! g:n_to_any
+ unlet! g:V_to_v
+ unlet! g:n_to_i
+ unlet! g:n_to_niI
+ unlet! g:niI_to_i
+ unlet! g:nany_to_i
+ unlet! g:i_to_n
+ unlet! g:nori_to_any
+ unlet! g:i_to_any
+endfunc
+
+func Test_recursive_ModeChanged()
+ au! ModeChanged * norm 0u
+ sil! norm 
+ au! ModeChanged
+endfunc
+
+func Test_ModeChanged_starts_visual()
+ " This was triggering ModeChanged before setting VIsual, causing a crash.
+ au! ModeChanged * norm 0u
+ sil! norm 
+
+ au! ModeChanged
+endfunc
+
+func Test_noname_autocmd()
+ augroup test_noname_autocmd_group
+ autocmd!
+ autocmd BufEnter * call add(s:li, ["BufEnter", expand("<afile>")])
+ autocmd BufDelete * call add(s:li, ["BufDelete", expand("<afile>")])
+ autocmd BufLeave * call add(s:li, ["BufLeave", expand("<afile>")])
+ autocmd BufUnload * call add(s:li, ["BufUnload", expand("<afile>")])
+ autocmd BufWipeout * call add(s:li, ["BufWipeout", expand("<afile>")])
+ augroup END
+
+ let s:li = []
+ edit foo
+ call assert_equal([['BufUnload', ''], ['BufDelete', ''], ['BufWipeout', ''], ['BufEnter', 'foo']], s:li)
+
+ au! test_noname_autocmd_group
+ augroup! test_noname_autocmd_group
+endfunc
+
+" Test for the autocmd_get() function
+func Test_autocmd_get()
+ augroup TestAutoCmdFns
+ au!
+ autocmd BufAdd *.vim echo "bufadd-vim"
+ autocmd BufAdd *.py echo "bufadd-py"
+ autocmd BufHidden *.vim echo "bufhidden"
+ augroup END
+ augroup TestAutoCmdFns2
+ autocmd BufAdd *.vim echo "bufadd-vim-2"
+ autocmd BufRead *.a1b2c3 echo "bufadd-vim-2"
+ augroup END
+
+ let l = autocmd_get()
+ call assert_true(l->len() > 0)
+
+ " Test for getting all the autocmds in a group
+ let expected = [
+ \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+ \ pattern: '*.vim', nested: v:false, once: v:false,
+ \ event: 'BufAdd'},
+ \ #{cmd: 'echo "bufadd-py"', group: 'TestAutoCmdFns',
+ \ pattern: '*.py', nested: v:false, once: v:false,
+ \ event: 'BufAdd'},
+ \ #{cmd: 'echo "bufhidden"', group: 'TestAutoCmdFns',
+ \ pattern: '*.vim', nested: v:false,
+ \ once: v:false, event: 'BufHidden'}]
+ call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+ " Test for getting autocmds for all the patterns in a group
+ call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns',
+ \ event: '*'}))
+
+ " Test for getting autocmds for an event in a group
+ let expected = [
+ \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+ \ pattern: '*.vim', nested: v:false, once: v:false,
+ \ event: 'BufAdd'},
+ \ #{cmd: 'echo "bufadd-py"', group: 'TestAutoCmdFns',
+ \ pattern: '*.py', nested: v:false, once: v:false,
+ \ event: 'BufAdd'}]
+ call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns',
+ \ event: 'BufAdd'}))
+
+ " Test for getting the autocmds for all the events in a group for particular
+ " pattern
+ call assert_equal([{'cmd': 'echo "bufadd-py"', 'group': 'TestAutoCmdFns',
+ \ 'pattern': '*.py', 'nested': v:false, 'once': v:false,
+ \ 'event': 'BufAdd'}],
+ \ autocmd_get(#{group: 'TestAutoCmdFns', event: '*', pattern: '*.py'}))
+
+ " Test for getting the autocmds for an events in a group for particular
+ " pattern
+ let l = autocmd_get(#{group: 'TestAutoCmdFns', event: 'BufAdd',
+ \ pattern: '*.vim'})
+ call assert_equal([
+ \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+ \ pattern: '*.vim', nested: v:false, once: v:false,
+ \ event: 'BufAdd'}], l)
+
+ " Test for getting the autocmds for a pattern in a group
+ let l = autocmd_get(#{group: 'TestAutoCmdFns', pattern: '*.vim'})
+ call assert_equal([
+ \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+ \ pattern: '*.vim', nested: v:false, once: v:false,
+ \ event: 'BufAdd'},
+ \ #{cmd: 'echo "bufhidden"', group: 'TestAutoCmdFns',
+ \ pattern: '*.vim', nested: v:false,
+ \ once: v:false, event: 'BufHidden'}], l)
+
+ " Test for getting the autocmds for a pattern in all the groups
+ let l = autocmd_get(#{pattern: '*.a1b2c3'})
+ call assert_equal([{'cmd': 'echo "bufadd-vim-2"', 'group': 'TestAutoCmdFns2',
+ \ 'pattern': '*.a1b2c3', 'nested': v:false, 'once': v:false,
+ \ 'event': 'BufRead'}], l)
+
+ " Test for getting autocmds for a pattern without any autocmds
+ call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns',
+ \ pattern: '*.abc'}))
+ call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns',
+ \ event: 'BufAdd', pattern: '*.abc'}))
+ call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns',
+ \ event: 'BufWipeout'}))
+ call assert_fails("call autocmd_get(#{group: 'abc', event: 'BufAdd'})",
+ \ 'E367:')
+ let cmd = "echo autocmd_get(#{group: 'TestAutoCmdFns', event: 'abc'})"
+ call assert_fails(cmd, 'E216:')
+ call assert_fails("call autocmd_get(#{group: 'abc'})", 'E367:')
+ call assert_fails("echo autocmd_get(#{event: 'abc'})", 'E216:')
+
+ augroup TestAutoCmdFns
+ au!
+ augroup END
+ call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+ " Test for nested and once autocmds
+ augroup TestAutoCmdFns
+ au!
+ autocmd VimSuspend * ++nested echo "suspend"
+ autocmd VimResume * ++once echo "resume"
+ augroup END
+
+ let expected = [
+ \ {'cmd': 'echo "suspend"', 'group': 'TestAutoCmdFns', 'pattern': '*',
+ \ 'nested': v:true, 'once': v:false, 'event': 'VimSuspend'},
+ \ {'cmd': 'echo "resume"', 'group': 'TestAutoCmdFns', 'pattern': '*',
+ \ 'nested': v:false, 'once': v:true, 'event': 'VimResume'}]
+ call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+ " Test for buffer-local autocmd
+ augroup TestAutoCmdFns
+ au!
+ autocmd TextYankPost <buffer> echo "textyankpost"
+ augroup END
+
+ let expected = [
+ \ {'cmd': 'echo "textyankpost"', 'group': 'TestAutoCmdFns',
+ \ 'pattern': '<buffer=' .. bufnr() .. '>', 'nested': v:false,
+ \ 'once': v:false, 'bufnr': bufnr(), 'event': 'TextYankPost'}]
+ call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+ augroup TestAutoCmdFns
+ au!
+ augroup END
+ augroup! TestAutoCmdFns
+ augroup TestAutoCmdFns2
+ au!
+ augroup END
+ augroup! TestAutoCmdFns2
+
+ call assert_fails("echo autocmd_get(#{group: []})", 'E730:')
+ call assert_fails("echo autocmd_get(#{event: {}})", 'E731:')
+ call assert_fails("echo autocmd_get([])", 'E1206:')
+endfunc
+
+" Test for the autocmd_add() function
+func Test_autocmd_add()
+ " Define a single autocmd in a group
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', pattern: '*.sh',
+ \ cmd: 'echo "bufadd"', once: v:true, nested: v:true}])
+ call assert_equal([#{cmd: 'echo "bufadd"', group: 'TestAcSet',
+ \ pattern: '*.sh', nested: v:true, once: v:true,
+ \ event: 'BufAdd'}], autocmd_get(#{group: 'TestAcSet'}))
+
+ " Define two autocmds in the same group
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', pattern: '*.sh',
+ \ cmd: 'echo "bufadd"'},
+ \ #{group: 'TestAcSet', event: 'BufEnter', pattern: '*.sh',
+ \ cmd: 'echo "bufenter"'}])
+ call assert_equal([
+ \ #{cmd: 'echo "bufadd"', group: 'TestAcSet', pattern: '*.sh',
+ \ nested: v:false, once: v:false, event: 'BufAdd'},
+ \ #{cmd: 'echo "bufenter"', group: 'TestAcSet', pattern: '*.sh',
+ \ nested: v:false, once: v:false, event: 'BufEnter'}],
+ \ autocmd_get(#{group: 'TestAcSet'}))
+
+ " Define a buffer-local autocmd
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'CursorHold',
+ \ bufnr: bufnr(), cmd: 'echo "cursorhold"'}])
+ call assert_equal([
+ \ #{cmd: 'echo "cursorhold"', group: 'TestAcSet',
+ \ pattern: '<buffer=' .. bufnr() .. '>', nested: v:false,
+ \ once: v:false, bufnr: bufnr(), event: 'CursorHold'}],
+ \ autocmd_get(#{group: 'TestAcSet'}))
+
+ " Use an invalid buffer number
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufEnter',
+ \ bufnr: -1, cmd: 'echo "bufenter"'}])
+ let l = [#{group: 'TestAcSet', event: 'BufAdd', bufnr: 9999,
+ \ cmd: 'echo "bufadd"'}]
+ call assert_fails("echo autocmd_add(l)", 'E680:')
+ let l = [#{group: 'TestAcSet', event: 'BufAdd', bufnr: 9999,
+ \ pattern: '*.py', cmd: 'echo "bufadd"'}]
+ call assert_fails("echo autocmd_add(l)", 'E680:')
+ let l = [#{group: 'TestAcSet', event: 'BufAdd', bufnr: 9999,
+ \ pattern: ['*.py', '*.c'], cmd: 'echo "bufadd"'}]
+ call assert_fails("echo autocmd_add(l)", 'E680:')
+ let l = [#{group: 'TestAcSet', event: 'BufRead', bufnr: [],
+ \ cmd: 'echo "bufread"'}]
+ call assert_fails("echo autocmd_add(l)", 'E745:')
+ call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+ " Add two commands to the same group, event and pattern
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufUnload',
+ \ pattern: 'abc', cmd: 'echo "cmd1"'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufUnload',
+ \ pattern: 'abc', cmd: 'echo "cmd2"'}])
+ call assert_equal([
+ \ #{cmd: 'echo "cmd1"', group: 'TestAcSet', pattern: 'abc',
+ \ nested: v:false, once: v:false, event: 'BufUnload'},
+ \ #{cmd: 'echo "cmd2"', group: 'TestAcSet', pattern: 'abc',
+ \ nested: v:false, once: v:false, event: 'BufUnload'}],
+ \ autocmd_get(#{group: 'TestAcSet'}))
+
+ " When adding a new autocmd, if the autocmd 'group' is not specified, then
+ " the current autocmd group should be used.
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ augroup TestAcSet
+ call autocmd_add([#{event: 'BufHidden', pattern: 'abc', cmd: 'echo "abc"'}])
+ augroup END
+ call assert_equal([
+ \ #{cmd: 'echo "abc"', group: 'TestAcSet', pattern: 'abc',
+ \ nested: v:false, once: v:false, event: 'BufHidden'}],
+ \ autocmd_get(#{group: 'TestAcSet'}))
+
+ " Test for replacing a cmd for an event in a group
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ call autocmd_add([#{replace: v:true, group: 'TestAcSet', event: 'BufEnter',
+ \ pattern: '*.py', cmd: 'echo "bufenter"'}])
+ call autocmd_add([#{replace: v:true, group: 'TestAcSet', event: 'BufEnter',
+ \ pattern: '*.py', cmd: 'echo "bufenter"'}])
+ call assert_equal([
+ \ #{cmd: 'echo "bufenter"', group: 'TestAcSet', pattern: '*.py',
+ \ nested: v:false, once: v:false, event: 'BufEnter'}],
+ \ autocmd_get(#{group: 'TestAcSet'}))
+
+ " Test for adding a command for an unsupported autocmd event
+ let l = [#{group: 'TestAcSet', event: 'abc', pattern: '*.sh',
+ \ cmd: 'echo "bufadd"'}]
+ call assert_fails('call autocmd_add(l)', 'E216:')
+
+ " Test for using a list of events and patterns
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ let l = [#{group: 'TestAcSet', event: ['BufEnter', 'BufLeave'],
+ \ pattern: ['*.py', '*.sh'], cmd: 'echo "bufcmds"'}]
+ call autocmd_add(l)
+ call assert_equal([
+ \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.py',
+ \ nested: v:false, once: v:false, event: 'BufEnter'},
+ \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.sh',
+ \ nested: v:false, once: v:false, event: 'BufEnter'},
+ \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.py',
+ \ nested: v:false, once: v:false, event: 'BufLeave'},
+ \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.sh',
+ \ nested: v:false, once: v:false, event: 'BufLeave'}],
+ \ autocmd_get(#{group: 'TestAcSet'}))
+
+ " Test for invalid values for 'event' item
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ let l = [#{group: 'TestAcSet', event: test_null_string(),
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E928:')
+ let l = [#{group: 'TestAcSet', event: test_null_list(),
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E714:')
+ let l = [#{group: 'TestAcSet', event: {},
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E777:')
+ let l = [#{group: 'TestAcSet', event: [{}],
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E928:')
+ let l = [#{group: 'TestAcSet', event: [test_null_string()],
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E928:')
+ let l = [#{group: 'TestAcSet', event: 'BufEnter,BufLeave',
+ \ pattern: '*.py', cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E216:')
+ let l = [#{group: 'TestAcSet', event: [],
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call autocmd_add(l)
+ let l = [#{group: 'TestAcSet', event: [""],
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E216:')
+ let l = [#{group: 'TestAcSet', event: "",
+ \ pattern: "*.py", cmd: 'echo "bufcmds"'}]
+ call autocmd_add(l)
+ call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+ " Test for invalid values for 'pattern' item
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: test_null_string(), cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E928:')
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: test_null_list(), cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E714:')
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: {}, cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E777:')
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: [{}], cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E928:')
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: [test_null_string()], cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E928:')
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: [], cmd: 'echo "bufcmds"'}]
+ call autocmd_add(l)
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: [""], cmd: 'echo "bufcmds"'}]
+ call autocmd_add(l)
+ let l = [#{group: 'TestAcSet', event: "BufEnter",
+ \ pattern: "", cmd: 'echo "bufcmds"'}]
+ call autocmd_add(l)
+ call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+ let l = [#{group: 'TestAcSet', event: 'BufEnter,abc,BufLeave',
+ \ pattern: '*.py', cmd: 'echo "bufcmds"'}]
+ call assert_fails('call autocmd_add(l)', 'E216:')
+
+ call assert_fails("call autocmd_add({})", 'E1211:')
+ call assert_equal(v:false, autocmd_add(test_null_list()))
+ call assert_true(autocmd_add([[]]))
+ call assert_true(autocmd_add([test_null_dict()]))
+
+ augroup TestAcSet
+ au!
+ augroup END
+
+ call autocmd_add([#{group: 'TestAcSet'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd'}])
+ call autocmd_add([#{group: 'TestAcSet', pat: '*.sh'}])
+ call autocmd_add([#{group: 'TestAcSet', cmd: 'echo "a"'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', pat: '*.sh'}])
+ call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', cmd: 'echo "a"'}])
+ call autocmd_add([#{group: 'TestAcSet', pat: '*.sh', cmd: 'echo "a"'}])
+ call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+ augroup! TestAcSet
+endfunc
+
+" Test for deleting autocmd events and groups
+func Test_autocmd_delete()
+ " Delete an event in an autocmd group
+ augroup TestAcSet
+ au!
+ au BufAdd *.sh echo "bufadd"
+ au BufEnter *.sh echo "bufenter"
+ augroup END
+ call autocmd_delete([#{group: 'TestAcSet', event: 'BufAdd'}])
+ call assert_equal([#{cmd: 'echo "bufenter"', group: 'TestAcSet',
+ \ pattern: '*.sh', nested: v:false, once: v:false,
+ \ event: 'BufEnter'}], autocmd_get(#{group: 'TestAcSet'}))
+
+ " Delete all the events in an autocmd group
+ augroup TestAcSet
+ au BufAdd *.sh echo "bufadd"
+ augroup END
+ call autocmd_delete([#{group: 'TestAcSet', event: '*'}])
+ call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+ " Delete a non-existing autocmd group
+ call assert_fails("call autocmd_delete([#{group: 'abc'}])", 'E367:')
+ " Delete a non-existing autocmd event
+ let l = [#{group: 'TestAcSet', event: 'abc'}]
+ call assert_fails("call autocmd_delete(l)", 'E216:')
+ " Delete a non-existing autocmd pattern
+ let l = [#{group: 'TestAcSet', event: 'BufAdd', pat: 'abc'}]
+ call assert_true(autocmd_delete(l))
+ " Delete an autocmd for a non-existing buffer
+ let l = [#{event: '*', bufnr: 9999, cmd: 'echo "x"'}]
+ call assert_fails('call autocmd_delete(l)', 'E680:')
+
+ " Delete an autocmd group
+ augroup TestAcSet
+ au!
+ au BufAdd *.sh echo "bufadd"
+ au BufEnter *.sh echo "bufenter"
+ augroup END
+ call autocmd_delete([#{group: 'TestAcSet'}])
+ call assert_fails("call autocmd_get(#{group: 'TestAcSet'})", 'E367:')
+
+ call assert_true(autocmd_delete([[]]))
+ call assert_true(autocmd_delete([test_null_dict()]))
+endfunc
+
+func Test_autocmd_split_dummy()
+ " Autocommand trying to split a window containing a dummy buffer.
+ auto BufReadPre * exe "sbuf " .. expand("<abuf>")
+ " Avoid the "W11" prompt
+ au FileChangedShell * let v:fcs_choice = 'reload'
+ func Xautocmd_changelist()
+ cal writefile(['Xtestfile2:4:4'], 'Xerr')
+ edit Xerr
+ lex 'Xtestfile2:4:4'
+ endfunc
+ call Xautocmd_changelist()
+ " Should get E86, but it doesn't always happen (timing?)
+ silent! call Xautocmd_changelist()
+
+ au! BufReadPre
+ au! FileChangedShell
+ delfunc Xautocmd_changelist
+ bwipe! Xerr
+ call delete('Xerr')
+endfunc
+
+" This was crashing because there was only one window to execute autocommands
+" in.
+func Test_autocmd_nested_setbufvar()
+ CheckFeature python3
+
+ set hidden
+ edit Xaaa
+ edit Xbbb
+ call setline(1, 'bar')
+ enew
+ au BufWriteCmd Xbbb ++nested call setbufvar('Xaaa', '&ft', 'foo') | bw! Xaaa
+ au FileType foo call py3eval('vim.current.buffer.options["cindent"]')
+ wall
+
+ au! BufWriteCmd
+ au! FileType foo
+ set nohidden
+ call delete('Xaaa')
+ call delete('Xbbb')
+ %bwipe!
+endfunc
+
+func SetupVimTest_shm()
+ let g:bwe = []
+ let g:brp = []
+ set shortmess+=F
+
+ let dirname='XVimTestSHM'
+ call mkdir(dirname, 'R')
+ call writefile(['test'], dirname .. '/1')
+ call writefile(['test'], dirname .. '/2')
+ call writefile(['test'], dirname .. '/3')
+
+ augroup test
+ autocmd!
+ autocmd BufWinEnter * call add(g:bwe, $'BufWinEnter: {expand('<amatch>')}')
+ autocmd BufReadPost * call add(g:brp, $'BufReadPost: {expand('<amatch>')}')
+ augroup END
+
+ call setqflist([
+ \ {'filename': dirname .. '/1', 'lnum': 1, 'col': 1, 'text': 'test', 'vcol': 0},
+ \ {'filename': dirname .. '/2', 'lnum': 1, 'col': 1, 'text': 'test', 'vcol': 0},
+ \ {'filename': dirname .. '/3', 'lnum': 1, 'col': 1, 'text': 'test', 'vcol': 0}
+ \ ])
+ cdo! substitute/test/TEST
+
+ " clean up
+ noa enew!
+ set shortmess&vim
+ augroup test
+ autocmd!
+ augroup END
+ augroup! test
+endfunc
+
+func Test_autocmd_shortmess()
+ CheckNotMSWindows
+
+ call SetupVimTest_shm()
+ let output = execute(':mess')->split('\n')
+
+ let info = copy(output)->filter({idx, val -> val =~# '\d of 3'} )
+ let bytes = copy(output)->filter({idx, val -> val =~# 'bytes'} )
+
+ " We test the following here:
+ " BufReadPost should have been triggered 3 times, once per file
+ " BufWinEnter should have been triggered 3 times, once per file
+ " FileInfoMessage should have been shown 3 times, regardless of shm option
+ " "(x of 3)" message from :cnext has been shown 3 times
+
+ call assert_equal(3, g:brp->len())
+ call assert_equal(3, g:bwe->len())
+ call assert_equal(3, info->len())
+ call assert_equal(3, bytes->len())
+
+ delfunc SetupVimTest_shm
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_autoload.vim b/src/testdir/test_autoload.vim
new file mode 100644
index 0000000..835b81e
--- /dev/null
+++ b/src/testdir/test_autoload.vim
@@ -0,0 +1,30 @@
+" Tests for autoload
+
+set runtimepath=./sautest
+
+func Test_autoload_dict_func()
+ let g:loaded_foo_vim = 0
+ let g:called_foo_bar_echo = 0
+ call g:foo#bar.echo()
+ call assert_equal(1, g:loaded_foo_vim)
+ call assert_equal(1, g:called_foo_bar_echo)
+
+ eval 'bar'->g:foo#addFoo()->assert_equal('barfoo')
+
+ " empty name works in legacy script
+ call assert_equal('empty', foo#())
+endfunc
+
+func Test_source_autoload()
+ let g:loaded_sourced_vim = 0
+ source sautest/autoload/sourced.vim
+ call assert_equal(1, g:loaded_sourced_vim)
+endfunc
+
+func Test_autoload_vim9script()
+ call assert_equal('some', auto9#Getsome())
+ call assert_equal(49, auto9#Add42(7))
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_backspace_opt.vim b/src/testdir/test_backspace_opt.vim
new file mode 100644
index 0000000..d70a6d0
--- /dev/null
+++ b/src/testdir/test_backspace_opt.vim
@@ -0,0 +1,141 @@
+" Tests for 'backspace' settings
+
+func Test_backspace_option()
+ set backspace=
+ call assert_equal('', &backspace)
+ set backspace=indent
+ call assert_equal('indent', &backspace)
+ set backspace=eol
+ call assert_equal('eol', &backspace)
+ set backspace=start
+ call assert_equal('start', &backspace)
+ set backspace=nostop
+ call assert_equal('nostop', &backspace)
+ " Add the value
+ set backspace=
+ set backspace=indent
+ call assert_equal('indent', &backspace)
+ set backspace+=eol
+ call assert_equal('indent,eol', &backspace)
+ set backspace+=start
+ call assert_equal('indent,eol,start', &backspace)
+ set backspace+=nostop
+ call assert_equal('indent,eol,start,nostop', &backspace)
+ " Delete the value
+ set backspace-=nostop
+ call assert_equal('indent,eol,start', &backspace)
+ set backspace-=indent
+ call assert_equal('eol,start', &backspace)
+ set backspace-=start
+ call assert_equal('eol', &backspace)
+ set backspace-=eol
+ call assert_equal('', &backspace)
+ " Check the error
+ call assert_fails('set backspace=ABC', 'E474:')
+ call assert_fails('set backspace+=def', 'E474:')
+ " NOTE: Vim doesn't check following error...
+ "call assert_fails('set backspace-=ghi', 'E474:')
+
+ " Check backwards compatibility with version 5.4 and earlier
+ set backspace=0
+ call assert_equal('0', &backspace)
+ set backspace=1
+ call assert_equal('1', &backspace)
+ set backspace=2
+ call assert_equal('2', &backspace)
+ set backspace=3
+ call assert_equal('3', &backspace)
+ call assert_fails('set backspace=4', 'E474:')
+ call assert_fails('set backspace=10', 'E474:')
+
+ " Cleared when 'compatible' is set
+ set compatible
+ call assert_equal('', &backspace)
+ set nocompatible viminfo+=nviminfo
+endfunc
+
+" Test with backspace set to the non-compatible setting
+func Test_backspace_ctrl_u()
+ new
+ call append(0, [
+ \ "1 this shouldn't be deleted",
+ \ "2 this shouldn't be deleted",
+ \ "3 this shouldn't be deleted",
+ \ "4 this should be deleted",
+ \ "5 this shouldn't be deleted",
+ \ "6 this shouldn't be deleted",
+ \ "7 this shouldn't be deleted",
+ \ "8 this shouldn't be deleted (not touched yet)"])
+ call cursor(2, 1)
+
+ set compatible
+ set backspace=2
+
+ exe "normal Avim1\<C-U>\<Esc>\<CR>"
+ exe "normal Avim2\<C-G>u\<C-U>\<Esc>\<CR>"
+
+ set cpo-=<
+ inoremap <c-u> <left><c-u>
+ exe "normal Avim3\<*C-U>\<Esc>\<CR>"
+ iunmap <c-u>
+ exe "normal Avim4\<C-U>\<C-U>\<Esc>\<CR>"
+
+ " Test with backspace set to the compatible setting
+ set backspace= visualbell
+ exe "normal A vim5\<Esc>A\<C-U>\<C-U>\<Esc>\<CR>"
+ exe "normal A vim6\<Esc>Azwei\<C-G>u\<C-U>\<Esc>\<CR>"
+
+ inoremap <c-u> <left><c-u>
+ exe "normal A vim7\<*C-U>\<*C-U>\<Esc>\<CR>"
+
+ call assert_equal([
+ \ "1 this shouldn't be deleted",
+ \ "2 this shouldn't be deleted",
+ \ "3 this shouldn't be deleted",
+ \ "4 this should be deleted3",
+ \ "",
+ \ "6 this shouldn't be deleted vim5",
+ \ "7 this shouldn't be deleted vim6",
+ \ "8 this shouldn't be deleted (not touched yet) vim7",
+ \ ""], getline(1, '$'))
+
+ " Reset values
+ set compatible&vim
+ set visualbell&vim
+ set backspace&vim
+
+ " Test new nostop option
+ %d_
+ let expected = "foo bar foobar"
+ call setline(1, expected)
+ call cursor(1, 8)
+ exe ":norm! ianotherone\<c-u>"
+ call assert_equal(expected, getline(1))
+ call cursor(1, 8)
+ exe ":norm! ianothertwo\<c-w>"
+ call assert_equal(expected, getline(1))
+
+ let content = getline(1)
+ for value in ['indent,nostop', 'eol,nostop', 'indent,eol,nostop', 'indent,eol,start,nostop']
+ exe ":set bs=".. value
+ %d _
+ call setline(1, content)
+ let expected = " foobar"
+ call cursor(1, 8)
+ exe ":norm! ianotherone\<c-u>"
+ call assert_equal(expected, getline(1), 'CTRL-U backspace value: '.. &bs)
+ let expected = "foo foobar"
+ call setline(1, content)
+ call cursor(1, 8)
+ exe ":norm! ianothertwo\<c-w>"
+ call assert_equal(expected, getline(1), 'CTRL-W backspace value: '.. &bs)
+ endfor
+
+ " Reset options
+ set compatible&vim
+ set visualbell&vim
+ set backspace&vim
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_backup.vim b/src/testdir/test_backup.vim
new file mode 100644
index 0000000..68f2985
--- /dev/null
+++ b/src/testdir/test_backup.vim
@@ -0,0 +1,88 @@
+" Tests for the backup function
+
+source check.vim
+
+func Test_backup()
+ set backup backupdir=. backupskip=
+ new
+ call setline(1, ['line1', 'line2'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ let l = readfile('Xbackup.txt~')
+ call assert_equal(['line1', 'line2'], l)
+ bw!
+ set backup&vim backupdir&vim backupskip&vim
+ call delete('Xbackup.txt')
+ call delete('Xbackup.txt~')
+endfunc
+
+func Test_backup_backupskip()
+ set backup backupdir=. backupskip=*.txt
+ new
+ call setline(1, ['line1', 'line2'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ call assert_false(filereadable('Xbackup.txt~'))
+ bw!
+ set backup&vim backupdir&vim backupskip&vim
+ call delete('Xbackup.txt')
+ call delete('Xbackup.txt~')
+endfunc
+
+func Test_backup2()
+ set backup backupdir=.// backupskip=
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f = expand('%')
+ call assert_match('%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim backupskip&vim
+endfunc
+
+func Test_backup2_backupcopy()
+ set backup backupdir=.// backupcopy=yes backupskip=
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f = expand('%')
+ call assert_match('%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim backupcopy&vim backupskip&vim
+endfunc
+
+" Test for using a non-existing directory as a backup directory
+func Test_non_existing_backupdir()
+ set backupdir=./non_existing_dir backupskip=
+ call writefile(['line1'], 'Xbackupdir', 'D')
+ new Xbackupdir
+ call assert_fails('write', 'E510:')
+
+ set backupdir&vim backupskip&vim
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_balloon.vim b/src/testdir/test_balloon.vim
new file mode 100644
index 0000000..5e84f9e
--- /dev/null
+++ b/src/testdir/test_balloon.vim
@@ -0,0 +1,67 @@
+" Tests for 'balloonevalterm'.
+" A few tests only work in the terminal.
+
+source check.vim
+CheckNotGui
+CheckFeature balloon_eval_term
+
+source screendump.vim
+CheckScreendump
+
+let s:common_script =<< trim [CODE]
+ call setline(1, ["one one one", "two tXo two", "three three three"])
+ set balloonevalterm balloonexpr=MyBalloonExpr()..s:trailing balloondelay=100
+ let s:trailing = '<' " check that script context is set
+ func MyBalloonExpr()
+ return "line " .. v:beval_lnum .. " column " .. v:beval_col .. ":\n" .. v:beval_text
+ endfun
+ redraw
+[CODE]
+
+func Test_balloon_eval_term()
+ " Use <Ignore> after <MouseMove> to return from vgetc() without removing
+ " the balloon.
+ let xtra_lines =<< trim [CODE]
+ set updatetime=300
+ au CursorHold * echo 'hold fired'
+ func Trigger()
+ call test_setmouse(2, 6)
+ call feedkeys("\<MouseMove>\<Ignore>", "xt")
+ endfunc
+ [CODE]
+ call writefile(s:common_script + xtra_lines, 'XTest_beval', 'D')
+
+ " Check that the balloon shows up after a mouse move
+ let buf = RunVimInTerminal('-S XTest_beval', {'rows': 10, 'cols': 50})
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, 'll')
+ call term_sendkeys(buf, ":call Trigger()\<CR>")
+ call VerifyScreenDump(buf, 'Test_balloon_eval_term_01', {})
+
+ " Make sure the balloon still shows after 'updatetime' passed and CursorHold
+ " was triggered.
+ call TermWait(buf, 150)
+ call VerifyScreenDump(buf, 'Test_balloon_eval_term_01a', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_balloon_eval_term_visual()
+ " Use <Ignore> after <MouseMove> to return from vgetc() without removing
+ " the balloon.
+ call writefile(s:common_script + [
+ \ 'call test_setmouse(3, 6)',
+ \ 'call feedkeys("3Gevfr\<MouseMove>\<Ignore>", "xt")',
+ \ ], 'XTest_beval_visual', 'D')
+
+ " Check that the balloon shows up after a mouse move
+ let buf = RunVimInTerminal('-S XTest_beval_visual', {'rows': 10, 'cols': 50})
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_balloon_eval_term_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_balloon_gui.vim b/src/testdir/test_balloon_gui.vim
new file mode 100644
index 0000000..d01114c
--- /dev/null
+++ b/src/testdir/test_balloon_gui.vim
@@ -0,0 +1,21 @@
+" Tests for 'ballooneval' in the GUI.
+
+source check.vim
+CheckGui
+CheckFeature balloon_eval
+
+func Test_balloon_show_gui()
+ let msg = 'this this this this'
+ call balloon_show(msg)
+ call assert_equal(msg, balloon_gettext())
+ sleep 10m
+ call balloon_show('')
+
+ let msg = 'that that'
+ eval msg->balloon_show()
+ call assert_equal(msg, balloon_gettext())
+ sleep 10m
+ call balloon_show('')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_behave.vim b/src/testdir/test_behave.vim
new file mode 100644
index 0000000..72411b4
--- /dev/null
+++ b/src/testdir/test_behave.vim
@@ -0,0 +1,31 @@
+" Test the :behave command
+
+func Test_behave()
+ behave mswin
+ call assert_equal('mouse,key', &selectmode)
+ call assert_equal('popup', &mousemodel)
+ call assert_equal('startsel,stopsel', &keymodel)
+ call assert_equal('exclusive', &selection)
+
+ behave xterm
+ call assert_equal('', &selectmode)
+ call assert_equal('extend', &mousemodel)
+ call assert_equal('', &keymodel)
+ call assert_equal('inclusive', &selection)
+
+ set selection&
+ set mousemodel&
+ set keymodel&
+ set selection&
+endfunc
+
+func Test_behave_completion()
+ call feedkeys(":behave \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"behave mswin xterm', @:)
+endfunc
+
+func Test_behave_error()
+ call assert_fails('behave x', 'E475:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_bench_regexp.vim b/src/testdir/test_bench_regexp.vim
new file mode 100644
index 0000000..eb9f5f8
--- /dev/null
+++ b/src/testdir/test_bench_regexp.vim
@@ -0,0 +1,24 @@
+" Test for benchmarking the RE engine
+
+source check.vim
+CheckFeature reltime
+
+func Measure(file, pattern, arg)
+ for re in range(3)
+ let sstart = reltime()
+ let before = ['set re=' .. re]
+ let after = ['call search("' .. escape(a:pattern, '\\') .. '", "", "", 10000)']
+ let after += ['quit!']
+ let args = empty(a:arg) ? '' : a:arg .. ' ' .. a:file
+ call RunVim(before, after, args)
+ let s = 'file: ' .. a:file .. ', re: ' .. re ..
+ \ ', time: ' .. reltimestr(reltime(sstart))
+ call writefile([s], 'benchmark.out', "a")
+ endfor
+endfunc
+
+func Test_Regex_Benchmark()
+ call Measure('samples/re.freeze.txt', '\s\+\%#\@<!$', '+5')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_blob.vim b/src/testdir/test_blob.vim
new file mode 100644
index 0000000..cccecb7
--- /dev/null
+++ b/src/testdir/test_blob.vim
@@ -0,0 +1,835 @@
+" Tests for the Blob types
+
+import './vim9.vim' as v9
+
+func TearDown()
+ " Run garbage collection after every test
+ call test_garbagecollect_now()
+endfunc
+
+" Tests for Blob type
+
+" Blob creation from constant
+func Test_blob_create()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call assert_equal(v:t_blob, type(b))
+ call assert_equal(4, len(b))
+ call assert_equal(0xDE, b[0])
+ call assert_equal(0xAD, b[1])
+ call assert_equal(0xBE, b[2])
+ call assert_equal(0xEF, b[3])
+ call assert_fails('VAR x = b[4]')
+
+ call assert_equal(0xDE, get(b, 0))
+ call assert_equal(0xEF, get(b, 3))
+
+ call assert_fails('VAR b = 0z1', 'E973:')
+ call assert_fails('VAR b = 0z1x', 'E973:')
+ call assert_fails('VAR b = 0z12345', 'E973:')
+
+ call assert_equal(0z, test_null_blob())
+
+ LET b = 0z001122.33445566.778899.aabbcc.dd
+ call assert_equal(0z00112233445566778899aabbccdd, b)
+ call assert_fails('VAR b = 0z1.1')
+ call assert_fails('VAR b = 0z.')
+ call assert_fails('VAR b = 0z001122.')
+ call assert_fails('call get("", 1)', 'E896:')
+ call assert_equal(0, len(test_null_blob()))
+ call assert_equal(0z, copy(test_null_blob()))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" assignment to a blob
+func Test_blob_assign()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ VAR b2 = b[1 : 2]
+ call assert_equal(0zADBE, b2)
+
+ VAR bcopy = b[:]
+ call assert_equal(b, bcopy)
+ call assert_false(b is bcopy)
+
+ LET b = 0zDEADBEEF
+ LET b2 = b
+ call assert_true(b is b2)
+ LET b[:] = 0z11223344
+ call assert_equal(0z11223344, b)
+ call assert_equal(0z11223344, b2)
+ call assert_true(b is b2)
+
+ LET b = 0zDEADBEEF
+ LET b[3 :] = 0z66
+ call assert_equal(0zDEADBE66, b)
+ LET b[: 1] = 0z8899
+ call assert_equal(0z8899BE66, b)
+
+ LET b = 0zDEADBEEF
+ LET b += 0z99
+ call assert_equal(0zDEADBEEF99, b)
+
+ VAR l = [0z12]
+ VAR m = deepcopy(l)
+ LET m[0] = 0z34 #" E742 or E741 should not occur.
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[2 : 3] = 0z112233
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E972:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[2 : 3] = 0z11
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E972:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[3 : 2] = 0z
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b ..= 0z33
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E734:', 'E1019:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b ..= "xx"
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E734:', 'E1019:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b += "xx"
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E734:', 'E1012:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[1 : 1] ..= 0z55
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E734:', 'E1183:', 'E734:'])
+
+ call assert_fails('let b = readblob("a1b2c3")', 'E484:')
+endfunc
+
+func Test_blob_get_range()
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ call assert_equal(0z2233, b[2 : 3])
+ call assert_equal(0z223344, b[2 : -1])
+ call assert_equal(0z00, b[0 : -5])
+ call assert_equal(0z, b[0 : -11])
+ call assert_equal(0z44, b[-1 :])
+ call assert_equal(0z0011223344, b[:])
+ call assert_equal(0z0011223344, b[: -1])
+ call assert_equal(0z, b[5 : 6])
+ call assert_equal(0z0011, b[-10 : 1])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " legacy script white space
+ let b = 0z0011223344
+ call assert_equal(0z2233, b[2:3])
+endfunc
+
+func Test_blob_get()
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ call assert_equal(0x00, get(b, 0))
+ call assert_equal(0x22, get(b, 2, 999))
+ call assert_equal(0x44, get(b, 4))
+ call assert_equal(0x44, get(b, -1))
+ call assert_equal(-1, get(b, 5))
+ call assert_equal(999, get(b, 5, 999))
+ call assert_equal(-1, get(b, -8))
+ call assert_equal(999, get(b, -8, 999))
+ call assert_equal(10, get(test_null_blob(), 2, 10))
+
+ call assert_equal(0x00, b[0])
+ call assert_equal(0x22, b[2])
+ call assert_equal(0x44, b[4])
+ call assert_equal(0x44, b[-1])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ echo b[5]
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ echo b[-8]
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+endfunc
+
+func Test_blob_to_string()
+ let lines =<< trim END
+ VAR b = 0z00112233445566778899aabbccdd
+ call assert_equal('0z00112233.44556677.8899AABB.CCDD', string(b))
+ call assert_equal(b, eval(string(b)))
+ call remove(b, 4, -1)
+ call assert_equal('0z00112233', string(b))
+ call remove(b, 0, 3)
+ call assert_equal('0z', string(b))
+ call assert_equal('0z', string(test_null_blob()))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_compare()
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR b3 = 0z001122
+ call assert_true(b1 == b1)
+ call assert_false(b1 == b2)
+ call assert_false(b1 == b3)
+ call assert_true(b1 != b2)
+ call assert_true(b1 != b3)
+ call assert_true(b1 == 0z0011)
+
+ call assert_false(b1 is b2)
+ LET b2 = b1
+ call assert_true(b1 == b2)
+ call assert_true(b1 is b2)
+ LET b2 = copy(b1)
+ call assert_true(b1 == b2)
+ call assert_false(b1 is b2)
+ LET b2 = b1[:]
+ call assert_true(b1 == b2)
+ call assert_false(b1 is b2)
+ call assert_true(b1 isnot b2)
+ call assert_true(0z != 0z10)
+ call assert_true(0z10 != 0z)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ echo b1 == 9
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E977:', 'E1072', 'E1072'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ echo b1 != 9
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E977:', 'E1072', 'E1072'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 > b2
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E978:', 'E1072:', 'E1072:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 < b2
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E978:', 'E1072:', 'E1072:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 - b2
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E974:', 'E1036:', 'E974:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 / b2
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E974:', 'E1036:', 'E974:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 * b2
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E974:', 'E1036:', 'E974:'])
+endfunc
+
+func Test_blob_index_assign()
+ let lines =<< trim END
+ VAR b = 0z00
+ LET b[1] = 0x11
+ LET b[2] = 0x22
+ LET b[0] = 0x33
+ call assert_equal(0z331122, b)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0z00
+ LET b[2] = 0x33
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z00
+ LET b[-2] = 0x33
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z00010203
+ LET b[0 : -1] = 0z33
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z00010203
+ LET b[3 : 4] = 0z3344
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+endfunc
+
+func Test_blob_for_loop()
+ let lines =<< trim END
+ VAR blob = 0z00010203
+ VAR i = 0
+ for byte in blob
+ call assert_equal(i, byte)
+ LET i += 1
+ endfor
+ call assert_equal(4, i)
+
+ LET blob = 0z00
+ call remove(blob, 0)
+ call assert_equal(0, len(blob))
+ for byte in blob
+ call assert_report('loop over empty blob')
+ endfor
+
+ LET blob = 0z0001020304
+ LET i = 0
+ for byte in blob
+ call assert_equal(i, byte)
+ if i == 1
+ call remove(blob, 0)
+ elseif i == 3
+ call remove(blob, 3)
+ endif
+ LET i += 1
+ endfor
+ call assert_equal(5, i)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_concatenate()
+ let lines =<< trim END
+ VAR b = 0z0011
+ LET b += 0z2233
+ call assert_equal(0z00112233, b)
+
+ LET b = 0zDEAD + 0zBEEF
+ call assert_equal(0zDEADBEEF, b)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ LET b += "a"
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E734:', 'E1012:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ LET b += 88
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E734:', 'E1012:', 'E734:'])
+endfunc
+
+func Test_blob_add()
+ let lines =<< trim END
+ VAR b = 0z0011
+ call add(b, 0x22)
+ call assert_equal(0z001122, b)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Only works in legacy script
+ let b = 0z0011
+ call add(b, '51')
+ call assert_equal(0z001133, b)
+ call assert_equal(1, add(test_null_blob(), 0x22))
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ call add(b, [9])
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E745:', 'E1012:', 'E1210:'])
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ call add("", 0x01)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E897:', 'E1013:', 'E1226:'])
+
+ let lines =<< trim END
+ add(test_null_blob(), 0x22)
+ END
+ call v9.CheckDefExecAndScriptFailure(lines, 'E1131:')
+
+ let lines =<< trim END
+ let b = 0zDEADBEEF
+ lockvar b
+ call add(b, 0)
+ unlockvar b
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+endfunc
+
+func Test_blob_empty()
+ call assert_false(empty(0z001122))
+ call assert_true(empty(0z))
+ call assert_true(empty(test_null_blob()))
+endfunc
+
+" Test removing items in blob
+func Test_blob_func_remove()
+ let lines =<< trim END
+ #" Test removing 1 element
+ VAR b = 0zDEADBEEF
+ call assert_equal(0xDE, remove(b, 0))
+ call assert_equal(0zADBEEF, b)
+
+ LET b = 0zDEADBEEF
+ call assert_equal(0xEF, remove(b, -1))
+ call assert_equal(0zDEADBE, b)
+
+ LET b = 0zDEADBEEF
+ call assert_equal(0xAD, remove(b, 1))
+ call assert_equal(0zDEBEEF, b)
+
+ #" Test removing range of element(s)
+ LET b = 0zDEADBEEF
+ call assert_equal(0zBE, remove(b, 2, 2))
+ call assert_equal(0zDEADEF, b)
+
+ LET b = 0zDEADBEEF
+ call assert_equal(0zADBE, remove(b, 1, 2))
+ call assert_equal(0zDEEF, b)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test invalid cases
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, 5)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, 1, 5)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, -10)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, 3, 2)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(test_null_blob(), 1, 2)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ let b = 0zDEADBEEF
+ lockvar b
+ call remove(b, 0)
+ unlockvar b
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+
+ " can only check at script level, not in a :def function
+ let lines =<< trim END
+ vim9script
+ var b = 0zDEADBEEF
+ lockvar b
+ remove(b, 0)
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+
+ call assert_fails('echo remove(0z1020, [])', 'E745:')
+ call assert_fails('echo remove(0z1020, 0, [])', 'E745:')
+endfunc
+
+func Test_blob_read_write()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call writefile(b, 'Xblob')
+ VAR br = readfile('Xblob', 'B')
+ call assert_equal(b, br)
+ VAR br2 = readblob('Xblob')
+ call assert_equal(b, br2)
+ VAR br3 = readblob('Xblob', 1)
+ call assert_equal(b[1 :], br3)
+ VAR br4 = readblob('Xblob', 1, 2)
+ call assert_equal(b[1 : 2], br4)
+ VAR br5 = readblob('Xblob', -3)
+ call assert_equal(b[-3 :], br5)
+ VAR br6 = readblob('Xblob', -3, 2)
+ call assert_equal(b[-3 : -2], br6)
+
+ #" reading past end of file, empty result
+ VAR br1e = readblob('Xblob', 10000)
+ call assert_equal(0z, br1e)
+
+ #" reading too much, result is truncated
+ VAR blong = readblob('Xblob', -1000)
+ call assert_equal(b, blong)
+ LET blong = readblob('Xblob', -10, 8)
+ call assert_equal(b, blong)
+ LET blong = readblob('Xblob', 0, 10)
+ call assert_equal(b, blong)
+
+ call delete('Xblob')
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ if filereadable('/dev/random')
+ let b = readblob('/dev/random', 0, 10)
+ call assert_equal(10, len(b))
+ endif
+
+ call assert_fails("call readblob('notexist')", 'E484:')
+ " TODO: How do we test for the E485 error?
+
+ " This was crashing when calling readfile() with a directory.
+ call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
+endfunc
+
+" filter() item in blob
+func Test_blob_filter()
+ let lines =<< trim END
+ call assert_equal(test_null_blob(), filter(test_null_blob(), '0'))
+ call assert_equal(0z, filter(0zDEADBEEF, '0'))
+ call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE'))
+ call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE'))
+ call assert_equal(0zDEADBE, filter(0zDEADBEEF, 'v:val != 0xEF'))
+ call assert_equal(0zDEADBEEF, filter(0zDEADBEEF, '1'))
+ call assert_equal(0z01030103, filter(0z010203010203, 'v:val != 0x02'))
+ call assert_equal(0zADEF, filter(0zDEADBEEF, 'v:key % 2'))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+ call assert_fails('echo filter(0z10, "a10")', 'E121:')
+endfunc
+
+" map() item in blob
+func Test_blob_map()
+ let lines =<< trim END
+ call assert_equal(0zDFAEBFF0, map(0zDEADBEEF, 'v:val + 1'))
+ call assert_equal(0z00010203, map(0zDEADBEEF, 'v:key'))
+ call assert_equal(0zDEAEC0F2, map(0zDEADBEEF, 'v:key + v:val'))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ call map(0z00, '[9]')
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E978:')
+ call assert_fails('echo map(0z10, "a10")', 'E121:')
+endfunc
+
+func Test_blob_index()
+ let lines =<< trim END
+ call assert_equal(2, index(0zDEADBEEF, 0xBE))
+ call assert_equal(-1, index(0zDEADBEEF, 0))
+ call assert_equal(2, index(0z11111111, 0x11, 2))
+ call assert_equal(3, 0z11110111->index(0x11, 2))
+ call assert_equal(2, index(0z11111111, 0x11, -2))
+ call assert_equal(3, index(0z11110111, 0x11, -2))
+ call assert_equal(0, index(0z11110111, 0x11, -10))
+ call assert_equal(-1, index(test_null_blob(), 1))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_insert()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0x33)
+ call assert_equal(0z33DEADBEEF, b)
+
+ LET b = 0zDEADBEEF
+ call insert(b, 0x33, 2)
+ call assert_equal(0zDEAD33BEEF, b)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " only works in legacy script
+ call assert_equal(0, insert(test_null_blob(), 0x33))
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, -1)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 257)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0, [9])
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E745:', 'E1013:', 'E1210:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0, -20)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0, 20)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, [])
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, ['E745:', 'E1013:', 'E1210:'])
+
+ let lines =<< trim END
+ insert(test_null_blob(), 0x33)
+ END
+ call v9.CheckDefExecAndScriptFailure(lines, 'E1131:')
+
+ let lines =<< trim END
+ let b = 0zDEADBEEF
+ lockvar b
+ call insert(b, 3)
+ unlockvar b
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+
+ let lines =<< trim END
+ vim9script
+ var b = 0zDEADBEEF
+ lockvar b
+ insert(b, 3)
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+endfunc
+
+func Test_blob_reverse()
+ let lines =<< trim END
+ call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
+ call assert_equal(0zBEADDE, reverse(0zDEADBE))
+ call assert_equal(0zADDE, reverse(0zDEAD))
+ call assert_equal(0zDE, reverse(0zDE))
+ call assert_equal(0z, reverse(test_null_blob()))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_json_encode()
+ let lines =<< trim END
+ call assert_equal('[222,173,190,239]', json_encode(0zDEADBEEF))
+ call assert_equal('[]', json_encode(0z))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_lock()
+ let lines =<< trim END
+ let b = 0z112233
+ lockvar b
+ unlockvar b
+ let b = 0z44
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ var b = 0z112233
+ lockvar b
+ unlockvar b
+ b = 0z44
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ let lines =<< trim END
+ let b = 0z112233
+ lockvar b
+ let b = 0z44
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+
+ let lines =<< trim END
+ vim9script
+ var b = 0z112233
+ lockvar b
+ b = 0z44
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+endfunc
+
+func Test_blob_sort()
+ call v9.CheckLegacyAndVim9Failure(['call sort([1.0, 0z11], "f")'], 'E975:')
+ call v9.CheckLegacyAndVim9Failure(['call sort([11, 0z11], "N")'], 'E974:')
+endfunc
+
+" Tests for the blob2list() function
+func Test_blob2list()
+ call assert_fails('let v = blob2list(10)', 'E1238: Blob required for argument 1')
+ eval 0zFFFF->blob2list()->assert_equal([255, 255])
+ let tests = [[0z0102, [1, 2]],
+ \ [0z00, [0]],
+ \ [0z, []],
+ \ [0z00000000, [0, 0, 0, 0]],
+ \ [0zAABB.CCDD, [170, 187, 204, 221]]]
+ for t in tests
+ call assert_equal(t[0]->blob2list(), t[1])
+ endfor
+ exe 'let v = 0z' .. repeat('000102030405060708090A0B0C0D0E0F', 64)
+ call assert_equal(1024, blob2list(v)->len())
+ call assert_equal([4, 8, 15], [v[100], v[1000], v[1023]])
+ call assert_equal([], blob2list(test_null_blob()))
+endfunc
+
+" Tests for the list2blob() function
+func Test_list2blob()
+ call assert_fails('let b = list2blob(0z10)', 'E1211: List required for argument 1')
+ let tests = [[[1, 2], 0z0102],
+ \ [[0], 0z00],
+ \ [[], 0z],
+ \ [[0, 0, 0, 0], 0z00000000],
+ \ [[255, 255], 0zFFFF],
+ \ [[170, 187, 204, 221], 0zAABB.CCDD],
+ \ ]
+ for t in tests
+ call assert_equal(t[1], t[0]->list2blob())
+ endfor
+ call assert_fails('let b = list2blob([1, []])', 'E745:')
+ call assert_fails('let b = list2blob([-1])', 'E1239:')
+ call assert_fails('let b = list2blob([256])', 'E1239:')
+ let b = range(16)->repeat(64)->list2blob()
+ call assert_equal(1024, b->len())
+ call assert_equal([4, 8, 15], [b[100], b[1000], b[1023]])
+
+ call assert_equal(0z, list2blob(test_null_list()))
+ call assert_equal(0z00010203, list2blob(range(4)))
+endfunc
+
+" The following used to cause an out-of-bounds memory access
+func Test_blob2string()
+ let v = '0z' .. repeat('01010101.', 444)
+ let v ..= '01'
+ exe 'let b = ' .. v
+ call assert_equal(v, string(b))
+endfunc
+
+func Test_blob_repeat()
+ call assert_equal(0z, repeat(0z00, 0))
+ call assert_equal(0z00, repeat(0z00, 1))
+ call assert_equal(0z0000, repeat(0z00, 2))
+ call assert_equal(0z00000000, repeat(0z0000, 2))
+
+ call assert_equal(0z, repeat(0z12, 0))
+ call assert_equal(0z, repeat(0z1234, 0))
+ call assert_equal(0z1234, repeat(0z1234, 1))
+ call assert_equal(0z12341234, repeat(0z1234, 2))
+endfunc
+
+" Test for blob allocation failure
+func Test_blob_alloc_failure()
+ " blob variable
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let v = 0z10', 'E342:')
+
+ " blob slice
+ let v = 0z1020
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = v[0:0]', 'E342:')
+ call assert_equal(0z1020, x)
+
+ " blob remove()
+ let v = 0z10203040
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = remove(v, 1, 2)', 'E342:')
+ call assert_equal(0, x)
+
+ " list2blob()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let a = list2blob([1, 2, 4])', 'E342:')
+ call assert_equal(0, a)
+
+ " mapnew()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = mapnew(0z1234, {_, v -> 1})', 'E342:')
+ call assert_equal(0, x)
+
+ " copy()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = copy(v)', 'E342:')
+ call assert_equal(0z, x)
+
+ " readblob()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = readblob("test_blob.vim")', 'E342:')
+ call assert_equal(0, x)
+endfunc
+
+" Test for the indexof() function
+func Test_indexof()
+ let b = 0zdeadbeef
+ call assert_equal(0, indexof(b, {i, v -> v == 0xde}))
+ call assert_equal(3, indexof(b, {i, v -> v == 0xef}))
+ call assert_equal(-1, indexof(b, {i, v -> v == 0x1}))
+ call assert_equal(1, indexof(b, "v:val == 0xad"))
+ call assert_equal(-1, indexof(b, "v:val == 0xff"))
+ call assert_equal(-1, indexof(b, {_, v -> "v == 0xad"}))
+
+ call assert_equal(-1, indexof(0z, "v:val == 0x0"))
+ call assert_equal(-1, indexof(test_null_blob(), "v:val == 0xde"))
+ call assert_equal(-1, indexof(b, test_null_string()))
+ call assert_equal(-1, indexof(b, test_null_function()))
+
+ let b = 0z01020102
+ call assert_equal(1, indexof(b, "v:val == 0x02", #{startidx: 0}))
+ call assert_equal(2, indexof(b, "v:val == 0x01", #{startidx: -2}))
+ call assert_equal(-1, indexof(b, "v:val == 0x01", #{startidx: 5}))
+ call assert_equal(0, indexof(b, "v:val == 0x01", #{startidx: -5}))
+ call assert_equal(0, indexof(b, "v:val == 0x01", test_null_dict()))
+
+ " failure cases
+ call assert_fails('let i = indexof(b, "val == 0xde")', 'E121:')
+ call assert_fails('let i = indexof(b, {})', 'E1256:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_blockedit.vim b/src/testdir/test_blockedit.vim
new file mode 100644
index 0000000..71f687b
--- /dev/null
+++ b/src/testdir/test_blockedit.vim
@@ -0,0 +1,134 @@
+" Test for block inserting
+
+func Test_blockinsert_indent()
+ new
+ filetype plugin indent on
+ setlocal sw=2 et ft=vim
+ call setline(1, ['let a=[', ' ''eins'',', ' ''zwei'',', ' ''drei'']'])
+ call cursor(2, 3)
+ exe "norm! \<c-v>2jI\\ \<esc>"
+ call assert_equal(['let a=[', ' \ ''eins'',', ' \ ''zwei'',', ' \ ''drei'']'],
+ \ getline(1,'$'))
+ " reset to sane state
+ filetype off
+ bwipe!
+endfunc
+
+func Test_blockinsert_autoindent()
+ new
+ let lines =<< trim END
+ vim9script
+ var d = {
+ a: () => 0,
+ b: () => 0,
+ c: () => 0,
+ }
+ END
+ call setline(1, lines)
+ filetype plugin indent on
+ setlocal sw=2 et ft=vim
+ setlocal indentkeys+=:
+ exe "norm! 3Gf)\<c-v>2jA: asdf\<esc>"
+ let expected =<< trim END
+ vim9script
+ var d = {
+ a: (): asdf => 0,
+ b: (): asdf => 0,
+ c: (): asdf => 0,
+ }
+ END
+ call assert_equal(expected, getline(1, 6))
+
+ " insert on the next column should do exactly the same
+ :%dele
+ call setline(1, lines)
+ exe "norm! 3Gf)l\<c-v>2jI: asdf\<esc>"
+ call assert_equal(expected, getline(1, 6))
+
+ :%dele
+ call setline(1, lines)
+ setlocal sw=8 noet
+ exe "norm! 3Gf)\<c-v>2jA: asdf\<esc>"
+ let expected =<< trim END
+ vim9script
+ var d = {
+ a: (): asdf => 0,
+ b: (): asdf => 0,
+ c: (): asdf => 0,
+ }
+ END
+ call assert_equal(expected, getline(1, 6))
+
+ " insert on the next column should do exactly the same
+ :%dele
+ call setline(1, lines)
+ exe "norm! 3Gf)l\<c-v>2jI: asdf\<esc>"
+ call assert_equal(expected, getline(1, 6))
+
+ filetype off
+ bwipe!
+endfunc
+
+func Test_blockinsert_delete()
+ new
+ let _bs = &bs
+ set bs=2
+ call setline(1, ['case Arg is ', ' when Name_Async,', ' when Name_Num_Gangs,', 'end if;'])
+ exe "norm! ggjVj\<c-v>$o$A\<bs>\<esc>"
+ "call feedkeys("Vj\<c-v>$o$A\<bs>\<esc>", 'ti')
+ call assert_equal(["case Arg is ", " when Name_Async", " when Name_Num_Gangs,", "end if;"],
+ \ getline(1,'$'))
+ " reset to sane state
+ let &bs = _bs
+ bwipe!
+endfunc
+
+func Test_blockappend_eol_cursor()
+ new
+ " Test 1 Move 1 char left
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! gg$\<c-v>2jA\<left>x\<esc>"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+ " Test 2 Move 2 chars left
+ sil %d
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! gg$\<c-v>2jA\<left>\<left>x\<esc>"
+ call assert_equal(['axaa', 'bxbb', 'cxcc'], getline(1, '$'))
+ " Test 3 Move 3 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "norm! ggl$\<c-v>2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['xaaa', 'bbb', 'ccc'], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_blockappend_eol_cursor2()
+ new
+ " Test 1 Move 1 char left
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>x\<esc>"
+ call assert_equal(['aaaaxa', 'bbbx', 'ccccxc'], getline(1, '$'))
+ " Test 2 Move 2 chars left
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>\<left>x\<esc>"
+ call assert_equal(['aaaxaa', 'bbbx', 'cccxcc'], getline(1, '$'))
+ " Test 3 Move 3 chars left (to the beginning of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! gg\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
+ " Test 4 Move 3 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$'))
+ " Test 5 Move 4 chars left (outside of the visual selection)
+ sil %d
+ call setline(1, ['aaaaa', 'bbb', 'ccccc'])
+ exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>\<left>x\<esc>"
+ call assert_equal(['axaaaa', 'bxbb', 'cxcccc'], getline(1, '$'))
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_breakindent.vim b/src/testdir/test_breakindent.vim
new file mode 100644
index 0000000..27b1d98
--- /dev/null
+++ b/src/testdir/test_breakindent.vim
@@ -0,0 +1,1055 @@
+" Test for breakindent
+"
+" Note: if you get strange failures when adding new tests, it might be that
+" while the test is run, the breakindent caching gets in its way.
+" It helps to change the tabstop setting and force a redraw (e.g. see
+" Test_breakindent08())
+source check.vim
+CheckOption breakindent
+
+source view_util.vim
+source screendump.vim
+
+func SetUp()
+ let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
+endfunc
+
+func s:screen_lines(lnum, width) abort
+ return ScreenLines([a:lnum, a:lnum + 2], a:width)
+endfunc
+
+func s:screen_lines2(lnums, lnume, width) abort
+ return ScreenLines([a:lnums, a:lnume], a:width)
+endfunc
+
+func s:compare_lines(expect, actual)
+ call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
+endfunc
+
+func s:test_windows(...)
+ call NewWindow(10, 20)
+ setl ts=4 sw=4 sts=4 breakindent
+ put =s:input
+ exe get(a:000, 0, '')
+endfunc
+
+func s:close_windows(...)
+ call CloseWindow()
+ exe get(a:000, 0, '')
+endfunc
+
+func Test_breakindent01()
+ " simple breakindent test
+ call s:test_windows('setl briopt=min:0')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrst",
+ \ " GHIJ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_breakindent01_vartabs()
+ " like 01 but with vartabs feature
+ CheckFeature vartabs
+ call s:test_windows('setl briopt=min:0 vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrst",
+ \ " GHIJ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent02()
+ " simple breakindent test with showbreak set
+ set sbr=>>
+ call s:test_windows('setl briopt=min:0 sbr=')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " >>qr",
+ \ " >>EF",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr=')
+endfunc
+
+func Test_breakindent02_vartabs()
+ CheckFeature vartabs
+ " simple breakindent test with showbreak set
+ call s:test_windows('setl briopt=min:0 sbr=>> vts=4')
+ let lines = s:screen_lines(line('.'), 8)
+ let expect = [
+ \ " abcd",
+ \ " >>qr",
+ \ " >>EF",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent03()
+ " simple breakindent test with showbreak set and briopt including sbr
+ call s:test_windows('setl briopt=sbr,min:0 sbr=++')
+ let lines = s:screen_lines(line('.'), 8)
+ let expect = [
+ \ " abcd",
+ \ "++ qrst",
+ \ "++ GHIJ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr=')
+endfunc
+
+func Test_breakindent03_vartabs()
+ " simple breakindent test with showbreak set and briopt including sbr
+ CheckFeature vartabs
+ call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4')
+ let lines = s:screen_lines(line('.'), 8)
+ let expect = [
+ \ " abcd",
+ \ "++ qrst",
+ \ "++ GHIJ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent04()
+ " breakindent set with min width 18
+ set sbr=<<<
+ call s:test_windows('setl sbr=NONE briopt=min:18')
+ let lines = s:screen_lines(line('.'), 8)
+ let expect = [
+ \ " abcd",
+ \ " qrstuv",
+ \ " IJKLMN",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr=')
+ set sbr=
+endfunc
+
+func Test_breakindent04_vartabs()
+ " breakindent set with min width 18
+ CheckFeature vartabs
+ call s:test_windows('setl sbr= briopt=min:18 vts=4')
+ let lines = s:screen_lines(line('.'), 8)
+ let expect = [
+ \ " abcd",
+ \ " qrstuv",
+ \ " IJKLMN",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent05()
+ " breakindent set and shift by 2
+ call s:test_windows('setl briopt=shift:2,min:0')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qr",
+ \ " EF",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_breakindent05_vartabs()
+ " breakindent set and shift by 2
+ CheckFeature vartabs
+ call s:test_windows('setl briopt=shift:2,min:0 vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qr",
+ \ " EF",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent06()
+ " breakindent set and shift by -1
+ call s:test_windows('setl briopt=shift:-1,min:0')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrstu",
+ \ " HIJKL",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_breakindent06_vartabs()
+ " breakindent set and shift by -1
+ CheckFeature vartabs
+ call s:test_windows('setl briopt=shift:-1,min:0 vts=4')
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ " abcd",
+ \ " qrstu",
+ \ " HIJKL",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent07()
+ " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "? m",
+ \ "? x",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= cpo-=n')
+endfunc
+
+func Test_breakindent07_vartabs()
+ CheckFeature vartabs
+ " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "? m",
+ \ "? x",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
+
+func Test_breakindent07a()
+ " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ " ? m",
+ \ " ? x",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr=')
+endfunc
+
+func Test_breakindent07a_vartabs()
+ CheckFeature vartabs
+ " breakindent set and shift by 1, Number set sbr=? and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ " ? m",
+ \ " ? x",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " clean up
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent08()
+ " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4')
+ " make sure, cache is invalidated!
+ set ts=8
+ redraw!
+ set ts=4
+ redraw!
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ "# opq",
+ \ "# BCD",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= cpo-=n')
+endfunc
+
+func Test_breakindent08_vartabs()
+ CheckFeature vartabs
+ " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4 vts=4')
+ " make sure, cache is invalidated!
+ set ts=8
+ redraw!
+ set ts=4
+ redraw!
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ "# opq",
+ \ "# BCD",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
+
+func Test_breakindent08a()
+ " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " # opq",
+ \ " # BCD",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr=')
+endfunc
+
+func Test_breakindent08a_vartabs()
+ CheckFeature vartabs
+ " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr
+ call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " # opq",
+ \ " # BCD",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent09()
+ " breakindent set and shift by 1, Number and list set sbr=#
+ call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " #op",
+ \ " #AB",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr=')
+endfunc
+
+func Test_breakindent09_vartabs()
+ CheckFeature vartabs
+ " breakindent set and shift by 1, Number and list set sbr=#
+ call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list vts=4')
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ^Iabcd",
+ \ " #op",
+ \ " #AB",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent10()
+ " breakindent set, Number set sbr=~
+ call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0')
+ " make sure, cache is invalidated!
+ set ts=8
+ redraw!
+ set ts=4
+ redraw!
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "~ mn",
+ \ "~ yz",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= cpo-=n')
+endfunc
+
+func Test_breakindent10_vartabs()
+ CheckFeature vartabs
+ " breakindent set, Number set sbr=~
+ call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0 vts=4')
+ " make sure, cache is invalidated!
+ set ts=8
+ redraw!
+ set ts=4
+ redraw!
+ let lines = s:screen_lines(line('.'),10)
+ let expect = [
+ \ " 2 ab",
+ \ "~ mn",
+ \ "~ yz",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set sbr= cpo-=n vts&')
+endfunc
+
+func Test_breakindent11()
+ " test strdisplaywidth()
+ call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4')
+ let text = getline(2)
+ let width = strlen(text[1:]) + indent(2) + strlen(&sbr) * 3 " text wraps 3 times
+ call assert_equal(width, strdisplaywidth(text))
+ call s:close_windows('set sbr=')
+ call assert_equal(4, strdisplaywidth("\t", 4))
+endfunc
+
+func Test_breakindent11_vartabs()
+ CheckFeature vartabs
+ " test strdisplaywidth()
+ call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4')
+ let text = getline(2)
+ let width = strlen(text[1:]) + 2->indent() + strlen(&sbr) * 3 " text wraps 3 times
+ call assert_equal(width, text->strdisplaywidth())
+ call s:close_windows('set sbr= vts&')
+endfunc
+
+func Test_breakindent12()
+ " test breakindent with long indent
+ let s:input = "\t\t\t\t\t{"
+ call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>-')
+ let lines = s:screen_lines(2,16)
+ let expect = [
+ \ " 2 >--->--->--->",
+ \ " ---{ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set nuw=4 listchars=')
+endfunc
+
+func Test_breakindent12_vartabs()
+ CheckFeature vartabs
+ " test breakindent with long indent
+ let s:input = "\t\t\t\t\t{"
+ call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>- vts=4')
+ let lines = s:screen_lines(2,16)
+ let expect = [
+ \ " 2 >--->--->--->",
+ \ " ---{ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set nuw=4 listchars= vts&')
+endfunc
+
+func Test_breakindent13()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:10 ts=8')
+ vert resize 20
+ call setline(1, [" a\tb\tc\td\te", " z y x w v"])
+ 1
+ norm! fbgj"ayl
+ 2
+ norm! fygj"byl
+ call assert_equal('d', @a)
+ call assert_equal('w', @b)
+ call s:close_windows()
+endfunc
+
+func Test_breakindent13_vartabs()
+ CheckFeature vartabs
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:10 ts=8 vts=8')
+ vert resize 20
+ call setline(1, [" a\tb\tc\td\te", " z y x w v"])
+ 1
+ norm! fbgj"ayl
+ 2
+ norm! fygj"byl
+ call assert_equal('d', @a)
+ call assert_equal('w', @b)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent14()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt= ts=8')
+ vert resize 30
+ norm! 3a1234567890
+ norm! a abcde
+ exec "norm! 0\<C-V>tex"
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ "e ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_breakindent14_vartabs()
+ CheckFeature vartabs
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt= ts=8 vts=8')
+ vert resize 30
+ norm! 3a1234567890
+ norm! a abcde
+ exec "norm! 0\<C-V>tex"
+ let lines = s:screen_lines(line('.'),8)
+ let expect = [
+ \ "e ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent15()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt= ts=8 sw=8')
+ vert resize 30
+ norm! 4a1234567890
+ exe "normal! >>\<C-V>3f0x"
+ let lines = s:screen_lines(line('.'),20)
+ let expect = [
+ \ " 1234567890 ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_breakindent15_vartabs()
+ CheckFeature vartabs
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt= ts=8 sw=8 vts=8')
+ vert resize 30
+ norm! 4a1234567890
+ exe "normal! >>\<C-V>3f0x"
+ let lines = s:screen_lines(line('.'),20)
+ let expect = [
+ \ " 1234567890 ",
+ \ "~ ",
+ \ "~ ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent16()
+ " Check that overlong lines are indented correctly.
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:0 ts=4')
+ call setline(1, "\t".repeat("1234567890", 10))
+ resize 6
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1,10)
+ let expect = [
+ \ "<<< 789012",
+ \ " 345678",
+ \ " 901234",
+ \ ]
+ call s:compare_lines(expect, lines)
+ let lines = s:screen_lines(4,10)
+ let expect = [
+ \ " 567890",
+ \ " 123456",
+ \ " 7890 ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_breakindent16_vartabs()
+ CheckFeature vartabs
+ " Check that overlong lines are indented correctly.
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:0 ts=4 vts=4')
+ call setline(1, "\t".repeat("1234567890", 10))
+ resize 6
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1,10)
+ let expect = [
+ \ "<<< 789012",
+ \ " 345678",
+ \ " 901234",
+ \ ]
+ call s:compare_lines(expect, lines)
+ let lines = s:screen_lines(4,10)
+ let expect = [
+ \ " 567890",
+ \ " 123456",
+ \ " 7890 ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set vts&')
+endfunc
+
+func Test_breakindent17_vartabs()
+ CheckFeature vartabs
+ let s:input = ""
+ call s:test_windows('setl breakindent list listchars=tab:<-> showbreak=+++')
+ call setline(1, "\t" . repeat('a', 63))
+ vert resize 30
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1, 30)
+ let expect = [
+ \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " +++aaaaaaaaaaaaaaaaaaaaaaa",
+ \ " +++aaaaaaaaaaaaaa ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& list& listchars& showbreak&')
+endfunc
+
+func Test_breakindent18_vartabs()
+ CheckFeature vartabs
+ let s:input = ""
+ call s:test_windows('setl breakindent list listchars=tab:<->')
+ call setline(1, "\t" . repeat('a', 63))
+ vert resize 30
+ norm! 1gg$
+ redraw!
+ let lines = s:screen_lines(1, 30)
+ let expect = [
+ \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaa ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& list& listchars&')
+endfunc
+
+func Test_breakindent19_sbr_nextpage()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=shift:2,sbr,min:18 sbr=>')
+ call setline(1, repeat('a', 200))
+ norm! 1gg
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "aaaaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ setl scrolloff=5
+ norm! 5gj
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "aaaaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ "> aaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ redraw!
+ " moving the cursor doesn't change the text offset
+ norm! l
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ call s:compare_lines(expect, lines)
+
+ setl breakindent briopt=min:18 sbr=>
+ norm! 5gj
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('set breakindent& briopt& sbr&')
+endfunc
+
+func Test_breakindent20_cpo_n_nextpage()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:14 cpo+=n number')
+ call setline(1, repeat('abcdefghijklmnopqrst', 10))
+ norm! 1gg
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ " 1 abcdefghijklmnop",
+ \ " qrstabcdefghijkl",
+ \ " mnopqrstabcdefgh",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ setl scrolloff=5
+ norm! 6gj
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "<<< qrstabcdefghijkl",
+ \ " mnopqrstabcdefgh",
+ \ " ijklmnopqrstabcd",
+ \ ]
+ call s:compare_lines(expect, lines)
+
+ setl briopt+=shift:2
+ norm! 1gg
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ " 1 abcdefghijklmnop",
+ \ " qrstabcdefghij",
+ \ " klmnopqrstabcd",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ norm! 6gj
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "<<< qrstabcdefghij",
+ \ " klmnopqrstabcd",
+ \ " efghijklmnopqr",
+ \ ]
+ call s:compare_lines(expect, lines)
+
+ call s:close_windows('set breakindent& briopt& cpo& number&')
+endfunc
+
+func Test_breakindent20_list()
+ call s:test_windows('setl breakindent breakindentopt= linebreak')
+ " default:
+ call setline(1, [' 1. Congress shall make no law',
+ \ ' 2.) Congress shall make no law',
+ \ ' 3.] Congress shall make no law'])
+ norm! 1gg
+ redraw!
+ let lines = s:screen_lines2(1, 6, 20)
+ let expect = [
+ \ " 1. Congress ",
+ \ "shall make no law ",
+ \ " 2.) Congress ",
+ \ "shall make no law ",
+ \ " 3.] Congress ",
+ \ "shall make no law ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " set minimum indent
+ setl briopt=min:5
+ redraw!
+ let lines = s:screen_lines2(1, 6, 20)
+ let expect = [
+ \ " 1. Congress ",
+ \ " shall make no law ",
+ \ " 2.) Congress ",
+ \ " shall make no law ",
+ \ " 3.] Congress ",
+ \ " shall make no law ",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " set additional handing indent
+ setl briopt+=list:4
+ redraw!
+ let expect = [
+ \ " 1. Congress ",
+ \ " shall make no ",
+ \ " law ",
+ \ " 2.) Congress ",
+ \ " shall make no ",
+ \ " law ",
+ \ " 3.] Congress ",
+ \ " shall make no ",
+ \ " law ",
+ \ ]
+ let lines = s:screen_lines2(1, 9, 20)
+ call s:compare_lines(expect, lines)
+
+ " reset linebreak option
+ " Note: it indents by one additional
+ " space, because of the leading space.
+ setl linebreak&vim list listchars=eol:$,space:_
+ redraw!
+ let expect = [
+ \ "__1.__Congress_shall",
+ \ " _make_no_law$ ",
+ \ "__2.)_Congress_shall",
+ \ " _make_no_law$ ",
+ \ "__3.]_Congress_shall",
+ \ " _make_no_law$ ",
+ \ ]
+ let lines = s:screen_lines2(1, 6, 20)
+ call s:compare_lines(expect, lines)
+
+ " check formatlistpat indent
+ setl briopt=min:5,list:-1
+ setl linebreak list&vim listchars&vim
+ let &l:flp = '^\s*\d\+\.\?[\]:)}\t ]\s*'
+ redraw!
+ let expect = [
+ \ " 1. Congress ",
+ \ " shall make no ",
+ \ " law ",
+ \ " 2.) Congress ",
+ \ " shall make no ",
+ \ " law ",
+ \ " 3.] Congress ",
+ \ " shall make no ",
+ \ " law ",
+ \ ]
+ let lines = s:screen_lines2(1, 9, 20)
+ call s:compare_lines(expect, lines)
+ " check formatlistpat indent with different list levels
+ let &l:flp = '^\s*\*\+\s\+'
+ %delete _
+ call setline(1, ['* Congress shall make no law',
+ \ '*** Congress shall make no law',
+ \ '**** Congress shall make no law'])
+ norm! 1gg
+ redraw!
+ let expect = [
+ \ "* Congress shall ",
+ \ " make no law ",
+ \ "*** Congress shall ",
+ \ " make no law ",
+ \ "**** Congress shall ",
+ \ " make no law ",
+ \ ]
+ let lines = s:screen_lines2(1, 6, 20)
+ call s:compare_lines(expect, lines)
+
+ " check formatlistpat indent with different list level
+ " showbreak and sbr
+ setl briopt=min:5,sbr,list:-1
+ setl showbreak=>
+ redraw!
+ let expect = [
+ \ "* Congress shall ",
+ \ "> make no law ",
+ \ "*** Congress shall ",
+ \ "> make no law ",
+ \ "**** Congress shall ",
+ \ "> make no law ",
+ \ ]
+ let lines = s:screen_lines2(1, 6, 20)
+ call s:compare_lines(expect, lines)
+
+ " check formatlistpat indent with different list level
+ " showbreak sbr and shift
+ setl briopt=min:5,sbr,list:-1,shift:2
+ setl showbreak=>
+ redraw!
+ let expect = [
+ \ "* Congress shall ",
+ \ "> make no law ",
+ \ "*** Congress shall ",
+ \ "> make no law ",
+ \ "**** Congress shall ",
+ \ "> make no law ",
+ \ ]
+ let lines = s:screen_lines2(1, 6, 20)
+ call s:compare_lines(expect, lines)
+
+ " check breakindent works if breakindentopt=list:-1
+ " for a non list content
+ %delete _
+ call setline(1, [' Congress shall make no law',
+ \ ' Congress shall make no law',
+ \ ' Congress shall make no law'])
+ norm! 1gg
+ setl briopt=min:5,list:-1
+ setl showbreak=
+ redraw!
+ let expect = [
+ \ " Congress shall ",
+ \ " make no law ",
+ \ " Congress shall ",
+ \ " make no law ",
+ \ " Congress shall ",
+ \ " make no law ",
+ \ ]
+ let lines = s:screen_lines2(1, 6, 20)
+ call s:compare_lines(expect, lines)
+
+ call s:close_windows('set breakindent& briopt& linebreak& list& listchars& showbreak&')
+endfunc
+
+" The following used to crash Vim. This is fixed by 8.2.3391.
+" This is a regression introduced by 8.2.2903.
+func Test_window_resize_with_linebreak()
+ new
+ 53vnew
+ setl linebreak
+ setl showbreak=>>
+ setl breakindent
+ setl breakindentopt=shift:4
+ call setline(1, "\naaaaaaaaa\n\na\naaaaa\n¯aaaaaaaaaa\naaaaaaaaaaaa\naaa\n\"a:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaa\"\naaaaaaaa\n\"a")
+ redraw!
+ call assert_equal([" >>aa^@\"a: "], ScreenLines(2, 14))
+ vertical resize 52
+ redraw!
+ call assert_equal([" >>aaa^@\"a:"], ScreenLines(2, 14))
+ set linebreak& showbreak& breakindent& breakindentopt&
+ %bw!
+endfunc
+
+func Test_cursor_position_with_showbreak()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ &signcolumn = 'yes'
+ &showbreak = '+ '
+ var leftcol: number = win_getid()->getwininfo()->get(0, {})->get('textoff')
+ repeat('x', &columns - leftcol - 1)->setline(1)
+ 'second line'->setline(2)
+ END
+ call writefile(lines, 'XscriptShowbreak', 'D')
+ let buf = RunVimInTerminal('-S XscriptShowbreak', #{rows: 6})
+
+ call term_sendkeys(buf, "AX")
+ call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_no_spurious_match()
+ let s:input = printf('- y %s y %s', repeat('x', 50), repeat('x', 50))
+ call s:test_windows('setl breakindent breakindentopt=list:-1 formatlistpat=^- hls')
+ let @/ = '\%>3v[y]'
+ redraw!
+ call searchcount().total->assert_equal(1)
+
+ " cleanup
+ set hls&vim
+ bwipeout!
+endfunc
+
+func Test_no_extra_indent()
+ call s:test_windows('setl breakindent breakindentopt=list:-1,min:10')
+ %d
+ let &l:formatlistpat='^\s*\d\+\.\s\+'
+ let text = 'word '
+ let len = text->strcharlen()
+ let line1 = text->repeat((winwidth(0) / len) * 2)
+ let line2 = repeat(' ', 2) .. '1. ' .. line1
+ call setline(1, [line2])
+ redraw!
+ " 1) matches formatlist pattern, so indent
+ let expect = [
+ \ " 1. word word word ",
+ \ " word word word ",
+ \ " word word ",
+ \ "~ ",
+ \ ]
+ let lines = s:screen_lines2(1, 4, 20)
+ call s:compare_lines(expect, lines)
+ " 2) change formatlist pattern
+ " -> indent adjusted
+ let &l:formatlistpat='^\s*\d\+\.'
+ let expect = [
+ \ " 1. word word word ",
+ \ " word word word ",
+ \ " word word ",
+ \ "~ ",
+ \ ]
+ let lines = s:screen_lines2(1, 4, 20)
+ " 3) no local formatlist pattern,
+ " so use global one -> indent
+ let g_flp = &g:flp
+ let &g:formatlistpat='^\s*\d\+\.\s\+'
+ let &l:formatlistpat=''
+ let expect = [
+ \ " 1. word word word ",
+ \ " word word word ",
+ \ " word word ",
+ \ "~ ",
+ \ ]
+ let lines = s:screen_lines2(1, 4, 20)
+ call s:compare_lines(expect, lines)
+ let &g:flp = g_flp
+ let &l:formatlistpat='^\s*\d\+\.'
+ " 4) add something in front, no additional indent
+ norm! gg0
+ exe ":norm! 5iword \<esc>"
+ redraw!
+ let expect = [
+ \ "word word word word ",
+ \ "word 1. word word ",
+ \ "word word word word ",
+ \ "word word ",
+ \ "~ ",
+ \ ]
+ let lines = s:screen_lines2(1, 5, 20)
+ call s:compare_lines(expect, lines)
+ bwipeout!
+endfunc
+
+func Test_breakindent_column()
+ call s:test_windows('setl breakindent breakindentopt=column:10')
+ redraw!
+ " 1) default: does not indent, too wide :(
+ let expect = [
+ \ " ",
+ \ " abcdefghijklmnop",
+ \ "qrstuvwxyzABCDEFGHIJ",
+ \ "KLMNOP "
+ \ ]
+ let lines = s:screen_lines2(1, 4, 20)
+ call s:compare_lines(expect, lines)
+ " 2) lower min value, so that breakindent works
+ setl breakindentopt+=min:5
+ redraw!
+ let expect = [
+ \ " ",
+ \ " abcdefghijklmnop",
+ \ " qrstuvwxyz",
+ \ " ABCDEFGHIJ",
+ \ " KLMNOP "
+ \ ]
+ let lines = s:screen_lines2(1, 5, 20)
+ " 3) set shift option -> no influence
+ setl breakindentopt+=shift:5
+ redraw!
+ let expect = [
+ \ " ",
+ \ " abcdefghijklmnop",
+ \ " qrstuvwxyz",
+ \ " ABCDEFGHIJ",
+ \ " KLMNOP "
+ \ ]
+ let lines = s:screen_lines2(1, 5, 20)
+ call s:compare_lines(expect, lines)
+ " 4) add showbreak value
+ setl showbreak=++
+ redraw!
+ let expect = [
+ \ " ",
+ \ " abcdefghijklmnop",
+ \ " ++qrstuvwx",
+ \ " ++yzABCDEF",
+ \ " ++GHIJKLMN",
+ \ " ++OP "
+ \ ]
+ let lines = s:screen_lines2(1, 6, 20)
+ call s:compare_lines(expect, lines)
+ bwipeout!
+endfunc
+
+func Test_linebreak_list()
+ " This was setting wlv.c_extra to NUL while wlv.p_extra is NULL
+ filetype plugin on
+ syntax enable
+ edit! $VIMRUNTIME/doc/index.txt
+ /v_P
+
+ setlocal list
+ setlocal listchars=tab:>-
+ setlocal linebreak
+ setlocal nowrap
+ setlocal filetype=help
+ redraw!
+
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_buffer.vim b/src/testdir/test_buffer.vim
new file mode 100644
index 0000000..bc29c21
--- /dev/null
+++ b/src/testdir/test_buffer.vim
@@ -0,0 +1,517 @@
+" Tests for Vim buffer
+
+source check.vim
+
+" Test for the :bunload command with an offset
+func Test_bunload_with_offset()
+ %bwipe!
+ call writefile(['B1'], 'Xb1', 'D')
+ call writefile(['B2'], 'Xb2', 'D')
+ call writefile(['B3'], 'Xb3', 'D')
+ call writefile(['B4'], 'Xb4', 'D')
+
+ " Load four buffers. Unload the second and third buffers and then
+ " execute .+3bunload to unload the last buffer.
+ edit Xb1
+ new Xb2
+ new Xb3
+ new Xb4
+
+ bunload Xb2
+ bunload Xb3
+ exe bufwinnr('Xb1') . 'wincmd w'
+ .+3bunload
+ call assert_equal(0, getbufinfo('Xb4')[0].loaded)
+ call assert_equal('Xb1',
+ \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
+
+ " Load four buffers. Unload the third and fourth buffers. Execute .+3bunload
+ " and check whether the second buffer is unloaded.
+ ball
+ bunload Xb3
+ bunload Xb4
+ exe bufwinnr('Xb1') . 'wincmd w'
+ .+3bunload
+ call assert_equal(0, getbufinfo('Xb2')[0].loaded)
+ call assert_equal('Xb1',
+ \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
+
+ " Load four buffers. Unload the second and third buffers and from the last
+ " buffer execute .-3bunload to unload the first buffer.
+ ball
+ bunload Xb2
+ bunload Xb3
+ exe bufwinnr('Xb4') . 'wincmd w'
+ .-3bunload
+ call assert_equal(0, getbufinfo('Xb1')[0].loaded)
+ call assert_equal('Xb4',
+ \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
+
+ " Load four buffers. Unload the first and second buffers. Execute .-3bunload
+ " from the last buffer and check whether the third buffer is unloaded.
+ ball
+ bunload Xb1
+ bunload Xb2
+ exe bufwinnr('Xb4') . 'wincmd w'
+ .-3bunload
+ call assert_equal(0, getbufinfo('Xb3')[0].loaded)
+ call assert_equal('Xb4',
+ \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
+
+ %bwipe!
+
+ call assert_fails('1,4bunload', 'E16:')
+ call assert_fails(',100bunload', 'E16:')
+
+ call assert_fails('$bunload', 'E90:')
+endfunc
+
+" Test for :buffer, :bnext, :bprevious, :brewind, :blast and :bmodified
+" commands
+func Test_buflist_browse()
+ %bwipe!
+ call assert_fails('buffer 1000', 'E86:')
+
+ call writefile(['foo1', 'foo2', 'foo3', 'foo4'], 'Xbrowse1', 'D')
+ call writefile(['bar1', 'bar2', 'bar3', 'bar4'], 'Xbrowse2', 'D')
+ call writefile(['baz1', 'baz2', 'baz3', 'baz4'], 'Xbrowse3', 'D')
+ edit Xbrowse1
+ let b1 = bufnr()
+ edit Xbrowse2
+ let b2 = bufnr()
+ edit +/baz4 Xbrowse3
+ let b3 = bufnr()
+
+ call assert_fails('buffer ' .. b1 .. ' abc', 'E488:')
+ call assert_equal(b3, bufnr())
+ call assert_equal(4, line('.'))
+ exe 'buffer +/bar2 ' .. b2
+ call assert_equal(b2, bufnr())
+ call assert_equal(2, line('.'))
+ exe 'buffer +/bar1'
+ call assert_equal(b2, bufnr())
+ call assert_equal(1, line('.'))
+
+ brewind +
+ call assert_equal(b1, bufnr())
+ call assert_equal(4, line('.'))
+
+ blast +/baz2
+ call assert_equal(b3, bufnr())
+ call assert_equal(2, line('.'))
+
+ bprevious +/bar4
+ call assert_equal(b2, bufnr())
+ call assert_equal(4, line('.'))
+
+ bnext +/baz3
+ call assert_equal(b3, bufnr())
+ call assert_equal(3, line('.'))
+
+ call assert_fails('bmodified', 'E84:')
+ call setbufvar(b2, '&modified', 1)
+ exe 'bmodified +/bar3'
+ call assert_equal(b2, bufnr())
+ call assert_equal(3, line('.'))
+
+ " With no listed buffers in the list, :bnext and :bprev should fail
+ %bwipe!
+ set nobuflisted
+ call assert_fails('bnext', 'E85:')
+ call assert_fails('bprev', 'E85:')
+ set buflisted
+
+ call assert_fails('sandbox bnext', 'E48:')
+
+ %bwipe!
+endfunc
+
+" Test for :bdelete
+func Test_bdelete_cmd()
+ %bwipe!
+ call assert_fails('bdelete 5', 'E516:')
+ call assert_fails('1,1bdelete 1 2', 'E488:')
+ call assert_fails('bdelete \)', 'E55:')
+
+ " Deleting a unlisted and unloaded buffer
+ edit Xbdelfile1
+ let bnr = bufnr()
+ set nobuflisted
+ enew
+ call assert_fails('bdelete ' .. bnr, 'E516:')
+
+ " Deleting more than one buffer
+ new Xbuf1
+ new Xbuf2
+ exe 'bdel ' .. bufnr('Xbuf2') .. ' ' .. bufnr('Xbuf1')
+ call assert_equal(1, winnr('$'))
+ call assert_equal(0, getbufinfo('Xbuf1')[0].loaded)
+ call assert_equal(0, getbufinfo('Xbuf2')[0].loaded)
+
+ " Deleting more than one buffer and an invalid buffer
+ new Xbuf1
+ new Xbuf2
+ let cmd = "exe 'bdel ' .. bufnr('Xbuf2') .. ' xxx ' .. bufnr('Xbuf1')"
+ call assert_fails(cmd, 'E94:')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, getbufinfo('Xbuf1')[0].loaded)
+ call assert_equal(0, getbufinfo('Xbuf2')[0].loaded)
+
+ %bwipe!
+endfunc
+
+func Test_buffer_error()
+ new foo1
+ new foo2
+
+ call assert_fails('buffer foo', 'E93:')
+ call assert_fails('buffer bar', 'E94:')
+ call assert_fails('buffer 0', 'E939:')
+
+ %bwipe
+endfunc
+
+" Test for the status messages displayed when unloading, deleting or wiping
+" out buffers
+func Test_buffer_statusmsg()
+ CheckEnglish
+ set report=1
+ new Xbuf1
+ new Xbuf2
+ let bnr = bufnr()
+ exe "normal 2\<C-G>"
+ call assert_match('buf ' .. bnr .. ':', v:statusmsg)
+ bunload Xbuf1 Xbuf2
+ call assert_equal('2 buffers unloaded', v:statusmsg)
+ bdel Xbuf1 Xbuf2
+ call assert_equal('2 buffers deleted', v:statusmsg)
+ bwipe Xbuf1 Xbuf2
+ call assert_equal('2 buffers wiped out', v:statusmsg)
+ set report&
+endfunc
+
+" Test for quitting the 'swapfile exists' dialog with the split buffer
+" command.
+func Test_buffer_sbuf_cleanup()
+ call writefile([], 'XsplitCleanup', 'D')
+ " first open the file in a buffer
+ new XsplitCleanup
+ let bnr = bufnr()
+ close
+ " create the swap file
+ call writefile([], '.XsplitCleanup.swp', 'D')
+ " Remove the catch-all that runtest.vim adds
+ au! SwapExists
+ augroup BufTest
+ au!
+ autocmd SwapExists XsplitCleanup let v:swapchoice='q'
+ augroup END
+ exe 'sbuf ' . bnr
+ call assert_equal(1, winnr('$'))
+ call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)
+
+ " test for :sball
+ sball
+ call assert_equal(1, winnr('$'))
+ call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)
+
+ %bw!
+ set shortmess+=F
+ let v:statusmsg = ''
+ edit XsplitCleanup
+ call assert_equal('', v:statusmsg)
+ call assert_equal(1, winnr('$'))
+ call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)
+ set shortmess&
+
+ augroup BufTest
+ au!
+ augroup END
+ augroup! BufTest
+endfunc
+
+" Test for deleting a modified buffer with :confirm
+func Test_bdel_with_confirm()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ new
+ call setline(1, 'test')
+ call assert_fails('bdel', 'E89:')
+ call feedkeys('c', 'L')
+ confirm bdel
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, &modified)
+ call feedkeys('n', 'L')
+ confirm bdel
+ call assert_equal(1, winnr('$'))
+endfunc
+
+" Test for editing another buffer from a modified buffer with :confirm
+func Test_goto_buf_with_confirm()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ new XgotoConf
+ enew
+ call setline(1, 'test')
+ call assert_fails('b XgotoConf', 'E37:')
+ call feedkeys('c', 'L')
+ call assert_fails('confirm b XgotoConf', 'E37:')
+ call assert_equal(1, &modified)
+ call assert_equal('', @%)
+ call feedkeys('y', 'L')
+ call assert_fails('confirm b XgotoConf', ['', 'E37:'])
+ call assert_equal(1, &modified)
+ call assert_equal('', @%)
+ call feedkeys('n', 'L')
+ confirm b XgotoConf
+ call assert_equal('XgotoConf', @%)
+ close!
+endfunc
+
+" Test for splitting buffer with 'switchbuf'
+func Test_buffer_switchbuf()
+ new Xswitchbuf
+ wincmd w
+ set switchbuf=useopen
+ sbuf Xswitchbuf
+ call assert_equal(1, winnr())
+ call assert_equal(2, winnr('$'))
+ set switchbuf=usetab
+ tabnew
+ sbuf Xswitchbuf
+ call assert_equal(1, tabpagenr())
+ call assert_equal(2, tabpagenr('$'))
+ set switchbuf&
+ %bw
+endfunc
+
+" Test for BufAdd autocommand wiping out the buffer
+func Test_bufadd_autocmd_bwipe()
+ %bw!
+ augroup BufAdd_Wipe
+ au!
+ autocmd BufAdd Xbwipe %bw!
+ augroup END
+ edit Xbwipe
+ call assert_equal('', @%)
+ call assert_equal(0, bufexists('Xbwipe'))
+ augroup BufAdd_Wipe
+ au!
+ augroup END
+ augroup! BufAdd_Wipe
+endfunc
+
+" Test for trying to load a buffer with text locked
+" <C-\>e in the command line is used to lock the text
+func Test_load_buf_with_text_locked()
+ new Xlockfile1
+ edit Xlockfile2
+ let cmd = ":\<C-\>eexecute(\"normal \<C-O>\")\<CR>\<C-C>"
+ call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
+ %bw!
+endfunc
+
+" Test for using CTRL-^ to edit the alternative file keeping the cursor
+" position with 'nostartofline'. Also test using the 'buf' command.
+func Test_buffer_edit_altfile()
+ call writefile(repeat(['one two'], 50), 'Xaltfile1', 'D')
+ call writefile(repeat(['five six'], 50), 'Xaltfile2', 'D')
+ set nosol
+ edit Xaltfile1
+ call cursor(25, 5)
+ edit Xaltfile2
+ call cursor(30, 4)
+ exe "normal \<C-^>"
+ call assert_equal([0, 25, 5, 0], getpos('.'))
+ exe "normal \<C-^>"
+ call assert_equal([0, 30, 4, 0], getpos('.'))
+ buf Xaltfile1
+ call assert_equal([0, 25, 5, 0], getpos('.'))
+ buf Xaltfile2
+ call assert_equal([0, 30, 4, 0], getpos('.'))
+ set sol&
+endfunc
+
+" Test for running the :sball command with a maximum window count and a
+" modified buffer
+func Test_sball_with_count()
+ %bw!
+ edit Xcountfile1
+ call setline(1, ['abc'])
+ new Xcountfile2
+ new Xcountfile3
+ new Xcountfile4
+ 2sball
+ call assert_equal(bufnr('Xcountfile4'), winbufnr(1))
+ call assert_equal(bufnr('Xcountfile1'), winbufnr(2))
+ call assert_equal(0, getbufinfo('Xcountfile2')[0].loaded)
+ call assert_equal(0, getbufinfo('Xcountfile3')[0].loaded)
+ %bw!
+endfunc
+
+func Test_badd_options()
+ new SomeNewBuffer
+ setlocal numberwidth=3
+ wincmd p
+ badd +1 SomeNewBuffer
+ new SomeNewBuffer
+ call assert_equal(3, &numberwidth)
+ close
+ close
+ bwipe! SomeNewBuffer
+
+ badd +3 XbaddFile
+ call writefile(range(6), 'XbaddFile', 'D')
+ buf XbaddFile
+ call assert_equal([0, 3, 1, 0], getpos('.'))
+
+ bwipe! XbaddFile
+endfunc
+
+func Test_balt()
+ new SomeNewBuffer
+ balt +3 OtherBuffer
+ e #
+ call assert_equal('OtherBuffer', bufname())
+endfunc
+
+" Test for buffer match URL(scheme) check
+" scheme is alpha and inner hyphen only.
+func Test_buffer_scheme()
+ CheckMSWindows
+
+ set noswapfile
+ set noshellslash
+ %bwipe!
+ let bufnames = [
+ \ #{id: 'ssb0', name: 'test://xyz/foo/ssb0' , match: 1},
+ \ #{id: 'ssb1', name: 'test+abc://xyz/foo/ssb1', match: 0},
+ \ #{id: 'ssb2', name: 'test_abc://xyz/foo/ssb2', match: 0},
+ \ #{id: 'ssb3', name: 'test-abc://xyz/foo/ssb3', match: 1},
+ \ #{id: 'ssb4', name: '-test://xyz/foo/ssb4' , match: 0},
+ \ #{id: 'ssb5', name: 'test-://xyz/foo/ssb5' , match: 0},
+ \]
+ for buf in bufnames
+ new `=buf.name`
+ if buf.match
+ call assert_equal(buf.name, getbufinfo(buf.id)[0].name)
+ else
+ " slashes will have become backslashes
+ call assert_notequal(buf.name, getbufinfo(buf.id)[0].name)
+ endif
+ bwipe
+ endfor
+
+ set shellslash&
+ set swapfile&
+endfunc
+
+" this was using a NULL pointer after failing to use the pattern
+func Test_buf_pattern_invalid()
+ vsplit 0000000
+ silent! buf [0--]\&\zs*\zs*e
+ bwipe!
+
+ vsplit 00000000000000000000000000
+ silent! buf [0--]\&\zs*\zs*e
+ bwipe!
+
+ " similar case with different code path
+ split 0
+ edit ÿ
+ silent! buf [0--]\&\zs*\zs*0
+ bwipe!
+endfunc
+
+" Test for the 'maxmem' and 'maxmemtot' options
+func Test_buffer_maxmem()
+ " use 1KB per buffer and 2KB for all the buffers
+ set maxmem=1 maxmemtot=2
+ new
+ let v:errmsg = ''
+ " try opening some files
+ edit test_arglist.vim
+ call assert_equal('test_arglist.vim', bufname())
+ edit test_eval_stuff.vim
+ call assert_equal('test_eval_stuff.vim', bufname())
+ b test_arglist.vim
+ call assert_equal('test_arglist.vim', bufname())
+ b test_eval_stuff.vim
+ call assert_equal('test_eval_stuff.vim', bufname())
+ close
+ call assert_equal('', v:errmsg)
+ set maxmem& maxmemtot&
+endfunc
+
+" Test for buffer allocation failure
+func Test_buflist_alloc_failure()
+ %bw!
+
+ edit XallocFail1
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('edit XallocFail2', 'E342:')
+
+ " test for bufadd()
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('call bufadd("Xbuffer")', 'E342:')
+
+ " test for setting the arglist
+ edit XallocFail2
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('next XallocFail3', 'E342:')
+
+ " test for setting the alternate buffer name when writing a file
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('write Xother', 'E342:')
+ call delete('Xother')
+
+ " test for creating a buffer using bufnr()
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails("call bufnr('Xnewbuf', v:true)", 'E342:')
+
+ " test for renaming buffer using :file
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('file Xnewfile', 'E342:')
+
+ " test for creating a buffer for a popup window
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('call popup_create("mypop", {})', 'E342:')
+
+ if has('terminal')
+ " test for creating a buffer for a terminal window
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('call term_start(&shell)', 'E342:')
+ %bw!
+ endif
+
+ " test for loading a new buffer after wiping out all the buffers
+ edit XallocFail4
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('%bw!', 'E342:')
+
+ " test for :checktime loading the buffer
+ call writefile(['one'], 'XallocFail5', 'D')
+ if has('unix')
+ edit XallocFail5
+ " sleep for some time to make sure the timestamp is different
+ sleep 200m
+ call writefile(['two'], 'XallocFail5')
+ set autoread
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('checktime', 'E342:')
+ set autoread&
+ bw!
+ endif
+
+ " test for :vimgrep loading a dummy buffer
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('vimgrep two XallocFail5', 'E342:')
+
+ " test for quickfix command loading a buffer
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('cexpr "XallocFail6:10:Line10"', 'E342:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_bufline.vim b/src/testdir/test_bufline.vim
new file mode 100644
index 0000000..4ada241
--- /dev/null
+++ b/src/testdir/test_bufline.vim
@@ -0,0 +1,341 @@
+" Tests for setbufline(), getbufline(), appendbufline(), deletebufline()
+
+source shared.vim
+source screendump.vim
+source check.vim
+
+func Test_setbufline_getbufline()
+ " similar to Test_set_get_bufline()
+ new
+ let b = bufnr('%')
+ hide
+ call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
+ call assert_equal(['foo'], getbufline(b, 1))
+ call assert_equal('foo', getbufoneline(b, 1))
+ call assert_equal(['bar'], getbufline(b, '$'))
+ call assert_equal('bar', getbufoneline(b, '$'))
+ call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ exe "bd!" b
+ call assert_equal([], getbufline(b, 1, 2))
+
+ split Xtest
+ call setline(1, ['a', 'b', 'c'])
+ let b = bufnr('%')
+ wincmd w
+
+ call assert_equal(1, setbufline(b, 5, 'x'))
+ call assert_equal(1, setbufline(b, 5, ['x']))
+ call assert_equal(0, setbufline(b, 5, []))
+ call assert_equal(0, setbufline(b, 5, test_null_list()))
+
+ call assert_equal(1, 'x'->setbufline(bufnr('$') + 1, 1))
+ call assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1))
+ call assert_equal(1, []->setbufline(bufnr('$') + 1, 1))
+ call assert_equal(1, test_null_list()->setbufline(bufnr('$') + 1, 1))
+
+ call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$'))
+
+ call assert_equal(0, setbufline(b, 4, ['d', 'e']))
+ call assert_equal(['c'], b->getbufline(3))
+ call assert_equal('c', b->getbufoneline(3))
+ call assert_equal(['d'], getbufline(b, 4))
+ call assert_equal('d', getbufoneline(b, 4))
+ call assert_equal(['e'], getbufline(b, 5))
+ call assert_equal('e', getbufoneline(b, 5))
+ call assert_equal([], getbufline(b, 6))
+ call assert_equal([], getbufline(b, 2, 1))
+
+ if has('job')
+ call setbufline(b, 2, [function('eval'), #{key: 123}, test_null_job()])
+ call assert_equal(["function('eval')",
+ \ "{'key': 123}",
+ \ "no process"],
+ \ getbufline(b, 2, 4))
+ endif
+ exe "bwipe! " . b
+endfunc
+
+func Test_setbufline_getbufline_fold()
+ split Xtest
+ setlocal foldmethod=expr foldexpr=0
+ let b = bufnr('%')
+ new
+ call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
+ call assert_equal(['foo'], getbufline(b, 1))
+ call assert_equal(['bar'], getbufline(b, 2))
+ call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ exe "bwipe!" b
+ bwipe!
+endfunc
+
+func Test_setbufline_getbufline_fold_tab()
+ split Xtest
+ setlocal foldmethod=expr foldexpr=0
+ let b = bufnr('%')
+ tab new
+ call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
+ call assert_equal(['foo'], getbufline(b, 1))
+ call assert_equal(['bar'], getbufline(b, 2))
+ call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ exe "bwipe!" b
+ bwipe!
+endfunc
+
+func Test_setline_startup()
+ let cmd = GetVimCommand('Xscript')
+ if cmd == ''
+ return
+ endif
+ call writefile(['call setline(1, "Hello")', 'silent w Xtest', 'q!'], 'Xscript', 'D')
+ call system(cmd)
+ sleep 50m
+ call assert_equal(['Hello'], readfile('Xtest'))
+
+ call assert_equal(0, setline(1, []))
+ call assert_equal(0, setline(1, test_null_list()))
+ call assert_equal(0, setline(5, []))
+ call assert_equal(0, setline(6, test_null_list()))
+
+ call delete('Xtest')
+endfunc
+
+func Test_appendbufline()
+ new
+ let b = bufnr('%')
+ hide
+
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ normal! 2gggg
+ call assert_equal(2, line("''"))
+
+ call assert_equal(0, appendbufline(b, 0, ['foo', 'bar']))
+ call assert_equal(['foo'], getbufline(b, 1))
+ call assert_equal(['bar'], getbufline(b, 2))
+ call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ call assert_equal(0, appendbufline(b, 0, 'baz'))
+ call assert_equal(['baz', 'foo', 'bar'], getbufline(b, 1, 3))
+
+ " appendbufline() in a hidden buffer shouldn't move marks in current window.
+ call assert_equal(2, line("''"))
+ bwipe!
+
+ exe "bd!" b
+ call assert_equal([], getbufline(b, 1, 3))
+
+ split Xtest
+ call setline(1, ['a', 'b', 'c'])
+ let b = bufnr('%')
+ wincmd w
+
+ call assert_equal(1, appendbufline(b, -1, 'x'))
+ call assert_equal(1, appendbufline(b, -1, ['x']))
+ call assert_equal(1, appendbufline(b, -1, []))
+ call assert_equal(1, appendbufline(b, -1, test_null_list()))
+
+ call assert_equal(1, appendbufline(b, 4, 'x'))
+ call assert_equal(1, appendbufline(b, 4, ['x']))
+ call assert_equal(0, appendbufline(b, 4, []))
+ call assert_equal(0, appendbufline(b, 4, test_null_list()))
+
+ call assert_equal(1, appendbufline(1234, 1, 'x'))
+ call assert_equal(1, appendbufline(1234, 1, ['x']))
+ call assert_equal(1, appendbufline(1234, 1, []))
+ call assert_equal(1, appendbufline(1234, 1, test_null_list()))
+
+ call assert_equal(0, appendbufline(b, 1, []))
+ call assert_equal(0, appendbufline(b, 1, test_null_list()))
+ call assert_equal(0, appendbufline(b, 3, []))
+ call assert_equal(0, appendbufline(b, 3, test_null_list()))
+
+ call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$'))
+
+ call assert_equal(0, appendbufline(b, 3, ['d', 'e']))
+ call assert_equal(['c'], getbufline(b, 3))
+ call assert_equal(['d'], getbufline(b, 4))
+ call assert_equal(['e'], getbufline(b, 5))
+ call assert_equal([], getbufline(b, 6))
+ exe "bwipe! " . b
+endfunc
+
+func Test_appendbufline_no_E315()
+ let after =<< trim [CODE]
+ set stl=%f ls=2
+ new
+ let buf = bufnr("%")
+ quit
+ vsp
+ exec "buffer" buf
+ wincmd w
+ call appendbufline(buf, 0, "abc")
+ redraw
+ while getbufline(buf, 1)[0] =~ "^\\s*$"
+ sleep 10m
+ endwhile
+ au VimLeavePre * call writefile([v:errmsg], "Xerror")
+ au VimLeavePre * call writefile(["done"], "Xdone")
+ qall!
+ [CODE]
+
+ if !RunVim([], after, '--clean')
+ return
+ endif
+ call assert_notmatch("^E315:", readfile("Xerror")[0])
+ call assert_equal("done", readfile("Xdone")[0])
+ call delete("Xerror")
+ call delete("Xdone")
+endfunc
+
+func Test_deletebufline()
+ new
+ let b = bufnr('%')
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ hide
+
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ normal! 2gggg
+ call assert_equal(2, line("''"))
+
+ call assert_equal(0, deletebufline(b, 2))
+ call assert_equal(['aaa', 'ccc'], getbufline(b, 1, 2))
+ call assert_equal(0, deletebufline(b, 2, 8))
+ call assert_equal(['aaa'], getbufline(b, 1, 2))
+
+ " deletebufline() in a hidden buffer shouldn't move marks in current window.
+ call assert_equal(2, line("''"))
+ bwipe!
+
+ exe "bd!" b
+ call assert_equal(1, b->deletebufline(1))
+
+ call assert_equal(1, deletebufline(-1, 1))
+
+ split Xtest
+ call setline(1, ['a', 'b', 'c'])
+ call cursor(line('$'), 1)
+ let b = bufnr('%')
+ wincmd w
+ call assert_equal(1, deletebufline(b, 4))
+ call assert_equal(0, deletebufline(b, 1))
+ call assert_equal(['b', 'c'], getbufline(b, 1, 2))
+ exe "bwipe! " . b
+
+ edit XbufOne
+ let one = bufnr()
+ call setline(1, ['a', 'b', 'c'])
+ setlocal nomodifiable
+ split XbufTwo
+ let two = bufnr()
+ call assert_fails('call deletebufline(one, 1)', 'E21:')
+ call assert_equal(two, bufnr())
+ bwipe! XbufTwo
+ bwipe! XbufOne
+endfunc
+
+func Test_appendbufline_redraw()
+ CheckScreendump
+
+ let lines =<< trim END
+ new foo
+ let winnr = 'foo'->bufwinnr()
+ let buf = bufnr('foo')
+ wincmd p
+ call appendbufline(buf, '$', range(1,200))
+ exe winnr .. 'wincmd w'
+ norm! G
+ wincmd p
+ call deletebufline(buf, 1, '$')
+ call appendbufline(buf, '$', 'Hello Vim world...')
+ END
+ call writefile(lines, 'XscriptMatchCommon', 'D')
+ let buf = RunVimInTerminal('-S XscriptMatchCommon', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_appendbufline_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_setbufline_select_mode()
+ new
+ call setline(1, ['foo', 'bar'])
+ call feedkeys("j^v2l\<C-G>", 'nx')
+
+ let bufnr = bufadd('Xdummy')
+ call bufload(bufnr)
+ call setbufline(bufnr, 1, ['abc'])
+
+ call feedkeys("x", 'nx')
+ call assert_equal(['foo', 'x'], getline(1, 2))
+
+ exe "bwipe! " .. bufnr
+ bwipe!
+endfunc
+
+func Test_deletebufline_select_mode()
+ new
+ call setline(1, ['foo', 'bar'])
+ call feedkeys("j^v2l\<C-G>", 'nx')
+
+ let bufnr = bufadd('Xdummy')
+ call bufload(bufnr)
+ call setbufline(bufnr, 1, ['abc', 'def'])
+ call deletebufline(bufnr, 1)
+
+ call feedkeys("x", 'nx')
+ call assert_equal(['foo', 'x'], getline(1, 2))
+
+ exe "bwipe! " .. bufnr
+ bwipe!
+endfunc
+
+func Test_deletebufline_popup_window()
+ let popupID = popup_create('foo', {})
+ let bufnr = winbufnr(popupID)
+
+ " Check that deletebufline() brings us back to the same window.
+ new
+ let winid_before = win_getid()
+ call deletebufline(bufnr, 1, '$')
+ call assert_equal(winid_before, win_getid())
+ bwipe
+
+ call popup_close(popupID)
+endfunc
+
+func Test_setbufline_startup_nofile()
+ let before =<< trim [CODE]
+ set shortmess+=F
+ file Xresult
+ set buftype=nofile
+ call setbufline('', 1, 'success')
+ [CODE]
+ let after =<< trim [CODE]
+ set buftype=
+ write
+ quit
+ [CODE]
+
+ if !RunVim(before, after, '--clean')
+ return
+ endif
+ call assert_equal(['success'], readfile('Xresult'))
+ call delete('Xresult')
+endfunc
+
+" Test that setbufline(), appendbufline() and deletebufline() should fail and
+" return 1 when "textlock" is active.
+func Test_change_bufline_with_textlock()
+ new
+ inoremap <buffer> <expr> <F2> setbufline('', 1, '')
+ call assert_fails("normal a\<F2>", 'E565:')
+ call assert_equal('1', getline(1))
+ inoremap <buffer> <expr> <F2> appendbufline('', 1, '')
+ call assert_fails("normal a\<F2>", 'E565:')
+ call assert_equal('11', getline(1))
+ inoremap <buffer> <expr> <F2> deletebufline('', 1)
+ call assert_fails("normal a\<F2>", 'E565:')
+ call assert_equal('111', getline(1))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_bufwintabinfo.vim b/src/testdir/test_bufwintabinfo.vim
new file mode 100644
index 0000000..403b98a
--- /dev/null
+++ b/src/testdir/test_bufwintabinfo.vim
@@ -0,0 +1,197 @@
+" Tests for the getbufinfo(), getwininfo() and gettabinfo() functions
+
+source check.vim
+
+func Test_getbufwintabinfo()
+ CheckFeature quickfix
+
+ edit Xtestfile1
+ edit Xtestfile2
+ let buflist = getbufinfo()
+ call assert_equal(2, len(buflist))
+ call assert_match('Xtestfile1', buflist[0].name)
+ call assert_match('Xtestfile2', getbufinfo('Xtestfile2')[0].name)
+ call assert_equal([], getbufinfo(2016))
+ edit Xtestfile1
+ hide edit Xtestfile2
+ hide enew
+ call assert_equal(3, len(getbufinfo({'bufloaded':1})))
+
+ set tabstop&vim
+ let b:editor = 'vim'
+ let l = getbufinfo('%')
+ call assert_equal(bufnr('%'), l[0].bufnr)
+ call assert_equal('vim', l[0].variables.editor)
+ call assert_notequal(-1, index(l[0].windows, '%'->bufwinid()))
+
+ let l = '%'->getbufinfo()
+ call assert_equal(bufnr('%'), l[0].bufnr)
+
+ " Test for getbufinfo() with 'bufmodified'
+ call assert_equal(0, len(getbufinfo({'bufmodified' : 1})))
+ call setbufline('Xtestfile1', 1, ["Line1"])
+ let l = getbufinfo({'bufmodified' : 1})
+ call assert_equal(1, len(l))
+ call assert_equal(bufnr('Xtestfile1'), l[0].bufnr)
+
+ if has('signs')
+ call append(0, ['Linux', 'Windows', 'Mac'])
+ sign define Mark text=>> texthl=Search
+ exe "sign place 2 line=3 name=Mark buffer=" . bufnr('%')
+ let l = getbufinfo('%')
+ call assert_equal(2, l[0].signs[0].id)
+ call assert_equal(3, l[0].signs[0].lnum)
+ call assert_equal('Mark', l[0].signs[0].name)
+ sign unplace *
+ sign undefine Mark
+ enew!
+ endif
+ call assert_notequal([], getbufinfo(test_null_dict()))
+
+ only
+ let w1_id = win_getid()
+ setl foldcolumn=3
+ new
+ let w2_id = win_getid()
+ tabnew | let w3_id = win_getid()
+ new | let w4_id = win_getid()
+ vert new | let w5_id = win_getid()
+ eval 'green'->setwinvar(0, 'signal')
+ tabfirst
+ let winlist = getwininfo()
+ call assert_equal(5, len(winlist))
+ call assert_equal(winwidth(1), winlist[0].width)
+ call assert_equal(1, winlist[0].wincol)
+ " tabline adds one row in terminal, not in GUI
+ let tablineheight = winlist[0].winrow == 2 ? 1 : 0
+ call assert_equal(tablineheight + 1, winlist[0].winrow)
+
+ call assert_equal(winbufnr(2), winlist[1].bufnr)
+ call assert_equal(winheight(2), winlist[1].height)
+ call assert_equal(1, winlist[1].wincol)
+ call assert_equal(3, winlist[1].textoff) " foldcolumn
+ call assert_equal(tablineheight + winheight(1) + 2, winlist[1].winrow)
+
+ call assert_equal(1, winlist[2].winnr)
+ call assert_equal(tablineheight + 1, winlist[2].winrow)
+ call assert_equal(1, winlist[2].wincol)
+
+ call assert_equal(winlist[2].width + 2, winlist[3].wincol)
+ call assert_equal(1, winlist[4].wincol)
+
+ call assert_equal(1, winlist[0].tabnr)
+ call assert_equal(1, winlist[1].tabnr)
+ call assert_equal(2, winlist[2].tabnr)
+ call assert_equal(2, winlist[3].tabnr)
+ call assert_equal(2, winlist[4].tabnr)
+
+ call assert_equal('green', winlist[2].variables.signal)
+ call assert_equal(w4_id, winlist[3].winid)
+ let winfo = w5_id->getwininfo()[0]
+ call assert_equal(2, winfo.tabnr)
+ call assert_equal([], getwininfo(3))
+
+ call settabvar(1, 'space', 'build')
+ let tablist = gettabinfo()
+ call assert_equal(2, len(tablist))
+ call assert_equal(3, len(tablist[1].windows))
+ call assert_equal(2, tablist[1].tabnr)
+ call assert_equal('build', tablist[0].variables.space)
+ call assert_equal(w2_id, tablist[0].windows[0])
+ call assert_equal([], 3->gettabinfo())
+
+ tabonly | only
+
+ lexpr ''
+ lopen
+ copen
+ let winlist = getwininfo()
+ call assert_false(winlist[0].quickfix)
+ call assert_false(winlist[0].loclist)
+ call assert_true(winlist[1].quickfix)
+ call assert_true(winlist[1].loclist)
+ call assert_true(winlist[2].quickfix)
+ call assert_false(winlist[2].loclist)
+ wincmd t | only
+endfunc
+
+function Test_get_buf_options()
+ let opts = bufnr()->getbufvar('&')
+ call assert_equal(v:t_dict, type(opts))
+ call assert_equal(8, opts.tabstop)
+endfunc
+
+function Test_get_win_options()
+ if has('folding')
+ set foldlevel=999
+ endif
+ set list
+ let opts = getwinvar(1, '&')
+ call assert_equal(v:t_dict, type(opts))
+ call assert_equal(0, opts.linebreak)
+ call assert_equal(1, opts.list)
+ if has('folding')
+ call assert_equal(999, opts.foldlevel)
+ endif
+ if has('signs')
+ call assert_equal('auto', opts.signcolumn)
+ endif
+
+ let opts = gettabwinvar(1, 1, '&')
+ call assert_equal(v:t_dict, type(opts))
+ call assert_equal(0, opts.linebreak)
+ call assert_equal(1, opts.list)
+ if has('signs')
+ call assert_equal('auto', opts.signcolumn)
+ endif
+ set list&
+ if has('folding')
+ set foldlevel=0
+ endif
+endfunc
+
+function Test_getbufinfo_lastused()
+ call test_settime(1234567)
+ edit Xtestfile1
+ enew
+ call test_settime(7654321)
+ edit Xtestfile2
+ enew
+ call assert_equal(getbufinfo('Xtestfile1')[0].lastused, 1234567)
+ call assert_equal(getbufinfo('Xtestfile2')[0].lastused, 7654321)
+ call test_settime(0)
+endfunc
+
+func Test_getbufinfo_lines()
+ new Xfoo
+ call setline(1, ['a', 'bc', 'd'])
+ let bn = bufnr('%')
+ hide
+ call assert_equal(3, getbufinfo(bn)[0]["linecount"])
+ edit Xfoo
+ bw!
+endfunc
+
+func Test_getwininfo_au()
+ enew
+ call setline(1, range(1, 16))
+
+ let g:info = #{}
+ augroup T1
+ au!
+ au WinEnter * let g:info = getwininfo(win_getid())[0]
+ augroup END
+
+ 4split
+ " Check that calling getwininfo() from WinEnter returns fresh values for
+ " topline and botline.
+ call assert_equal(1, g:info.topline)
+ call assert_equal(4, g:info.botline)
+ close
+
+ unlet g:info
+ augroup! T1
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cd.vim b/src/testdir/test_cd.vim
new file mode 100644
index 0000000..9fb5958
--- /dev/null
+++ b/src/testdir/test_cd.vim
@@ -0,0 +1,247 @@
+" Test for :cd and chdir()
+
+source shared.vim
+source check.vim
+
+func Test_cd_large_path()
+ " This used to crash with a heap write overflow.
+ call assert_fails('cd ' . repeat('x', 5000), 'E344:')
+endfunc
+
+func Test_cd_up_and_down()
+ let path = getcwd()
+ cd ..
+ call assert_notequal(path, getcwd())
+ exe 'cd ' .. fnameescape(path)
+ call assert_equal(path, getcwd())
+endfunc
+
+func Test_cd_no_arg()
+ if has('unix')
+ " Test that cd without argument goes to $HOME directory on Unix systems.
+ let path = getcwd()
+ cd
+ call assert_equal($HOME, getcwd())
+ call assert_notequal(path, getcwd())
+ exe 'cd ' .. fnameescape(path)
+ call assert_equal(path, getcwd())
+ else
+ " Test that cd without argument echoes cwd on non-Unix systems.
+ call assert_match(getcwd(), execute('cd'))
+ endif
+endfunc
+
+func Test_cd_minus()
+ " Test the :cd - goes back to the previous directory.
+ let path = getcwd()
+ cd ..
+ let path_dotdot = getcwd()
+ call assert_notequal(path, path_dotdot)
+ cd -
+ call assert_equal(path, getcwd())
+ cd -
+ call assert_equal(path_dotdot, getcwd())
+ cd -
+ call assert_equal(path, getcwd())
+
+ " Test for :cd - after a failed :cd
+ call assert_fails('cd /nonexistent', 'E344:')
+ call assert_equal(path, getcwd())
+ cd -
+ call assert_equal(path_dotdot, getcwd())
+ cd -
+
+ " Test for :cd - without a previous directory
+ let lines =<< trim [SCRIPT]
+ call assert_fails('cd -', 'E186:')
+ call assert_fails('call chdir("-")', 'E186:')
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test for chdir()
+func Test_chdir_func()
+ let topdir = getcwd()
+ call mkdir('Xchdir/y/z', 'pR')
+
+ " Create a few tabpages and windows with different directories
+ new
+ cd Xchdir
+ tabnew
+ tcd y
+ below new
+ below new
+ lcd z
+
+ tabfirst
+ call assert_match('^\[global\] .*/Xchdir$', trim(execute('verbose pwd')))
+ call chdir('..')
+ call assert_equal('y', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('z', fnamemodify(3->getcwd(2), ':t'))
+ tabnext | wincmd t
+ call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd')))
+ eval '..'->chdir()
+ call assert_equal('Xchdir', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('Xchdir', fnamemodify(getcwd(2, 2), ':t'))
+ call assert_equal('z', fnamemodify(getcwd(3, 2), ':t'))
+ call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
+ 3wincmd w
+ call assert_match('^\[window\] .*/z$', trim(execute('verbose pwd')))
+ call chdir('..')
+ call assert_equal('Xchdir', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('Xchdir', fnamemodify(getcwd(2, 2), ':t'))
+ call assert_equal('y', fnamemodify(getcwd(3, 2), ':t'))
+ call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t'))
+
+ " Error case
+ call assert_fails("call chdir('dir-abcd')", 'E344:')
+ silent! let d = chdir("dir_abcd")
+ call assert_equal("", d)
+ " Should not crash
+ call chdir(d)
+ call assert_equal('', chdir([]))
+
+ only | tabonly
+ call chdir(topdir)
+endfunc
+
+" Test for changing to the previous directory '-'
+func Test_prev_dir()
+ let topdir = getcwd()
+ call mkdir('Xprevdir/a/b/c', 'pR')
+
+ " Create a few tabpages and windows with different directories
+ new | only
+ tabnew | new
+ tabnew
+ tabfirst
+ cd Xprevdir
+ tabnext | wincmd t
+ tcd a
+ wincmd w
+ lcd b
+ tabnext
+ tcd a/b/c
+
+ " Change to the previous directory twice in all the windows.
+ tabfirst
+ cd - | cd -
+ tabnext | wincmd t
+ tcd - | tcd -
+ wincmd w
+ lcd - | lcd -
+ tabnext
+ tcd - | tcd -
+
+ " Check the directory of all the windows
+ tabfirst
+ call assert_equal('Xprevdir', fnamemodify(getcwd(), ':t'))
+ tabnext | wincmd t
+ call assert_equal('a', fnamemodify(getcwd(), ':t'))
+ wincmd w
+ call assert_equal('b', fnamemodify(getcwd(), ':t'))
+ tabnext
+ call assert_equal('c', fnamemodify(getcwd(), ':t'))
+
+ " Change to the previous directory using chdir()
+ tabfirst
+ call chdir("-") | call chdir("-")
+ tabnext | wincmd t
+ call chdir("-") | call chdir("-")
+ wincmd w
+ call chdir("-") | call chdir("-")
+ tabnext
+ call chdir("-") | call chdir("-")
+
+ " Check the directory of all the windows
+ tabfirst
+ call assert_equal('Xprevdir', fnamemodify(getcwd(), ':t'))
+ tabnext | wincmd t
+ call assert_equal('a', fnamemodify(getcwd(), ':t'))
+ wincmd w
+ call assert_equal('b', fnamemodify(getcwd(), ':t'))
+ tabnext
+ call assert_equal('c', fnamemodify(getcwd(), ':t'))
+
+ only | tabonly
+ call chdir(topdir)
+endfunc
+
+func Test_lcd_split()
+ let curdir = getcwd()
+ lcd ..
+ split
+ lcd -
+ call assert_equal(curdir, getcwd())
+ quit!
+endfunc
+
+func Test_cd_from_non_existing_dir()
+ CheckNotMSWindows
+
+ let saveddir = getcwd()
+ call mkdir('Xdeleted_dir')
+ cd Xdeleted_dir
+ call delete(saveddir .. '/Xdeleted_dir', 'd')
+
+ " Expect E187 as the current directory was deleted.
+ call assert_fails('pwd', 'E187:')
+ call assert_equal('', getcwd())
+ cd -
+ call assert_equal(saveddir, getcwd())
+endfunc
+
+func Test_cd_completion()
+ call mkdir('XComplDir1', 'D')
+ call mkdir('XComplDir2', 'D')
+ call writefile([], 'XComplFile', 'D')
+
+ for cmd in ['cd', 'chdir', 'lcd', 'lchdir', 'tcd', 'tchdir']
+ call feedkeys(':' .. cmd .. " XCompl\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"' .. cmd .. ' XComplDir1/ XComplDir2/', @:)
+ endfor
+endfunc
+
+func Test_cd_unknown_dir()
+ call mkdir('Xa', 'R')
+ cd Xa
+ call writefile(['text'], 'Xb.txt')
+ edit Xa/Xb.txt
+ let first_buf = bufnr()
+ cd ..
+ edit
+ call assert_equal(first_buf, bufnr())
+ edit Xa/Xb.txt
+ call assert_notequal(first_buf, bufnr())
+
+ bwipe!
+ exe "bwipe! " .. first_buf
+endfunc
+
+func Test_getcwd_actual_dir()
+ CheckOption autochdir
+
+ let startdir = getcwd()
+ call mkdir('Xactual', 'R')
+ call test_autochdir()
+ set autochdir
+ edit Xactual/file.txt
+ call assert_match('testdir.Xactual$', getcwd())
+ lcd ..
+ call assert_match('testdir$', getcwd())
+ edit
+ call assert_match('testdir.Xactual$', getcwd())
+ call assert_match('testdir$', getcwd(win_getid()))
+
+ set noautochdir
+ bwipe!
+ call chdir(startdir)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cdo.vim b/src/testdir/test_cdo.vim
new file mode 100644
index 0000000..dbed7df
--- /dev/null
+++ b/src/testdir/test_cdo.vim
@@ -0,0 +1,216 @@
+" Tests for the :cdo, :cfdo, :ldo and :lfdo commands
+
+source check.vim
+CheckFeature quickfix
+
+" Create the files used by the tests
+func SetUp()
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2')
+ call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3')
+endfunc
+
+" Remove the files used by the tests
+func TearDown()
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+ call delete('Xtestfile3')
+endfunc
+
+" Returns the current line in '<filename> <linenum>L <column>C' format
+func GetRuler()
+ return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C'
+endfunc
+
+" Tests for the :cdo and :ldo commands
+func XdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cdo and ldo commands
+ let Xdo = a:cchar . 'do'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let Xprev = a:cchar. 'prev'
+ let XdoCmd = Xdo . ' call add(l, GetRuler())'
+
+ " Try with an empty list
+ let l = []
+ exe XdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ enew
+ exe "2,3" . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ " Boundary condition tests
+ let l = []
+ enew
+ exe "1,1" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C'], l)
+
+ let l = []
+ enew
+ exe "3" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Range test commands
+ let l = []
+ enew
+ exe "%" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe "1,$" . XdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l)
+
+ let l = []
+ enew
+ exe Xprev
+ exe "." . XdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ let l = []
+ enew
+ exe "+" . XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+ " Invalid error lines test
+ let l = []
+ enew
+ exe "silent! 27" . XdoCmd
+ exe "silent! 4,5" . XdoCmd
+ call assert_equal([], l)
+
+ " Run commands from an unsaved buffer
+ let v:errmsg=''
+ let l = []
+ enew
+ setlocal modified
+ exe "silent! 2,2" . XdoCmd
+ if v:errmsg !~# 'No write since last change'
+ call add(v:errors, 'Unsaved file change test failed')
+ endif
+
+ " If the executed command fails, then the operation should be aborted
+ enew!
+ let subst_count = 0
+ exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1"
+ if subst_count != 1 || getline('.') != 'xLine1'
+ call add(v:errors, 'Abort command on error test failed')
+ endif
+
+ let l = []
+ exe "2,2" . Xdo . "! call add(l, GetRuler())"
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with no valid error entries
+ let l = []
+ edit! +2 Xtestfile1
+ exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']"
+ exe XdoCmd
+ call assert_equal([], l)
+ exe "silent! 2" . XdoCmd
+ call assert_equal([], l)
+ let v:errmsg=''
+ exe "%" . XdoCmd
+ exe "1,$" . XdoCmd
+ exe "." . XdoCmd
+ call assert_equal('', v:errmsg)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile3:3:1:Line3']"
+ exe XdoCmd
+ call assert_equal(['Xtestfile3 3L 1C'], l)
+
+endfunc
+
+" Tests for the :cfdo and :lfdo commands
+func XfdoTests(cchar)
+ enew
+
+ " Shortcuts for calling the cfdo and lfdo commands
+ let Xfdo = a:cchar . 'fdo'
+ let Xgetexpr = a:cchar . 'getexpr'
+ let XfdoCmd = Xfdo . ' call add(l, GetRuler())'
+ let Xpfile = a:cchar. 'pfile'
+
+ " Clear the quickfix/location list
+ exe Xgetexpr . " []"
+
+ " Try with an empty list
+ let l = []
+ exe XfdoCmd
+ call assert_equal([], l)
+
+ " Populate the list and then try
+ exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']"
+
+ let l = []
+ exe XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Run command only on selected error lines
+ let l = []
+ exe "2,3" . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ " Boundary condition tests
+ let l = []
+ exe "3" . XfdoCmd
+ call assert_equal(['Xtestfile3 2L 3C'], l)
+
+ " Range test commands
+ let l = []
+ exe "%" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe "1,$" . XfdoCmd
+ call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l)
+
+ let l = []
+ exe Xpfile
+ exe "." . XfdoCmd
+ call assert_equal(['Xtestfile2 2L 2C'], l)
+
+ " List with only one valid entry
+ let l = []
+ exe Xgetexpr . " ['Xtestfile2:2:5:Line2']"
+ exe XfdoCmd
+ call assert_equal(['Xtestfile2 2L 5C'], l)
+
+endfunc
+
+" Tests for cdo and cfdo
+func Test_cdo()
+ call XdoTests('c')
+ call XfdoTests('c')
+endfunc
+
+" Tests for ldo and lfdo
+func Test_ldo()
+ call XdoTests('l')
+ call XfdoTests('l')
+endfunc
+
+" Test for making 'shm' doesn't interfere with the output.
+func Test_cdo_print()
+ enew | only!
+ cgetexpr ["Xtestfile1:1:Line1", "Xtestfile2:1:Line1", "Xtestfile3:1:Line1"]
+ cdo print
+ call assert_equal('Line1', Screenline(&lines))
+ call assert_equal('Line1', Screenline(&lines - 3))
+ call assert_equal('Line1', Screenline(&lines - 6))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_changedtick.vim b/src/testdir/test_changedtick.vim
new file mode 100644
index 0000000..b7e7cd6
--- /dev/null
+++ b/src/testdir/test_changedtick.vim
@@ -0,0 +1,97 @@
+" Tests for b:changedtick
+
+func Test_changedtick_increments()
+ new
+ " New buffer has an empty line, tick starts at 2.
+ let expected = 2
+ call assert_equal(expected, b:changedtick)
+ call assert_equal(expected, b:['changedtick'])
+ call setline(1, 'hello')
+ let expected += 1
+ call assert_equal(expected, b:changedtick)
+ call assert_equal(expected, b:['changedtick'])
+ undo
+ " Somehow undo counts as two changes.
+ let expected += 2
+ call assert_equal(expected, b:changedtick)
+ call assert_equal(expected, b:['changedtick'])
+ bwipe!
+endfunc
+
+func Test_changedtick_dict_entry()
+ let d = b:
+ call assert_equal(b:changedtick, d['changedtick'])
+endfunc
+
+func Test_changedtick_bdel()
+ new
+ let bnr = bufnr('%')
+ let v = b:changedtick
+ bdel
+ " Delete counts as a change too.
+ call assert_equal(v + 1, getbufvar(bnr, 'changedtick'))
+endfunc
+
+func Test_changedtick_islocked()
+ call assert_equal(0, islocked('b:changedtick'))
+ let d = b:
+ call assert_equal(0, islocked('d.changedtick'))
+endfunc
+
+func Test_changedtick_fixed()
+ call assert_fails('let b:changedtick = 4', 'E46:')
+ call assert_fails('let b:["changedtick"] = 4', 'E46:')
+
+ call assert_fails('lockvar b:changedtick', 'E940:')
+ call assert_fails('lockvar b:["changedtick"]', 'E46:')
+ call assert_fails('unlockvar b:changedtick', 'E940:')
+ call assert_fails('unlockvar b:["changedtick"]', 'E46:')
+ call assert_fails('unlet b:changedtick', 'E795:')
+ call assert_fails('unlet b:["changedtick"]', 'E46:')
+
+ let d = b:
+ call assert_fails('lockvar d["changedtick"]', 'E46:')
+ call assert_fails('unlockvar d["changedtick"]', 'E46:')
+ call assert_fails('unlet d["changedtick"]', 'E46:')
+
+endfunc
+
+func Test_changedtick_not_incremented_with_write()
+ new
+ let fname = "XChangeTick"
+ exe 'w ' .. fname
+
+ " :write when the buffer is not changed does not increment changedtick
+ let expected = b:changedtick
+ w
+ call assert_equal(expected, b:changedtick)
+
+ " :write when the buffer IS changed DOES increment changedtick
+ let expected = b:changedtick + 1
+ setlocal modified
+ w
+ call assert_equal(expected, b:changedtick)
+
+ " Two ticks: change + write
+ let expected = b:changedtick + 2
+ call setline(1, 'hello')
+ w
+ call assert_equal(expected, b:changedtick)
+
+ " Two ticks: start insert + write
+ let expected = b:changedtick + 2
+ normal! o
+ w
+ call assert_equal(expected, b:changedtick)
+
+ " Three ticks: start insert + change + write
+ let expected = b:changedtick + 3
+ normal! ochanged
+ w
+ call assert_equal(expected, b:changedtick)
+
+ bwipe
+ call delete(fname)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_changelist.vim b/src/testdir/test_changelist.vim
new file mode 100644
index 0000000..c9d8ea4
--- /dev/null
+++ b/src/testdir/test_changelist.vim
@@ -0,0 +1,105 @@
+" Tests for the changelist functionality
+
+" When splitting a window the changelist position is wrong.
+" Test the changelist position after splitting a window.
+" Test for the bug fixed by 7.4.386
+func Test_changelist()
+ let save_ul = &ul
+ enew!
+ call append('$', ['1', '2'])
+ exe "normal i\<C-G>u"
+ exe "normal Gkylpa\<C-G>u"
+ set ul=100
+ exe "normal Gylpa\<C-G>u"
+ set ul=100
+ normal gg
+ vsplit
+ normal g;
+ call assert_equal([3, 2], [line('.'), col('.')])
+ normal g;
+ call assert_equal([2, 2], [line('.'), col('.')])
+ call assert_fails('normal g;', 'E662:')
+ new
+ call assert_fails('normal g;', 'E664:')
+ %bwipe!
+ let &ul = save_ul
+endfunc
+
+" Moving a split should not change its changelist index.
+func Test_changelist_index_move_split()
+ exe "norm! iabc\<C-G>u\ndef\<C-G>u\nghi"
+ vsplit
+ normal 99g;
+ call assert_equal(0, getchangelist('%')[1])
+ wincmd L
+ call assert_equal(0, getchangelist('%')[1])
+endfunc
+
+" Tests for the getchangelist() function
+func Test_changelist_index()
+ edit Xgclfile1.txt
+ exe "normal iabc\<C-G>u\ndef\<C-G>u\nghi"
+ call assert_equal(3, getchangelist('%')[1])
+ " Move one step back in the changelist.
+ normal 2g;
+
+ hide edit Xgclfile2.txt
+ exe "normal iabcd\<C-G>u\ndefg\<C-G>u\nghij"
+ call assert_equal(3, getchangelist('%')[1])
+ " Move to the beginning of the changelist.
+ normal 99g;
+
+ " Check the changelist indices.
+ call assert_equal(0, getchangelist('%')[1])
+ call assert_equal(1, getchangelist('#')[1])
+
+ bwipe!
+ call delete('Xgclfile1.txt')
+ call delete('Xgclfile2.txt')
+endfunc
+
+func Test_getchangelist()
+ bwipe!
+ enew
+ call assert_equal([], 10->getchangelist())
+ call assert_equal([[], 0], getchangelist())
+
+ call writefile(['line1', 'line2', 'line3'], 'Xclistfile1.txt', 'D')
+ call writefile(['line1', 'line2', 'line3'], 'Xclistfile2.txt', 'D')
+
+ edit Xclistfile1.txt
+ let buf_1 = bufnr()
+ exe "normal 1Goline\<C-G>u1.1"
+ exe "normal 3Goline\<C-G>u2.1"
+ exe "normal 5Goline\<C-G>u3.1"
+ normal g;
+ call assert_equal([[
+ \ {'lnum' : 2, 'col' : 4, 'coladd' : 0},
+ \ {'lnum' : 4, 'col' : 4, 'coladd' : 0},
+ \ {'lnum' : 6, 'col' : 4, 'coladd' : 0}], 2],
+ \ getchangelist('%'))
+
+ hide edit Xclistfile2.txt
+ let buf_2 = bufnr()
+ exe "normal 1GOline\<C-G>u1.0"
+ exe "normal 2Goline\<C-G>u2.0"
+ call assert_equal([[
+ \ {'lnum' : 1, 'col' : 6, 'coladd' : 0},
+ \ {'lnum' : 3, 'col' : 6, 'coladd' : 0}], 2],
+ \ getchangelist('%'))
+ hide enew
+
+ call assert_equal([[
+ \ {'lnum' : 2, 'col' : 4, 'coladd' : 0},
+ \ {'lnum' : 4, 'col' : 4, 'coladd' : 0},
+ \ {'lnum' : 6, 'col' : 4, 'coladd' : 0}], 2],
+ \ getchangelist(buf_1))
+ call assert_equal([[
+ \ {'lnum' : 1, 'col' : 6, 'coladd' : 0},
+ \ {'lnum' : 3, 'col' : 6, 'coladd' : 0}], 2],
+ \ getchangelist(buf_2))
+
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_channel.py b/src/testdir/test_channel.py
new file mode 100644
index 0000000..35c34fc
--- /dev/null
+++ b/src/testdir/test_channel.py
@@ -0,0 +1,288 @@
+#!/usr/bin/env python
+#
+# Server that will accept connections from a Vim channel.
+# Used by test_channel.vim.
+#
+# This requires Python 2.6 or later.
+
+from __future__ import print_function
+import json
+import socket
+import sys
+import time
+import threading
+
+try:
+ # Python 3
+ import socketserver
+except ImportError:
+ # Python 2
+ import SocketServer as socketserver
+
+class TestingRequestHandler(socketserver.BaseRequestHandler):
+ def handle(self):
+ print("=== socket opened ===")
+ while True:
+ try:
+ received = self.request.recv(4096).decode('utf-8')
+ except socket.error:
+ print("=== socket error ===")
+ break
+ except IOError:
+ print("=== socket closed ===")
+ break
+ if received == '':
+ print("=== socket closed ===")
+ break
+ print("received: {0}".format(received))
+
+ # We may receive two messages at once. Take the part up to the
+ # newline, which should be after the matching "]".
+ todo = received
+ while todo != '':
+ splitidx = todo.find('\n')
+ if splitidx < 0:
+ used = todo
+ todo = ''
+ else:
+ used = todo[:splitidx]
+ todo = todo[splitidx + 1:]
+ if used != received:
+ print("using: {0}".format(used))
+
+ try:
+ decoded = json.loads(used)
+ except ValueError:
+ print("json decoding failed")
+ decoded = [-1, '']
+
+ # Send a response if the sequence number is positive.
+ if decoded[0] >= 0:
+ if decoded[1] == 'hello!':
+ # simply send back a string
+ response = "got it"
+ elif decoded[1] == 'malformed1':
+ cmd = '["ex",":"]wrong!["ex","smi"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ # Need to wait for Vim to give up, otherwise it
+ # sometimes fails on OS X.
+ time.sleep(0.2)
+ elif decoded[1] == 'malformed2':
+ cmd = '"unterminated string'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ # Need to wait for Vim to give up, otherwise the double
+ # quote in the "ok" response terminates the string.
+ time.sleep(0.2)
+ elif decoded[1] == 'malformed3':
+ cmd = '["ex","missing ]"'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ # Need to wait for Vim to give up, otherwise the ]
+ # in the "ok" response terminates the list.
+ time.sleep(0.2)
+ elif decoded[1] == 'split':
+ cmd = '["ex","let '
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ time.sleep(0.01)
+ cmd = 'g:split = 123"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1].startswith("echo "):
+ # send back the argument
+ response = decoded[1][5:]
+ elif decoded[1] == 'make change':
+ # Send two ex commands at the same time, before
+ # replying to the request.
+ cmd = '["ex","call append(\\"$\\",\\"added1\\")"]'
+ cmd += '["ex","call append(\\"$\\",\\"added2\\")"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'echoerr':
+ cmd = '["ex","echoerr \\\"this is an error\\\""]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ # Wait a bit, so that the "ex" command is handled
+ # before the "ch_evalexpr() returns. Otherwise we are
+ # outside the try/catch when the "ex" command is
+ # handled.
+ time.sleep(0.02)
+ elif decoded[1] == 'bad command':
+ cmd = '["ex","foo bar"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'do normal':
+ # Send a normal command.
+ cmd = '["normal","G$s more\u001b"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'eval-works':
+ # Send an eval request. We ignore the response.
+ cmd = '["expr","\\"foo\\" . 123", -1]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'eval-special':
+ # Send an eval request. We ignore the response.
+ cmd = '["expr","\\"foo\x7f\x10\x01bar\\"", -2]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'eval-getline':
+ # Send an eval request. We ignore the response.
+ cmd = '["expr","getline(3)", -3]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'eval-fails':
+ # Send an eval request that will fail.
+ cmd = '["expr","xxx", -4]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'eval-error':
+ # Send an eval request that works but the result can't
+ # be encoded.
+ cmd = '["expr","function(\\"tr\\")", -5]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'eval-bad':
+ # Send an eval request missing the third argument.
+ cmd = '["expr","xxx"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'an expr':
+ # Send an expr request.
+ cmd = '["expr","setline(\\"$\\", [\\"one\\",\\"two\\",\\"three\\"])"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'call-func':
+ cmd = '["call","MyFunction",[1,2,3], 0]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'redraw':
+ cmd = '["redraw",""]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'redraw!':
+ cmd = '["redraw","force"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'empty-request':
+ cmd = '[]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'eval-result':
+ # Send back the last received eval result.
+ response = last_eval
+ elif decoded[1] == 'call me':
+ cmd = '[0,"we called you"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "ok"
+ elif decoded[1] == 'call me again':
+ cmd = '[0,"we did call you"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = ""
+ elif decoded[1] == 'send zero':
+ cmd = '[0,"zero index"]'
+ print("sending: {0}".format(cmd))
+ self.request.sendall(cmd.encode('utf-8'))
+ response = "sent zero"
+ elif decoded[1] == 'close me':
+ print("closing")
+ self.request.close()
+ response = ""
+ elif decoded[1] == 'wait a bit':
+ time.sleep(0.2)
+ response = "waited"
+ elif decoded[1] == '!quit!':
+ # we're done
+ self.server.shutdown()
+ return
+ elif decoded[1] == '!crash!':
+ # Crash!
+ 42 / 0
+ else:
+ response = "what?"
+
+ if response == "":
+ print("no response")
+ else:
+ encoded = json.dumps([decoded[0], response])
+ print("sending: {0}".format(encoded))
+ self.request.sendall(encoded.encode('utf-8'))
+
+ # Negative numbers are used for "eval" responses.
+ elif decoded[0] < 0:
+ last_eval = decoded
+
+class ThreadedTCPRequestHandler(TestingRequestHandler):
+ def setup(self):
+ self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
+ pass
+
+def writePortInFile(port):
+ # Write the port number in Xportnr, so that the test knows it.
+ f = open("Xportnr", "w")
+ f.write("{0}".format(port))
+ f.close()
+
+def main(host, port, server_class=ThreadedTCPServer):
+ # Wait half a second before opening the port to test waittime in ch_open().
+ # We do want to get the port number, get that first. We cannot open the
+ # socket, guess a port is free.
+ if len(sys.argv) >= 2 and sys.argv[1] == 'delay':
+ port = 13684
+ writePortInFile(port)
+
+ print("Wait for it...")
+ time.sleep(0.5)
+
+ addrs = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_TCP)
+ # Each addr is a (family, type, proto, canonname, sockaddr) tuple
+ sockaddr = addrs[0][4]
+ server_class.address_family = addrs[0][0]
+
+ server = server_class(sockaddr[0:2], ThreadedTCPRequestHandler)
+ ip, port = server.server_address[0:2]
+
+ # Start a thread with the server. That thread will then start a new thread
+ # for each connection.
+ server_thread = threading.Thread(target=server.serve_forever)
+ server_thread.start()
+
+ writePortInFile(port)
+
+ print("Listening on port {0}".format(port))
+
+ # Main thread terminates, but the server continues running
+ # until server.shutdown() is called.
+ try:
+ while server_thread.is_alive():
+ server_thread.join(1)
+ except (KeyboardInterrupt, SystemExit):
+ server.shutdown()
+
+if __name__ == "__main__":
+ main("localhost", 0)
diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim
new file mode 100644
index 0000000..a6b25c9
--- /dev/null
+++ b/src/testdir/test_channel.vim
@@ -0,0 +1,2696 @@
+" Test for channel and job functions.
+
+" When +channel is supported then +job is too, so we don't check for that.
+source check.vim
+CheckFeature channel
+
+source shared.vim
+source screendump.vim
+source view_util.vim
+
+let s:python = PythonProg()
+if s:python == ''
+ " Can't run this test without Python.
+ throw 'Skipped: Python command missing'
+endif
+
+" Uncomment the next line to see what happens. Output is in
+" src/testdir/channellog.
+" Add ch_log() calls where you want to see what happens.
+" call ch_logfile('channellog', 'w')
+
+func SetUp()
+ if g:testfunc =~ '_ipv6()$'
+ let s:localhost = '[::1]:'
+ let s:testscript = 'test_channel_6.py'
+ elseif g:testfunc =~ '_unix()$'
+ let s:localhost = 'unix:Xtestsocket'
+ let s:testscript = 'test_channel_unix.py'
+ else
+ let s:localhost = 'localhost:'
+ let s:testscript = 'test_channel.py'
+ endif
+ let s:chopt = {}
+ call ch_log(g:testfunc)
+
+ " Most tests use job_start(), which can be flaky
+ let g:test_is_flaky = 1
+endfunc
+
+" Run "testfunc" after starting the server and stop the server afterwards.
+func s:run_server(testfunc, ...)
+ call RunServer(s:testscript, a:testfunc, a:000)
+endfunc
+
+" Returns the address of the test server.
+func s:address(port)
+ if s:localhost =~ '^unix:'
+ return s:localhost
+ else
+ return s:localhost . a:port
+ end
+endfunc
+
+" Return a list of open files.
+" Can be used to make sure no resources leaked.
+" Returns an empty list on systems where this is not supported.
+func s:get_resources()
+ let pid = getpid()
+
+ if executable('lsof')
+ return systemlist('lsof -p ' . pid . ' | awk ''$4~/^[0-9]*[rwu]$/&&$5=="REG"{print$NF}''')
+ elseif isdirectory('/proc/' . pid . '/fd/')
+ return systemlist('readlink /proc/' . pid . '/fd/* | grep -v ''^/dev/''')
+ else
+ return []
+ endif
+endfunc
+
+let g:Ch_responseMsg = ''
+func Ch_requestHandler(handle, msg)
+ let g:Ch_responseHandle = a:handle
+ let g:Ch_responseMsg = a:msg
+endfunc
+
+func Ch_communicate(port)
+ " Avoid dropping messages, since we don't use a callback here.
+ let s:chopt.drop = 'never'
+ " Also add the noblock flag to try it out.
+ let s:chopt.noblock = 1
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+
+ " check that getjob without a job is handled correctly
+ call assert_equal('no process', string(ch_getjob(handle)))
+
+ let dict = handle->ch_info()
+ call assert_true(dict.id != 0)
+ call assert_equal('open', dict.status)
+ if has_key(dict, 'port')
+ " Channels using Unix sockets have no 'port' entry.
+ call assert_equal(a:port, string(dict.port))
+ end
+ call assert_equal('open', dict.sock_status)
+ call assert_equal('socket', dict.sock_io)
+
+ " Simple string request and reply.
+ call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
+
+ " Malformed command should be ignored.
+ call assert_equal('ok', ch_evalexpr(handle, 'malformed1'))
+ call assert_equal('ok', ch_evalexpr(handle, 'malformed2'))
+ call assert_equal('ok', ch_evalexpr(handle, 'malformed3'))
+
+ " split command should work
+ call assert_equal('ok', ch_evalexpr(handle, 'split'))
+ call WaitFor('exists("g:split")')
+ call assert_equal(123, g:split)
+
+ " string with ][ should work
+ call assert_equal('this][that', ch_evalexpr(handle, 'echo this][that'))
+
+ " nothing to read now
+ call assert_equal(0, ch_canread(handle))
+
+ " sending three messages quickly then reading should work
+ for i in range(3)
+ call ch_sendexpr(handle, 'echo hello ' . i)
+ endfor
+ call assert_equal('hello 0', ch_read(handle)[1])
+ call assert_equal('hello 1', ch_read(handle)[1])
+ call assert_equal('hello 2', ch_read(handle)[1])
+
+ " Request that triggers sending two ex commands. These will usually be
+ " handled before getting the response, but it's not guaranteed, thus wait a
+ " tiny bit for the commands to get executed.
+ call assert_equal('ok', ch_evalexpr(handle, 'make change'))
+ call WaitForAssert({-> assert_equal("added2", getline("$"))})
+ call assert_equal('added1', getline(line('$') - 1))
+
+ " Request command "echoerr 'this is an error'".
+ " This will throw an exception, catch it here.
+ let caught = 'no'
+ try
+ call assert_equal('ok', ch_evalexpr(handle, 'echoerr'))
+ catch /this is an error/
+ let caught = 'yes'
+ endtry
+ if caught != 'yes'
+ call assert_report("Expected exception from error message")
+ endif
+
+ " Request command "foo bar", which fails silently.
+ call assert_equal('ok', ch_evalexpr(handle, 'bad command'))
+ call WaitForAssert({-> assert_match("E492:.*foo bar", v:errmsg)})
+
+ call assert_equal('ok', ch_evalexpr(handle, 'do normal', {'timeout': 100}))
+ call WaitForAssert({-> assert_equal('added more', getline('$'))})
+
+ " Send a request with a specific handler.
+ call ch_sendexpr(handle, 'hello!', {'callback': 'Ch_requestHandler'})
+ call WaitFor('exists("g:Ch_responseHandle")')
+ if !exists('g:Ch_responseHandle')
+ call assert_report('g:Ch_responseHandle was not set')
+ else
+ call assert_equal(handle, g:Ch_responseHandle)
+ unlet g:Ch_responseHandle
+ endif
+ call assert_equal('got it', g:Ch_responseMsg)
+
+ let g:Ch_responseMsg = ''
+ call ch_sendexpr(handle, 'hello!', {'callback': function('Ch_requestHandler')})
+ call WaitFor('exists("g:Ch_responseHandle")')
+ if !exists('g:Ch_responseHandle')
+ call assert_report('g:Ch_responseHandle was not set')
+ else
+ call assert_equal(handle, g:Ch_responseHandle)
+ unlet g:Ch_responseHandle
+ endif
+ call assert_equal('got it', g:Ch_responseMsg)
+
+ " Using lambda.
+ let g:Ch_responseMsg = ''
+ call ch_sendexpr(handle, 'hello!', {'callback': {a, b -> Ch_requestHandler(a, b)}})
+ call WaitFor('exists("g:Ch_responseHandle")')
+ if !exists('g:Ch_responseHandle')
+ call assert_report('g:Ch_responseHandle was not set')
+ else
+ call assert_equal(handle, g:Ch_responseHandle)
+ unlet g:Ch_responseHandle
+ endif
+ call assert_equal('got it', g:Ch_responseMsg)
+
+ " Collect garbage, tests that our handle isn't collected.
+ call test_garbagecollect_now()
+
+ " check setting options (without testing the effect)
+ eval handle->ch_setoptions({'callback': 's:NotUsed'})
+ call ch_setoptions(handle, {'timeout': 1111})
+ call ch_setoptions(handle, {'mode': 'json'})
+ call assert_fails("call ch_setoptions(handle, {'waittime': 111})", 'E475:')
+ call ch_setoptions(handle, {'callback': ''})
+ call ch_setoptions(handle, {'drop': 'never'})
+ call ch_setoptions(handle, {'drop': 'auto'})
+ call assert_fails("call ch_setoptions(handle, {'drop': 'bad'})", 'E475:')
+ call assert_equal(0, ch_setoptions(handle, test_null_dict()))
+ call assert_equal(0, ch_setoptions(test_null_channel(), {'drop' : 'never'}))
+
+ " Send an eval request that works.
+ call assert_equal('ok', ch_evalexpr(handle, 'eval-works'))
+ sleep 10m
+ call assert_equal([-1, 'foo123'], ch_evalexpr(handle, 'eval-result'))
+
+ " Send an eval request with special characters.
+ call assert_equal('ok', ch_evalexpr(handle, 'eval-special'))
+ sleep 10m
+ call assert_equal([-2, "foo\x7f\x10\x01bar"], ch_evalexpr(handle, 'eval-result'))
+
+ " Send an eval request to get a line with special characters.
+ call setline(3, "a\nb\<CR>c\x01d\x7fe")
+ call assert_equal('ok', ch_evalexpr(handle, 'eval-getline'))
+ sleep 10m
+ call assert_equal([-3, "a\nb\<CR>c\x01d\x7fe"], ch_evalexpr(handle, 'eval-result'))
+
+ " Send an eval request that fails.
+ call assert_equal('ok', ch_evalexpr(handle, 'eval-fails'))
+ sleep 10m
+ call assert_equal([-4, 'ERROR'], ch_evalexpr(handle, 'eval-result'))
+
+ " Send an eval request that works but can't be encoded.
+ call assert_equal('ok', ch_evalexpr(handle, 'eval-error'))
+ sleep 10m
+ call assert_equal([-5, 'ERROR'], ch_evalexpr(handle, 'eval-result'))
+
+ " Send a bad eval request. There will be no response.
+ call assert_equal('ok', ch_evalexpr(handle, 'eval-bad'))
+ sleep 10m
+ call assert_equal([-5, 'ERROR'], ch_evalexpr(handle, 'eval-result'))
+
+ " Send an expr request
+ call assert_equal('ok', ch_evalexpr(handle, 'an expr'))
+ call WaitForAssert({-> assert_equal('three', getline('$'))})
+ call assert_equal('one', getline(line('$') - 2))
+ call assert_equal('two', getline(line('$') - 1))
+
+ " Request a redraw, we don't check for the effect.
+ call assert_equal('ok', ch_evalexpr(handle, 'redraw'))
+ call assert_equal('ok', ch_evalexpr(handle, 'redraw!'))
+
+ call assert_equal('ok', ch_evalexpr(handle, 'empty-request'))
+
+ " Reading while there is nothing available.
+ call assert_equal(v:none, ch_read(handle, {'timeout': 0}))
+ if exists('*reltimefloat')
+ let start = reltime()
+ call assert_equal(v:none, ch_read(handle, {'timeout': 333}))
+ let elapsed = reltime(start)
+ call assert_inrange(0.3, 0.6, reltimefloat(reltime(start)))
+ endif
+
+ " Send without waiting for a response, then wait for a response.
+ call ch_sendexpr(handle, 'wait a bit')
+ let resp = ch_read(handle)
+ call assert_equal(type([]), type(resp))
+ call assert_equal(type(11), type(resp[0]))
+ call assert_equal('waited', resp[1])
+
+ " make the server quit, can't check if this works, should not hang.
+ call ch_sendexpr(handle, '!quit!')
+endfunc
+
+func Test_communicate()
+ call s:run_server('Ch_communicate')
+endfunc
+
+func Test_communicate_ipv6()
+ CheckIPv6
+ call Test_communicate()
+endfunc
+
+func Test_communicate_unix()
+ CheckUnix
+ call Test_communicate()
+ call delete('Xtestsocket')
+endfunc
+
+
+" Test that we can open two channels.
+func Ch_two_channels(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ call assert_equal(v:t_channel, type(handle))
+ if handle->ch_status() == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+
+ call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
+
+ let newhandle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(newhandle) == "fail"
+ call assert_report("Can't open second channel")
+ return
+ endif
+ call assert_equal('got it', ch_evalexpr(newhandle, 'hello!'))
+ call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
+
+ call ch_close(handle)
+ call assert_equal('got it', ch_evalexpr(newhandle, 'hello!'))
+
+ call ch_close(newhandle)
+ call assert_fails("call ch_close(newhandle)", 'E906:')
+endfunc
+
+func Test_two_channels()
+ eval 'Test_two_channels()'->ch_log()
+ call s:run_server('Ch_two_channels')
+endfunc
+
+func Test_two_channels_ipv6()
+ CheckIPv6
+ call Test_two_channels()
+endfunc
+
+func Test_two_channels_unix()
+ CheckUnix
+ call Test_two_channels()
+ call delete('Xtestsocket')
+endfunc
+
+" Test that a server crash is handled gracefully.
+func Ch_server_crash(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+
+ call ch_evalexpr(handle, '!crash!')
+
+ sleep 10m
+endfunc
+
+func Test_server_crash()
+ call s:run_server('Ch_server_crash')
+endfunc
+
+func Test_server_crash_ipv6()
+ CheckIPv6
+ call Test_server_crash()
+endfunc
+
+func Test_server_crash_unix()
+ CheckUnix
+ call Test_server_crash()
+ call delete('Xtestsocket')
+endfunc
+
+"""""""""
+
+func Ch_handler(chan, msg)
+ call ch_log('Ch_handler()')
+ unlet g:Ch_reply
+ let g:Ch_reply = a:msg
+endfunc
+
+func Ch_channel_handler(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+
+ " Test that it works while waiting on a numbered message.
+ call assert_equal('ok', ch_evalexpr(handle, 'call me'))
+ call WaitForAssert({-> assert_equal('we called you', g:Ch_reply)})
+
+ " Test that it works while not waiting on a numbered message.
+ call ch_sendexpr(handle, 'call me again')
+ call WaitForAssert({-> assert_equal('we did call you', g:Ch_reply)})
+endfunc
+
+func Test_channel_handler()
+ let g:Ch_reply = ""
+ let s:chopt.callback = 'Ch_handler'
+ call s:run_server('Ch_channel_handler')
+ let g:Ch_reply = ""
+ let s:chopt.callback = function('Ch_handler')
+ call s:run_server('Ch_channel_handler')
+endfunc
+
+func Test_channel_handler_ipv6()
+ CheckIPv6
+ call Test_channel_handler()
+endfunc
+
+func Test_channel_handler_unix()
+ CheckUnix
+ call Test_channel_handler()
+ call delete('Xtestsocket')
+endfunc
+
+"""""""""
+
+let g:Ch_reply = ''
+func Ch_zeroHandler(chan, msg)
+ unlet g:Ch_reply
+ let g:Ch_reply = a:msg
+endfunc
+
+let g:Ch_zero_reply = ''
+func Ch_oneHandler(chan, msg)
+ unlet g:Ch_zero_reply
+ let g:Ch_zero_reply = a:msg
+endfunc
+
+func Ch_channel_zero(port)
+ let handle = (s:address(a:port))->ch_open(s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+
+ " Check that eval works.
+ call assert_equal('got it', ch_evalexpr(handle, 'hello!'))
+
+ " Check that eval works if a zero id message is sent back.
+ let g:Ch_reply = ''
+ call assert_equal('sent zero', ch_evalexpr(handle, 'send zero'))
+ if s:has_handler
+ call WaitForAssert({-> assert_equal('zero index', g:Ch_reply)})
+ else
+ sleep 20m
+ call assert_equal('', g:Ch_reply)
+ endif
+
+ " Check that handler works if a zero id message is sent back.
+ let g:Ch_reply = ''
+ let g:Ch_zero_reply = ''
+ call ch_sendexpr(handle, 'send zero', {'callback': 'Ch_oneHandler'})
+ call WaitForAssert({-> assert_equal('sent zero', g:Ch_zero_reply)})
+ if s:has_handler
+ call assert_equal('zero index', g:Ch_reply)
+ else
+ call assert_equal('', g:Ch_reply)
+ endif
+endfunc
+
+func Test_zero_reply()
+ " Run with channel handler
+ let s:has_handler = 1
+ let s:chopt.callback = 'Ch_zeroHandler'
+ call s:run_server('Ch_channel_zero')
+ unlet s:chopt.callback
+
+ " Run without channel handler
+ let s:has_handler = 0
+ call s:run_server('Ch_channel_zero')
+endfunc
+
+func Test_zero_reply_ipv6()
+ CheckIPv6
+ call Test_zero_reply()
+endfunc
+
+func Test_zero_reply_unix()
+ CheckUnix
+ call Test_zero_reply()
+ call delete('Xtestsocket')
+endfunc
+
+
+"""""""""
+
+let g:Ch_reply1 = ""
+func Ch_handleRaw1(chan, msg)
+ unlet g:Ch_reply1
+ let g:Ch_reply1 = a:msg
+endfunc
+
+let g:Ch_reply2 = ""
+func Ch_handleRaw2(chan, msg)
+ unlet g:Ch_reply2
+ let g:Ch_reply2 = a:msg
+endfunc
+
+let g:Ch_reply3 = ""
+func Ch_handleRaw3(chan, msg)
+ unlet g:Ch_reply3
+ let g:Ch_reply3 = a:msg
+endfunc
+
+func Ch_raw_one_time_callback(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+ call ch_setoptions(handle, {'mode': 'raw'})
+
+ " The messages are sent raw, we do our own JSON strings here.
+ call ch_sendraw(handle, "[1, \"hello!\"]\n", {'callback': 'Ch_handleRaw1'})
+ call WaitForAssert({-> assert_equal("[1, \"got it\"]", g:Ch_reply1)})
+ call ch_sendraw(handle, "[2, \"echo something\"]\n", {'callback': 'Ch_handleRaw2'})
+ call ch_sendraw(handle, "[3, \"wait a bit\"]\n", {'callback': 'Ch_handleRaw3'})
+ call WaitForAssert({-> assert_equal("[2, \"something\"]", g:Ch_reply2)})
+ " wait for the 200 msec delayed reply
+ call WaitForAssert({-> assert_equal("[3, \"waited\"]", g:Ch_reply3)})
+endfunc
+
+func Test_raw_one_time_callback()
+ call s:run_server('Ch_raw_one_time_callback')
+endfunc
+
+func Test_raw_one_time_callback_ipv6()
+ CheckIPv6
+ call Test_raw_one_time_callback()
+endfunc
+
+func Test_raw_one_time_callback_unix()
+ CheckUnix
+ call Test_raw_one_time_callback()
+ call delete('Xtestsocket')
+endfunc
+
+"""""""""
+
+" Test that trying to connect to a non-existing port fails quickly.
+func Test_connect_waittime()
+ CheckFunction reltimefloat
+ " this is timing sensitive
+
+ let start = reltime()
+ let handle = ch_open('localhost:9876', s:chopt)
+ if ch_status(handle) != "fail"
+ " Oops, port does exists.
+ call ch_close(handle)
+ else
+ let elapsed = reltime(start)
+ call assert_inrange(0.0, 1.0, reltimefloat(elapsed))
+ endif
+
+ " We intend to use a socket that doesn't exist and wait for half a second
+ " before giving up. If the socket does exist it can fail in various ways.
+ " Check for "Connection reset by peer" to avoid flakiness.
+ let start = reltime()
+ try
+ let handle = ch_open('localhost:9867', {'waittime': 500})
+ if ch_status(handle) != "fail"
+ " Oops, port does exists.
+ call ch_close(handle)
+ else
+ " Failed connection should wait about 500 msec. Can be longer if the
+ " computer is busy with other things.
+ call assert_inrange(0.3, 1.5, reltimefloat(reltime(start)))
+ endif
+ catch
+ if v:exception !~ 'Connection reset by peer'
+ call assert_report("Caught exception: " . v:exception)
+ endif
+ endtry
+endfunc
+
+"""""""""
+
+func Test_raw_pipe()
+ " Add a dummy close callback to avoid that messages are dropped when calling
+ " ch_canread().
+ " Also test the non-blocking option.
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'mode': 'raw', 'drop': 'never', 'noblock': 1})
+ call assert_equal(v:t_job, type(job))
+ call assert_equal("run", job_status(job))
+
+ call assert_equal("open", ch_status(job))
+ call assert_equal("open", ch_status(job), {"part": "out"})
+ call assert_equal("open", ch_status(job), {"part": "err"})
+ call assert_fails('call ch_status(job, {"in_mode": "raw"})', 'E475:')
+ call assert_fails('call ch_status(job, {"part": "in"})', 'E475:')
+
+ let dict = ch_info(job)
+ call assert_true(dict.id != 0)
+ call assert_equal('open', dict.status)
+ call assert_equal('open', dict.out_status)
+ call assert_equal('RAW', dict.out_mode)
+ call assert_equal('pipe', dict.out_io)
+ call assert_equal('open', dict.err_status)
+ call assert_equal('RAW', dict.err_mode)
+ call assert_equal('pipe', dict.err_io)
+
+ try
+ " For a change use the job where a channel is expected.
+ call ch_sendraw(job, "echo something\n")
+ let msg = ch_readraw(job)
+ call assert_equal("something\n", substitute(msg, "\r", "", 'g'))
+
+ call ch_sendraw(job, "double this\n")
+ let g:handle = job->job_getchannel()
+ call WaitFor('g:handle->ch_canread()')
+ unlet g:handle
+ let msg = ch_readraw(job)
+ call assert_equal("this\nAND this\n", substitute(msg, "\r", "", 'g'))
+
+ let g:Ch_reply = ""
+ call ch_sendraw(job, "double this\n", {'callback': 'Ch_handler'})
+ call WaitForAssert({-> assert_equal("this\nAND this\n", substitute(g:Ch_reply, "\r", "", 'g'))})
+
+ call assert_fails("let i = ch_evalraw(job, '2 + 2', {'callback' : 'abc'})", 'E917:')
+ call assert_fails("let i = ch_evalexpr(job, '2 + 2')", 'E912:')
+ call assert_fails("let i = ch_evalraw(job, '2 + 2', {'drop' : ''})", 'E475:')
+ call assert_fails("let i = ch_evalraw(test_null_job(), '2 + 2')", 'E906:')
+
+ let reply = job->ch_evalraw("quit\n", {'timeout': 100})
+ call assert_equal("Goodbye!\n", substitute(reply, "\r", "", 'g'))
+ finally
+ call job_stop(job)
+ endtry
+
+ let g:Ch_job = job
+ call WaitForAssert({-> assert_equal("dead", job_status(g:Ch_job))})
+ let info = job->job_info()
+ call assert_equal("dead", info.status)
+ call assert_equal("term", info.stoponexit)
+ call assert_equal(2, len(info.cmd))
+ call assert_equal("test_channel_pipe.py", info.cmd[1])
+
+ let found = 0
+ for j in job_info()
+ if j == job
+ let found += 1
+ endif
+ endfor
+ call assert_equal(1, found)
+
+ call assert_fails("call job_stop('abc')", 'E475:')
+ call assert_fails("call job_stop(job, [])", 'E730:')
+ call assert_fails("call job_stop(test_null_job())", 'E916:')
+
+ " Try to use the job and channel where a number is expected. This is not
+ " related to testing the raw pipe. This test is here just to reuse the
+ " already created job/channel.
+ let ch = job_getchannel(job)
+ call assert_fails('let i = job + 1', 'E910:')
+ call assert_fails('let j = ch + 1', 'E913:')
+ call assert_fails('echo 2.0 == job', 'E911:')
+ call assert_fails('echo 2.0 == ch', 'E914:')
+endfunc
+
+func Test_raw_pipe_blob()
+ " Add a dummy close callback to avoid that messages are dropped when calling
+ " ch_canread().
+ " Also test the non-blocking option.
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'mode': 'raw', 'drop': 'never', 'noblock': 1})
+ call assert_equal(v:t_job, type(job))
+ call assert_equal("run", job_status(job))
+
+ call assert_equal("open", ch_status(job))
+ call assert_equal("open", ch_status(job), {"part": "out"})
+
+ try
+ " Create a blob with the echo command and write it.
+ let blob = 0z00
+ let cmd = "echo something\n"
+ for i in range(0, len(cmd) - 1)
+ let blob[i] = char2nr(cmd[i])
+ endfor
+ call assert_equal(len(cmd), len(blob))
+ call ch_sendraw(job, blob)
+
+ " Read a blob with the reply.
+ let msg = job->ch_readblob()
+ let expected = 'something'
+ for i in range(0, len(expected) - 1)
+ call assert_equal(char2nr(expected[i]), msg[i])
+ endfor
+
+ let reply = ch_evalraw(job, "quit\n", {'timeout': 100})
+ call assert_equal("Goodbye!\n", substitute(reply, "\r", "", 'g'))
+ finally
+ call job_stop(job)
+ endtry
+
+ let g:Ch_job = job
+ call WaitForAssert({-> assert_equal("dead", job_status(g:Ch_job))})
+ let info = job_info(job)
+ call assert_equal("dead", info.status)
+endfunc
+
+func Test_nl_pipe()
+ let job = job_start([s:python, "test_channel_pipe.py"])
+ call assert_equal("run", job_status(job))
+ try
+ let handle = job_getchannel(job)
+ call ch_sendraw(handle, "echo something\n")
+ call assert_equal("something", handle->ch_readraw())
+
+ call ch_sendraw(handle, "echoerr wrong\n")
+ call assert_equal("wrong", ch_readraw(handle, {'part': 'err'}))
+
+ call ch_sendraw(handle, "double this\n")
+ call assert_equal("this", ch_readraw(handle))
+ call assert_equal("AND this", ch_readraw(handle))
+
+ call ch_sendraw(handle, "split this line\n")
+ call assert_equal("this linethis linethis line", handle->ch_read())
+
+ let reply = ch_evalraw(handle, "quit\n")
+ call assert_equal("Goodbye!", reply)
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Stop_g_job()
+ call job_stop(g:job)
+ if has('win32')
+ " On MS-Windows the server must close the file handle before we are able
+ " to delete the file.
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ sleep 10m
+ endif
+endfunc
+
+func Test_nl_read_file()
+ call writefile(['echo something', 'echoerr wrong', 'double this'], 'Xinput', 'D')
+ let g:job = job_start(s:python . " test_channel_pipe.py",
+ \ {'in_io': 'file', 'in_name': 'Xinput'})
+ call assert_equal("run", job_status(g:job))
+ try
+ let handle = job_getchannel(g:job)
+ call assert_equal("something", ch_readraw(handle))
+ call assert_equal("wrong", ch_readraw(handle, {'part': 'err'}))
+ call assert_equal("this", ch_readraw(handle))
+ call assert_equal("AND this", ch_readraw(handle))
+ finally
+ call Stop_g_job()
+ endtry
+ call assert_fails("echo ch_read(test_null_channel(), {'callback' : 'abc'})", 'E475:')
+endfunc
+
+func Test_nl_write_out_file()
+ let g:job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_io': 'file', 'out_name': 'Xoutput'})
+ call assert_equal("run", job_status(g:job))
+ try
+ let handle = job_getchannel(g:job)
+ call ch_sendraw(handle, "echo line one\n")
+ call ch_sendraw(handle, "echo line two\n")
+ call ch_sendraw(handle, "double this\n")
+ call WaitForAssert({-> assert_equal(['line one', 'line two', 'this', 'AND this'], readfile('Xoutput'))})
+ finally
+ call Stop_g_job()
+ call assert_equal(-1, match(s:get_resources(), '\(^\|/\)Xoutput$'))
+ call delete('Xoutput')
+ endtry
+endfunc
+
+func Test_nl_write_err_file()
+ let g:job = job_start(s:python . " test_channel_pipe.py",
+ \ {'err_io': 'file', 'err_name': 'Xoutput'})
+ call assert_equal("run", job_status(g:job))
+ try
+ let handle = job_getchannel(g:job)
+ call ch_sendraw(handle, "echoerr line one\n")
+ call ch_sendraw(handle, "echoerr line two\n")
+ call ch_sendraw(handle, "doubleerr this\n")
+ call WaitForAssert({-> assert_equal(['line one', 'line two', 'this', 'AND this'], readfile('Xoutput'))})
+ finally
+ call Stop_g_job()
+ call delete('Xoutput')
+ endtry
+endfunc
+
+func Test_nl_write_both_file()
+ let g:job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_io': 'file', 'out_name': 'Xoutput', 'err_io': 'out'})
+ call assert_equal("run", job_status(g:job))
+ try
+ let handle = job_getchannel(g:job)
+ call ch_sendraw(handle, "echoerr line one\n")
+ call ch_sendraw(handle, "echo line two\n")
+ call ch_sendraw(handle, "double this\n")
+ call ch_sendraw(handle, "doubleerr that\n")
+ call WaitForAssert({-> assert_equal(['line one', 'line two', 'this', 'AND this', 'that', 'AND that'], readfile('Xoutput'))})
+ finally
+ call Stop_g_job()
+ call assert_equal(-1, match(s:get_resources(), '\(^\|/\)Xoutput$'))
+ call delete('Xoutput')
+ endtry
+endfunc
+
+func BufCloseCb(ch)
+ let g:Ch_bufClosed = 'yes'
+endfunc
+
+func Run_test_pipe_to_buffer(use_name, nomod, do_msg)
+ let g:Ch_bufClosed = 'no'
+ let options = {'out_io': 'buffer', 'close_cb': 'BufCloseCb'}
+ let expected = ['', 'line one', 'line two', 'this', 'AND this', 'Goodbye!']
+ if a:use_name
+ let options['out_name'] = 'pipe-output'
+ if a:do_msg
+ let expected[0] = 'Reading from channel output...'
+ else
+ let options['out_msg'] = 0
+ call remove(expected, 0)
+ endif
+ else
+ sp pipe-output
+ let options['out_buf'] = bufnr('%')
+ quit
+ call remove(expected, 0)
+ endif
+ if a:nomod
+ let options['out_modifiable'] = 0
+ endif
+ let job = job_start(s:python . " test_channel_pipe.py", options)
+ call assert_equal("run", job_status(job))
+ try
+ let handle = job_getchannel(job)
+ call ch_sendraw(handle, "echo line one\n")
+ call ch_sendraw(handle, "echo line two\n")
+ call ch_sendraw(handle, "double this\n")
+ call ch_sendraw(handle, "quit\n")
+ sp pipe-output
+ call WaitFor('line("$") == ' . len(expected) . ' && g:Ch_bufClosed == "yes"')
+ call assert_equal(expected, getline(1, '$'))
+ if a:nomod
+ call assert_equal(0, &modifiable)
+ else
+ call assert_equal(1, &modifiable)
+ endif
+ call assert_equal('yes', g:Ch_bufClosed)
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_pipe_to_buffer_name()
+ call Run_test_pipe_to_buffer(1, 0, 1)
+endfunc
+
+func Test_pipe_to_buffer_nr()
+ call Run_test_pipe_to_buffer(0, 0, 1)
+endfunc
+
+func Test_pipe_to_buffer_name_nomod()
+ call Run_test_pipe_to_buffer(1, 1, 1)
+endfunc
+
+func Test_pipe_to_buffer_name_nomsg()
+ call Run_test_pipe_to_buffer(1, 0, 1)
+endfunc
+
+func Test_close_output_buffer()
+ let g:test_is_flaky = 1
+ enew!
+ let test_lines = ['one', 'two']
+ call setline(1, test_lines)
+ let options = {'out_io': 'buffer'}
+ let options['out_name'] = 'buffer-output'
+ let options['out_msg'] = 0
+ split buffer-output
+ let job = job_start(s:python . " test_channel_write.py", options)
+ call assert_equal("run", job_status(job))
+ try
+ call WaitForAssert({-> assert_equal(3, line('$'))})
+ quit!
+ sleep 100m
+ " Make sure the write didn't happen to the wrong buffer.
+ call assert_equal(test_lines, getline(1, line('$')))
+ call assert_equal(-1, bufwinnr('buffer-output'))
+ sbuf buffer-output
+ call assert_notequal(-1, bufwinnr('buffer-output'))
+ sleep 100m
+ close " no more writes
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Run_test_pipe_err_to_buffer(use_name, nomod, do_msg)
+ let options = {'err_io': 'buffer'}
+ let expected = ['', 'line one', 'line two', 'this', 'AND this']
+ if a:use_name
+ let options['err_name'] = 'pipe-err'
+ if a:do_msg
+ let expected[0] = 'Reading from channel error...'
+ else
+ let options['err_msg'] = 0
+ call remove(expected, 0)
+ endif
+ else
+ sp pipe-err
+ let options['err_buf'] = bufnr('%')
+ quit
+ call remove(expected, 0)
+ endif
+ if a:nomod
+ let options['err_modifiable'] = 0
+ endif
+ let job = job_start(s:python . " test_channel_pipe.py", options)
+ call assert_equal("run", job_status(job))
+ try
+ let handle = job_getchannel(job)
+ call ch_sendraw(handle, "echoerr line one\n")
+ call ch_sendraw(handle, "echoerr line two\n")
+ call ch_sendraw(handle, "doubleerr this\n")
+ call ch_sendraw(handle, "quit\n")
+ sp pipe-err
+ call WaitForAssert({-> assert_equal(expected, getline(1, '$'))})
+ if a:nomod
+ call assert_equal(0, &modifiable)
+ else
+ call assert_equal(1, &modifiable)
+ endif
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_pipe_err_to_buffer_name()
+ call Run_test_pipe_err_to_buffer(1, 0, 1)
+endfunc
+
+func Test_pipe_err_to_buffer_nr()
+ call Run_test_pipe_err_to_buffer(0, 0, 1)
+endfunc
+
+func Test_pipe_err_to_buffer_name_nomod()
+ call Run_test_pipe_err_to_buffer(1, 1, 1)
+endfunc
+
+func Test_pipe_err_to_buffer_name_nomsg()
+ call Run_test_pipe_err_to_buffer(1, 0, 0)
+endfunc
+
+func Test_pipe_both_to_buffer()
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_io': 'buffer', 'out_name': 'pipe-err', 'err_io': 'out'})
+ call assert_equal("run", job_status(job))
+ let handle = job_getchannel(job)
+ call assert_equal(bufnr('pipe-err'), ch_getbufnr(handle, 'out'))
+ call assert_equal(bufnr('pipe-err'), ch_getbufnr(handle, 'err'))
+ try
+ call ch_sendraw(handle, "echo line one\n")
+ call ch_sendraw(handle, "echoerr line two\n")
+ call ch_sendraw(handle, "double this\n")
+ call ch_sendraw(handle, "doubleerr that\n")
+ call ch_sendraw(handle, "quit\n")
+ sp pipe-err
+ call WaitForAssert({-> assert_equal(['Reading from channel output...', 'line one', 'line two', 'this', 'AND this', 'that', 'AND that', 'Goodbye!'], getline(1, '$'))})
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Run_test_pipe_from_buffer(use_name)
+ sp pipe-input
+ call setline(1, ['echo one', 'echo two', 'echo three'])
+ let options = {'in_io': 'buffer', 'block_write': 1}
+ if a:use_name
+ let options['in_name'] = 'pipe-input'
+ else
+ let options['in_buf'] = bufnr('%')
+ endif
+
+ let job = job_start(s:python . " test_channel_pipe.py", options)
+ call assert_equal("run", job_status(job))
+ if has('unix') && !a:use_name
+ call assert_equal(bufnr('%'), ch_getbufnr(job, 'in'))
+ endif
+ try
+ let handle = job_getchannel(job)
+ call assert_equal('one', ch_read(handle))
+ call assert_equal('two', ch_read(handle))
+ call assert_equal('three', ch_read(handle))
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_pipe_from_buffer_name()
+ call Run_test_pipe_from_buffer(1)
+endfunc
+
+func Test_pipe_from_buffer_nr()
+ call Run_test_pipe_from_buffer(0)
+endfunc
+
+func Run_pipe_through_sort(all, use_buffer)
+ CheckExecutable sort
+ let g:test_is_flaky = 1
+
+ let options = {'out_io': 'buffer', 'out_name': 'sortout'}
+ if a:use_buffer
+ split sortin
+ call setline(1, ['ccc', 'aaa', 'ddd', 'bbb', 'eee'])
+ let options.in_io = 'buffer'
+ let options.in_name = 'sortin'
+ endif
+ if !a:all
+ let options.in_top = 2
+ let options.in_bot = 4
+ endif
+ let job = job_start('sort', options)
+
+ if !a:use_buffer
+ call assert_equal("run", job_status(job))
+ call ch_sendraw(job, "ccc\naaa\nddd\nbbb\neee\n")
+ eval job->ch_close_in()
+ endif
+
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+
+ sp sortout
+ call WaitFor('line("$") > 3')
+ call assert_equal('Reading from channel output...', getline(1))
+ if a:all
+ call assert_equal(['aaa', 'bbb', 'ccc', 'ddd', 'eee'], getline(2, 6))
+ else
+ call assert_equal(['aaa', 'bbb', 'ddd'], getline(2, 4))
+ endif
+
+ call job_stop(job)
+ if a:use_buffer
+ bwipe! sortin
+ endif
+ bwipe! sortout
+endfunc
+
+func Test_pipe_through_sort_all()
+ call Run_pipe_through_sort(1, 1)
+endfunc
+
+func Test_pipe_through_sort_some()
+ call Run_pipe_through_sort(0, 1)
+endfunc
+
+func Test_pipe_through_sort_feed()
+ call Run_pipe_through_sort(1, 0)
+endfunc
+
+func Test_pipe_to_nameless_buffer()
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_io': 'buffer'})
+ call assert_equal("run", job_status(job))
+ try
+ let handle = job_getchannel(job)
+ call ch_sendraw(handle, "echo line one\n")
+ call ch_sendraw(handle, "echo line two\n")
+ exe handle->ch_getbufnr("out") .. 'sbuf'
+ call WaitFor('line("$") >= 3')
+ call assert_equal(['Reading from channel output...', 'line one', 'line two'], getline(1, '$'))
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_pipe_to_buffer_json()
+ CheckFunction reltimefloat
+
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_io': 'buffer', 'out_mode': 'json'})
+ call assert_equal("run", job_status(job))
+ try
+ let handle = job_getchannel(job)
+ call ch_sendraw(handle, "echo [0, \"hello\"]\n")
+ call ch_sendraw(handle, "echo [-2, 12.34]\n")
+ exe ch_getbufnr(handle, "out") . 'sbuf'
+ call WaitFor('line("$") >= 3')
+ call assert_equal(['Reading from channel output...', '[0,"hello"]', '[-2,12.34]'], getline(1, '$'))
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+" Wait a little while for the last line, minus "offset", to equal "line".
+func s:wait_for_last_line(line, offset)
+ for i in range(100)
+ if getline(line('$') - a:offset) == a:line
+ break
+ endif
+ sleep 10m
+ endfor
+endfunc
+
+func Test_pipe_io_two_buffers()
+ " Create two buffers, one to read from and one to write to.
+ split pipe-output
+ set buftype=nofile
+ split pipe-input
+ set buftype=nofile
+
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'in_io': 'buffer', 'in_name': 'pipe-input', 'in_top': 0,
+ \ 'out_io': 'buffer', 'out_name': 'pipe-output',
+ \ 'block_write': 1})
+ call assert_equal("run", job_status(job))
+ try
+ exe "normal Gaecho hello\<CR>"
+ exe bufwinnr('pipe-output') . "wincmd w"
+ call s:wait_for_last_line('hello', 0)
+ call assert_equal('hello', getline('$'))
+
+ exe bufwinnr('pipe-input') . "wincmd w"
+ exe "normal Gadouble this\<CR>"
+ exe bufwinnr('pipe-output') . "wincmd w"
+ call s:wait_for_last_line('AND this', 0)
+ call assert_equal('this', getline(line('$') - 1))
+ call assert_equal('AND this', getline('$'))
+
+ bwipe!
+ exe bufwinnr('pipe-input') . "wincmd w"
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_pipe_io_one_buffer()
+ " Create one buffer to read from and to write to.
+ split pipe-io
+ set buftype=nofile
+
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'in_io': 'buffer', 'in_name': 'pipe-io', 'in_top': 0,
+ \ 'out_io': 'buffer', 'out_name': 'pipe-io',
+ \ 'block_write': 1})
+ call assert_equal("run", job_status(job))
+ try
+ exe "normal Goecho hello\<CR>"
+ call s:wait_for_last_line('hello', 1)
+ call assert_equal('hello', getline(line('$') - 1))
+
+ exe "normal Gadouble this\<CR>"
+ call s:wait_for_last_line('AND this', 1)
+ call assert_equal('this', getline(line('$') - 2))
+ call assert_equal('AND this', getline(line('$') - 1))
+
+ bwipe!
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_write_to_buffer_and_scroll()
+ CheckScreendump
+
+ let lines =<< trim END
+ new Xscrollbuffer
+ call setline(1, range(1, 200))
+ $
+ redraw
+ wincmd w
+ call deletebufline('Xscrollbuffer', 1, '$')
+ if has('win32')
+ let cmd = ['cmd', '/c', 'echo sometext']
+ else
+ let cmd = [&shell, &shellcmdflag, 'echo sometext']
+ endif
+ call job_start(cmd, #{out_io: 'buffer', out_name: 'Xscrollbuffer'})
+ END
+ call writefile(lines, 'XtestBufferScroll', 'D')
+ let buf = RunVimInTerminal('-S XtestBufferScroll', #{rows: 10})
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_job_buffer_scroll_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_pipe_null()
+ " We cannot check that no I/O works, we only check that the job starts
+ " properly.
+ let job = job_start(s:python . " test_channel_pipe.py something",
+ \ {'in_io': 'null'})
+ call assert_equal("run", job_status(job))
+ try
+ call assert_equal('something', ch_read(job))
+ finally
+ call job_stop(job)
+ endtry
+
+ let job = job_start(s:python . " test_channel_pipe.py err-out",
+ \ {'out_io': 'null'})
+ call assert_equal("run", job_status(job))
+ try
+ call assert_equal('err-out', ch_read(job, {"part": "err"}))
+ finally
+ call job_stop(job)
+ endtry
+
+ let job = job_start(s:python . " test_channel_pipe.py something",
+ \ {'err_io': 'null'})
+ call assert_equal("run", job_status(job))
+ try
+ call assert_equal('something', ch_read(job))
+ finally
+ call job_stop(job)
+ endtry
+
+ " This causes spurious leak errors with valgrind.
+ if !RunningWithValgrind()
+ let job = job_start(s:python . " test_channel_pipe.py something",
+ \ {'out_io': 'null', 'err_io': 'out'})
+ call assert_equal("run", job_status(job))
+ call job_stop(job)
+
+ let job = job_start(s:python . " test_channel_pipe.py something",
+ \ {'in_io': 'null', 'out_io': 'null', 'err_io': 'null'})
+ call assert_equal("run", job_status(job))
+ call assert_equal('channel fail', string(job_getchannel(job)))
+ call assert_equal('fail', ch_status(job))
+ call assert_equal('no process', string(test_null_job()))
+ call assert_equal('channel fail', string(test_null_channel()))
+ call job_stop(job)
+ endif
+endfunc
+
+func Test_pipe_to_buffer_raw()
+ let options = {'out_mode': 'raw', 'out_io': 'buffer', 'out_name': 'testout'}
+ split testout
+ let job = job_start([s:python, '-c',
+ \ 'import sys; [sys.stdout.write(".") and sys.stdout.flush() for _ in range(10000)]'], options)
+ " the job may be done quickly, also accept "dead"
+ call assert_match('^\%(dead\|run\)$', job_status(job))
+ call WaitFor('len(join(getline(1, "$"), "")) >= 10000')
+ try
+ let totlen = 0
+ for line in getline(1, '$')
+ call assert_equal('', substitute(line, '^\.*', '', ''))
+ let totlen += len(line)
+ endfor
+ call assert_equal(10000, totlen)
+ finally
+ call job_stop(job)
+ bwipe!
+ endtry
+endfunc
+
+func Test_reuse_channel()
+ let job = job_start(s:python . " test_channel_pipe.py")
+ call assert_equal("run", job_status(job))
+ let handle = job_getchannel(job)
+ try
+ call ch_sendraw(handle, "echo something\n")
+ call assert_equal("something", ch_readraw(handle))
+ finally
+ call job_stop(job)
+ endtry
+
+ let job = job_start(s:python . " test_channel_pipe.py", {'channel': handle})
+ call assert_equal("run", job_status(job))
+ let handle = job_getchannel(job)
+ try
+ call ch_sendraw(handle, "echo again\n")
+ call assert_equal("again", ch_readraw(handle))
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_out_cb()
+ let g:test_is_flaky = 1
+ let dict = {'thisis': 'dict: '}
+ func dict.outHandler(chan, msg) dict
+ if type(a:msg) == v:t_string
+ let g:Ch_outmsg = self.thisis . a:msg
+ else
+ let g:Ch_outobj = a:msg
+ endif
+ endfunc
+ func dict.errHandler(chan, msg) dict
+ let g:Ch_errmsg = self.thisis . a:msg
+ endfunc
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_cb': dict.outHandler,
+ \ 'out_mode': 'json',
+ \ 'err_cb': dict.errHandler,
+ \ 'err_mode': 'json'})
+ call assert_equal("run", job_status(job))
+ call test_garbagecollect_now()
+ try
+ let g:Ch_outmsg = ''
+ let g:Ch_errmsg = ''
+ call ch_sendraw(job, "echo [0, \"hello\"]\n")
+ call ch_sendraw(job, "echoerr [0, \"there\"]\n")
+ call WaitForAssert({-> assert_equal("dict: hello", g:Ch_outmsg)})
+ call WaitForAssert({-> assert_equal("dict: there", g:Ch_errmsg)})
+
+ " Receive a json object split in pieces
+ let g:Ch_outobj = ''
+ call ch_sendraw(job, "echosplit [0, {\"one\": 1,| \"tw|o\": 2, \"three\": 3|}]\n")
+ " For unknown reasons this can be very slow on Mac.
+ " Increase the timeout on every run.
+ if g:run_nr == 1
+ let timeout = 5000
+ elseif g:run_nr == 2
+ let timeout = 10000
+ elseif g:run_nr == 3
+ let timeout = 20000
+ else
+ let timeout = 40000
+ endif
+ call WaitForAssert({-> assert_equal({'one': 1, 'two': 2, 'three': 3}, g:Ch_outobj)}, timeout)
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_out_close_cb()
+ let s:counter = 1
+ let g:Ch_msg1 = ''
+ let g:Ch_closemsg = 0
+ func! OutHandler(chan, msg)
+ if s:counter == 1
+ let g:Ch_msg1 = a:msg
+ endif
+ let s:counter += 1
+ endfunc
+ func! CloseHandler(chan)
+ let g:Ch_closemsg = s:counter
+ let s:counter += 1
+ endfunc
+ let job = job_start(s:python . " test_channel_pipe.py quit now",
+ \ {'out_cb': 'OutHandler',
+ \ 'close_cb': 'CloseHandler'})
+ " the job may be done quickly, also accept "dead"
+ call assert_match('^\%(dead\|run\)$', job_status(job))
+ try
+ call WaitForAssert({-> assert_equal('quit', g:Ch_msg1)})
+ call WaitForAssert({-> assert_equal(2, g:Ch_closemsg)})
+ finally
+ call job_stop(job)
+ delfunc OutHandler
+ delfunc CloseHandler
+ endtry
+endfunc
+
+func Test_read_in_close_cb()
+ let g:Ch_received = ''
+ func! CloseHandler(chan)
+ let g:Ch_received = ch_read(a:chan)
+ endfunc
+ let job = job_start(s:python . " test_channel_pipe.py quit now",
+ \ {'close_cb': 'CloseHandler'})
+ " the job may be done quickly, also accept "dead"
+ call assert_match('^\%(dead\|run\)$', job_status(job))
+ try
+ call WaitForAssert({-> assert_equal('quit', g:Ch_received)})
+ finally
+ call job_stop(job)
+ delfunc CloseHandler
+ endtry
+endfunc
+
+" Use channel in NL mode but received text does not end in NL.
+func Test_read_in_close_cb_incomplete()
+ let g:Ch_received = ''
+ func! CloseHandler(chan)
+ while ch_status(a:chan, {'part': 'out'}) == 'buffered'
+ let g:Ch_received .= ch_read(a:chan)
+ endwhile
+ endfunc
+ let job = job_start(s:python . " test_channel_pipe.py incomplete",
+ \ {'close_cb': 'CloseHandler'})
+ " the job may be done quickly, also accept "dead"
+ call assert_match('^\%(dead\|run\)$', job_status(job))
+ try
+ call WaitForAssert({-> assert_equal('incomplete', g:Ch_received)})
+ finally
+ call job_stop(job)
+ delfunc CloseHandler
+ endtry
+endfunc
+
+func Test_out_cb_lambda()
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'out_cb': {ch, msg -> execute("let g:Ch_outmsg = 'lambda: ' . msg")},
+ \ 'out_mode': 'json',
+ \ 'err_cb': {ch, msg -> execute(":let g:Ch_errmsg = 'lambda: ' . msg")},
+ \ 'err_mode': 'json'})
+ call assert_equal("run", job_status(job))
+ try
+ let g:Ch_outmsg = ''
+ let g:Ch_errmsg = ''
+ call ch_sendraw(job, "echo [0, \"hello\"]\n")
+ call ch_sendraw(job, "echoerr [0, \"there\"]\n")
+ call WaitForAssert({-> assert_equal("lambda: hello", g:Ch_outmsg)})
+ call WaitForAssert({-> assert_equal("lambda: there", g:Ch_errmsg)})
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_close_and_exit_cb()
+ let g:test_is_flaky = 1
+ let g:retdict = {'ret': {}}
+ func g:retdict.close_cb(ch) dict
+ let self.ret['close_cb'] = a:ch->ch_getjob()->job_status()
+ endfunc
+ func g:retdict.exit_cb(job, status) dict
+ let self.ret['exit_cb'] = job_status(a:job)
+ endfunc
+
+ let job = job_start([&shell, &shellcmdflag, 'echo'],
+ \ {'close_cb': g:retdict.close_cb,
+ \ 'exit_cb': g:retdict.exit_cb})
+ " the job may be done quickly, also accept "dead"
+ call assert_match('^\%(dead\|run\)$', job_status(job))
+ call WaitForAssert({-> assert_equal(2, len(g:retdict.ret))})
+ call assert_match('^\%(dead\|run\)$', g:retdict.ret['close_cb'])
+ call assert_equal('dead', g:retdict.ret['exit_cb'])
+ unlet g:retdict
+endfunc
+
+""""""""""
+
+function ExitCbWipe(job, status)
+ exe g:wipe_buf 'bw!'
+endfunction
+
+" This caused a crash, because messages were handled while peeking for a
+" character.
+func Test_exit_cb_wipes_buf()
+ CheckFeature timers
+ set cursorline lazyredraw
+ call test_override('redraw_flag', 1)
+ new
+ let g:wipe_buf = bufnr('')
+
+ let job = job_start(has('win32') ? 'cmd /c echo:' : ['true'],
+ \ {'exit_cb': 'ExitCbWipe'})
+ let timer = timer_start(300, {-> feedkeys("\<Esc>", 'nt')}, {'repeat': 5})
+ call feedkeys(repeat('g', 1000) . 'o', 'ntx!')
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call timer_stop(timer)
+
+ set nocursorline nolazyredraw
+ unlet g:wipe_buf
+ call test_override('ALL', 0)
+endfunc
+
+""""""""""
+
+let g:Ch_unletResponse = ''
+func s:UnletHandler(handle, msg)
+ let g:Ch_unletResponse = a:msg
+ unlet s:channelfd
+endfunc
+
+" Test that "unlet handle" in a handler doesn't crash Vim.
+func Ch_unlet_handle(port)
+ let s:channelfd = ch_open(s:address(a:port), s:chopt)
+ eval s:channelfd->ch_sendexpr("test", {'callback': function('s:UnletHandler')})
+ call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)})
+endfunc
+
+func Test_unlet_handle()
+ call s:run_server('Ch_unlet_handle')
+endfunc
+
+func Test_unlet_handle_ipv6()
+ CheckIPv6
+ call Test_unlet_handle()
+endfunc
+
+""""""""""
+
+let g:Ch_unletResponse = ''
+func Ch_CloseHandler(handle, msg)
+ let g:Ch_unletResponse = a:msg
+ eval s:channelfd->ch_close()
+endfunc
+
+" Test that "unlet handle" in a handler doesn't crash Vim.
+func Ch_close_handle(port)
+ let s:channelfd = ch_open(s:address(a:port), s:chopt)
+ call ch_sendexpr(s:channelfd, "test", {'callback': function('Ch_CloseHandler')})
+ call WaitForAssert({-> assert_equal('what?', g:Ch_unletResponse)})
+endfunc
+
+func Test_close_handle()
+ call s:run_server('Ch_close_handle')
+endfunc
+
+func Test_close_handle_ipv6()
+ CheckIPv6
+ call Test_close_handle()
+endfunc
+
+""""""""""
+
+func Ch_open_ipv6(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ call assert_notequal('fail', ch_status(handle))
+endfunc
+
+func Test_open_ipv6()
+ CheckIPv6
+ call s:run_server('Ch_open_ipv6')
+endfunc
+
+""""""""""
+
+func Test_open_fail()
+ call assert_fails("let ch = ch_open('noserver')", 'E475:')
+ echo ch
+ let d = ch
+ call assert_fails("let ch = ch_open('noserver', 10)", 'E1206:')
+ call assert_fails("let ch = ch_open('localhost:-1')", 'E475:')
+ call assert_fails("let ch = ch_open('localhost:65537')", 'E475:')
+ call assert_fails("let ch = ch_open('localhost:8765', {'timeout' : -1})",
+ \ 'E474:')
+ call assert_fails("let ch = ch_open('localhost:8765', {'axby' : 1})",
+ \ 'E475:')
+ call assert_fails("let ch = ch_open('localhost:8765', {'mode' : 'abc'})",
+ \ 'E475:')
+ call assert_fails("let ch = ch_open('localhost:8765', {'part' : 'out'})",
+ \ 'E475:')
+ call assert_fails("let ch = ch_open('[::]')", 'E475:')
+ call assert_fails("let ch = ch_open('[::.80')", 'E475:')
+ call assert_fails("let ch = ch_open('[::]8080')", 'E475:')
+endfunc
+
+func Test_ch_info_fail()
+ call assert_fails("let x = ch_info(10)", 'E475:')
+endfunc
+
+""""""""""
+
+func Ch_open_delay(port)
+ " Wait up to a second for the port to open.
+ let s:chopt.waittime = 1000
+ let channel = ch_open(s:address(a:port), s:chopt)
+ if ch_status(channel) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+ call assert_equal('got it', channel->ch_evalexpr('hello!'))
+ call ch_close(channel)
+endfunc
+
+func Test_open_delay()
+ " The server will wait half a second before creating the port.
+ call s:run_server('Ch_open_delay', 'delay')
+endfunc
+
+func Test_open_delay_ipv6()
+ CheckIPv6
+ call Test_open_delay()
+endfunc
+
+"""""""""
+
+function MyFunction(a,b,c)
+ let g:Ch_call_ret = [a:a, a:b, a:c]
+endfunc
+
+function Ch_test_call(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+
+ let g:Ch_call_ret = []
+ call assert_equal('ok', ch_evalexpr(handle, 'call-func'))
+ call WaitForAssert({-> assert_equal([1, 2, 3], g:Ch_call_ret)})
+
+ call assert_fails("let i = ch_evalexpr(handle, '2 + 2', {'callback' : 'abc'})", 'E917:')
+ call assert_fails("let i = ch_evalexpr(handle, '2 + 2', {'drop' : ''})", 'E475:')
+ call assert_fails("let i = ch_evalexpr(test_null_job(), '2 + 2')", 'E906:')
+endfunc
+
+func Test_call()
+ call s:run_server('Ch_test_call')
+endfunc
+
+func Test_call_ipv6()
+ CheckIPv6
+ call Test_call()
+endfunc
+
+func Test_call_unix()
+ CheckUnix
+ call Test_call()
+ call delete('Xtestsocket')
+endfunc
+
+"""""""""
+
+let g:Ch_job_exit_ret = 'not yet'
+function MyExitCb(job, status)
+ let g:Ch_job_exit_ret = 'done'
+endfunc
+
+function Ch_test_exit_callback(port)
+ eval g:currentJob->job_setoptions({'exit_cb': 'MyExitCb'})
+ let g:Ch_exit_job = g:currentJob
+ call assert_equal('MyExitCb', job_info(g:currentJob)['exit_cb'])
+endfunc
+
+func Test_exit_callback()
+ call s:run_server('Ch_test_exit_callback')
+
+ " wait up to a second for the job to exit
+ for i in range(100)
+ if g:Ch_job_exit_ret == 'done'
+ break
+ endif
+ sleep 10m
+ " calling job_status() triggers the callback
+ call job_status(g:Ch_exit_job)
+ endfor
+
+ call assert_equal('done', g:Ch_job_exit_ret)
+ call assert_equal('dead', job_info(g:Ch_exit_job).status)
+ unlet g:Ch_exit_job
+endfunc
+
+function MyExitTimeCb(job, status)
+ if job_info(a:job).process == g:exit_cb_val.process
+ let g:exit_cb_val.end = reltime(g:exit_cb_val.start)
+ endif
+ call Resume()
+endfunction
+
+func Test_exit_callback_interval()
+ CheckFunction reltimefloat
+ let g:test_is_flaky = 1
+
+ let g:exit_cb_val = {'start': reltime(), 'end': 0, 'process': 0}
+ let job = [s:python, '-c', 'import time;time.sleep(0.5)']->job_start({'exit_cb': 'MyExitTimeCb'})
+ let g:exit_cb_val.process = job_info(job).process
+ try
+ call WaitFor('type(g:exit_cb_val.end) != v:t_number || g:exit_cb_val.end != 0')
+ catch
+ call add(v:errors, "Job status: " .. string(job->job_info()))
+ throw v:exception
+ endtry
+ let elapsed = reltimefloat(g:exit_cb_val.end)
+ call assert_inrange(0.5, 1.0, elapsed)
+
+ " case: unreferenced job, using timer
+ if !has('timers')
+ return
+ endif
+
+ let g:exit_cb_val = {'start': reltime(), 'end': 0, 'process': 0}
+ let g:job = job_start([s:python, '-c', 'import time;time.sleep(0.5)'], {'exit_cb': 'MyExitTimeCb'})
+ let g:exit_cb_val.process = job_info(g:job).process
+ unlet g:job
+ call Standby(1000)
+ if type(g:exit_cb_val.end) != v:t_number || g:exit_cb_val.end != 0
+ let elapsed = reltimefloat(g:exit_cb_val.end)
+ else
+ let elapsed = 1.0
+ endif
+ call assert_inrange(0.5, 1.0, elapsed)
+endfunc
+
+"""""""""
+
+let g:Ch_close_ret = 'alive'
+function MyCloseCb(ch)
+ let g:Ch_close_ret = 'closed'
+endfunc
+
+function Ch_test_close_callback(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+ call ch_setoptions(handle, {'close_cb': 'MyCloseCb'})
+
+ call assert_equal('', ch_evalexpr(handle, 'close me'))
+ call WaitForAssert({-> assert_equal('closed', g:Ch_close_ret)})
+endfunc
+
+func Test_close_callback()
+ call s:run_server('Ch_test_close_callback')
+endfunc
+
+func Test_close_callback_ipv6()
+ CheckIPv6
+ call Test_close_callback()
+endfunc
+
+func Test_close_callback_unix()
+ CheckUnix
+ call Test_close_callback()
+ call delete('Xtestsocket')
+endfunc
+
+function Ch_test_close_partial(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+ let g:Ch_d = {}
+ func g:Ch_d.closeCb(ch) dict
+ let self.close_ret = 'closed'
+ endfunc
+ call ch_setoptions(handle, {'close_cb': g:Ch_d.closeCb})
+
+ call assert_equal('', ch_evalexpr(handle, 'close me'))
+ call WaitForAssert({-> assert_equal('closed', g:Ch_d.close_ret)})
+ unlet g:Ch_d
+endfunc
+
+func Test_close_partial()
+ call s:run_server('Ch_test_close_partial')
+endfunc
+
+func Test_close_partial_ipv6()
+ CheckIPv6
+ call Test_close_partial()
+endfunc
+
+func Test_close_partial_unix()
+ CheckUnix
+ call Test_close_partial()
+ call delete('Xtestsocket')
+endfunc
+
+func Test_job_start_fails()
+ " this was leaking memory
+ call assert_fails("call job_start([''])", "E474:")
+ call assert_fails('call job_start($x)', 'E474:')
+ call assert_fails('call job_start("")', 'E474:')
+ call assert_fails('call job_start("ls", {"out_io" : "abc"})', 'E475:')
+ call assert_fails('call job_start("ls", {"err_io" : "abc"})', 'E475:')
+ call assert_fails('call job_start("ls", [])', 'E715:')
+ call assert_fails("call job_start('ls', {'in_top' : -1})", 'E475:')
+ call assert_fails("call job_start('ls', {'in_bot' : -1})", 'E475:')
+ call assert_fails("call job_start('ls', {'channel' : -1})", 'E475:')
+ call assert_fails("call job_start('ls', {'callback' : -1})", 'E921:')
+ call assert_fails("call job_start('ls', {'out_cb' : -1})", 'E921:')
+ call assert_fails("call job_start('ls', {'err_cb' : -1})", 'E921:')
+ call assert_fails("call job_start('ls', {'close_cb' : -1})", 'E921:')
+ call assert_fails("call job_start('ls', {'exit_cb' : -1})", 'E921:')
+ call assert_fails("call job_start('ls', {'term_name' : []})", 'E475:')
+ call assert_fails("call job_start('ls', {'term_finish' : 'run'})", 'E475:')
+ call assert_fails("call job_start('ls', {'term_api' : []})", 'E475:')
+ call assert_fails("call job_start('ls', {'stoponexit' : []})", 'E730:')
+ call assert_fails("call job_start('ls', {'in_io' : 'file'})", 'E920:')
+ call assert_fails("call job_start('ls', {'out_io' : 'file'})", 'E920:')
+ call assert_fails("call job_start('ls', {'err_io' : 'file'})", 'E920:')
+ call assert_fails("call job_start('ls', {'in_mode' : 'abc'})", 'E475:')
+ call assert_fails("call job_start('ls', {'out_mode' : 'abc'})", 'E475:')
+ call assert_fails("call job_start('ls', {'err_mode' : 'abc'})", 'E475:')
+ call assert_fails("call job_start('ls',
+ \ {'in_io' : 'buffer', 'in_buf' : 99999})", 'E86:')
+ call assert_fails("call job_start('ls',
+ \ {'out_io' : 'buffer', 'out_buf' : 99999})", 'E86:')
+ call assert_fails("call job_start('ls',
+ \ {'err_io' : 'buffer', 'err_buf' : 99999})", 'E86:')
+
+ call assert_fails("call job_start('ls',
+ \ {'in_io' : 'buffer', 'in_buf' : -1})", 'E475:')
+ call assert_fails("call job_start('ls',
+ \ {'out_io' : 'buffer', 'out_buf' : -1})", 'E475:')
+ call assert_fails("call job_start('ls',
+ \ {'err_io' : 'buffer', 'err_buf' : -1})", 'E475:')
+
+ let cmd = has('win32') ? "cmd /c dir" : "ls"
+
+ set nomodifiable
+ call assert_fails("call job_start(cmd,
+ \ {'out_io' : 'buffer', 'out_buf' :" .. bufnr() .. "})", 'E21:')
+ call assert_fails("call job_start(cmd,
+ \ {'err_io' : 'buffer', 'err_buf' :" .. bufnr() .. "})", 'E21:')
+ set modifiable
+
+ call assert_fails("call job_start(cmd, {'in_io' : 'buffer'})", 'E915:')
+
+ edit! XXX
+ let bnum = bufnr()
+ enew
+ call assert_fails("call job_start(cmd,
+ \ {'in_io' : 'buffer', 'in_buf' : bnum})", 'E918:')
+
+ " Empty job tests
+ " This was crashing on MS-Windows.
+ call assert_fails('let job = job_start([""])', 'E474:')
+ call assert_fails('let job = job_start([" "])', 'E474:')
+ call assert_fails('let job = job_start("")', 'E474:')
+ call assert_fails('let job = job_start(" ")', 'E474:')
+ call assert_fails('let job = job_start(["ls", []])', 'E730:')
+ call assert_fails('call job_setoptions(test_null_job(), {})', 'E916:')
+ %bw!
+endfunc
+
+func Test_job_stop_immediately()
+ " With valgrind this causes spurious leak reports
+ CheckNotValgrind
+
+ let g:job = job_start([s:python, '-c', 'import time;time.sleep(10)'])
+ try
+ eval g:job->job_stop()
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ finally
+ call job_stop(g:job, 'kill')
+ unlet g:job
+ endtry
+endfunc
+
+func Test_null_job_eval()
+ call assert_fails('eval test_null_job()->eval()', 'E121:')
+endfunc
+
+" This was leaking memory.
+func Test_partial_in_channel_cycle()
+ let d = {}
+ let d.a = function('string', [d])
+ try
+ let d.b = ch_open('nowhere:123', {'close_cb': d.a})
+ call test_garbagecollect_now()
+ catch
+ call assert_exception('E901:')
+ endtry
+ unlet d
+endfunc
+
+func Test_using_freed_memory()
+ let g:a = job_start(['ls'])
+ sleep 10m
+ call test_garbagecollect_now()
+endfunc
+
+func Test_collapse_buffers()
+ let g:test_is_flaky = 1
+ CheckExecutable cat
+
+ sp test_channel.vim
+ let g:linecount = line('$')
+ close
+ split testout
+ 1,$delete
+ call job_start('cat test_channel.vim', {'out_io': 'buffer', 'out_name': 'testout'})
+ call WaitForAssert({-> assert_inrange(g:linecount, g:linecount + 1, line('$'))})
+ bwipe!
+endfunc
+
+func Test_write_to_deleted_buffer()
+ CheckExecutable echo
+ CheckFeature quickfix
+
+ let job = job_start('echo hello', {'out_io': 'buffer', 'out_name': 'test_buffer', 'out_msg': 0})
+ let bufnr = bufnr('test_buffer')
+ call WaitForAssert({-> assert_equal(['hello'], getbufline(bufnr, 1, '$'))})
+ call assert_equal('nofile', getbufvar(bufnr, '&buftype'))
+ call assert_equal('hide', getbufvar(bufnr, '&bufhidden'))
+
+ bdel test_buffer
+ call assert_equal([], getbufline(bufnr, 1, '$'))
+
+ let job = job_start('echo hello', {'out_io': 'buffer', 'out_name': 'test_buffer', 'out_msg': 0})
+ call WaitForAssert({-> assert_equal(['hello'], getbufline(bufnr, 1, '$'))})
+ call assert_equal('nofile', getbufvar(bufnr, '&buftype'))
+ call assert_equal('hide', getbufvar(bufnr, '&bufhidden'))
+
+ bwipe! test_buffer
+endfunc
+
+func Test_cmd_parsing()
+ CheckUnix
+
+ call assert_false(filereadable("file with space"))
+ let job = job_start('touch "file with space"')
+ call WaitForAssert({-> assert_true(filereadable("file with space"))})
+ call delete("file with space")
+
+ let job = job_start('touch file\ with\ space')
+ call WaitForAssert({-> assert_true(filereadable("file with space"))})
+ call delete("file with space")
+endfunc
+
+func Test_raw_passes_nul()
+ CheckExecutable cat
+
+ " Test lines from the job containing NUL are stored correctly in a buffer.
+ new
+ call setline(1, ["asdf\nasdf", "xxx\n", "\nyyy"])
+ w! Xtestread
+ bwipe!
+ split testout
+ 1,$delete
+ call job_start('cat Xtestread', {'out_io': 'buffer', 'out_name': 'testout'})
+ call WaitFor('line("$") > 2')
+ call assert_equal("asdf\nasdf", getline(1))
+ call assert_equal("xxx\n", getline(2))
+ call assert_equal("\nyyy", getline(3))
+
+ call delete('Xtestread')
+ bwipe!
+
+ " Test lines from a buffer with NUL bytes are written correctly to the job.
+ new mybuffer
+ call setline(1, ["asdf\nasdf", "xxx\n", "\nyyy"])
+ let g:Ch_job = job_start('cat', {'in_io': 'buffer', 'in_name': 'mybuffer', 'out_io': 'file', 'out_name': 'Xtestwrite'})
+ call WaitForAssert({-> assert_equal("dead", job_status(g:Ch_job))})
+ bwipe!
+ split Xtestwrite
+ call assert_equal("asdf\nasdf", getline(1))
+ call assert_equal("xxx\n", getline(2))
+ call assert_equal("\nyyy", getline(3))
+ call assert_equal(-1, match(s:get_resources(), '\(^\|/\)Xtestwrite$'))
+
+ call delete('Xtestwrite')
+ bwipe!
+endfunc
+
+func Test_read_nonl_line()
+ let g:linecount = 0
+ let arg = 'import sys;sys.stdout.write("1\n2\n3")'
+ call job_start([s:python, '-c', arg], {'callback': {-> execute('let g:linecount += 1')}})
+ call WaitForAssert({-> assert_equal(3, g:linecount)})
+ unlet g:linecount
+endfunc
+
+func Test_read_nonl_in_close_cb()
+ func s:close_cb(ch)
+ while ch_status(a:ch) == 'buffered'
+ let g:out .= ch_read(a:ch)
+ endwhile
+ endfunc
+
+ let g:out = ''
+ let arg = 'import sys;sys.stdout.write("1\n2\n3")'
+ call job_start([s:python, '-c', arg], {'close_cb': function('s:close_cb')})
+ call test_garbagecollect_now()
+ call WaitForAssert({-> assert_equal('123', g:out)})
+ unlet g:out
+ delfunc s:close_cb
+endfunc
+
+func Test_read_from_terminated_job()
+ let g:linecount = 0
+ let arg = 'import os,sys;os.close(1);sys.stderr.write("test\n")'
+ call job_start([s:python, '-c', arg], {'callback': {-> execute('let g:linecount += 1')}})
+ call WaitForAssert({-> assert_equal(1, g:linecount)})
+ call test_garbagecollect_now()
+ unlet g:linecount
+endfunc
+
+func Test_job_start_windows()
+ CheckMSWindows
+
+ " Check that backslash in $COMSPEC is handled properly.
+ let g:echostr = ''
+ let cmd = $COMSPEC . ' /c echo 123'
+ let job = job_start(cmd, {'callback': {ch,msg -> execute(":let g:echostr .= msg")}})
+ let info = job_info(job)
+ call assert_equal([$COMSPEC, '/c', 'echo', '123'], info.cmd)
+
+ call WaitForAssert({-> assert_equal("123", g:echostr)})
+ unlet g:echostr
+endfunc
+
+func Test_env()
+ let g:envstr = ''
+ if has('win32')
+ let cmd = ['cmd', '/c', 'echo %FOO%']
+ else
+ let cmd = [&shell, &shellcmdflag, 'echo $FOO']
+ endif
+ call assert_fails('call job_start(cmd, {"env": 1})', 'E475:')
+ let job = job_start(cmd, {'callback': {ch,msg -> execute(":let g:envstr .= msg")}, 'env': {'FOO': 'bar'}})
+ if WaitForAssert({-> assert_equal("bar", g:envstr)}, 500) != 0
+ call add(v:errors, "Job status: " .. string(job->job_info()))
+ endif
+ unlet g:envstr
+endfunc
+
+func Test_cwd()
+ let g:test_is_flaky = 1
+ let g:envstr = ''
+ if has('win32')
+ let expect = $TEMP
+ let cmd = ['cmd', '/c', 'echo %CD%']
+ else
+ let expect = $HOME
+ let cmd = ['pwd']
+ endif
+ let job = job_start(cmd, {'callback': {ch,msg -> execute(":let g:envstr .= msg")}, 'cwd': expect})
+ try
+ call WaitForAssert({-> assert_notequal("", g:envstr)})
+ " There may be a trailing slash or not, ignore it
+ let expect = substitute(expect, '[/\\]$', '', '')
+ let g:envstr = substitute(g:envstr, '[/\\]$', '', '')
+ " on CI there can be /private prefix or not, ignore it
+ if $CI != '' && stridx(expect, '/private/') == 0
+ let expect = expect[8:]
+ endif
+ if $CI != '' && stridx(g:envstr, '/private/') == 0
+ let g:envstr = g:envstr[8:]
+ endif
+ call assert_equal(expect, g:envstr)
+ finally
+ call job_stop(job)
+ unlet g:envstr
+ endtry
+endfunc
+
+function Ch_test_close_lambda(port)
+ let handle = ch_open(s:address(a:port), s:chopt)
+ if ch_status(handle) == "fail"
+ call assert_report("Can't open channel")
+ return
+ endif
+ let g:Ch_close_ret = ''
+ call ch_setoptions(handle, {'close_cb': {ch -> execute("let g:Ch_close_ret = 'closed'")}})
+ call test_garbagecollect_now()
+
+ call assert_equal('', ch_evalexpr(handle, 'close me'))
+ call WaitForAssert({-> assert_equal('closed', g:Ch_close_ret)})
+endfunc
+
+func Test_close_lambda()
+ call s:run_server('Ch_test_close_lambda')
+endfunc
+
+func Test_close_lambda_ipv6()
+ CheckIPv6
+ call Test_close_lambda()
+endfunc
+
+func Test_close_lambda_unix()
+ CheckUnix
+ call Test_close_lambda()
+ call delete('Xtestsocket')
+endfunc
+
+func s:test_list_args(cmd, out, remove_lf)
+ try
+ let g:out = ''
+ let job = job_start([s:python, '-c', a:cmd], {'callback': {ch, msg -> execute('let g:out .= msg')}, 'out_mode': 'raw'})
+ try
+ call WaitFor('"" != g:out')
+ catch
+ call add(v:errors, "Job status: " .. string(job->job_info()))
+ throw v:exception
+ endtry
+ if has('win32')
+ let g:out = substitute(g:out, '\r', '', 'g')
+ endif
+ if a:remove_lf
+ let g:out = substitute(g:out, '\n$', '', 'g')
+ endif
+ call assert_equal(a:out, g:out)
+ finally
+ call job_stop(job)
+ unlet g:out
+ endtry
+endfunc
+
+func Test_list_args()
+ call s:test_list_args('import sys;sys.stdout.write("hello world")', "hello world", 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello\nworld")', "hello\nworld", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello\nworld'')', "hello\nworld", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello"world'')', "hello\"world", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello^world'')', "hello^world", 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello&&world")', "hello&&world", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello\\world'')', "hello\\world", 0)
+ call s:test_list_args('import sys;sys.stdout.write(''hello\\\\world'')', "hello\\\\world", 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello\"world\"")', 'hello"world"', 0)
+ call s:test_list_args('import sys;sys.stdout.write("h\"ello worl\"d")', 'h"ello worl"d', 0)
+ call s:test_list_args('import sys;sys.stdout.write("h\"e\\\"llo wor\\\"l\"d")', 'h"e\"llo wor\"l"d', 0)
+ call s:test_list_args('import sys;sys.stdout.write("h\"e\\\"llo world")', 'h"e\"llo world', 0)
+ call s:test_list_args('import sys;sys.stdout.write("hello\tworld")', "hello\tworld", 0)
+
+ " tests which not contain spaces in the argument
+ call s:test_list_args('print("hello\nworld")', "hello\nworld", 1)
+ call s:test_list_args('print(''hello\nworld'')', "hello\nworld", 1)
+ call s:test_list_args('print(''hello"world'')', "hello\"world", 1)
+ call s:test_list_args('print(''hello^world'')', "hello^world", 1)
+ call s:test_list_args('print("hello&&world")', "hello&&world", 1)
+ call s:test_list_args('print(''hello\\world'')', "hello\\world", 1)
+ call s:test_list_args('print(''hello\\\\world'')', "hello\\\\world", 1)
+ call s:test_list_args('print("hello\"world\"")', 'hello"world"', 1)
+ call s:test_list_args('print("hello\tworld")', "hello\tworld", 1)
+endfunc
+
+func Test_keep_pty_open()
+ CheckUnix
+
+ let job = job_start(s:python . ' -c "import time;time.sleep(0.2)"',
+ \ {'out_io': 'null', 'err_io': 'null', 'pty': 1})
+ let elapsed = WaitFor({-> job_status(job) ==# 'dead'})
+ call assert_inrange(200, 1000, elapsed)
+ call job_stop(job)
+endfunc
+
+func Test_job_start_in_timer()
+ CheckFeature timers
+ CheckFunction reltimefloat
+
+ func OutCb(chan, msg)
+ let g:val += 1
+ endfunc
+
+ func ExitCb(job, status)
+ let g:val += 1
+ call Resume()
+ endfunc
+
+ func TimerCb(timer)
+ if has('win32')
+ let cmd = ['cmd', '/c', 'echo.']
+ else
+ let cmd = ['echo']
+ endif
+ let g:job = job_start(cmd, {'out_cb': 'OutCb', 'exit_cb': 'ExitCb'})
+ call substitute(repeat('a', 100000), '.', '', 'g')
+ endfunc
+
+ " We should be interrupted before 'updatetime' elapsed.
+ let g:val = 0
+ call timer_start(1, 'TimerCb')
+ let elapsed = Standby(&ut)
+ call assert_inrange(1, &ut / 2, elapsed)
+
+ " Wait for both OutCb() and ExitCb() to have been called before deleting
+ " them.
+ call WaitForAssert({-> assert_equal(2, g:val)})
+ call job_stop(g:job)
+
+ delfunc OutCb
+ delfunc ExitCb
+ delfunc TimerCb
+ unlet! g:val
+ unlet! g:job
+endfunc
+
+func Test_raw_large_data()
+ try
+ let g:out = ''
+ let job = job_start(s:python . " test_channel_pipe.py",
+ \ {'mode': 'raw', 'drop': 'never', 'noblock': 1,
+ \ 'callback': {ch, msg -> execute('let g:out .= msg')}})
+
+ let outlen = 79999
+ let want = repeat('X', outlen) . "\n"
+ eval job->ch_sendraw(want)
+ call WaitFor({-> len(g:out) >= outlen}, 10000)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call assert_equal(want, substitute(g:out, '\r', '', 'g'))
+ finally
+ call job_stop(job)
+ unlet g:out
+ endtry
+endfunc
+
+func Test_no_hang_windows()
+ CheckMSWindows
+
+ try
+ let job = job_start(s:python . " test_channel_pipe.py busy",
+ \ {'mode': 'raw', 'drop': 'never', 'noblock': 0})
+ call assert_fails('call ch_sendraw(job, repeat("X", 80000))', 'E631:')
+ finally
+ call job_stop(job)
+ endtry
+endfunc
+
+func Test_job_exitval_and_termsig()
+ CheckUnix
+
+ " Terminate job normally
+ let cmd = ['echo']
+ let job = job_start(cmd)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ let info = job_info(job)
+ call assert_equal(0, info.exitval)
+ call assert_equal("", info.termsig)
+
+ " Terminate job by signal
+ let cmd = ['sleep', '10']
+ let job = job_start(cmd)
+ " 10m usually works but 50m is needed when running Valgrind
+ sleep 50m
+ call job_stop(job)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ let info = job_info(job)
+ call assert_equal(-1, info.exitval)
+ call assert_equal("term", info.termsig)
+endfunc
+
+func Test_job_tty_in_out()
+ CheckUnix
+
+ call writefile(['test'], 'Xtestin', 'D')
+ let in_opts = [{},
+ \ {'in_io': 'null'},
+ \ {'in_io': 'file', 'in_name': 'Xtestin'}]
+ let out_opts = [{},
+ \ {'out_io': 'null'},
+ \ {'out_io': 'file', 'out_name': 'Xtestout'}]
+ let err_opts = [{},
+ \ {'err_io': 'null'},
+ \ {'err_io': 'file', 'err_name': 'Xtesterr'},
+ \ {'err_io': 'out'}]
+ let opts = []
+
+ for in_opt in in_opts
+ let x = copy(in_opt)
+ for out_opt in out_opts
+ let x = extend(copy(x), out_opt)
+ for err_opt in err_opts
+ let x = extend(copy(x), err_opt)
+ let opts += [extend({'pty': 1}, x)]
+ endfor
+ endfor
+ endfor
+
+ for opt in opts
+ let job = job_start('echo', opt)
+ let info = job_info(job)
+ let msg = printf('option={"in_io": "%s", "out_io": "%s", "err_io": "%s"}',
+ \ get(opt, 'in_io', 'tty'),
+ \ get(opt, 'out_io', 'tty'),
+ \ get(opt, 'err_io', 'tty'))
+
+ if !has_key(opt, 'in_io') || !has_key(opt, 'out_io') || !has_key(opt, 'err_io')
+ call assert_notequal('', info.tty_in, msg)
+ else
+ call assert_equal('', info.tty_in, msg)
+ endif
+ call assert_equal(info.tty_in, info.tty_out, msg)
+
+ call WaitForAssert({-> assert_equal('dead', job_status(job))})
+ endfor
+
+ call delete('Xtestout')
+ call delete('Xtesterr')
+endfunc
+
+" Do this last, it stops any channel log.
+func Test_zz_nl_err_to_out_pipe()
+
+ eval 'Xlog'->ch_logfile()
+ call ch_log('Test_zz_nl_err_to_out_pipe()')
+ let job = job_start(s:python . " test_channel_pipe.py", {'err_io': 'out'})
+ call assert_equal("run", job_status(job))
+ try
+ let handle = job_getchannel(job)
+ call ch_sendraw(handle, "echo something\n")
+ call assert_equal("something", ch_readraw(handle))
+
+ call ch_sendraw(handle, "echoerr wrong\n")
+ call assert_equal("wrong", ch_readraw(handle))
+ finally
+ call job_stop(job)
+ call ch_logfile('')
+ let loglines = readfile('Xlog')
+ call assert_true(len(loglines) > 10)
+ let found_test = 0
+ let found_send = 0
+ let found_recv = 0
+ let found_stop = 0
+ for l in loglines
+ if l =~ 'Test_zz_nl_err_to_out_pipe'
+ let found_test = 1
+ endif
+ if l =~ 'SEND on.*echo something'
+ let found_send = 1
+ endif
+ if l =~ 'RECV on.*something'
+ let found_recv = 1
+ endif
+ if l =~ 'Stopping job with'
+ let found_stop = 1
+ endif
+ endfor
+ call assert_equal(1, found_test)
+ call assert_equal(1, found_send)
+ call assert_equal(1, found_recv)
+ call assert_equal(1, found_stop)
+ " On MS-Windows need to sleep for a moment to be able to delete the file.
+ sleep 10m
+ call delete('Xlog')
+ endtry
+endfunc
+
+" Do this last, it stops any channel log.
+func Test_zz_ch_log()
+ call ch_logfile('Xlog', 'w')
+ call ch_log('hello there')
+ call ch_log('%s%s')
+ call ch_logfile('')
+ let text = readfile('Xlog')
+ call assert_match("start log session", text[0])
+ call assert_match("ch_log(): hello there", text[1])
+ call assert_match("%s%s", text[2])
+ call mkdir("Xchlogdir1", 'D')
+ call assert_fails("call ch_logfile('Xchlogdir1')", 'E484:')
+
+ call delete('Xlog')
+endfunc
+
+func Test_issue_5150()
+ if has('win32')
+ let cmd = 'cmd /c pause'
+ else
+ let cmd = 'grep foo'
+ endif
+
+ let g:job = job_start(cmd, {})
+ sleep 50m " give the job time to start
+ call job_stop(g:job)
+ call WaitForAssert({-> assert_equal(-1, job_info(g:job).exitval)})
+
+ let g:job = job_start(cmd, {})
+ sleep 50m
+ call job_stop(g:job, 'term')
+ call WaitForAssert({-> assert_equal(-1, job_info(g:job).exitval)})
+
+ let g:job = job_start(cmd, {})
+ sleep 50m
+ call job_stop(g:job, 'kill')
+ call WaitForAssert({-> assert_equal(-1, job_info(g:job).exitval)})
+endfunc
+
+func Test_issue_5485()
+ let $VAR1 = 'global'
+ let g:Ch_reply = ""
+ let l:job = job_start([&shell, &shellcmdflag, has('win32') ? 'echo %VAR1% %VAR2%' : 'echo $VAR1 $VAR2'], {'env': {'VAR1': 'local', 'VAR2': 'local'}, 'callback': 'Ch_handler'})
+ let g:Ch_job = l:job
+ call WaitForAssert({-> assert_equal("local local", trim(g:Ch_reply))})
+ unlet $VAR1
+endfunc
+
+func Test_job_trailing_space_unix()
+ CheckUnix
+ CheckExecutable cat
+
+ let job = job_start("cat ", #{in_io: 'null'})
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call assert_equal(0, job_info(job).exitval)
+
+ call delete('Xtestsocket')
+endfunc
+
+func Test_ch_getbufnr()
+ let ch = test_null_channel()
+ call assert_equal(-1, ch_getbufnr(ch, 'in'))
+ call assert_equal(-1, ch_getbufnr(ch, 'out'))
+ call assert_equal(-1, ch_getbufnr(ch, 'err'))
+ call assert_equal(-1, ch_getbufnr(ch, ''))
+endfunc
+
+" Test for unsupported options passed to ch_status()
+func Test_invalid_job_chan_options()
+ let ch = test_null_channel()
+ let invalid_opts = [
+ \ {'in_io' : 'null'},
+ \ {'out_io' : 'null'},
+ \ {'err_io' : 'null'},
+ \ {'mode' : 'json'},
+ \ {'out_mode' : 'json'},
+ \ {'err_mode' : 'json'},
+ \ {'noblock' : 1},
+ \ {'in_name' : '/a/b'},
+ \ {'pty' : 1},
+ \ {'in_buf' : 1},
+ \ {'out_buf' : 1},
+ \ {'err_buf' : 1},
+ \ {'out_modifiable' : 1},
+ \ {'err_modifiable' : 1},
+ \ {'out_msg' : 1},
+ \ {'err_msg' : 1},
+ \ {'in_top' : 1},
+ \ {'in_bot' : 1},
+ \ {'channel' : ch},
+ \ {'callback' : ''},
+ \ {'out_cb' : ''},
+ \ {'err_cb' : ''},
+ \ {'close_cb' : ''},
+ \ {'exit_cb' : ''},
+ \ {'term_opencmd' : ''},
+ \ {'eof_chars' : ''},
+ \ {'term_rows' : 10},
+ \ {'term_cols' : 10},
+ \ {'vertical' : 0},
+ \ {'curwin' : 1},
+ \ {'bufnr' : 1},
+ \ {'hidden' : 0},
+ \ {'norestore' : 0},
+ \ {'term_kill' : 'kill'},
+ \ {'tty_type' : ''},
+ \ {'term_highlight' : ''},
+ \ {'env' : {}},
+ \ {'cwd' : ''},
+ \ {'timeout' : 0},
+ \ {'out_timeout' : 0},
+ \ {'err_timeout' : 0},
+ \ {'id' : 0},
+ \ {'stoponexit' : ''},
+ \ {'block_write' : 1}
+ \ ]
+ if has('gui')
+ call add(invalid_opts, {'ansi_colors' : []})
+ endif
+
+ for opt in invalid_opts
+ call assert_fails("let x = ch_status(ch, opt)", 'E475:')
+ endfor
+ call assert_equal('fail', ch_status(ch, test_null_dict()))
+endfunc
+
+" Test for passing the command and the arguments as List on MS-Windows
+func Test_job_with_list_args()
+ CheckMSWindows
+
+ enew!
+ let bnum = bufnr()
+ let job = job_start(['cmd', '/c', 'echo', 'Hello', 'World'], {'out_io' : 'buffer', 'out_buf' : bnum})
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call assert_equal('Hello World', getline(1))
+ %bw!
+endfunc
+
+func ExitCb_cb_with_input(job, status)
+ call feedkeys(":\<C-u>echo input('', 'default')\<CR>\<CR>", 'nx')
+ call assert_equal('default', Screenline(&lines))
+ let g:wait_exit_cb = 0
+endfunc
+
+func Test_cb_with_input()
+ let g:wait_exit_cb = 1
+
+ if has('win32')
+ let cmd = 'cmd /c echo "Vim''s test"'
+ else
+ let cmd = 'echo "Vim''s test"'
+ endif
+
+ let job = job_start(cmd, {'out_cb': 'ExitCb_cb_with_input'})
+ call WaitFor({-> job_status(job) == "dead"})
+ call WaitForAssert({-> assert_equal(0, g:wait_exit_cb)})
+
+ unlet g:wait_exit_cb
+endfunc
+
+function s:HandleBufEnter() abort
+ let queue = []
+ let job = job_start(['date'], {'callback': { j, d -> add(queue, d) }})
+ while empty(queue)
+ sleep! 10m
+ endwhile
+endfunction
+
+func Test_parse_messages_in_autocmd()
+ CheckUnix
+
+ " Check that in the BufEnter autocommand events are being handled
+ augroup bufenterjob
+ autocmd!
+ autocmd BufEnter Xbufenterjob call s:HandleBufEnter()
+ augroup END
+
+ only
+ split Xbufenterjob
+ wincmd p
+ redraw
+
+ close
+ augroup bufenterjob
+ autocmd!
+ augroup END
+endfunc
+
+func Test_job_start_with_invalid_argument()
+ call assert_fails('call job_start([0zff])', 'E976:')
+endfunc
+
+" Test for the 'lsp' channel mode
+func LspCb(chan, msg)
+ call add(g:lspNotif, a:msg)
+endfunc
+
+func LspOtCb(chan, msg)
+ call add(g:lspOtMsgs, a:msg)
+endfunc
+
+func LspTests(port)
+ " call ch_logfile('Xlspclient.log', 'w')
+ let ch = ch_open(s:localhost .. a:port, #{mode: 'lsp', callback: 'LspCb'})
+ if ch_status(ch) == "fail"
+ call assert_report("Can't open the lsp channel")
+ return
+ endif
+
+ " check for channel information
+ let info = ch_info(ch)
+ call assert_equal('LSP', info.sock_mode)
+
+ " Evaluate an expression
+ let resp = ch_evalexpr(ch, #{method: 'simple-rpc', params: [10, 20]})
+ call assert_false(empty(resp))
+ call assert_equal(#{id: 1, jsonrpc: '2.0', result: 'simple-rpc'}, resp)
+
+ " Evaluate an expression. While waiting for the response, a notification
+ " message is delivered.
+ let g:lspNotif = []
+ let resp = ch_evalexpr(ch, #{method: 'rpc-with-notif', params: {'v': 10}})
+ call assert_false(empty(resp))
+ call assert_equal(#{id: 2, jsonrpc: '2.0', result: 'rpc-with-notif-resp'},
+ \ resp)
+ call assert_equal([#{jsonrpc: '2.0', result: 'rpc-with-notif-notif'}],
+ \ g:lspNotif)
+
+ " Wrong payload notification test
+ let g:lspNotif = []
+ let r = ch_sendexpr(ch, #{method: 'wrong-payload', params: {}})
+ call assert_equal({}, r)
+ " Send a ping to wait for all the notification messages to arrive
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([#{jsonrpc: '2.0', result: 'wrong-payload'}], g:lspNotif)
+
+ " Test for receiving a response with incorrect 'id' and additional
+ " notification messages while evaluating an expression.
+ let g:lspNotif = []
+ let resp = ch_evalexpr(ch, #{method: 'rpc-resp-incorrect-id',
+ \ params: {'a': [1, 2]}})
+ call assert_false(empty(resp))
+ call assert_equal(#{id: 4, jsonrpc: '2.0',
+ \ result: 'rpc-resp-incorrect-id-4'}, resp)
+ call assert_equal([#{jsonrpc: '2.0', result: 'rpc-resp-incorrect-id-1'},
+ \ #{jsonrpc: '2.0', result: 'rpc-resp-incorrect-id-2'},
+ \ #{jsonrpc: '2.0', id: 1, result: 'rpc-resp-incorrect-id-3'}],
+ \ g:lspNotif)
+
+ " simple notification test
+ let g:lspNotif = []
+ call ch_sendexpr(ch, #{method: 'simple-notif', params: [#{a: 10, b: []}]})
+ " Send a ping to wait for all the notification messages to arrive
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([#{jsonrpc: '2.0', result: 'simple-notif'}], g:lspNotif)
+
+ " multiple notifications test
+ let g:lspNotif = []
+ call ch_sendexpr(ch, #{method: 'multi-notif', params: [#{a: {}, b: {}}]})
+ " Send a ping to wait for all the notification messages to arrive
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([#{jsonrpc: '2.0', result: 'multi-notif1'},
+ \ #{jsonrpc: '2.0', result: 'multi-notif2'}], g:lspNotif)
+
+ " Test for sending a message with an identifier.
+ let g:lspNotif = []
+ call ch_sendexpr(ch, #{method: 'msg-with-id', id: 93, params: #{s: 'str'}})
+ " Send a ping to wait for all the notification messages to arrive
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([#{jsonrpc: '2.0', id: 93, result: 'msg-with-id'}],
+ \ g:lspNotif)
+
+ " Test for setting the 'id' value in a request message
+ let resp = ch_evalexpr(ch, #{method: 'ping', id: 1, params: {}})
+ call assert_equal(#{id: 8, jsonrpc: '2.0', result: 'alive'}, resp)
+
+ " Test for using a one time callback function to process a response
+ let g:lspOtMsgs = []
+ let r = ch_sendexpr(ch, #{method: 'msg-specific-cb', params: {}},
+ \ #{callback: 'LspOtCb'})
+ call assert_equal(9, r.id)
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([#{id: 9, jsonrpc: '2.0', result: 'msg-specific-cb'}],
+ \ g:lspOtMsgs)
+
+ " Test for generating a request message from the other end (server)
+ let g:lspNotif = []
+ call ch_sendexpr(ch, #{method: 'server-req', params: #{}})
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([{'id': 201, 'jsonrpc': '2.0',
+ \ 'result': {'method': 'checkhealth', 'params': {'a': 20}}}],
+ \ g:lspNotif)
+
+ " Test for sending a message without an id
+ let g:lspNotif = []
+ call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'msg-without-id'}})
+ " Send a ping to wait for all the notification messages to arrive
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([#{jsonrpc: '2.0', result:
+ \ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'msg-without-id'}}}],
+ \ g:lspNotif)
+
+ " Test for sending a notification message with an id
+ let g:lspNotif = []
+ call ch_sendexpr(ch, #{method: 'echo', id: 110, params: #{s: 'msg-with-id'}})
+ " Send a ping to wait for all the notification messages to arrive
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([#{jsonrpc: '2.0', result:
+ \ #{method: 'echo', jsonrpc: '2.0', id: 110,
+ \ params: #{s: 'msg-with-id'}}}], g:lspNotif)
+
+ " Test for processing the extra fields in the HTTP header
+ let resp = ch_evalexpr(ch, #{method: 'extra-hdr-fields', params: {}})
+ call assert_equal({'id': 14, 'jsonrpc': '2.0', 'result': 'extra-hdr-fields'},
+ \ resp)
+
+ " Test for processing delayed payload
+ let resp = ch_evalexpr(ch, #{method: 'delayed-payload', params: {}})
+ call assert_equal({'id': 15, 'jsonrpc': '2.0', 'result': 'delayed-payload'},
+ \ resp)
+
+ " Test for processing a HTTP header without the Content-Length field
+ let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}},
+ \ #{timeout: 200})
+ call assert_equal({}, resp)
+ " send a ping to make sure communication still works
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+
+ " Test for processing a HTTP header with wrong length
+ let resp = ch_evalexpr(ch, #{method: 'hdr-with-wrong-len', params: {}},
+ \ #{timeout: 200})
+ call assert_equal({}, resp)
+ " send a ping to make sure communication still works
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+
+ " Test for processing a HTTP header with negative length
+ let resp = ch_evalexpr(ch, #{method: 'hdr-with-negative-len', params: {}},
+ \ #{timeout: 200})
+ call assert_equal({}, resp)
+ " send a ping to make sure communication still works
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+
+ " Test for an empty header
+ let resp = ch_evalexpr(ch, #{method: 'empty-header', params: {}},
+ \ #{timeout: 200})
+ call assert_equal({}, resp)
+ " send a ping to make sure communication still works
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+
+ " Test for an empty payload
+ let resp = ch_evalexpr(ch, #{method: 'empty-payload', params: {}},
+ \ #{timeout: 200})
+ call assert_equal({}, resp)
+ " send a ping to make sure communication still works
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+
+ " Test for a large payload
+ let content = repeat('abcdef', 11000)
+ let resp = ch_evalexpr(ch, #{method: 'large-payload',
+ \ params: #{text: content}})
+ call assert_equal(#{jsonrpc: '2.0', id: 26, result:
+ \ #{method: 'large-payload', jsonrpc: '2.0', id: 26,
+ \ params: #{text: content}}}, resp)
+ " send a ping to make sure communication still works
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+
+ " Test for invoking an unsupported method
+ let resp = ch_evalexpr(ch, #{method: 'xyz', params: {}}, #{timeout: 200})
+ call assert_equal({}, resp)
+
+ " Test for sending a message without a callback function. Notification
+ " message should be dropped but RPC response should not be dropped.
+ call ch_setoptions(ch, #{callback: ''})
+ let g:lspNotif = []
+ call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'no-callback'}})
+ " Send a ping to wait for all the notification messages to arrive
+ call assert_equal('alive', ch_evalexpr(ch, #{method: 'ping'}).result)
+ call assert_equal([], g:lspNotif)
+ " Restore the callback function
+ call ch_setoptions(ch, #{callback: 'LspCb'})
+
+ " " Test for sending a raw message
+ " let g:lspNotif = []
+ " let s = "Content-Length: 62\r\n"
+ " let s ..= "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n"
+ " let s ..= "\r\n"
+ " let s ..= '{"method":"echo","jsonrpc":"2.0","params":{"m":"raw-message"}}'
+ " call ch_sendraw(ch, s)
+ " call ch_evalexpr(ch, #{method: 'ping'})
+ " call assert_equal([{'jsonrpc': '2.0',
+ " \ 'result': {'method': 'echo', 'jsonrpc': '2.0',
+ " \ 'params': {'m': 'raw-message'}}}], g:lspNotif)
+
+ " Invalid arguments to ch_evalexpr() and ch_sendexpr()
+ call assert_fails('call ch_sendexpr(ch, #{method: "cookie", id: "cookie"})',
+ \ 'E475:')
+ call assert_fails('call ch_evalexpr(ch, #{method: "ping", id: [{}]})', 'E475:')
+ call assert_fails('call ch_evalexpr(ch, [1, 2, 3])', 'E1206:')
+ call assert_fails('call ch_sendexpr(ch, "abc")', 'E1206:')
+ call assert_fails('call ch_evalexpr(ch, #{method: "ping"}, #{callback: "LspOtCb"})', 'E917:')
+ " call ch_logfile('', 'w')
+endfunc
+
+func Test_channel_lsp_mode()
+ call RunServer('test_channel_lsp.py', 'LspTests', [])
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_channel_6.py b/src/testdir/test_channel_6.py
new file mode 100644
index 0000000..5bd17a3
--- /dev/null
+++ b/src/testdir/test_channel_6.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+#
+# Server that will accept connections from a Vim channel.
+# Used by test_channel.vim.
+#
+# This requires Python 2.6 or later.
+
+from test_channel import main, ThreadedTCPServer
+import socket
+
+class ThreadedTCP6Server(ThreadedTCPServer):
+ address_family = socket.AF_INET6
+
+if __name__ == "__main__":
+ main("::", 0, ThreadedTCP6Server)
diff --git a/src/testdir/test_channel_lsp.py b/src/testdir/test_channel_lsp.py
new file mode 100644
index 0000000..10b4fb4
--- /dev/null
+++ b/src/testdir/test_channel_lsp.py
@@ -0,0 +1,333 @@
+#!/usr/bin/env python
+#
+# Server that will accept connections from a Vim channel.
+# Used by test_channel.vim to test LSP functionality.
+#
+# This requires Python 2.6 or later.
+
+from __future__ import print_function
+import json
+import socket
+import sys
+import time
+import threading
+
+try:
+ # Python 3
+ import socketserver
+except ImportError:
+ # Python 2
+ import SocketServer as socketserver
+
+class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
+
+ def setup(self):
+ self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+ def debuglog(self, msg):
+ if self.debug:
+ with open("Xlspserver.log", "a") as myfile:
+ myfile.write(msg)
+
+ def send_lsp_msg(self, msgid, resp_dict):
+ v = {'jsonrpc': '2.0', 'result': resp_dict}
+ if msgid != -1:
+ v['id'] = msgid
+ s = json.dumps(v)
+ resp = "Content-Length: " + str(len(s)) + "\r\n"
+ resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n"
+ resp += "\r\n"
+ resp += s
+ if self.debug:
+ self.debuglog("SEND: ({0} bytes) '{1}'\n".format(len(resp), resp))
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_wrong_payload(self):
+ v = 'wrong-payload'
+ s = json.dumps(v)
+ resp = "Content-Length: " + str(len(s)) + "\r\n"
+ resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n"
+ resp += "\r\n"
+ resp += s
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_empty_header(self, msgid, resp_dict):
+ v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
+ s = json.dumps(v)
+ resp = "\r\n"
+ resp += s
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_empty_payload(self):
+ resp = "Content-Length: 0\r\n"
+ resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n"
+ resp += "\r\n"
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_extra_hdr_fields(self, msgid, resp_dict):
+ # test for sending extra fields in the http header
+ v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
+ s = json.dumps(v)
+ resp = "Host: abc.vim.org\r\n"
+ resp += "User-Agent: Python\r\n"
+ resp += "Accept-Language: en-US,en\r\n"
+ resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n"
+ resp += "Content-Length: " + str(len(s)) + "\r\n"
+ resp += "\r\n"
+ resp += s
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_delayed_payload(self, msgid, resp_dict):
+ # test for sending the hdr first and then after some delay, send the
+ # payload
+ v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
+ s = json.dumps(v)
+ resp = "Content-Length: " + str(len(s)) + "\r\n"
+ resp += "\r\n"
+ self.request.sendall(resp.encode('utf-8'))
+ time.sleep(0.05)
+ resp = s
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_hdr_without_len(self, msgid, resp_dict):
+ # test for sending the http header without length
+ v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
+ s = json.dumps(v)
+ resp = "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n"
+ resp += "\r\n"
+ resp += s
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_hdr_with_wrong_len(self, msgid, resp_dict):
+ # test for sending the http header with wrong length
+ v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
+ s = json.dumps(v)
+ resp = "Content-Length: 1000\r\n"
+ resp += "\r\n"
+ resp += s
+ self.request.sendall(resp.encode('utf-8'))
+
+ def send_hdr_with_negative_len(self, msgid, resp_dict):
+ # test for sending the http header with negative length
+ v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict}
+ s = json.dumps(v)
+ resp = "Content-Length: -1\r\n"
+ resp += "\r\n"
+ resp += s
+ self.request.sendall(resp.encode('utf-8'))
+
+ def do_ping(self, payload):
+ time.sleep(0.2)
+ self.send_lsp_msg(payload['id'], 'alive')
+
+ def do_echo(self, payload):
+ self.send_lsp_msg(-1, payload)
+
+ def do_simple_rpc(self, payload):
+ # test for a simple RPC request
+ self.send_lsp_msg(payload['id'], 'simple-rpc')
+
+ def do_rpc_with_notif(self, payload):
+ # test for sending a notification before replying to a request message
+ self.send_lsp_msg(-1, 'rpc-with-notif-notif')
+ # sleep for some time to make sure the notification is delivered
+ time.sleep(0.2)
+ self.send_lsp_msg(payload['id'], 'rpc-with-notif-resp')
+
+ def do_wrong_payload(self, payload):
+ # test for sending a non dict payload
+ self.send_wrong_payload()
+ time.sleep(0.2)
+ self.send_lsp_msg(-1, 'wrong-payload')
+
+ def do_large_payload(self, payload):
+ # test for sending a large (> 64K) payload
+ self.send_lsp_msg(payload['id'], payload)
+
+ def do_rpc_resp_incorrect_id(self, payload):
+ self.send_lsp_msg(-1, 'rpc-resp-incorrect-id-1')
+ self.send_lsp_msg(-1, 'rpc-resp-incorrect-id-2')
+ self.send_lsp_msg(1, 'rpc-resp-incorrect-id-3')
+ time.sleep(0.2)
+ self.send_lsp_msg(payload['id'], 'rpc-resp-incorrect-id-4')
+
+ def do_simple_notif(self, payload):
+ # notification message test
+ self.send_lsp_msg(-1, 'simple-notif')
+
+ def do_multi_notif(self, payload):
+ # send multiple notifications
+ self.send_lsp_msg(-1, 'multi-notif1')
+ self.send_lsp_msg(-1, 'multi-notif2')
+
+ def do_msg_with_id(self, payload):
+ self.send_lsp_msg(payload['id'], 'msg-with-id')
+
+ def do_msg_specific_cb(self, payload):
+ self.send_lsp_msg(payload['id'], 'msg-specific-cb')
+
+ def do_server_req(self, payload):
+ self.send_lsp_msg(201, {'method': 'checkhealth', 'params': {'a': 20}})
+
+ def do_extra_hdr_fields(self, payload):
+ self.send_extra_hdr_fields(payload['id'], 'extra-hdr-fields')
+
+ def do_delayad_payload(self, payload):
+ self.send_delayed_payload(payload['id'], 'delayed-payload')
+
+ def do_hdr_without_len(self, payload):
+ self.send_hdr_without_len(payload['id'], 'hdr-without-len')
+
+ def do_hdr_with_wrong_len(self, payload):
+ self.send_hdr_with_wrong_len(payload['id'], 'hdr-with-wrong-len')
+
+ def do_hdr_with_negative_len(self, payload):
+ self.send_hdr_with_negative_len(payload['id'], 'hdr-with-negative-len')
+
+ def do_empty_header(self, payload):
+ self.send_empty_header(payload['id'], 'empty-header')
+
+ def do_empty_payload(self, payload):
+ self.send_empty_payload()
+
+ def process_msg(self, msg):
+ try:
+ decoded = json.loads(msg)
+ if 'method' in decoded:
+ test_map = {
+ 'ping': self.do_ping,
+ 'echo': self.do_echo,
+ 'simple-rpc': self.do_simple_rpc,
+ 'rpc-with-notif': self.do_rpc_with_notif,
+ 'wrong-payload': self.do_wrong_payload,
+ 'large-payload': self.do_large_payload,
+ 'rpc-resp-incorrect-id': self.do_rpc_resp_incorrect_id,
+ 'simple-notif': self.do_simple_notif,
+ 'multi-notif': self.do_multi_notif,
+ 'msg-with-id': self.do_msg_with_id,
+ 'msg-specific-cb': self.do_msg_specific_cb,
+ 'server-req': self.do_server_req,
+ 'extra-hdr-fields': self.do_extra_hdr_fields,
+ 'delayed-payload': self.do_delayad_payload,
+ 'hdr-without-len': self.do_hdr_without_len,
+ 'hdr-with-wrong-len': self.do_hdr_with_wrong_len,
+ 'hdr-with-negative-len': self.do_hdr_with_negative_len,
+ 'empty-header': self.do_empty_header,
+ 'empty-payload': self.do_empty_payload
+ }
+ if decoded['method'] in test_map:
+ test_map[decoded['method']](decoded)
+ else:
+ self.debuglog("Error: Unsupported method - " + decoded['method'] + "\n")
+ else:
+ self.debuglog("Error: 'method' field is not found\n")
+
+ except ValueError:
+ self.debuglog("Error: json decoding failed\n")
+
+ def process_msgs(self, msgbuf):
+ while True:
+ sidx = msgbuf.find('Content-Length: ')
+ if sidx == -1:
+ # partial message received
+ return msgbuf
+ sidx += 16
+ eidx = msgbuf.find('\r\n')
+ if eidx == -1:
+ # partial message received
+ return msgbuf
+ msglen = int(msgbuf[sidx:eidx])
+
+ hdrend = msgbuf.find('\r\n\r\n')
+ if hdrend == -1:
+ # partial message received
+ return msgbuf
+
+ if msglen > len(msgbuf[hdrend + 4:]):
+ if self.debug:
+ self.debuglog("Partial message ({0} bytes)\n".format(len(msgbuf)))
+ # partial message received
+ return msgbuf
+
+ if self.debug:
+ self.debuglog("Complete message ({0} bytes) received\n".format(msglen))
+
+ # Remove the header
+ msgbuf = msgbuf[hdrend + 4:]
+ payload = msgbuf[:msglen]
+
+ self.process_msg(payload)
+
+ # Remove the processed message
+ msgbuf = msgbuf[msglen:]
+
+ def handle(self):
+ self.debug = False
+ self.debuglog("=== socket opened ===\n")
+ msgbuf = ''
+ while True:
+ try:
+ received = self.request.recv(4096).decode('utf-8')
+ except socket.error:
+ self.debuglog("=== socket error ===\n")
+ break
+ except IOError:
+ self.debuglog("=== socket closed ===\n")
+ break
+ if received == '':
+ self.debuglog("=== socket closed ===\n")
+ break
+
+ # Write the received lines into the file for debugging
+ if self.debug:
+ self.debuglog("RECV: ({0} bytes) '{1}'\n".format(len(received), received))
+
+ # Can receive more than one line in a response or a partial line.
+ # Accumulate all the received characters and process one line at
+ # a time.
+ msgbuf += received
+ msgbuf = self.process_msgs(msgbuf)
+
+class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
+ pass
+
+def writePortInFile(port):
+ # Write the port number in Xportnr, so that the test knows it.
+ f = open("Xportnr", "w")
+ f.write("{0}".format(port))
+ f.close()
+
+def main(host, port, server_class=ThreadedTCPServer):
+ # Wait half a second before opening the port to test waittime in ch_open().
+ # We do want to get the port number, get that first. We cannot open the
+ # socket, guess a port is free.
+ if len(sys.argv) >= 2 and sys.argv[1] == 'delay':
+ port = 13684
+ writePortInFile(port)
+ time.sleep(0.5)
+
+ addrs = socket.getaddrinfo(host, port, 0, 0, socket.IPPROTO_TCP)
+ # Each addr is a (family, type, proto, canonname, sockaddr) tuple
+ sockaddr = addrs[0][4]
+ server_class.address_family = addrs[0][0]
+
+ server = server_class(sockaddr[0:2], ThreadedTCPRequestHandler)
+ ip, port = server.server_address[0:2]
+
+ # Start a thread with the server. That thread will then start a new thread
+ # for each connection.
+ server_thread = threading.Thread(target=server.serve_forever)
+ server_thread.start()
+
+ writePortInFile(port)
+
+ # Main thread terminates, but the server continues running
+ # until server.shutdown() is called.
+ try:
+ while server_thread.is_alive():
+ server_thread.join(1)
+ except (KeyboardInterrupt, SystemExit):
+ server.shutdown()
+
+if __name__ == "__main__":
+ main("localhost", 0)
diff --git a/src/testdir/test_channel_pipe.py b/src/testdir/test_channel_pipe.py
new file mode 100644
index 0000000..22e58b4
--- /dev/null
+++ b/src/testdir/test_channel_pipe.py
@@ -0,0 +1,76 @@
+#!/usr/bin/python
+#
+# Server that will communicate over stdin/stderr
+#
+# This requires Python 2.6 or later.
+
+from __future__ import print_function
+import os
+import sys
+import time
+
+if __name__ == "__main__":
+
+ if len(sys.argv) > 1:
+ if sys.argv[1].startswith("err"):
+ print(sys.argv[1], file=sys.stderr)
+ sys.stderr.flush()
+ elif sys.argv[1].startswith("incomplete"):
+ print(sys.argv[1], end='')
+ sys.stdout.flush()
+ sys.exit(0)
+ elif sys.argv[1].startswith("busy"):
+ time.sleep(100)
+ sys.exit(0)
+ else:
+ print(sys.argv[1])
+ sys.stdout.flush()
+ if sys.argv[1].startswith("quit"):
+ sys.exit(0)
+
+ if os.getenv('CI'):
+ try:
+ import thread_util
+ thread_util.set_high_priority()
+ except Exception:
+ pass
+
+ while True:
+ typed = sys.stdin.readline()
+ if typed == "": # EOF -- stop
+ break
+ if typed.startswith("quit"):
+ print("Goodbye!")
+ sys.stdout.flush()
+ break
+ if typed.startswith("echo "):
+ print(typed[5:-1])
+ sys.stdout.flush()
+ if typed.startswith("echosplit "):
+ for part in typed[10:-1].split('|'):
+ sys.stdout.write(part)
+ sys.stdout.flush()
+ time.sleep(0.05)
+ if typed.startswith("double "):
+ print(typed[7:-1] + "\nAND " + typed[7:-1])
+ sys.stdout.flush()
+ if typed.startswith("split "):
+ print(typed[6:-1], end='')
+ sys.stdout.flush()
+ time.sleep(0.05)
+ print(typed[6:-1], end='')
+ sys.stdout.flush()
+ time.sleep(0.05)
+ print(typed[6:-1])
+ sys.stdout.flush()
+ if typed.startswith("echoerr "):
+ print(typed[8:-1], file=sys.stderr)
+ sys.stderr.flush()
+ if typed.startswith("doubleerr "):
+ print(typed[10:-1] + "\nAND " + typed[10:-1], file=sys.stderr)
+ sys.stderr.flush()
+ if typed.startswith("XXX"):
+ print(typed, end='')
+ sys.stderr.flush()
+ break
+
diff --git a/src/testdir/test_channel_unix.py b/src/testdir/test_channel_unix.py
new file mode 100644
index 0000000..85e780a
--- /dev/null
+++ b/src/testdir/test_channel_unix.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+#
+# Server that will accept connections from a Vim channel.
+# Used by test_channel.vim.
+#
+# This requires Python 2.6 or later.
+
+from __future__ import print_function
+from test_channel import ThreadedTCPServer, TestingRequestHandler, \
+ writePortInFile
+import socket
+import threading
+import os
+
+try:
+ FileNotFoundError
+except NameError:
+ # Python 2
+ FileNotFoundError = (IOError, OSError)
+
+if not hasattr(socket, "AF_UNIX"):
+ raise NotImplementedError("Unix sockets are not supported on this platform")
+
+class ThreadedUnixServer(ThreadedTCPServer):
+ address_family = socket.AF_UNIX
+
+class ThreadedUnixRequestHandler(TestingRequestHandler):
+ pass
+
+def main(path):
+ server = ThreadedUnixServer(path, ThreadedUnixRequestHandler)
+
+ # Start a thread with the server. That thread will then start a new thread
+ # for each connection.
+ server_thread = threading.Thread(target=server.serve_forever)
+ server_thread.start()
+
+ # Signal the test harness we're ready, the port value has no meaning.
+ writePortInFile(1234)
+
+ print("Listening on {0}".format(server.server_address))
+
+ # Main thread terminates, but the server continues running
+ # until server.shutdown() is called.
+ try:
+ while server_thread.is_alive():
+ server_thread.join(1)
+ except (KeyboardInterrupt, SystemExit):
+ server.shutdown()
+
+if __name__ == "__main__":
+ try:
+ os.remove("Xtestsocket")
+ except FileNotFoundError:
+ pass
+ main("Xtestsocket")
diff --git a/src/testdir/test_channel_write.py b/src/testdir/test_channel_write.py
new file mode 100644
index 0000000..9c8813b
--- /dev/null
+++ b/src/testdir/test_channel_write.py
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+#
+# Program that writes a number to stdout repeatedly
+#
+# This requires Python 2.6 or later.
+
+from __future__ import print_function
+import sys
+import time
+
+if __name__ == "__main__":
+
+ done = 0
+ while done < 10:
+ done = done + 1
+ print(done)
+ sys.stdout.flush()
+ time.sleep(0.05) # sleep 50 msec
diff --git a/src/testdir/test_charsearch.vim b/src/testdir/test_charsearch.vim
new file mode 100644
index 0000000..142e6c8
--- /dev/null
+++ b/src/testdir/test_charsearch.vim
@@ -0,0 +1,98 @@
+" Test for character search commands - t, T, f, F, ; and ,
+
+func Test_charsearch()
+ enew!
+ call append(0, ['Xabcdefghijkemnopqretuvwxyz',
+ \ 'Yabcdefghijkemnopqretuvwxyz',
+ \ 'Zabcdefghijkemnokqretkvwxyz'])
+ " check that "fe" and ";" work
+ 1
+ normal! ylfep;;p,,p
+ call assert_equal('XabcdeXfghijkeXmnopqreXtuvwxyz', getline(1))
+ " check that save/restore works
+ 2
+ normal! ylfep
+ let csave = getcharsearch()
+ normal! fip
+ call setcharsearch(csave)
+ normal! ;p;p
+ call assert_equal('YabcdeYfghiYjkeYmnopqreYtuvwxyz', getline(2))
+
+ " check that setcharsearch() changes the settings.
+ 3
+ normal! ylfep
+ eval {'char': 'k'}->setcharsearch()
+ normal! ;p
+ call setcharsearch({'forward': 0})
+ normal! $;p
+ call setcharsearch({'until': 1})
+ set cpo-=;
+ normal! ;;p
+ call assert_equal('ZabcdeZfghijkZZemnokqretkZvwxyz', getline(3))
+
+ " check that repeating a search before and after a line fails
+ normal 3Gfv
+ call assert_beeps('normal ;')
+ call assert_beeps('normal ,')
+
+ " clear the character search
+ call setcharsearch({'char' : ''})
+ call assert_equal('', getcharsearch().char)
+
+ call assert_fails("call setcharsearch([])", 'E1206:')
+ enew!
+endfunc
+
+" Test for character search in virtual edit mode with <Tab>
+func Test_csearch_virtualedit()
+ new
+ set virtualedit=all
+ call setline(1, "a\tb")
+ normal! tb
+ call assert_equal([0, 1, 2, 6], getpos('.'))
+ set virtualedit&
+ bw!
+endfunc
+
+" Test for character search failure in latin1 encoding
+func Test_charsearch_latin1()
+ new
+ let save_enc = &encoding
+ set encoding=latin1
+ call setline(1, 'abcdefghijk')
+ call assert_beeps('normal fz')
+ call assert_beeps('normal tx')
+ call assert_beeps('normal $Fz')
+ call assert_beeps('normal $Tx')
+ let &encoding = save_enc
+ bw!
+endfunc
+
+" Test for using character search to find a multibyte character with composing
+" characters.
+func Test_charsearch_composing_char()
+ new
+ call setline(1, "one two thq\u0328\u0301r\u0328\u0301ree")
+ call feedkeys("fr\u0328\u0301", 'xt')
+ call assert_equal([0, 1, 16, 0, 12], getcurpos())
+
+ " use character search with a multi-byte character followed by a
+ " non-composing character
+ call setline(1, "abc deȉf ghi")
+ call feedkeys("ggcf\u0209\u0210", 'xt')
+ call assert_equal("\u0210f ghi", getline(1))
+ bw!
+endfunc
+
+" Test for character search with 'hkmap'
+func Test_charsearch_hkmap()
+ new
+ set hkmap
+ call setline(1, "ùðáâ÷ëòéïçìêöî")
+ call feedkeys("fë", 'xt')
+ call assert_equal([0, 1, 11, 0, 6], getcurpos())
+ set hkmap&
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_charsearch_utf8.vim b/src/testdir/test_charsearch_utf8.vim
new file mode 100644
index 0000000..82a807a
--- /dev/null
+++ b/src/testdir/test_charsearch_utf8.vim
@@ -0,0 +1,19 @@
+" Tests for related f{char} and t{char} using utf-8.
+
+" Test for t,f,F,T movement commands
+func Test_search_cmds()
+ new!
+ call setline(1, "・最åˆã‹ã‚‰æœ€å¾Œã¾ã§æœ€å¼·ã®Vimã¯æœ€é«˜")
+ 1
+ normal! f最
+ call assert_equal([0, 1, 4, 0], getpos('.'))
+ normal! ;
+ call assert_equal([0, 1, 16, 0], getpos('.'))
+ normal! 2;
+ call assert_equal([0, 1, 43, 0], getpos('.'))
+ normal! ,
+ call assert_equal([0, 1, 28, 0], getpos('.'))
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_checkpath.vim b/src/testdir/test_checkpath.vim
new file mode 100644
index 0000000..97da8c2
--- /dev/null
+++ b/src/testdir/test_checkpath.vim
@@ -0,0 +1,116 @@
+" Tests for the :checkpath command
+
+" Test for 'include' without \zs or \ze
+func Test_checkpath1()
+ call mkdir("Xcheckdir1/dir2", "pR")
+ call writefile(['#include "bar.a"'], 'Xcheckdir1/dir2/foo.a')
+ call writefile(['#include "baz.a"'], 'Xcheckdir1/dir2/bar.a')
+ call writefile(['#include "foo.a"'], 'Xcheckdir1/dir2/baz.a')
+ call writefile(['#include <foo.a>'], 'Xbase.a')
+
+ edit Xbase.a
+ set path=Xcheckdir1/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xcheckdir1/dir2/foo.a',
+ \ 'Xcheckdir1/dir2/foo.a -->',
+ \ ' Xcheckdir1/dir2/bar.a',
+ \ ' Xcheckdir1/dir2/bar.a -->',
+ \ ' Xcheckdir1/dir2/baz.a',
+ \ ' Xcheckdir1/dir2/baz.a -->',
+ \ ' "foo.a" (Already listed)'], res)
+
+ enew
+ call delete("./Xbase.a")
+ set path&
+endfunc
+
+func DotsToSlashes()
+ return substitute(v:fname, '\.', '/', 'g') . '.b'
+endfunc
+
+" Test for 'include' with \zs and \ze
+func Test_checkpath2()
+ call mkdir("Xcheckdir2/dir2", "pR")
+ call writefile(['%inc /bar/'], 'Xcheckdir2/dir2/foo.b')
+ call writefile(['%inc /baz/'], 'Xcheckdir2/dir2/bar.b')
+ call writefile(['%inc /foo/'], 'Xcheckdir2/dir2/baz.b')
+ call writefile(['%inc /foo/'], 'Xbase.b', 'D')
+
+ let &include='^\s*%inc\s*/\zs[^/]\+\ze'
+ let &includeexpr='DotsToSlashes()'
+
+ edit Xbase.b
+ set path=Xcheckdir2/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xcheckdir2/dir2/foo.b',
+ \ 'Xcheckdir2/dir2/foo.b -->',
+ \ ' Xcheckdir2/dir2/bar.b',
+ \ ' Xcheckdir2/dir2/bar.b -->',
+ \ ' Xcheckdir2/dir2/baz.b',
+ \ ' Xcheckdir2/dir2/baz.b -->',
+ \ ' foo (Already listed)'], res)
+
+ enew
+ set path&
+ set include&
+ set includeexpr&
+endfunc
+
+func StripNewlineChar()
+ if v:fname =~ '\n$'
+ return v:fname[:-2]
+ endif
+ return v:fname
+endfunc
+
+" Test for 'include' with \zs and no \ze
+func Test_checkpath3()
+ call mkdir("Xcheckdir3/dir2", "pR")
+ call writefile(['%inc bar.c'], 'Xcheckdir3/dir2/foo.c')
+ call writefile(['%inc baz.c'], 'Xcheckdir3/dir2/bar.c')
+ call writefile(['%inc foo.c'], 'Xcheckdir3/dir2/baz.c')
+ call writefile(['%inc foo.c'], 'Xcheckdir3/dir2/FALSE.c')
+ call writefile(['%inc FALSE.c foo.c'], 'Xbase.c', 'D')
+
+ let &include='^\s*%inc\s*\%([[:upper:]][^[:space:]]*\s\+\)\?\zs\S\+\ze'
+ let &includeexpr='StripNewlineChar()'
+
+ edit Xbase.c
+ set path=Xcheckdir3/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xcheckdir3/dir2/foo.c',
+ \ 'Xcheckdir3/dir2/foo.c -->',
+ \ ' Xcheckdir3/dir2/bar.c',
+ \ ' Xcheckdir3/dir2/bar.c -->',
+ \ ' Xcheckdir3/dir2/baz.c',
+ \ ' Xcheckdir3/dir2/baz.c -->',
+ \ ' foo.c (Already listed)'], res)
+
+ enew
+ set path&
+ set include&
+ set includeexpr&
+endfunc
+
+" Test for invalid regex in 'include' and 'define' options
+func Test_checkpath_errors()
+ let save_include = &include
+ set include=\\%(
+ call assert_fails('checkpath', 'E53:')
+ let &include = save_include
+
+ let save_define = &define
+ set define=\\%(
+ call assert_fails('dsearch abc', 'E53:')
+ let &define = save_define
+
+ call assert_fails('psearch \%(', 'E53:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cindent.vim b/src/testdir/test_cindent.vim
new file mode 100644
index 0000000..96e99ac
--- /dev/null
+++ b/src/testdir/test_cindent.vim
@@ -0,0 +1,5442 @@
+" Test for cinoptions and cindent
+
+def Test_cino_hash()
+ # Test that curbuf->b_ind_hash_comment is correctly reset
+ new
+ setlocal cindent cinoptions=#1
+ setlocal cinoptions=
+ setline(1, ["#include <iostream>"])
+ cursor(1, 1)
+ norm! o#include
+ assert_equal(["#include <iostream>", "#include"], getline(1, 2))
+
+ bwipe!
+enddef
+
+def Test_cino_extern_c()
+ # Test for cino-E
+
+ var without_ind =<< trim [CODE]
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+ int func_a(void);
+ #ifdef __cplusplus
+ }
+ #endif
+ [CODE]
+
+ var with_ind =<< trim [CODE]
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+ int func_a(void);
+ #ifdef __cplusplus
+ }
+ #endif
+ [CODE]
+ new
+ setlocal cindent cinoptions=E0
+ setline(1, without_ind)
+ feedkeys("gg=G", 'tx')
+ assert_equal(with_ind, getline(1, '$'))
+
+ setlocal cinoptions=E-s
+ setline(1, with_ind)
+ feedkeys("gg=G", 'tx')
+ assert_equal(without_ind, getline(1, '$'))
+
+ setlocal cinoptions=Es
+ var tests = [
+ \ ['recognized', ['extern "C" {'], "\t\t;"],
+ \ ['recognized', ['extern "C++" {'], "\t\t;"],
+ \ ['recognized', ['extern /* com */ "C"{'], "\t\t;"],
+ \ ['recognized', ['extern"C"{'], "\t\t;"],
+ \ ['recognized', ['extern "C"', '{'], "\t\t;"],
+ \ ['not recognized', ['extern {'], "\t;"],
+ \ ['not recognized', ['extern /*"C"*/{'], "\t;"],
+ \ ['not recognized', ['extern "C" //{'], ";"],
+ \ ['not recognized', ['extern "C" /*{*/'], ";"],
+ \ ]
+
+ for pair in tests
+ var lines = pair[1]
+ setline(1, lines)
+ feedkeys(len(lines) .. "Go;", 'tx')
+ assert_equal(pair[2], getline(len(lines) + 1),
+ 'Failed for "' .. string(lines) .. '"')
+ endfor
+
+ bwipe!
+enddef
+
+def Test_cindent_rawstring()
+ new
+ setl cindent
+ feedkeys("i" ..
+ \ "int main() {\<CR>" ..
+ \ "R\"(\<CR>" ..
+ \ ")\";\<CR>" ..
+ \ "statement;\<Esc>", "x")
+ assert_equal("\tstatement;", getline(line('.')))
+
+ bwipe!
+enddef
+
+def Test_cindent_expr()
+ new
+ def g:MyIndentFunction(): number
+ return v:lnum == 1 ? shiftwidth() : 0
+ enddef
+ setl expandtab sw=8 indentkeys+=; indentexpr=g:MyIndentFunction()
+ var testinput =<< trim [CODE]
+ var_a = something()
+ b = something()
+ [CODE]
+ setline(1, testinput)
+ cursor(1, 1)
+ feedkeys("^\<c-v>j$A;\<esc>", 'tnix')
+ var expected =<< [CODE]
+ var_a = something();
+b = something();
+[CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ :%d
+ testinput =<< [CODE]
+ var_a = something()
+ b = something()
+[CODE]
+ setline(1, testinput)
+ cursor(1, 1)
+ feedkeys("^\<c-v>j$A;\<esc>", 'tnix')
+ expected =<< [CODE]
+ var_a = something();
+ b = something()
+[CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ delfunc g:MyIndentFunction
+
+ bwipe!
+enddef
+
+def Test_cindent_func()
+ new
+ setlocal cindent
+ setline(1, ['int main(void)', '{', 'return 0;', '}'])
+ assert_equal(-1, cindent(0))
+ assert_equal(&sw, 3->cindent())
+ assert_equal(-1, cindent(line('$') + 1))
+
+ bwipe!
+enddef
+
+def Test_cindent_1()
+ new
+ setl cindent ts=4 sw=4
+ setl cino& sts&
+
+ var code =<< trim [CODE]
+ /* start of AUTO matically checked vim: set ts=4 : */
+ {
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ }
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ else
+ }
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd;
+ }
+
+ if (test)
+ cmd;
+ }
+
+ {
+ if (test) {
+ cmd;
+ }
+
+ if (test) cmd;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ cmd3;
+ }
+ }
+
+
+ /* Test for 'cindent' do/while mixed with if/else: */
+
+ {
+ do
+ if (asdf)
+ asdfasd;
+ while (cond);
+
+ do
+ if (asdf)
+ while (asdf)
+ asdf;
+ while (asdf);
+ }
+
+ /* Test for 'cindent' with two ) on a continuation line */
+ {
+ if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
+ aal;sdkjf ( ;asldfkja;sldfk
+ al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
+ line up here;
+ }
+
+
+ /* C++ tests: */
+
+ // foo() these three lines should remain in column 0
+ // {
+ // }
+
+ /* Test for continuation and unterminated lines: */
+ {
+ i = 99 + 14325 +
+ 21345 +
+ 21345 +
+ 21345 + ( 21345 +
+ 21345) +
+ 2345 +
+ 1234;
+ c = 1;
+ }
+
+ /*
+ testje for indent with empty line
+
+ here */
+
+ {
+ if (testing &&
+ not a joke ||
+ line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ )line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ line up here))
+ hay;
+ }
+
+
+ {
+ switch (c)
+ {
+ case xx:
+ do
+ if (asdf)
+ do
+ asdfasdf;
+ while (asdf);
+ else
+ asdfasdf;
+ while (cond);
+ case yy:
+ case xx:
+ case zz:
+ testing;
+ }
+ }
+
+ {
+ if (cond) {
+ foo;
+ }
+ else
+ {
+ bar;
+ }
+ }
+
+ {
+ if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
+ alsdkfj (asldk;fj
+ awith cino=(0 ;lf this one goes to below the paren with ==
+ ;laksjfd ;lsakdjf ;alskdf asd)
+ asdfasdf;)))
+ asdfasdf;
+ }
+
+ int
+ func(a, b)
+ int a;
+ int c;
+ {
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3)
+ )
+ }
+
+ {
+ while (asd)
+ {
+ if (asdf)
+ if (test)
+ if (that)
+ {
+ if (asdf)
+ do
+ cdasd;
+ while (as
+ df);
+ }
+ else
+ if (asdf)
+ asdf;
+ else
+ asdf;
+ asdf;
+ }
+ }
+
+ {
+ s = "/*"; b = ';'
+ s = "/*"; b = ';';
+ a = b;
+ }
+
+ {
+ switch (a)
+ {
+ case a:
+ switch (t)
+ {
+ case 1:
+ cmd;
+ break;
+ case 2:
+ cmd;
+ break;
+ }
+ cmd;
+ break;
+ case b:
+ {
+ int i;
+ cmd;
+ }
+ break;
+ case c: {
+ int i;
+ cmd;
+ }
+ case d: if (cond &&
+ test) { /* this line doesn't work right */
+ int i;
+ cmd;
+ }
+ break;
+ }
+ }
+
+ {
+ if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
+ (bp_to->b_p_initialized ||
+ (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
+ return;
+ label :
+ asdf = asdf ?
+ asdf : asdf;
+ asdf = asdf ?
+ asdf: asdf;
+ }
+
+ /* Special Comments : This function has the added complexity (compared */
+ /* : to addtolist) of having to check for a detail */
+ /* : texture and add that to the list first. */
+
+ char *(array[100]) = {
+ "testje",
+ "foo",
+ "bar",
+ }
+
+ enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ typedef enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ public static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ static private enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ {
+ int a,
+ b;
+ }
+
+ {
+ struct Type
+ {
+ int i;
+ char *str;
+ } var[] =
+ {
+ 0, "zero",
+ 1, "one",
+ 2, "two",
+ 3, "three"
+ };
+
+ float matrix[3][3] =
+ {
+ {
+ 0,
+ 1,
+ 2
+ },
+ {
+ 3,
+ 4,
+ 5
+ },
+ {
+ 6,
+ 7,
+ 8
+ }
+ };
+ }
+
+ {
+ /* blah ( blah */
+ /* where does this go? */
+
+ /* blah ( blah */
+ cmd;
+
+ func(arg1,
+ /* comment */
+ arg2);
+ a;
+ {
+ b;
+ {
+ c; /* Hey, NOW it indents?! */
+ }
+ }
+
+ {
+ func(arg1,
+ arg2,
+ arg3);
+ /* Hey, what am I doing here? Is this coz of the ","? */
+ }
+ }
+
+ main ()
+ {
+ if (cond)
+ {
+ a = b;
+ }
+ if (cond) {
+ a = c;
+ }
+ if (cond)
+ a = d;
+ return;
+ }
+
+ {
+ case 2: if (asdf &&
+ asdfasdf)
+ aasdf;
+ a = 9;
+ case 3: if (asdf)
+ aasdf;
+ a = 9;
+ case 4: x = 1;
+ y = 2;
+
+ label: if (asdf)
+ here;
+
+ label: if (asdf &&
+ asdfasdf)
+ {
+ }
+
+ label: if (asdf &&
+ asdfasdf) {
+ there;
+ }
+
+ label: if (asdf &&
+ asdfasdf)
+ there;
+ }
+
+ {
+ /*
+ hello with ":set comments= cino=c5"
+ */
+
+ /*
+ hello with ":set comments= cino="
+ */
+ }
+
+
+ {
+ if (a < b) {
+ a = a + 1;
+ } else
+ a = a + 2;
+
+ if (a)
+ do {
+ testing;
+ } while (asdfasdf);
+ a = b + 1;
+ asdfasdf
+ }
+
+ {
+ for ( int i = 0;
+ i < 10; i++ )
+ {
+ }
+ i = 0;
+ }
+
+ class bob
+ {
+ int foo() {return 1;}
+ int bar;
+ }
+
+ main()
+ {
+ while(1)
+ if (foo)
+ {
+ bar;
+ }
+ else {
+ asdf;
+ }
+ misplacedline;
+ }
+
+ {
+ if (clipboard.state == SELECT_DONE
+ && ((row == clipboard.start.lnum
+ && col >= clipboard.start.col)
+ || row > clipboard.start.lnum))
+ }
+
+ {
+ if (1) {i += 4;}
+ where_am_i;
+ return 0;
+ }
+
+ {
+ {
+ } // sdf(asdf
+ if (asdf)
+ asd;
+ }
+
+ {
+ label1:
+ label2:
+ }
+
+ {
+ int fooRet = foo(pBar1, false /*fKB*/,
+ true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
+ f() {
+ for ( i = 0;
+ i < m;
+ /* c */ i++ ) {
+ a = b;
+ }
+ }
+ }
+
+ {
+ f1(/*comment*/);
+ f2();
+ }
+
+ {
+ do {
+ if (foo) {
+ } else
+ ;
+ } while (foo);
+ foo(); // was wrong
+ }
+
+ int x; // no extra indent because of the ;
+ void func()
+ {
+ }
+
+ char *tab[] = {"aaa",
+ "};", /* }; */ NULL}
+ int indented;
+ {}
+
+ char *a[] = {"aaa", "bbb",
+ "ccc", NULL};
+ // here
+
+ char *tab[] = {"aaa",
+ "xx", /* xx */}; /* asdf */
+ int not_indented;
+
+ {
+ do {
+ switch (bla)
+ {
+ case 1: if (foo)
+ bar;
+ }
+ } while (boo);
+ wrong;
+ }
+
+ int foo,
+ bar;
+ int foo;
+
+ #if defined(foo) \
+ && defined(bar)
+ char * xx = "asdf\
+ foo\
+ bor";
+ int x;
+
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+
+ void f()
+ {
+ #if defined(foo) \
+ && defined(bar)
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ {
+ int i;
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ }
+ #endif
+ }
+ #endif
+
+ int y; // comment
+ // comment
+
+ // comment
+
+ {
+ Constructor(int a,
+ int b ) : BaseClass(a)
+ {
+ }
+ }
+
+ void foo()
+ {
+ char one,
+ two;
+ struct bla piet,
+ jan;
+ enum foo kees,
+ jannie;
+ static unsigned sdf,
+ krap;
+ unsigned int piet,
+ jan;
+ int
+ kees,
+ jan;
+ }
+
+ {
+ t(int f,
+ int d); // )
+ d();
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b),
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) :
+ BaseClass(a)
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) /*x*/ : /*x*/ BaseClass(a),
+ member(b)
+ {
+ }
+
+ A::A(int a, int b)
+ : aa(a),
+ bb(b),
+ cc(c)
+ {
+ }
+
+ class CAbc :
+ public BaseClass1,
+ protected BaseClass2
+ {
+ int Test() { return FALSE; }
+ int Test1() { return TRUE; }
+
+ CAbc(int a, int b ) :
+ BaseClass(a)
+ {
+ switch(xxx)
+ {
+ case abc:
+ asdf();
+ break;
+
+ case 999:
+ baer();
+ break;
+ }
+ }
+
+ public: // <-- this was incorrectly indented before!!
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ class CAbc : public BaseClass1,
+ protected BaseClass2
+ {
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ {
+ 123,
+ 456
+ },
+ {
+ 123,
+ 456
+ }
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ { 123, 456 },
+ { 123, 456 }
+ };
+
+ void asdf() /* ind_maxparen may cause trouble here */
+ {
+ if ((0
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1)) break;
+ }
+
+ foo()
+ {
+ a = cond ? foo() : asdf
+ + asdf;
+
+ a = cond ?
+ foo() : asdf
+ + asdf;
+ }
+
+ int main(void)
+ {
+ if (a)
+ if (b)
+ 2;
+ else 3;
+ next_line_of_code();
+ }
+
+ barry()
+ {
+ Foo::Foo (int one,
+ int two)
+ : something(4)
+ {}
+ }
+
+ barry()
+ {
+ Foo::Foo (int one, int two)
+ : something(4)
+ {}
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b)
+ {
+ }
+ int main ()
+ {
+ if (lala)
+ do
+ ++(*lolo);
+ while (lili
+ && lele);
+ lulu;
+ }
+
+ int main ()
+ {
+ switch (c)
+ {
+ case 'c': if (cond)
+ {
+ }
+ }
+ }
+
+ main()
+ {
+ (void) MyFancyFuasdfadsfnction(
+ argument);
+ }
+
+ main()
+ {
+ char foo[] = "/*";
+ /* as
+ df */
+ hello
+ }
+
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+
+ void getstring() {
+ /* Raw strings */
+ const char* s = R"(
+ test {
+ # comment
+ field: 123
+ }
+ )";
+ }
+
+ void getstring() {
+ const char* s = R"foo(
+ test {
+ # comment
+ field: 123
+ }
+ )foo";
+ }
+
+ {
+ int a[4] = {
+ [0] = 0,
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ };
+ }
+
+ {
+ a = b[2]
+ + 3;
+ }
+
+ {
+ if (1)
+ /* aaaaa
+ * bbbbb
+ */
+ a = 1;
+ }
+
+ void func()
+ {
+ switch (foo)
+ {
+ case (bar):
+ if (baz())
+ quux();
+ break;
+ case (shmoo):
+ if (!bar)
+ {
+ }
+ case (foo1):
+ switch (bar)
+ {
+ case baz:
+ baz_f();
+ break;
+ }
+ break;
+ default:
+ baz();
+ baz();
+ break;
+ }
+ }
+
+ /* end of AUTO */
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('start of AUTO')
+ exe "normal =/end of AUTO\<CR>"
+
+ var expected =<< trim [CODE]
+ /* start of AUTO matically checked vim: set ts=4 : */
+ {
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ }
+ }
+
+ {
+ if (test)
+ {
+ cmd1;
+ else
+ }
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ cmd2;
+ }
+
+ {
+ while (this)
+ if (test)
+ cmd1;
+ else
+ cmd2;
+ }
+
+ {
+ if (test)
+ {
+ cmd;
+ }
+
+ if (test)
+ cmd;
+ }
+
+ {
+ if (test) {
+ cmd;
+ }
+
+ if (test) cmd;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+ }
+
+ {
+ cmd1;
+ for (blah)
+ while (this)
+ if (test)
+ cmd2;
+ cmd3;
+
+ if (test)
+ {
+ cmd1;
+ cmd2;
+ cmd3;
+ }
+ }
+
+
+ /* Test for 'cindent' do/while mixed with if/else: */
+
+ {
+ do
+ if (asdf)
+ asdfasd;
+ while (cond);
+
+ do
+ if (asdf)
+ while (asdf)
+ asdf;
+ while (asdf);
+ }
+
+ /* Test for 'cindent' with two ) on a continuation line */
+ {
+ if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d
+ aal;sdkjf ( ;asldfkja;sldfk
+ al;sdjfka ;slkdf ) sa;ldkjfsa dlk;)
+ line up here;
+ }
+
+
+ /* C++ tests: */
+
+ // foo() these three lines should remain in column 0
+ // {
+ // }
+
+ /* Test for continuation and unterminated lines: */
+ {
+ i = 99 + 14325 +
+ 21345 +
+ 21345 +
+ 21345 + ( 21345 +
+ 21345) +
+ 2345 +
+ 1234;
+ c = 1;
+ }
+
+ /*
+ testje for indent with empty line
+
+ here */
+
+ {
+ if (testing &&
+ not a joke ||
+ line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ )line up here)
+ hay;
+ if (testing &&
+ (not a joke || testing
+ line up here))
+ hay;
+ }
+
+
+ {
+ switch (c)
+ {
+ case xx:
+ do
+ if (asdf)
+ do
+ asdfasdf;
+ while (asdf);
+ else
+ asdfasdf;
+ while (cond);
+ case yy:
+ case xx:
+ case zz:
+ testing;
+ }
+ }
+
+ {
+ if (cond) {
+ foo;
+ }
+ else
+ {
+ bar;
+ }
+ }
+
+ {
+ if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf
+ alsdkfj (asldk;fj
+ awith cino=(0 ;lf this one goes to below the paren with ==
+ ;laksjfd ;lsakdjf ;alskdf asd)
+ asdfasdf;)))
+ asdfasdf;
+ }
+
+ int
+ func(a, b)
+ int a;
+ int c;
+ {
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3)
+ )
+ }
+
+ {
+ while (asd)
+ {
+ if (asdf)
+ if (test)
+ if (that)
+ {
+ if (asdf)
+ do
+ cdasd;
+ while (as
+ df);
+ }
+ else
+ if (asdf)
+ asdf;
+ else
+ asdf;
+ asdf;
+ }
+ }
+
+ {
+ s = "/*"; b = ';'
+ s = "/*"; b = ';';
+ a = b;
+ }
+
+ {
+ switch (a)
+ {
+ case a:
+ switch (t)
+ {
+ case 1:
+ cmd;
+ break;
+ case 2:
+ cmd;
+ break;
+ }
+ cmd;
+ break;
+ case b:
+ {
+ int i;
+ cmd;
+ }
+ break;
+ case c: {
+ int i;
+ cmd;
+ }
+ case d: if (cond &&
+ test) { /* this line doesn't work right */
+ int i;
+ cmd;
+ }
+ break;
+ }
+ }
+
+ {
+ if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) &&
+ (bp_to->b_p_initialized ||
+ (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL)))
+ return;
+ label :
+ asdf = asdf ?
+ asdf : asdf;
+ asdf = asdf ?
+ asdf: asdf;
+ }
+
+ /* Special Comments : This function has the added complexity (compared */
+ /* : to addtolist) of having to check for a detail */
+ /* : texture and add that to the list first. */
+
+ char *(array[100]) = {
+ "testje",
+ "foo",
+ "bar",
+ }
+
+ enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ typedef enum soppie
+ {
+ yes = 0,
+ no,
+ maybe
+ };
+
+ static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ public static enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ static private enum
+ {
+ yes = 0,
+ no,
+ maybe
+ } soppie;
+
+ {
+ int a,
+ b;
+ }
+
+ {
+ struct Type
+ {
+ int i;
+ char *str;
+ } var[] =
+ {
+ 0, "zero",
+ 1, "one",
+ 2, "two",
+ 3, "three"
+ };
+
+ float matrix[3][3] =
+ {
+ {
+ 0,
+ 1,
+ 2
+ },
+ {
+ 3,
+ 4,
+ 5
+ },
+ {
+ 6,
+ 7,
+ 8
+ }
+ };
+ }
+
+ {
+ /* blah ( blah */
+ /* where does this go? */
+
+ /* blah ( blah */
+ cmd;
+
+ func(arg1,
+ /* comment */
+ arg2);
+ a;
+ {
+ b;
+ {
+ c; /* Hey, NOW it indents?! */
+ }
+ }
+
+ {
+ func(arg1,
+ arg2,
+ arg3);
+ /* Hey, what am I doing here? Is this coz of the ","? */
+ }
+ }
+
+ main ()
+ {
+ if (cond)
+ {
+ a = b;
+ }
+ if (cond) {
+ a = c;
+ }
+ if (cond)
+ a = d;
+ return;
+ }
+
+ {
+ case 2: if (asdf &&
+ asdfasdf)
+ aasdf;
+ a = 9;
+ case 3: if (asdf)
+ aasdf;
+ a = 9;
+ case 4: x = 1;
+ y = 2;
+
+ label: if (asdf)
+ here;
+
+ label: if (asdf &&
+ asdfasdf)
+ {
+ }
+
+ label: if (asdf &&
+ asdfasdf) {
+ there;
+ }
+
+ label: if (asdf &&
+ asdfasdf)
+ there;
+ }
+
+ {
+ /*
+ hello with ":set comments= cino=c5"
+ */
+
+ /*
+ hello with ":set comments= cino="
+ */
+ }
+
+
+ {
+ if (a < b) {
+ a = a + 1;
+ } else
+ a = a + 2;
+
+ if (a)
+ do {
+ testing;
+ } while (asdfasdf);
+ a = b + 1;
+ asdfasdf
+ }
+
+ {
+ for ( int i = 0;
+ i < 10; i++ )
+ {
+ }
+ i = 0;
+ }
+
+ class bob
+ {
+ int foo() {return 1;}
+ int bar;
+ }
+
+ main()
+ {
+ while(1)
+ if (foo)
+ {
+ bar;
+ }
+ else {
+ asdf;
+ }
+ misplacedline;
+ }
+
+ {
+ if (clipboard.state == SELECT_DONE
+ && ((row == clipboard.start.lnum
+ && col >= clipboard.start.col)
+ || row > clipboard.start.lnum))
+ }
+
+ {
+ if (1) {i += 4;}
+ where_am_i;
+ return 0;
+ }
+
+ {
+ {
+ } // sdf(asdf
+ if (asdf)
+ asd;
+ }
+
+ {
+ label1:
+ label2:
+ }
+
+ {
+ int fooRet = foo(pBar1, false /*fKB*/,
+ true /*fPTB*/, 3 /*nT*/, false /*fDF*/);
+ f() {
+ for ( i = 0;
+ i < m;
+ /* c */ i++ ) {
+ a = b;
+ }
+ }
+ }
+
+ {
+ f1(/*comment*/);
+ f2();
+ }
+
+ {
+ do {
+ if (foo) {
+ } else
+ ;
+ } while (foo);
+ foo(); // was wrong
+ }
+
+ int x; // no extra indent because of the ;
+ void func()
+ {
+ }
+
+ char *tab[] = {"aaa",
+ "};", /* }; */ NULL}
+ int indented;
+ {}
+
+ char *a[] = {"aaa", "bbb",
+ "ccc", NULL};
+ // here
+
+ char *tab[] = {"aaa",
+ "xx", /* xx */}; /* asdf */
+ int not_indented;
+
+ {
+ do {
+ switch (bla)
+ {
+ case 1: if (foo)
+ bar;
+ }
+ } while (boo);
+ wrong;
+ }
+
+ int foo,
+ bar;
+ int foo;
+
+ #if defined(foo) \
+ && defined(bar)
+ char * xx = "asdf\
+ foo\
+ bor";
+ int x;
+
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+
+ void f()
+ {
+ #if defined(foo) \
+ && defined(bar)
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ {
+ int i;
+ char *foo = "asdf\
+ asdf\
+ asdf",
+ *bar;
+ }
+ #endif
+ }
+ #endif
+
+ int y; // comment
+ // comment
+
+ // comment
+
+ {
+ Constructor(int a,
+ int b ) : BaseClass(a)
+ {
+ }
+ }
+
+ void foo()
+ {
+ char one,
+ two;
+ struct bla piet,
+ jan;
+ enum foo kees,
+ jannie;
+ static unsigned sdf,
+ krap;
+ unsigned int piet,
+ jan;
+ int
+ kees,
+ jan;
+ }
+
+ {
+ t(int f,
+ int d); // )
+ d();
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b),
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) :
+ BaseClass(a)
+ {
+ }
+
+ Constructor::Constructor(int a,
+ int b ) /*x*/ : /*x*/ BaseClass(a),
+ member(b)
+ {
+ }
+
+ A::A(int a, int b)
+ : aa(a),
+ bb(b),
+ cc(c)
+ {
+ }
+
+ class CAbc :
+ public BaseClass1,
+ protected BaseClass2
+ {
+ int Test() { return FALSE; }
+ int Test1() { return TRUE; }
+
+ CAbc(int a, int b ) :
+ BaseClass(a)
+ {
+ switch(xxx)
+ {
+ case abc:
+ asdf();
+ break;
+
+ case 999:
+ baer();
+ break;
+ }
+ }
+
+ public: // <-- this was incorrectly indented before!!
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ class CAbc : public BaseClass1,
+ protected BaseClass2
+ {
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ {
+ 123,
+ 456
+ },
+ {
+ 123,
+ 456
+ }
+ };
+
+ static struct
+ {
+ int a;
+ int b;
+ } variable[COUNT] =
+ {
+ { 123, 456 },
+ { 123, 456 }
+ };
+
+ void asdf() /* ind_maxparen may cause trouble here */
+ {
+ if ((0
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1
+ && 1)) break;
+ }
+
+ foo()
+ {
+ a = cond ? foo() : asdf
+ + asdf;
+
+ a = cond ?
+ foo() : asdf
+ + asdf;
+ }
+
+ int main(void)
+ {
+ if (a)
+ if (b)
+ 2;
+ else 3;
+ next_line_of_code();
+ }
+
+ barry()
+ {
+ Foo::Foo (int one,
+ int two)
+ : something(4)
+ {}
+ }
+
+ barry()
+ {
+ Foo::Foo (int one, int two)
+ : something(4)
+ {}
+ }
+
+ Constructor::Constructor(int a,
+ int b
+ ) :
+ BaseClass(a,
+ b,
+ c),
+ mMember(b)
+ {
+ }
+ int main ()
+ {
+ if (lala)
+ do
+ ++(*lolo);
+ while (lili
+ && lele);
+ lulu;
+ }
+
+ int main ()
+ {
+ switch (c)
+ {
+ case 'c': if (cond)
+ {
+ }
+ }
+ }
+
+ main()
+ {
+ (void) MyFancyFuasdfadsfnction(
+ argument);
+ }
+
+ main()
+ {
+ char foo[] = "/*";
+ /* as
+ df */
+ hello
+ }
+
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+
+ void getstring() {
+ /* Raw strings */
+ const char* s = R"(
+ test {
+ # comment
+ field: 123
+ }
+ )";
+ }
+
+ void getstring() {
+ const char* s = R"foo(
+ test {
+ # comment
+ field: 123
+ }
+ )foo";
+ }
+
+ {
+ int a[4] = {
+ [0] = 0,
+ [1] = 1,
+ [2] = 2,
+ [3] = 3,
+ };
+ }
+
+ {
+ a = b[2]
+ + 3;
+ }
+
+ {
+ if (1)
+ /* aaaaa
+ * bbbbb
+ */
+ a = 1;
+ }
+
+ void func()
+ {
+ switch (foo)
+ {
+ case (bar):
+ if (baz())
+ quux();
+ break;
+ case (shmoo):
+ if (!bar)
+ {
+ }
+ case (foo1):
+ switch (bar)
+ {
+ case baz:
+ baz_f();
+ break;
+ }
+ break;
+ default:
+ baz();
+ baz();
+ break;
+ }
+ }
+
+ /* end of AUTO */
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_2()
+ new
+ setl cindent ts=4 sw=4
+ setl tw=0 noai fo=croq
+ &wm = &columns - 20
+
+ var code =<< trim [CODE]
+ {
+
+ /* this is
+ * a real serious important big
+ * comment
+ */
+ /* insert " about life, the universe, and the rest" after "serious" */
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('serious', 'e')
+ normal a about life, the universe, and the rest
+
+ var expected =<< trim [CODE]
+ {
+
+ /* this is
+ * a real serious
+ * about life, the
+ * universe, and the
+ * rest important big
+ * comment
+ */
+ /* insert " about life, the universe, and the rest" after "serious" */
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+ set wm&
+
+ bwipe!
+enddef
+
+def Test_cindent_3()
+ new
+ setl nocindent ts=4 sw=4
+
+ var code =<< trim [CODE]
+ {
+ /*
+ * Testing for comments, without 'cin' set
+ */
+
+ /*
+ * what happens here?
+ */
+
+ /*
+ the end of the comment, try inserting a line below */
+
+ /* how about
+ this one */
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('comments')
+ normal joabout life
+ search('happens')
+ normal jothere
+ search('below')
+ normal oline
+ search('this')
+ normal Ohello
+
+ var expected =<< trim [CODE]
+ {
+ /*
+ * Testing for comments, without 'cin' set
+ */
+ about life
+
+ /*
+ * what happens here?
+ */
+ there
+
+ /*
+ the end of the comment, try inserting a line below */
+ line
+
+ /* how about
+ hello
+ this one */
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_4()
+ new
+ setl cindent ts=4 sw=4
+
+ var code =<< trim [CODE]
+ {
+ var = this + that + vec[0] * vec[0]
+ + vec[1] * vec[1]
+ + vec2[2] * vec[2];
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('vec2')
+ normal ==
+
+ var expected =<< trim [CODE]
+ {
+ var = this + that + vec[0] * vec[0]
+ + vec[1] * vec[1]
+ + vec2[2] * vec[2];
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_5()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=}4
+
+ var code =<< trim [CODE]
+ {
+ asdf asdflkajds f;
+ if (tes & ting) {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing1;
+ if (tes & ting)
+ {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing2;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('testing1')
+ exe "normal k2==/testing2\<CR>"
+ normal k2==
+
+ var expected =<< trim [CODE]
+ {
+ asdf asdflkajds f;
+ if (tes & ting) {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing1;
+ if (tes & ting)
+ {
+ asdf asdf asdf ;
+ asdfa sdf asdf;
+ }
+ testing2;
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_6()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,)20
+
+ var code =<< trim [CODE]
+ main ( int first_par, /*
+ * Comment for
+ * first par
+ */
+ int second_par /*
+ * Comment for
+ * second par
+ */
+ )
+ {
+ func( first_par, /*
+ * Comment for
+ * first par
+ */
+ second_par /*
+ * Comment for
+ * second par
+ */
+ );
+
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('main')
+ normal =][
+
+ var expected =<< trim [CODE]
+ main ( int first_par, /*
+ * Comment for
+ * first par
+ */
+ int second_par /*
+ * Comment for
+ * second par
+ */
+ )
+ {
+ func( first_par, /*
+ * Comment for
+ * first par
+ */
+ second_par /*
+ * Comment for
+ * second par
+ */
+ );
+
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_7()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=es,n0s
+
+ var code =<< trim [CODE]
+ main(void)
+ {
+ /* Make sure that cino=X0s is not parsed like cino=Xs. */
+ if (cond)
+ foo();
+ else
+ {
+ bar();
+ }
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('main')
+ normal =][
+
+ var expected =<< trim [CODE]
+ main(void)
+ {
+ /* Make sure that cino=X0s is not parsed like cino=Xs. */
+ if (cond)
+ foo();
+ else
+ {
+ bar();
+ }
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_8()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ var code =<< trim [CODE]
+
+ {
+ do
+ {
+ if ()
+ {
+ if ()
+ asdf;
+ else
+ asdf;
+ }
+ } while ();
+ cmd; /* this should go under the } */
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+
+ {
+ do
+ {
+ if ()
+ {
+ if ()
+ asdf;
+ else
+ asdf;
+ }
+ } while ();
+ cmd; /* this should go under the } */
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_9()
+ new
+ setl cindent ts=4 sw=4
+
+ var code =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() ) {
+ l();
+
+ } else { /* Start (two words) end */
+ m();
+ }
+
+ n();
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() ) {
+ l();
+
+ } else { /* Start (two words) end */
+ m();
+ }
+
+ n();
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_10()
+ new
+ setl cindent ts=4 sw=4
+ setl cino={s,e-s
+
+ var code =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() )
+ {
+ l();
+ } else { /* Start (two words) end */
+ m();
+ }
+ n(); /* should be under the if () */
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+
+ void f()
+ {
+ if ( k() )
+ {
+ l();
+ } else { /* Start (two words) end */
+ m();
+ }
+ n(); /* should be under the if () */
+ }
+
+ [CODE]
+
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_11()
+ new
+ setl cindent ts=4 sw=4
+ setl cino={s,fs
+
+ var code =<< trim [CODE]
+ void bar(void)
+ {
+ static array[2][2] =
+ {
+ { 1, 2 },
+ { 3, 4 },
+ }
+
+ while (a)
+ {
+ foo(&a);
+ }
+
+ {
+ int a;
+ {
+ a = a + 1;
+ }
+ }
+ b = a;
+ }
+
+ void func(void)
+ {
+ a = 1;
+ {
+ b = 2;
+ }
+ c = 3;
+ d = 4;
+ }
+ /* foo */
+ [CODE]
+
+ append(0, code)
+ normal gg
+ exe "normal ]]=/ foo\<CR>"
+
+ var expected =<< trim [CODE]
+ void bar(void)
+ {
+ static array[2][2] =
+ {
+ { 1, 2 },
+ { 3, 4 },
+ }
+
+ while (a)
+ {
+ foo(&a);
+ }
+
+ {
+ int a;
+ {
+ a = a + 1;
+ }
+ }
+ b = a;
+ }
+
+ void func(void)
+ {
+ a = 1;
+ {
+ b = 2;
+ }
+ c = 3;
+ d = 4;
+ }
+ /* foo */
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_12()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ var code =<< trim [CODE]
+ a()
+ {
+ do {
+ a = a +
+ a;
+ } while ( a ); /* add text under this line */
+ if ( a )
+ a;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('while')
+ normal ohere
+
+ var expected =<< trim [CODE]
+ a()
+ {
+ do {
+ a = a +
+ a;
+ } while ( a ); /* add text under this line */
+ here
+ if ( a )
+ a;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_13()
+ new
+ setl cindent ts=4 sw=4
+ setl cino= com=
+
+ var code =<< trim [CODE]
+ a()
+ {
+ label1:
+ /* hmm */
+ // comment
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('comment')
+ exe "normal olabel2: b();\rlabel3 /* post */:\r/* pre */ label4:\r"
+ .. "f(/*com*/);\rif (/*com*/)\rcmd();"
+
+ var expected =<< trim [CODE]
+ a()
+ {
+ label1:
+ /* hmm */
+ // comment
+ label2: b();
+ label3 /* post */:
+ /* pre */ label4:
+ f(/*com*/);
+ if (/*com*/)
+ cmd();
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_14()
+ new
+ setl cindent ts=4 sw=4
+ setl comments& comments^=s:/*,m:**,ex:*/
+
+ var code =<< trim [CODE]
+ /*
+ * A simple comment
+ */
+
+ /*
+ ** A different comment
+ */
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('simple')
+ normal =5j
+
+ var expected =<< trim [CODE]
+ /*
+ * A simple comment
+ */
+
+ /*
+ ** A different comment
+ */
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_15()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=c0
+ setl comments& comments-=s1:/* comments^=s0:/*
+
+ var code =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_16()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=c0,C1
+ setl comments& comments-=s1:/* comments^=s0:/*
+
+ var code =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+
+ /*********
+ A comment.
+ *********/
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_17()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_18()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_19()
+ new
+ setl cindent ts=4 sw=4
+ set cino=(s,U1
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_20()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_21()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,w1
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_22()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_23()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(s,m1
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ c = c1 && (
+ c2 ||
+ c3
+ ) && c4;
+ if (
+ c1 && c2
+ )
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_24()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=b1
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ switch (x)
+ {
+ case 1:
+ a = b;
+ break;
+ default:
+ a = 0;
+ break;
+ }
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ switch (x)
+ {
+ case 1:
+ a = b;
+ break;
+ default:
+ a = 0;
+ break;
+ }
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_25()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,W5
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ invokeme(
+ argu,
+ ment);
+ invokeme(
+ argu,
+ ment
+ );
+ invokeme(argu,
+ ment
+ );
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ invokeme(
+ argu,
+ ment);
+ invokeme(
+ argu,
+ ment
+ );
+ invokeme(argu,
+ ment
+ );
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_26()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=/6
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_27()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=
+
+ var code =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ exe "normal ]]/comment 1/+1\<CR>=="
+
+ var expected =<< trim [CODE]
+ void f()
+ {
+ statement;
+ // comment 1
+ // comment 2
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_28()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=g0
+
+ var code =<< trim [CODE]
+ class CAbc
+ {
+ int Test() { return FALSE; }
+
+ public: // comment
+ void testfall();
+ protected:
+ void testfall();
+ };
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ class CAbc
+ {
+ int Test() { return FALSE; }
+
+ public: // comment
+ void testfall();
+ protected:
+ void testfall();
+ };
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_29()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,gs,hs
+
+ var code =<< trim [CODE]
+ class Foo : public Bar
+ {
+ public:
+ virtual void method1(void) = 0;
+ virtual void method2(int arg1,
+ int arg2,
+ int arg3) = 0;
+ };
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ class Foo : public Bar
+ {
+ public:
+ virtual void method1(void) = 0;
+ virtual void method2(int arg1,
+ int arg2,
+ int arg3) = 0;
+ };
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_30()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=+20
+
+ var code =<< [CODE]
+ void
+foo()
+{
+ if (a)
+ {
+ } else
+ asdf;
+}
+[CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< [CODE]
+ void
+foo()
+{
+ if (a)
+ {
+ } else
+ asdf;
+}
+
+[CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_31()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,W2s
+
+ var code =<< trim [CODE]
+
+ {
+ averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
+ asdasdf,
+ func(asdf,
+ asdfadsf),
+ asdfasdf
+ );
+
+ /* those are ugly, but consequent */
+
+ func()->asd(asdasdf,
+ averylongfunctionname(
+ abc,
+ dec)->averylongfunctionname(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf
+ ),
+ asdasdf
+ );
+
+ averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
+ abc,
+ dec)->asdfasdfasdf(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf),
+ asdasdf
+ );
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+
+ {
+ averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd(
+ asdasdf,
+ func(asdf,
+ asdfadsf),
+ asdfasdf
+ );
+
+ /* those are ugly, but consequent */
+
+ func()->asd(asdasdf,
+ averylongfunctionname(
+ abc,
+ dec)->averylongfunctionname(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf
+ ),
+ asdasdf
+ );
+
+ averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf(
+ abc,
+ dec)->asdfasdfasdf(
+ asdfadsf,
+ asdfasdf,
+ asdfasdf,
+ ),
+ func(asdfadf,
+ asdfasdf),
+ asdasdf
+ );
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_32()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=M1
+
+ var code =<< trim [CODE]
+ int main ()
+ {
+ if (cond1 &&
+ cond2
+ )
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ int main ()
+ {
+ if (cond1 &&
+ cond2
+ )
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_33()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0,ts
+
+ var code =<< trim [CODE]
+ void func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal 2j=][
+
+ var expected =<< trim [CODE]
+ void func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_34()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=(0
+
+ var code =<< trim [CODE]
+
+ void
+ func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal =][
+
+ var expected =<< trim [CODE]
+
+ void
+ func(int a
+ #if defined(FOO)
+ , int b
+ , int c
+ #endif
+ )
+ {
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_35()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if(x==y)
+ if(y==z)
+ foo=1;
+ else { bar=1;
+ baz=2;
+ }
+ printf("Foo!\n");
+ }
+
+ void func1(void)
+ {
+ char* tab[] = {"foo", "bar",
+ "baz", "quux",
+ "this line used", "to be indented incorrectly"};
+ foo();
+ }
+
+ void func2(void)
+ {
+ int tab[] =
+ {1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("This line used to be indented incorrectly.\n");
+ }
+
+ int foo[]
+ #ifdef BAR
+
+ = { 1, 2, 3,
+ 4, 5, 6 }
+
+ #endif
+ ;
+ int baz;
+
+ void func3(void)
+ {
+ int tab[] = {
+ 1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("Don't you dare indent this line incorrectly!\n");
+ }
+
+ void
+ func4(a, b,
+ c)
+ int a;
+ int b;
+ int c;
+ {
+ }
+
+ void
+ func5(
+ int a,
+ int b)
+ {
+ }
+
+ void
+ func6(
+ int a)
+ {
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=7][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if(x==y)
+ if(y==z)
+ foo=1;
+ else { bar=1;
+ baz=2;
+ }
+ printf("Foo!\n");
+ }
+
+ void func1(void)
+ {
+ char* tab[] = {"foo", "bar",
+ "baz", "quux",
+ "this line used", "to be indented incorrectly"};
+ foo();
+ }
+
+ void func2(void)
+ {
+ int tab[] =
+ {1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("This line used to be indented incorrectly.\n");
+ }
+
+ int foo[]
+ #ifdef BAR
+
+ = { 1, 2, 3,
+ 4, 5, 6 }
+
+ #endif
+ ;
+ int baz;
+
+ void func3(void)
+ {
+ int tab[] = {
+ 1, 2,
+ 3, 4,
+ 5, 6};
+
+ printf("Don't you dare indent this line incorrectly!\n");
+ }
+
+ void
+ func4(a, b,
+ c)
+ int a;
+ int b;
+ int c;
+ {
+ }
+
+ void
+ func5(
+ int a,
+ int b)
+ {
+ }
+
+ void
+ func6(
+ int a)
+ {
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_36()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+ setl cino+=l1
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ int tab[] =
+ {
+ 1, 2, 3,
+ 4, 5, 6};
+
+ printf("Indent this line correctly!\n");
+
+ switch (foo)
+ {
+ case bar:
+ printf("bar");
+ break;
+ case baz: {
+ printf("baz");
+ break;
+ }
+ case quux:
+ printf("But don't break the indentation of this instruction\n");
+ break;
+ }
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ int tab[] =
+ {
+ 1, 2, 3,
+ 4, 5, 6};
+
+ printf("Indent this line correctly!\n");
+
+ switch (foo)
+ {
+ case bar:
+ printf("bar");
+ break;
+ case baz: {
+ printf("baz");
+ break;
+ }
+ case quux:
+ printf("But don't break the indentation of this instruction\n");
+ break;
+ }
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_37()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ cout << "a"
+ << "b"
+ << ") :"
+ << "c";
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ cout << "a"
+ << "b"
+ << ") :"
+ << "c";
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_38()
+ new
+ setl cindent ts=4 sw=4
+ setl com=s1:/*,m:*,ex:*/
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ /*
+ * This is a comment.
+ */
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]3jofoo();
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ /*
+ * This is a comment.
+ */
+ foo();
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_39()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ for (int i = 0; i < 10; ++i)
+ if (i & 1) {
+ foo(1);
+ } else
+ foo(0);
+ baz();
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ for (int i = 0; i < 10; ++i)
+ if (i & 1) {
+ foo(1);
+ } else
+ foo(0);
+ baz();
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_40()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_41()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(s
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_42()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(s,U1
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ c = c1 &&
+ (
+ c2 ||
+ c3
+ ) && c4;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_43()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0,W4
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+
+ a_long_line(
+ argument,
+ argument);
+ a_short_line(argument,
+ argument);
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+
+ a_long_line(
+ argument,
+ argument);
+ a_short_line(argument,
+ argument);
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_44()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,u2
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_45()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2s,(0,w1
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ if (c123456789
+ && (c22345
+ || c3))
+ printf("foo\n");
+
+ if ( c1
+ && ( c2
+ || c3))
+ foo;
+ func( c1
+ && ( c2
+ || c3))
+ foo;
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_46()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=k2,(s
+
+ var code =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ void func(void)
+ {
+ if (condition1
+ && condition2)
+ action();
+ function(argument1
+ && argument2);
+
+ if (c1 && (c2 ||
+ c3))
+ foo;
+ if (c1 &&
+ (c2 || c3))
+ {
+ }
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_47()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=N-s
+
+ var code =<< trim [CODE]
+ NAMESPACESTART
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace test::cpp17
+ {
+ 111111111111111111;
+ }
+ namespace ::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test::incorrectcpp17::
+ {
+ 111111111111111111;
+ }
+ namespace test:incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test:::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+ NAMESPACEEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ call search('^NAMESPACESTART')
+ exe "normal =/^NAMESPACEEND\n"
+
+ var expected =<< trim [CODE]
+ NAMESPACESTART
+ /* valid namespaces with normal indent */
+ namespace
+ {
+ {
+ 111111111111;
+ }
+ }
+ namespace /* test */
+ {
+ 11111111111111111;
+ }
+ namespace // test
+ {
+ 111111111111111111;
+ }
+ namespace
+ {
+ 111111111111111111;
+ }
+ namespace test
+ {
+ 111111111111111111;
+ }
+ namespace test::cpp17
+ {
+ 111111111111111111;
+ }
+ namespace ::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test::incorrectcpp17::
+ {
+ 111111111111111111;
+ }
+ namespace test:incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace test:::incorrectcpp17
+ {
+ 111111111111111111;
+ }
+ namespace{
+ 111111111111111111;
+ }
+ namespace test{
+ 111111111111111111;
+ }
+ namespace {
+ 111111111111111111;
+ }
+ namespace test {
+ 111111111111111111;
+ namespace test2 {
+ 22222222222222222;
+ }
+ }
+ inline namespace {
+ 111111111111111111;
+ }
+ inline /* test */ namespace {
+ 111111111111111111;
+ }
+ inline/* test */namespace {
+ 111111111111111111;
+ }
+
+ /* invalid namespaces use block indent */
+ namespace test test2 {
+ 111111111111111111111;
+ }
+ namespace11111111111 {
+ 111111111111;
+ }
+ namespace() {
+ 1111111111111;
+ }
+ namespace()
+ {
+ 111111111111111111;
+ }
+ namespace test test2
+ {
+ 1111111111111111111;
+ }
+ namespace111111111
+ {
+ 111111111111111111;
+ }
+ inlinenamespace {
+ 111111111111111111;
+ }
+ NAMESPACEEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_48()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ var code =<< trim [CODE]
+ JSSTART
+ var bar = {
+ foo: {
+ that: this,
+ some: ok,
+ },
+ "bar":{
+ a : 2,
+ b: "123abc",
+ x: 4,
+ "y": 5
+ }
+ }
+ JSEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ var expected =<< trim [CODE]
+ JSSTART
+ var bar = {
+ foo: {
+ that: this,
+ some: ok,
+ },
+ "bar":{
+ a : 2,
+ b: "123abc",
+ x: 4,
+ "y": 5
+ }
+ }
+ JSEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_49()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ var code =<< trim [CODE]
+ JSSTART
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ JSEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ var expected =<< trim [CODE]
+ JSSTART
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ JSEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_50()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ var code =<< trim [CODE]
+ JSSTART
+ function bar() {
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ }
+ JSEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ var expected =<< trim [CODE]
+ JSSTART
+ function bar() {
+ var foo = [
+ 1,
+ 2,
+ 3
+ ];
+ }
+ JSEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_51()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ var code =<< trim [CODE]
+ JSSTART
+ (function($){
+
+ if (cond &&
+ cond) {
+ stmt;
+ }
+ window.something.left =
+ (width - 50 + offset) + "px";
+ var class_name='myclass';
+
+ function private_method() {
+ }
+
+ var public_method={
+ method: function(options,args){
+ private_method();
+ }
+ }
+
+ function init(options) {
+
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+
+ $.fn[class_name]=function() {
+
+ var _arguments=arguments;
+ return this.each(function(){
+
+ var options=$(this).data(class_name+'_public');
+ if (!options) {
+ init.apply(this,_arguments);
+
+ } else {
+ var method=public_method[_arguments[0]];
+
+ if (typeof(method)!='function') {
+ console.log(class_name+' has no method "'+_arguments[0]+'"');
+ return false;
+ }
+ _arguments[0]=options;
+ method.apply(this,_arguments);
+ }
+ });
+ }
+
+ })(jQuery);
+ JSEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ var expected =<< trim [CODE]
+ JSSTART
+ (function($){
+
+ if (cond &&
+ cond) {
+ stmt;
+ }
+ window.something.left =
+ (width - 50 + offset) + "px";
+ var class_name='myclass';
+
+ function private_method() {
+ }
+
+ var public_method={
+ method: function(options,args){
+ private_method();
+ }
+ }
+
+ function init(options) {
+
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+
+ $.fn[class_name]=function() {
+
+ var _arguments=arguments;
+ return this.each(function(){
+
+ var options=$(this).data(class_name+'_public');
+ if (!options) {
+ init.apply(this,_arguments);
+
+ } else {
+ var method=public_method[_arguments[0]];
+
+ if (typeof(method)!='function') {
+ console.log(class_name+' has no method "'+_arguments[0]+'"');
+ return false;
+ }
+ _arguments[0]=options;
+ method.apply(this,_arguments);
+ }
+ });
+ }
+
+ })(jQuery);
+ JSEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_52()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ var code =<< trim [CODE]
+ JSSTART
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ JSEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ var expected =<< trim [CODE]
+ JSSTART
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ JSEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_53()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1
+
+ var code =<< trim [CODE]
+ JSSTART
+ (function($){
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ })(jQuery);
+ JSEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ var expected =<< trim [CODE]
+ JSSTART
+ (function($){
+ function init(options) {
+ $(this).data(class_name+'_public',$.extend({},{
+ foo: 'bar',
+ bar: 2,
+ foobar: [
+ 1,
+ 2,
+ 3
+ ],
+ callback: function(){
+ return true;
+ }
+ }, options||{}));
+ }
+ })(jQuery);
+ JSEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_54()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=j1,J1,+2
+
+ var code =<< trim [CODE]
+ JSSTART
+ // Results of JavaScript indent
+ // 1
+ (function(){
+ var a = [
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 2
+ (function(){
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 3
+ (function(){
+ var a = [
+ 0 +
+ // comment 1
+ 5 *
+ /* comment 2 */
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 4
+ {
+ var a = [
+ 0,
+ 1
+ ];
+ var b;
+ var c;
+ }
+
+ // 5
+ {
+ var a = [
+ [
+ 0
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 6
+ {
+ var a = [
+ [
+ 0,
+ 1
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 7
+ {
+ var a = [
+ // [
+ 0,
+ // 1
+ // ],
+ 2,
+ 3
+ ];
+ }
+
+ // 8
+ var x = [
+ (function(){
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ })
+ ];
+
+ // 9
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+
+ // 10
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ JSEND
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('^JSSTART')
+ exe "normal =/^JSEND\n"
+
+ var expected =<< trim [CODE]
+ JSSTART
+ // Results of JavaScript indent
+ // 1
+ (function(){
+ var a = [
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 2
+ (function(){
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 3
+ (function(){
+ var a = [
+ 0 +
+ // comment 1
+ 5 *
+ /* comment 2 */
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+ }())
+
+ // 4
+ {
+ var a = [
+ 0,
+ 1
+ ];
+ var b;
+ var c;
+ }
+
+ // 5
+ {
+ var a = [
+ [
+ 0
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 6
+ {
+ var a = [
+ [
+ 0,
+ 1
+ ],
+ 2,
+ 3
+ ];
+ }
+
+ // 7
+ {
+ var a = [
+ // [
+ 0,
+ // 1
+ // ],
+ 2,
+ 3
+ ];
+ }
+
+ // 8
+ var x = [
+ (function(){
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ })
+ ];
+
+ // 9
+ var a = [
+ 0 +
+ 5 *
+ 9 *
+ 'a',
+ 'b',
+ 0 +
+ 5 *
+ 9 *
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ 'h',
+ 'i'
+ ];
+
+ // 10
+ var a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g,
+ h,
+ i;
+ JSEND
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_55()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ var code =<< trim [CODE]
+ /* start of define */
+ {
+ }
+ #define AAA \
+ BBB\
+ CCC
+
+ #define CNT \
+ 1 + \
+ 2 + \
+ 4
+ /* end of define */
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('start of define')
+ exe "normal =/end of define\n"
+
+ var expected =<< trim [CODE]
+ /* start of define */
+ {
+ }
+ #define AAA \
+ BBB\
+ CCC
+
+ #define CNT \
+ 1 + \
+ 2 + \
+ 4
+ /* end of define */
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_56()
+ new
+ setl cindent ts=4 sw=4
+ setl cino&
+
+ var code =<< trim [CODE]
+ {
+ a = second/*bug*/*line;
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ search('a = second')
+ normal ox
+
+ var expected =<< trim [CODE]
+ {
+ a = second/*bug*/*line;
+ x
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+" this was going beyond the end of the line.
+def Test_cindent_case()
+ new
+ setline(1, 'case x: // x')
+ set cindent
+ norm! f:a:
+ assert_equal('case x:: // x', getline(1))
+ set cindent&
+ bwipe!
+enddef
+
+" Test for changing multiple lines (using c) with cindent
+def Test_cindent_change_multline()
+ new
+ setlocal cindent
+ setline(1, ['if (a)', '{', ' i = 1;', '}'])
+ normal! jc3jm = 2;
+ assert_equal("\tm = 2;", getline(2))
+ bwipe!
+enddef
+
+" This was reading past the end of the line
+def Test_cindent_check_funcdecl()
+ new
+ sil norm o0('\0=L
+ bwipe!
+enddef
+
+def Test_cindent_scopedecls()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=g0
+ setl cinsd+=public\ slots,signals
+
+ var code =<< trim [CODE]
+ class Foo
+ {
+ public:
+ virtual void foo() = 0;
+ public slots:
+ void onBar();
+ signals:
+ void baz();
+ private:
+ int x;
+ };
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal ]]=][
+
+ var expected =<< trim [CODE]
+ class Foo
+ {
+ public:
+ virtual void foo() = 0;
+ public slots:
+ void onBar();
+ signals:
+ void baz();
+ private:
+ int x;
+ };
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_cindent_pragma()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=Ps
+
+ var code =<< trim [CODE]
+ {
+ #pragma omp parallel
+ {
+ #pragma omp task
+ foo();
+ # pragma omp taskwait
+ }
+ }
+ [CODE]
+
+ append(0, code)
+ normal gg
+ normal =G
+
+ var expected =<< trim [CODE]
+ {
+ #pragma omp parallel
+ {
+ #pragma omp task
+ foo();
+ # pragma omp taskwait
+ }
+ }
+
+ [CODE]
+ assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+enddef
+
+def Test_backslash_at_end_of_line()
+ new
+ exe "norm v>O'\\\<C-m>-"
+ exe "norm \<C-q>="
+ bwipe!
+enddef
+
+def Test_find_brace_backwards()
+ # this was looking beyond the end of the line
+ new
+ norm R/*
+ norm o0{
+ norm o//
+ norm V{=
+ assert_equal(['/*', ' 0{', '//'], getline(1, 3))
+ bwipe!
+enddef
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cjk_linebreak.vim b/src/testdir/test_cjk_linebreak.vim
new file mode 100644
index 0000000..17c5916
--- /dev/null
+++ b/src/testdir/test_cjk_linebreak.vim
@@ -0,0 +1,101 @@
+" Test for CJK linebreak
+
+scriptencoding utf-8
+
+func Run_cjk_linebreak_after(rigorous)
+ set textwidth=12
+ for punct in [
+ \ '!', '%', ')', ',', ':', ';', '>', '?', ']', '}', '’', 'â€', '†', '‡',
+ \ '…', '‰', '‱', '‼', 'â‡', 'âˆ', 'â‰', '℃', '℉', 'ã€', '。', '〉', '》',
+ \ 'ã€', 'ã€', '】', '〕', '〗', '〙', '〛', 'ï¼', ')', ',', '.', ':',
+ \ 'ï¼›', '?', 'ï¼½', 'ï½']
+ call setline('.', '这是一个测试' .. punct.'试试 CJK è¡Œç¦åˆ™è¡¥ä¸ã€‚')
+ normal gqq
+ if a:rigorous
+ call assert_equal('这是一个测', getline(1))
+ else
+ call assert_equal('这是一个测试' .. punct, getline(1))
+ endif
+ %d_
+ endfor
+endfunc
+
+func Test_cjk_linebreak_after()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_after(0)
+endfunc
+
+func Test_cjk_linebreak_after_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_after(1)
+endfunc
+
+func Run_cjk_linebreak_before()
+ set textwidth=12
+ for punct in [
+ \ '(', '<', '[', '`', '{', '‘', '“', '〈', '《', '「', '『', 'ã€', '〔',
+ \ '〖', '〘', '〚', '(', '[', '{']
+ call setline('.', '这是个测试' .. punct.'试试 CJK è¡Œç¦åˆ™è¡¥ä¸ã€‚')
+ normal gqq
+ call assert_equal('这是个测试', getline(1))
+ %d_
+ endfor
+endfunc
+
+func Test_cjk_linebreak_before()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_before()
+endfunc
+
+func Test_cjk_linebreak_before_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_before()
+endfunc
+
+func Run_cjk_linebreak_nobetween(rigorous)
+ " …… must not start a line
+ call setline('.', '这是个测试……试试 CJK è¡Œç¦åˆ™è¡¥ä¸ã€‚')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ if a:rigorous
+ call assert_equal('这是个测', getline(1))
+ else
+ call assert_equal('这是个测试……', getline(1))
+ endif
+ %d_
+
+ call setline('.', '这是一个测试……试试 CJK è¡Œç¦åˆ™è¡¥ä¸ã€‚')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ call assert_equal('这是一个测', getline(1))
+ %d_
+
+ " but —— can
+ call setline('.', '这是个测试——试试 CJK è¡Œç¦åˆ™è¡¥ä¸ã€‚')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ call assert_equal('这是个测试', getline(1))
+endfunc
+
+func Test_cjk_linebreak_nobetween()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_nobetween(0)
+endfunc
+
+func Test_cjk_linebreak_nobetween_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_nobetween(1)
+endfunc
+
+func Test_cjk_linebreak_join_punct()
+ for punct in ['——', '〗', ',', '。', '……']
+ call setline(1, '文本文本' .. punct)
+ call setline(2, 'English')
+ set formatoptions=croqn2mB1j
+ normal ggJ
+ call assert_equal('文本文本' .. punct.'English', getline(1))
+ %d_
+ endfor
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_clientserver.vim b/src/testdir/test_clientserver.vim
new file mode 100644
index 0000000..64c9ab8
--- /dev/null
+++ b/src/testdir/test_clientserver.vim
@@ -0,0 +1,196 @@
+" Tests for the +clientserver feature.
+
+source check.vim
+CheckFeature job
+
+if !has('clientserver')
+ call assert_fails('call remote_startserver("local")', 'E942:')
+endif
+
+CheckFeature clientserver
+
+source shared.vim
+
+func Check_X11_Connection()
+ if has('x11')
+ CheckEnv DISPLAY
+ try
+ call remote_send('xxx', '')
+ catch
+ if v:exception =~ 'E240:'
+ throw 'Skipped: no connection to the X server'
+ endif
+ " ignore other errors
+ endtry
+ endif
+endfunc
+
+func Test_client_server()
+ let g:test_is_flaky = 1
+ let cmd = GetVimCommand()
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+ call Check_X11_Connection()
+
+ let name = 'XVIMTEST'
+ let cmd .= ' --servername ' . name
+ let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+
+ " Takes a short while for the server to be active.
+ " When using valgrind it takes much longer.
+ call WaitForAssert({-> assert_match(name, serverlist())})
+
+ if !has('win32')
+ if RunVim([], [], '--serverlist >Xtest_serverlist')
+ let lines = readfile('Xtest_serverlist')
+ call assert_true(index(lines, 'XVIMTEST') >= 0)
+ endif
+ call delete('Xtest_serverlist')
+ endif
+
+ eval name->remote_foreground()
+
+ call remote_send(name, ":let testvar = 'yes'\<CR>")
+ call WaitFor('remote_expr("' . name . '", "exists(\"testvar\") ? testvar : \"\"", "", 1) == "yes"')
+ call assert_equal('yes', remote_expr(name, "testvar", "", 2))
+ call assert_fails("let x=remote_expr(name, '2+x')", 'E449:')
+ call assert_fails("let x=remote_expr('[], '2+2')", 'E116:')
+
+ if has('unix') && has('gui') && !has('gui_running')
+ " Running in a terminal and the GUI is available: Tell the server to open
+ " the GUI and check that the remote command still works.
+ " Need to wait for the GUI to start up, otherwise the send hangs in trying
+ " to send to the terminal window.
+ if has('gui_motif')
+ " For this GUI ignore the 'failed to create input context' error.
+ call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>")
+ else
+ call remote_send(name, ":gui -f\<CR>")
+ endif
+ " Wait for the server to be up and answering requests.
+ " When using valgrind this can be very, very slow.
+ sleep 1
+ call WaitForAssert({-> assert_match('\d', name->remote_expr("v:version", "", 1))}, 10000)
+
+ call remote_send(name, ":let testvar = 'maybe'\<CR>")
+ call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))})
+ endif
+
+ call assert_fails('call remote_send("XXX", ":let testvar = ''yes''\<CR>")', 'E241:')
+
+ call writefile(['one'], 'Xclientfile')
+ let cmd = GetVimProg() .. ' --servername ' .. name .. ' --remote Xclientfile'
+ call system(cmd)
+ call WaitForAssert({-> assert_equal('Xclientfile', remote_expr(name, "bufname()", "", 2))})
+ call WaitForAssert({-> assert_equal('one', remote_expr(name, "getline(1)", "", 2))})
+ call writefile(['one', 'two'], 'Xclientfile')
+ call system(cmd)
+ call WaitForAssert({-> assert_equal('two', remote_expr(name, "getline(2)", "", 2))})
+ call delete('Xclientfile')
+
+ " Expression evaluated locally.
+ if v:servername == ''
+ eval 'MYSELF'->remote_startserver()
+ " May get MYSELF1 when running the test again.
+ call assert_match('MYSELF', v:servername)
+ call assert_fails("call remote_startserver('MYSELF')", 'E941:')
+ endif
+ let g:testvar = 'myself'
+ call assert_equal('myself', remote_expr(v:servername, 'testvar'))
+ call remote_send(v:servername, ":let g:testvar2 = 75\<CR>")
+ call feedkeys('', 'x')
+ call assert_equal(75, g:testvar2)
+ call assert_fails('let v = remote_expr(v:servername, "/2")', ['E15:.*/2'])
+
+ call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid')
+ call assert_equal('got it', g:myserverid->remote_read(2))
+
+ call remote_send(name, ":eval expand('<client>')->server2client('another')\<CR>", 'g:myserverid')
+ let peek_result = 'nothing'
+ let r = g:myserverid->remote_peek('peek_result')
+ " unpredictable whether the result is already available.
+ if r > 0
+ call assert_equal('another', peek_result)
+ elseif r == 0
+ call assert_equal('nothing', peek_result)
+ else
+ call assert_report('remote_peek() failed')
+ endif
+ let g:peek_result = 'empty'
+ call WaitFor('remote_peek(g:myserverid, "g:peek_result") > 0')
+ call assert_equal('another', g:peek_result)
+ call assert_equal('another', remote_read(g:myserverid, 2))
+
+ if !has('gui_running')
+ " In GUI vim, the following tests display a dialog box
+
+ let cmd = GetVimProg() .. ' --servername ' .. name
+
+ " Run a separate instance to send a command to the server
+ call remote_expr(name, 'execute("only")')
+ call system(cmd .. ' --remote-send ":new Xclientfile<CR>"')
+ call assert_equal('2', remote_expr(name, 'winnr("$")'))
+ call assert_equal('Xclientfile', remote_expr(name, 'winbufnr(1)->bufname()'))
+ call remote_expr(name, 'execute("only")')
+
+ " Invoke a remote-expr. On MS-Windows, the returned value has a carriage
+ " return.
+ let l = system(cmd .. ' --remote-expr "2 + 2"')
+ call assert_equal(['4'], split(l, "\n"))
+
+ " Edit multiple files using --remote
+ call system(cmd .. ' --remote Xclientfile1 Xclientfile2 Xclientfile3')
+ call assert_match(".*Xclientfile1\n.*Xclientfile2\n.*Xclientfile3\n", remote_expr(name, 'argv()'))
+ eval name->remote_send(":%bw!\<CR>")
+
+ " Edit files in separate tab pages
+ call system(cmd .. ' --remote-tab Xclientfile1 Xclientfile2 Xclientfile3')
+ call WaitForAssert({-> assert_equal('3', remote_expr(name, 'tabpagenr("$")'))})
+ call assert_match('.*\<Xclientfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])'))
+ eval name->remote_send(":%bw!\<CR>")
+
+ " Edit a file using --remote-wait
+ eval name->remote_send(":source $VIMRUNTIME/plugin/rrhelper.vim\<CR>")
+ call system(cmd .. ' --remote-wait +enew Xclientfile1')
+ call assert_match('.*\<Xclientfile1', remote_expr(name, 'bufname("#")'))
+ eval name->remote_send(":%bw!\<CR>")
+
+ " Edit files using --remote-tab-wait
+ call system(cmd .. ' --remote-tabwait +tabonly\|enew Xclientfile1 Xclientfile2')
+ call assert_equal('1', remote_expr(name, 'tabpagenr("$")'))
+ eval name->remote_send(":%bw!\<CR>")
+
+ " Error cases
+ if v:lang == "C" || v:lang =~ '^[Ee]n'
+ let l = split(system(cmd .. ' --remote +pwd'), "\n")
+ call assert_equal("Argument missing after: \"+pwd\"", l[1])
+ endif
+ let l = system(cmd .. ' --remote-expr "abcd"')
+ call assert_match('^E449: ', l)
+ endif
+
+ eval name->remote_send(":%bw!\<CR>")
+ eval name->remote_send(":qa!\<CR>")
+ try
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ finally
+ if job_status(job) != 'dead'
+ call assert_report('Server did not exit')
+ call job_stop(job, 'kill')
+ endif
+ endtry
+
+ call assert_fails('call remote_startserver([])', 'E730:')
+ call assert_fails("let x = remote_peek([])", 'E730:')
+ call assert_fails("let x = remote_read('vim10')",
+ \ has('unix') ? ['E573:.*vim10'] : 'E277:')
+ call assert_fails("call server2client('abc', 'xyz')",
+ \ has('unix') ? ['E573:.*abc'] : 'E258:')
+endfunc
+
+" Uncomment this line to get a debugging log
+" call ch_logfile('channellog', 'w')
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_close_count.vim b/src/testdir/test_close_count.vim
new file mode 100644
index 0000000..c0c3730
--- /dev/null
+++ b/src/testdir/test_close_count.vim
@@ -0,0 +1,176 @@
+" Tests for :[count]close! command
+
+func Test_close_count()
+ enew! | only
+
+ let wids = [win_getid()]
+ for i in range(5)
+ new
+ call add(wids, win_getid())
+ endfor
+
+ 4wincmd w
+ close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids)
+
+ 1close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids)
+
+ $close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[3], wids[1]], ids)
+
+ 1wincmd w
+ 2close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[1]], ids)
+
+ 1wincmd w
+ new
+ call add(wids, win_getid())
+ new
+ call add(wids, win_getid())
+ 2wincmd w
+ -1close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[6], wids[4], wids[1]], ids)
+
+ 2wincmd w
+ +1close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[6], wids[4]], ids)
+
+ only!
+endfunc
+
+" Tests for :[count]hide command
+func Test_hide_count()
+ enew! | only
+
+ let wids = [win_getid()]
+ for i in range(5)
+ new
+ call add(wids, win_getid())
+ endfor
+
+ 4wincmd w
+ .hide
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids)
+
+ 1hide
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids)
+
+ $hide
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[3], wids[1]], ids)
+
+ 1wincmd w
+ 2hide
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[1]], ids)
+
+ 1wincmd w
+ new
+ call add(wids, win_getid())
+ new
+ call add(wids, win_getid())
+ 3wincmd w
+ -hide
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[7], wids[4], wids[1]], ids)
+
+ 2wincmd w
+ +hide
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[7], wids[4]], ids)
+
+ only!
+endfunc
+
+" Tests for :[count]close! command with 'hidden'
+func Test_hidden_close_count()
+ enew! | only
+
+ let wids = [win_getid()]
+ for i in range(5)
+ new
+ call add(wids, win_getid())
+ endfor
+
+ set hidden
+
+ $ hide
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[5], wids[4], wids[3], wids[2], wids[1]], ids)
+
+ $-1 close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[5], wids[4], wids[3], wids[1]], ids)
+
+ 1wincmd w
+ .+close!
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[5], wids[3], wids[1]], ids)
+
+ set nohidden
+ only!
+endfunc
+
+" Tests for 'CTRL-W c' command to close windows.
+func Test_winclose_command()
+ enew! | only
+
+ let wids = [win_getid()]
+ for i in range(5)
+ new
+ call add(wids, win_getid())
+ endfor
+
+ set hidden
+
+ 4wincmd w
+ exe "normal \<C-W>c"
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids)
+
+ exe "normal 1\<C-W>c"
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids)
+
+ exe "normal 9\<C-W>c"
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[3], wids[1]], ids)
+
+ 1wincmd w
+ exe "normal 2\<C-W>c"
+ let ids = []
+ windo call add(ids, win_getid())
+ call assert_equal([wids[4], wids[1]], ids)
+
+ set nohidden
+ only!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cmd_lists.vim b/src/testdir/test_cmd_lists.vim
new file mode 100644
index 0000000..77ae85e
--- /dev/null
+++ b/src/testdir/test_cmd_lists.vim
@@ -0,0 +1,70 @@
+" Test to verify that the cmd list in runtime/doc/index.txt contains all of
+" the commands in src/ex_cmds.h. It doesn't map the other way round because
+" index.txt contains some shorthands like :!! which are useful to list, but
+" they don't exist as an independent entry in src/ex_cmds.h.
+"
+" Currently this just checks for existence, and we aren't checking for whether
+" they are sorted in the index, or whether the substring needed (e.g.
+" 'defc[ompile]') is correct or not.
+
+func Test_cmd_lists()
+
+ " Create a list of the commands in ex_cmds.h:CMD_index.
+ enew!
+ read ../ex_cmds.h
+ 1,/^enum CMD_index$/d
+ call search('^};$')
+ .,$d
+ v/^EXCMD/d
+ %s/^.*"\(\S\+\)".*$/\1/
+ " Special case ':*' because it's represented as ':star'
+ %s/^\*$/star/
+ sort u
+ let l:command_list = getline(1, '$')
+
+ " Verify that the ':help ex-cmd-index' list contains all known commands.
+ enew!
+ if filereadable('../../doc/index.txt')
+ " unpacked MS-Windows zip archive
+ read ../../doc/index.txt
+ else
+ read ../../runtime/doc/index.txt
+ endif
+ call search('\*ex-cmd-index\*')
+ 1,.d
+ v/^|:/d
+ %s/^|:\(\S*\)|.*/\1/
+ sort u
+ norm gg
+ let l:missing_cmds = []
+ for cmd in l:command_list
+ " Reserved Vim 9 commands or other script-only syntax aren't useful to
+ " document as Ex commands.
+ let l:vim9cmds = [
+ \ 'abstract',
+ \ 'class',
+ \ 'endclass',
+ \ 'endenum',
+ \ 'endinterface',
+ \ 'enum',
+ \ 'interface',
+ \ 'public',
+ \ 'static',
+ \ 'this',
+ \ 'type',
+ \ '++',
+ \ '--',
+ \ '{',
+ \ '}']
+ if index(l:vim9cmds, cmd) != -1
+ continue
+ endif
+
+ if search('^\V' .. cmd .. '\v$', 'cW') == 0
+ call add(l:missing_cmds, ':' .. cmd)
+ endif
+ endfor
+ call assert_equal(0, len(l:missing_cmds), "Missing commands from `:help ex-cmd-index`: " .. string(l:missing_cmds))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
new file mode 100644
index 0000000..ddcb260
--- /dev/null
+++ b/src/testdir/test_cmdline.vim
@@ -0,0 +1,3447 @@
+" Tests for editing the command line.
+
+source check.vim
+source screendump.vim
+source view_util.vim
+source shared.vim
+import './vim9.vim' as v9
+
+func SetUp()
+ func SaveLastScreenLine()
+ let g:Sline = Screenline(&lines - 1)
+ return ''
+ endfunc
+ cnoremap <expr> <F4> SaveLastScreenLine()
+endfunc
+
+func TearDown()
+ delfunc SaveLastScreenLine
+ cunmap <F4>
+endfunc
+
+func Test_complete_tab()
+ call writefile(['testfile'], 'Xtestfile', 'D')
+ call feedkeys(":e Xtest\t\r", "tx")
+ call assert_equal('testfile', getline(1))
+
+ " Pressing <Tab> after '%' completes the current file, also on MS-Windows
+ call feedkeys(":e %\t\r", "tx")
+ call assert_equal('e Xtestfile', @:)
+endfunc
+
+func Test_complete_list()
+ " We can't see the output, but at least we check the code runs properly.
+ call feedkeys(":e test\<C-D>\r", "tx")
+ call assert_equal('test', expand('%:t'))
+
+ " If a command doesn't support completion, then CTRL-D should be literally
+ " used.
+ call feedkeys(":chistory \<C-D>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"chistory \<C-D>", @:)
+
+ " Test for displaying the tail of the completion matches
+ set wildmode=longest,full
+ call mkdir('Xtest', 'R')
+ call writefile([], 'Xtest/a.c')
+ call writefile([], 'Xtest/a.h')
+ let g:Sline = ''
+ call feedkeys(":e Xtest/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('a.c a.h', g:Sline)
+ call assert_equal('"e Xtest/', @:)
+ if has('win32')
+ " Test for 'completeslash'
+ set completeslash=backslash
+ call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xtest\', @:)
+ call feedkeys(":e Xtest/\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xtest\a.', @:)
+ set completeslash=slash
+ call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xtest/', @:)
+ call feedkeys(":e Xtest\\\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xtest/a.', @:)
+ set completeslash&
+ endif
+
+ " Test for displaying the tail with wildcards
+ let g:Sline = ''
+ call feedkeys(":e Xtes?/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('Xtest/a.c Xtest/a.h', g:Sline)
+ call assert_equal('"e Xtes?/', @:)
+ let g:Sline = ''
+ call feedkeys(":e Xtes*/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('Xtest/a.c Xtest/a.h', g:Sline)
+ call assert_equal('"e Xtes*/', @:)
+ let g:Sline = ''
+ call feedkeys(":e Xtes[/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal(':e Xtes[/', g:Sline)
+ call assert_equal('"e Xtes[/', @:)
+
+ set wildmode&
+endfunc
+
+func Test_complete_wildmenu()
+ call mkdir('Xwilddir1/Xdir2', 'pR')
+ call writefile(['testfile1'], 'Xwilddir1/Xtestfile1')
+ call writefile(['testfile2'], 'Xwilddir1/Xtestfile2')
+ call writefile(['testfile3'], 'Xwilddir1/Xdir2/Xtestfile3')
+ call writefile(['testfile3'], 'Xwilddir1/Xdir2/Xtestfile4')
+ set wildmenu
+
+ " Pressing <Tab> completes, and moves to next files when pressing again.
+ call feedkeys(":e Xwilddir1/\<Tab>\<Tab>\<CR>", 'tx')
+ call assert_equal('testfile1', getline(1))
+ call feedkeys(":e Xwilddir1/\<Tab>\<Tab>\<Tab>\<CR>", 'tx')
+ call assert_equal('testfile2', getline(1))
+
+ " <S-Tab> is like <Tab> but begin with the last match and then go to
+ " previous.
+ call feedkeys(":e Xwilddir1/Xtest\<S-Tab>\<CR>", 'tx')
+ call assert_equal('testfile2', getline(1))
+ call feedkeys(":e Xwilddir1/Xtest\<S-Tab>\<S-Tab>\<CR>", 'tx')
+ call assert_equal('testfile1', getline(1))
+
+ " <Left>/<Right> to move to previous/next file.
+ call feedkeys(":e Xwilddir1/\<Tab>\<Right>\<CR>", 'tx')
+ call assert_equal('testfile1', getline(1))
+ call feedkeys(":e Xwilddir1/\<Tab>\<Right>\<Right>\<CR>", 'tx')
+ call assert_equal('testfile2', getline(1))
+ call feedkeys(":e Xwilddir1/\<Tab>\<Right>\<Right>\<Left>\<CR>", 'tx')
+ call assert_equal('testfile1', getline(1))
+
+ " <Up>/<Down> to go up/down directories.
+ call feedkeys(":e Xwilddir1/\<Tab>\<Down>\<CR>", 'tx')
+ call assert_equal('testfile3', getline(1))
+ call feedkeys(":e Xwilddir1/\<Tab>\<Down>\<Up>\<Right>\<CR>", 'tx')
+ call assert_equal('testfile1', getline(1))
+
+ " this fails in some Unix GUIs, not sure why
+ if !has('unix') || !has('gui_running')
+ " <C-J>/<C-K> mappings to go up/down directories when 'wildcharm' is
+ " different than 'wildchar'.
+ set wildcharm=<C-Z>
+ cnoremap <C-J> <Down><C-Z>
+ cnoremap <C-K> <Up><C-Z>
+ call feedkeys(":e Xwilddir1/\<Tab>\<C-J>\<CR>", 'tx')
+ call assert_equal('testfile3', getline(1))
+ call feedkeys(":e Xwilddir1/\<Tab>\<C-J>\<C-K>\<CR>", 'tx')
+ call assert_equal('testfile1', getline(1))
+ set wildcharm=0
+ cunmap <C-J>
+ cunmap <C-K>
+ endif
+
+ " Test for canceling the wild menu by adding a character
+ redrawstatus
+ call feedkeys(":e Xwilddir1/\<Tab>x\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xwilddir1/Xdir2/x', @:)
+
+ " Completion using a relative path
+ cd Xwilddir1/Xdir2
+ call feedkeys(":e ../\<Tab>\<Right>\<Down>\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"e Xtestfile3 Xtestfile4', @:)
+ cd -
+
+ " test for wildmenumode()
+ cnoremap <expr> <F2> wildmenumode()
+ call feedkeys(":cd Xwilddir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cd Xwilddir1/0', @:)
+ call feedkeys(":e Xwilddir1/\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"e Xwilddir1/Xdir2/1', @:)
+ cunmap <F2>
+
+ " Test for canceling the wild menu by pressing <PageDown> or <PageUp>.
+ " After this pressing <Left> or <Right> should not change the selection.
+ call feedkeys(":sign \<Tab>\<PageDown>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign define', @:)
+ call histadd('cmd', 'TestWildMenu')
+ call feedkeys(":sign \<Tab>\<PageUp>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"TestWildMenu', @:)
+
+ " cleanup
+ %bwipe
+ set nowildmenu
+endfunc
+
+func Test_wildmenu_screendump()
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ set wildmenu hlsearch
+ [SCRIPT]
+ call writefile(lines, 'XTest_wildmenu', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_wildmenu', {'rows': 8})
+ call term_sendkeys(buf, ":vim\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_1', {})
+
+ call term_sendkeys(buf, "\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_2', {})
+
+ call term_sendkeys(buf, "\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_3', {})
+
+ call term_sendkeys(buf, "\<Tab>\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_4', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_redraw_in_autocmd()
+ CheckScreendump
+
+ let lines =<< trim END
+ set cmdheight=2
+ autocmd CmdlineChanged * redraw
+ END
+ call writefile(lines, 'XTest_redraw', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_redraw', {'rows': 8})
+ call term_sendkeys(buf, ":for i in range(3)\<CR>")
+ call VerifyScreenDump(buf, 'Test_redraw_in_autocmd_1', {})
+
+ call term_sendkeys(buf, "let i =")
+ call VerifyScreenDump(buf, 'Test_redraw_in_autocmd_2', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_redrawstatus_in_autocmd()
+ CheckScreendump
+
+ let lines =<< trim END
+ set laststatus=2
+ set statusline=%=:%{getcmdline()}
+ autocmd CmdlineChanged * redrawstatus
+ END
+ call writefile(lines, 'XTest_redrawstatus', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_redrawstatus', {'rows': 8})
+ " :redrawstatus is postponed if messages have scrolled
+ call term_sendkeys(buf, ":echo \"one\\ntwo\\nthree\\nfour\"\<CR>")
+ call term_sendkeys(buf, ":foobar")
+ call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_1', {})
+ " it is not postponed if messages have not scrolled
+ call term_sendkeys(buf, "\<Esc>:for in in range(3)")
+ call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_2', {})
+ " with cmdheight=1 messages have scrolled when typing :endfor
+ call term_sendkeys(buf, "\<CR>:endfor")
+ call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_3', {})
+ call term_sendkeys(buf, "\<CR>:set cmdheight=2\<CR>")
+ " with cmdheight=2 messages haven't scrolled when typing :for or :endfor
+ call term_sendkeys(buf, ":for in in range(3)")
+ call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_4', {})
+ call term_sendkeys(buf, "\<CR>:endfor")
+ call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_5', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_changing_cmdheight()
+ CheckScreendump
+
+ let lines =<< trim END
+ set cmdheight=1 laststatus=2
+ func EchoTwo()
+ set laststatus=2
+ set cmdheight=5
+ echo 'foo'
+ echo 'bar'
+ set cmdheight=1
+ endfunc
+ END
+ call writefile(lines, 'XTest_cmdheight', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_cmdheight', {'rows': 8})
+ call term_sendkeys(buf, ":resize -3\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_1', {})
+
+ " using the space available doesn't change the status line
+ call term_sendkeys(buf, ":set cmdheight+=3\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {})
+
+ " using more space moves the status line up
+ call term_sendkeys(buf, ":set cmdheight+=1\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_3', {})
+
+ " reducing cmdheight moves status line down
+ call term_sendkeys(buf, ":set cmdheight-=2\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {})
+
+ " reducing window size and then setting cmdheight
+ call term_sendkeys(buf, ":resize -1\<CR>")
+ call term_sendkeys(buf, ":set cmdheight=1\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {})
+
+ " setting 'cmdheight' works after outputting two messages
+ call term_sendkeys(buf, ":call EchoTwo()\<CR>")
+ call VerifyScreenDump(buf, 'Test_changing_cmdheight_6', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cmdheight_tabline()
+ CheckScreendump
+
+ let buf = RunVimInTerminal('-c "set ls=2" -c "set stal=2" -c "set cmdheight=1"', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_cmdheight_tabline_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_map_completion()
+ call feedkeys(":map <unique> <si\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <unique> <silent>', getreg(':'))
+ call feedkeys(":map <script> <un\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <script> <unique>', getreg(':'))
+ call feedkeys(":map <expr> <sc\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <expr> <script>', getreg(':'))
+ call feedkeys(":map <buffer> <e\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <buffer> <expr>', getreg(':'))
+ call feedkeys(":map <nowait> <b\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <nowait> <buffer>', getreg(':'))
+ call feedkeys(":map <special> <no\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <special> <nowait>', getreg(':'))
+ call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <silent> <special>', getreg(':'))
+
+ map <Middle>x middle
+
+ map ,f commaf
+ map ,g commaf
+ map <Left> left
+ map <A-Left>x shiftleft
+ call feedkeys(":map ,\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map ,f', getreg(':'))
+ call feedkeys(":map ,\<Tab>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map ,g', getreg(':'))
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ call feedkeys(":map <A-Left>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <A-Left>\<Tab>", getreg(':'))
+ call feedkeys(":map <M-Left>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <M-Left>x", getreg(':'))
+ unmap ,f
+ unmap ,g
+ unmap <Left>
+ unmap <A-Left>x
+
+ set cpo-=< cpo-=k
+ map <Left> left
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ call feedkeys(":map <M\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <M\<Tab>", getreg(':'))
+ call feedkeys(":map \<C-V>\<C-V><M\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map \<C-V><Middle>x", getreg(':'))
+ unmap <Left>
+
+ set cpo+=<
+ map <Left> left
+ exe "set t_k6=\<Esc>[17~"
+ call feedkeys(":map \<Esc>[17~x f6x\<CR>", 'xt')
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ if !has('gui_running')
+ call feedkeys(":map \<Esc>[17~\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <F6>x", getreg(':'))
+ endif
+ unmap <Left>
+ call feedkeys(":unmap \<Esc>[17~x\<CR>", 'xt')
+ set cpo-=<
+
+ set cpo+=B
+ map <Left> left
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ unmap <Left>
+ set cpo-=B
+
+ set cpo+=k
+ map <Left> left
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ unmap <Left>
+ set cpo-=k
+
+ call assert_fails('call feedkeys(":map \\\\%(\<Tab>\<Home>\"\<CR>", "xt")', 'E53:')
+
+ unmap <Middle>x
+ set cpo&vim
+endfunc
+
+func Test_match_completion()
+ hi Aardig ctermfg=green
+ call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"match Aardig', @:)
+ call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"match none', @:)
+ call feedkeys(":match | chist\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"match | chistory', @:)
+endfunc
+
+func Test_highlight_completion()
+ hi Aardig ctermfg=green
+ call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"hi Aardig', getreg(':'))
+ call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"hi default Aardig', getreg(':'))
+ call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"hi clear Aardig', getreg(':'))
+ call feedkeys(":hi li\<S-Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"hi link', getreg(':'))
+ call feedkeys(":hi d\<S-Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"hi default', getreg(':'))
+ call feedkeys(":hi c\<S-Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"hi clear', getreg(':'))
+ call feedkeys(":hi clear Aardig Aard\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"hi clear Aardig Aardig', getreg(':'))
+ call feedkeys(":hi Aardig \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"hi Aardig \t", getreg(':'))
+
+ " A cleared group does not show up in completions.
+ hi Anders ctermfg=green
+ call assert_equal(['Aardig', 'Anders'], getcompletion('A', 'highlight'))
+ hi clear Aardig
+ call assert_equal(['Anders'], getcompletion('A', 'highlight'))
+ hi clear Anders
+ call assert_equal([], getcompletion('A', 'highlight'))
+endfunc
+
+" Test for command-line expansion of "hi Ni " (easter egg)
+func Test_highlight_easter_egg()
+ call test_override('ui_delay', 1)
+ call feedkeys(":hi Ni \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"hi Ni \<Tab>", @:)
+ call test_override('ALL', 0)
+endfunc
+
+func Test_getcompletion()
+ let groupcount = len(getcompletion('', 'event'))
+ call assert_true(groupcount > 0)
+ let matchcount = len('File'->getcompletion('event'))
+ call assert_true(matchcount > 0)
+ call assert_true(groupcount > matchcount)
+
+ if has('menu')
+ source $VIMRUNTIME/menu.vim
+ let matchcount = len(getcompletion('', 'menu'))
+ call assert_true(matchcount > 0)
+ call assert_equal(['File.'], getcompletion('File', 'menu'))
+ call assert_true(matchcount > 0)
+ let matchcount = len(getcompletion('File.', 'menu'))
+ call assert_true(matchcount > 0)
+ source $VIMRUNTIME/delmenu.vim
+ endif
+
+ let l = getcompletion('v:n', 'var')
+ call assert_true(index(l, 'v:null') >= 0)
+ let l = getcompletion('v:notexists', 'var')
+ call assert_equal([], l)
+
+ args a.c b.c
+ let l = getcompletion('', 'arglist')
+ call assert_equal(['a.c', 'b.c'], l)
+ let l = getcompletion('a.', 'buffer')
+ call assert_equal(['a.c'], l)
+ %argdelete
+
+ let l = getcompletion('', 'augroup')
+ call assert_true(index(l, 'END') >= 0)
+ let l = getcompletion('blahblah', 'augroup')
+ call assert_equal([], l)
+
+ let l = getcompletion('', 'behave')
+ call assert_true(index(l, 'mswin') >= 0)
+ let l = getcompletion('not', 'behave')
+ call assert_equal([], l)
+
+ let l = getcompletion('', 'color')
+ call assert_true(index(l, 'default') >= 0)
+ let l = getcompletion('dirty', 'color')
+ call assert_equal([], l)
+
+ let l = getcompletion('', 'command')
+ call assert_true(index(l, 'sleep') >= 0)
+ let l = getcompletion('awake', 'command')
+ call assert_equal([], l)
+
+ let l = getcompletion('', 'dir')
+ call assert_true(index(l, 'samples/') >= 0)
+ let l = getcompletion('NoMatch', 'dir')
+ call assert_equal([], l)
+
+ if glob('~/*') !=# ''
+ let l = getcompletion('~/', 'dir')
+ call assert_true(l[0][0] ==# '~')
+ endif
+
+ let l = getcompletion('exe', 'expression')
+ call assert_true(index(l, 'executable(') >= 0)
+ let l = getcompletion('kill', 'expression')
+ call assert_equal([], l)
+
+ let l = getcompletion('tag', 'function')
+ call assert_true(index(l, 'taglist(') >= 0)
+ let l = getcompletion('paint', 'function')
+ call assert_equal([], l)
+
+ if !has('ruby')
+ " global_functions[] has an entry but it doesn't have an implementation
+ let l = getcompletion('ruby', 'function')
+ call assert_equal([], l)
+ endif
+
+ let Flambda = {-> 'hello'}
+ let l = getcompletion('', 'function')
+ let l = filter(l, {i, v -> v =~ 'lambda'})
+ call assert_equal([], l)
+
+ let l = getcompletion('run', 'file')
+ call assert_true(index(l, 'runtest.vim') >= 0)
+ let l = getcompletion('walk', 'file')
+ call assert_equal([], l)
+ set wildignore=*.vim
+ let l = getcompletion('run', 'file', 1)
+ call assert_true(index(l, 'runtest.vim') < 0)
+ set wildignore&
+ " Directory name with space character
+ call mkdir('Xdir with space', 'D')
+ call assert_equal(['Xdir with space/'], getcompletion('Xdir\ w', 'shellcmd'))
+ call assert_equal(['./Xdir with space/'], getcompletion('./Xdir', 'shellcmd'))
+
+ let l = getcompletion('ha', 'filetype')
+ call assert_true(index(l, 'hamster') >= 0)
+ let l = getcompletion('horse', 'filetype')
+ call assert_equal([], l)
+
+ let l = getcompletion('z', 'syntax')
+ call assert_true(index(l, 'zimbu') >= 0)
+ let l = getcompletion('emacs', 'syntax')
+ call assert_equal([], l)
+
+ let l = getcompletion('jikes', 'compiler')
+ call assert_true(index(l, 'jikes') >= 0)
+ let l = getcompletion('break', 'compiler')
+ call assert_equal([], l)
+
+ let l = getcompletion('last', 'help')
+ call assert_true(index(l, ':tablast') >= 0)
+ let l = getcompletion('giveup', 'help')
+ call assert_equal([], l)
+
+ let l = getcompletion('time', 'option')
+ call assert_true(index(l, 'timeoutlen') >= 0)
+ let l = getcompletion('space', 'option')
+ call assert_equal([], l)
+
+ let l = getcompletion('er', 'highlight')
+ call assert_true(index(l, 'ErrorMsg') >= 0)
+ let l = getcompletion('dark', 'highlight')
+ call assert_equal([], l)
+
+ let l = getcompletion('', 'messages')
+ call assert_true(index(l, 'clear') >= 0)
+ let l = getcompletion('not', 'messages')
+ call assert_equal([], l)
+
+ let l = getcompletion('', 'mapclear')
+ call assert_true(index(l, '<buffer>') >= 0)
+ let l = getcompletion('not', 'mapclear')
+ call assert_equal([], l)
+
+ let l = getcompletion('.', 'shellcmd')
+ call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"'))
+ call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
+ let root = has('win32') ? 'C:\\' : '/'
+ let l = getcompletion(root, 'shellcmd')
+ let expected = map(filter(glob(root . '*', 0, 1),
+ \ 'isdirectory(v:val) || executable(v:val)'), 'isdirectory(v:val) ? v:val . ''/'' : v:val')
+ call assert_equal(expected, l)
+
+ if has('cscope')
+ let l = getcompletion('', 'cscope')
+ let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show']
+ call assert_equal(cmds, l)
+ " using cmdline completion must not change the result
+ call feedkeys(":cscope find \<c-d>\<c-c>", 'xt')
+ let l = getcompletion('', 'cscope')
+ call assert_equal(cmds, l)
+ let keys = ['a', 'c', 'd', 'e', 'f', 'g', 'i', 's', 't']
+ let l = getcompletion('find ', 'cscope')
+ call assert_equal(keys, l)
+ endif
+
+ if has('signs')
+ sign define Testing linehl=Comment
+ let l = getcompletion('', 'sign')
+ let cmds = ['define', 'jump', 'list', 'place', 'undefine', 'unplace']
+ call assert_equal(cmds, l)
+ " using cmdline completion must not change the result
+ call feedkeys(":sign list \<c-d>\<c-c>", 'xt')
+ let l = getcompletion('', 'sign')
+ call assert_equal(cmds, l)
+ let l = getcompletion('list ', 'sign')
+ call assert_equal(['Testing'], l)
+ let l = getcompletion('de*', 'sign')
+ call assert_equal(['define'], l)
+ let l = getcompletion('p?', 'sign')
+ call assert_equal(['place'], l)
+ let l = getcompletion('j.', 'sign')
+ call assert_equal(['jump'], l)
+ endif
+
+ " Command line completion tests
+ let l = getcompletion('cd ', 'cmdline')
+ call assert_true(index(l, 'samples/') >= 0)
+ let l = getcompletion('cd NoMatch', 'cmdline')
+ call assert_equal([], l)
+ let l = getcompletion('let v:n', 'cmdline')
+ call assert_true(index(l, 'v:null') >= 0)
+ let l = getcompletion('let v:notexists', 'cmdline')
+ call assert_equal([], l)
+ let l = getcompletion('call tag', 'cmdline')
+ call assert_true(index(l, 'taglist(') >= 0)
+ let l = getcompletion('call paint', 'cmdline')
+ call assert_equal([], l)
+
+ func T(a, c, p)
+ let g:cmdline_compl_params = [a:a, a:c, a:p]
+ return "oneA\noneB\noneC"
+ endfunc
+ command -nargs=1 -complete=custom,T MyCmd
+ let l = getcompletion('MyCmd ', 'cmdline')
+ call assert_equal(['oneA', 'oneB', 'oneC'], l)
+ call assert_equal(['', 'MyCmd ', 6], g:cmdline_compl_params)
+
+ delcommand MyCmd
+ delfunc T
+ unlet g:cmdline_compl_params
+
+ " For others test if the name is recognized.
+ let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user']
+ if has('cmdline_hist')
+ call add(names, 'history')
+ endif
+ if has('gettext')
+ call add(names, 'locale')
+ endif
+ if has('profile')
+ call add(names, 'syntime')
+ endif
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", "word\tfile\tcmd"], 'Xtags', 'D')
+
+ for name in names
+ let matchcount = len(getcompletion('', name))
+ call assert_true(matchcount >= 0, 'No matches for ' . name)
+ endfor
+
+ set tags&
+
+ edit a~b
+ enew
+ call assert_equal(['a~b'], getcompletion('a~', 'buffer'))
+ bw a~b
+
+ if has('unix')
+ edit Xtest\
+ enew
+ call assert_equal(['Xtest\'], getcompletion('Xtest\', 'buffer'))
+ bw Xtest\
+ endif
+
+ call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:')
+ call assert_fails('call getcompletion("", "burp")', 'E475:')
+ call assert_fails('call getcompletion("abc", [])', 'E1174:')
+endfunc
+
+func Test_multibyte_expression()
+ " Get a dialog in the GUI
+ CheckNotGui
+
+ " This was using uninitialized memory.
+ let lines =<< trim END
+ set verbose=6
+ norm @=Ù·
+ qall!
+ END
+ call writefile(lines, 'XmultiScript', 'D')
+ call RunVim('', '', '-u NONE -n -e -s -S XmultiScript')
+endfunc
+
+" Test for getcompletion() with "fuzzy" in 'wildoptions'
+func Test_getcompletion_wildoptions()
+ let save_wildoptions = &wildoptions
+ set wildoptions&
+ let l = getcompletion('space', 'option')
+ call assert_equal([], l)
+ let l = getcompletion('ier', 'command')
+ call assert_equal([], l)
+ set wildoptions=fuzzy
+ let l = getcompletion('space', 'option')
+ call assert_true(index(l, 'backspace') >= 0)
+ let l = getcompletion('ier', 'command')
+ call assert_true(index(l, 'compiler') >= 0)
+ let &wildoptions = save_wildoptions
+endfunc
+
+func Test_complete_autoload_error()
+ let save_rtp = &rtp
+ let lines =<< trim END
+ vim9script
+ export def Complete(..._): string
+ return 'match'
+ enddef
+ echo this will cause an error
+ END
+ call mkdir('Xdir/autoload', 'pR')
+ call writefile(lines, 'Xdir/autoload/script.vim')
+ exe 'set rtp+=' .. getcwd() .. '/Xdir'
+
+ let lines =<< trim END
+ vim9script
+ import autoload 'script.vim'
+ command -nargs=* -complete=custom,script.Complete Cmd eval 0 + 0
+ &wildcharm = char2nr("\<Tab>")
+ feedkeys(":Cmd \<Tab>", 'xt')
+ END
+ call v9.CheckScriptFailure(lines, 'E121: Undefined variable: this')
+
+ let &rtp = save_rtp
+endfunc
+
+func Test_fullcommand()
+ let tests = {
+ \ '': '',
+ \ ':': '',
+ \ ':::': '',
+ \ ':::5': '',
+ \ 'not_a_cmd': '',
+ \ 'Check': '',
+ \ 'syntax': 'syntax',
+ \ ':syntax': 'syntax',
+ \ '::::syntax': 'syntax',
+ \ 'sy': 'syntax',
+ \ 'syn': 'syntax',
+ \ 'synt': 'syntax',
+ \ ':sy': 'syntax',
+ \ '::::sy': 'syntax',
+ \ 'match': 'match',
+ \ '2match': 'match',
+ \ '3match': 'match',
+ \ 'aboveleft': 'aboveleft',
+ \ 'abo': 'aboveleft',
+ \ 'en': 'endif',
+ \ 'end': 'endif',
+ \ 'endi': 'endif',
+ \ 's': 'substitute',
+ \ '5s': 'substitute',
+ \ ':5s': 'substitute',
+ \ "'<,'>s": 'substitute',
+ \ ":'<,'>s": 'substitute',
+ \ 'CheckLin': 'CheckLinux',
+ \ 'CheckLinux': 'CheckLinux',
+ \ }
+
+ for [in, want] in items(tests)
+ call assert_equal(want, fullcommand(in))
+ endfor
+ call assert_equal('', fullcommand(test_null_string()))
+
+ call assert_equal('syntax', 'syn'->fullcommand())
+
+ command -buffer BufferLocalCommand :
+ command GlobalCommand :
+ call assert_equal('GlobalCommand', fullcommand('GlobalCom'))
+ call assert_equal('BufferLocalCommand', fullcommand('BufferL'))
+ delcommand BufferLocalCommand
+ delcommand GlobalCommand
+endfunc
+
+func Test_shellcmd_completion()
+ let save_path = $PATH
+
+ call mkdir('Xpathdir/Xpathsubdir', 'pR')
+ call writefile([''], 'Xpathdir/Xfile.exe')
+ call setfperm('Xpathdir/Xfile.exe', 'rwx------')
+
+ " Set PATH to example directory without trailing slash.
+ let $PATH = getcwd() . '/Xpathdir'
+
+ " Test for the ":!<TAB>" case. Previously, this would include subdirs of
+ " dirs in the PATH, even though they won't be executed. We check that only
+ " subdirs of the PWD and executables from the PATH are included in the
+ " suggestions.
+ let actual = getcompletion('X', 'shellcmd')
+ let expected = map(filter(glob('*', 0, 1), 'isdirectory(v:val) && v:val[0] == "X"'), 'v:val . "/"')
+ call insert(expected, 'Xfile.exe')
+ call assert_equal(expected, actual)
+
+ let $PATH = save_path
+endfunc
+
+func Test_expand_star_star()
+ call mkdir('a/b', 'pR')
+ call writefile(['asdfasdf'], 'a/b/fileXname')
+ call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt')
+ call assert_equal('find a/b/fileXname', @:)
+ bwipe!
+endfunc
+
+func Test_cmdline_paste()
+ let @a = "def"
+ call feedkeys(":abc \<C-R>a ghi\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abc def ghi', @:)
+
+ new
+ call setline(1, 'asdf.x /tmp/some verylongword a;b-c*d ')
+
+ call feedkeys(":aaa \<C-R>\<C-W> bbb\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"aaa asdf bbb', @:)
+
+ call feedkeys("ft:aaa \<C-R>\<C-F> bbb\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"aaa /tmp/some bbb', @:)
+
+ call feedkeys(":aaa \<C-R>\<C-L> bbb\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"aaa '.getline(1).' bbb', @:)
+
+ set incsearch
+ call feedkeys("fy:aaa veryl\<C-R>\<C-W> bbb\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"aaa verylongword bbb', @:)
+
+ call feedkeys("f;:aaa \<C-R>\<C-A> bbb\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"aaa a;b-c*d bbb', @:)
+
+ call feedkeys(":\<C-\>etoupper(getline(1))\<CR>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"ASDF.X /TMP/SOME VERYLONGWORD A;B-C*D ', @:)
+ bwipe!
+
+ " Error while typing a command used to cause that it was not executed
+ " in the end.
+ new
+ try
+ call feedkeys(":file \<C-R>%Xtestfile\<CR>", 'tx')
+ catch /^Vim\%((\a\+)\)\=:E32/
+ " ignore error E32
+ endtry
+ call assert_equal("Xtestfile", bufname("%"))
+
+ " Try to paste an invalid register using <C-R>
+ call feedkeys(":\"one\<C-R>\<C-X>two\<CR>", 'xt')
+ call assert_equal('"onetwo', @:)
+
+ " Test for pasting register containing CTRL-H using CTRL-R and CTRL-R CTRL-R
+ let @a = "xy\<C-H>z"
+ call feedkeys(":\"\<C-R>a\<CR>", 'xt')
+ call assert_equal('"xz', @:)
+ call feedkeys(":\"\<C-R>\<C-R>a\<CR>", 'xt')
+ call assert_equal("\"xy\<C-H>z", @:)
+ call feedkeys(":\"\<C-R>\<C-O>a\<CR>", 'xt')
+ call assert_equal("\"xy\<C-H>z", @:)
+
+ " Test for pasting register containing CTRL-V using CTRL-R and CTRL-R CTRL-R
+ let @a = "xy\<C-V>z"
+ call feedkeys(":\"\<C-R>=@a\<CR>\<cr>", 'xt')
+ call assert_equal('"xyz', @:)
+ call feedkeys(":\"\<C-R>\<C-R>=@a\<CR>\<cr>", 'xt')
+ call assert_equal("\"xy\<C-V>z", @:)
+
+ call assert_beeps('call feedkeys(":\<C-R>=\<C-R>=\<Esc>", "xt")')
+
+ bwipe!
+endfunc
+
+func Test_cmdline_remove_char()
+ let encoding_save = &encoding
+
+ for e in ['utf8', 'latin1']
+ exe 'set encoding=' . e
+
+ call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abc ef', @:, e)
+
+ call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abcdef', @:)
+
+ call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abc ghi', @:, e)
+
+ call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"def', @:, e)
+
+ " This was going before the start in latin1.
+ call feedkeys(": \<C-W>\<CR>", 'tx')
+ endfor
+
+ let &encoding = encoding_save
+endfunc
+
+func Test_cmdline_keymap_ctrl_hat()
+ CheckFeature keymap
+
+ set keymap=esperanto
+ call feedkeys(":\"Jxauxdo \<C-^>Jxauxdo \<C-^>Jxauxdo\<CR>", 'tx')
+ call assert_equal('"Jxauxdo Ä´aÅ­do Jxauxdo', @:)
+ set keymap=
+endfunc
+
+func Test_illegal_address1()
+ new
+ 2;'(
+ 2;')
+ quit
+endfunc
+
+func Test_illegal_address2()
+ call writefile(['c', 'x', ' x', '.', '1;y'], 'Xtest.vim', 'D')
+ new
+ source Xtest.vim
+ " Trigger calling validate_cursor()
+ diffsp Xtest.vim
+ quit!
+ bwipe!
+endfunc
+
+func Test_mark_from_line_zero()
+ " this was reading past the end of the first (empty) line
+ new
+ norm oxxxx
+ call assert_fails("0;'(", 'E20:')
+ bwipe!
+endfunc
+
+func Test_cmdline_complete_wildoptions()
+ help
+ call feedkeys(":tag /\<c-a>\<c-b>\"\<cr>", 'tx')
+ let a = join(sort(split(@:)),' ')
+ set wildoptions=tagfile
+ call feedkeys(":tag /\<c-a>\<c-b>\"\<cr>", 'tx')
+ let b = join(sort(split(@:)),' ')
+ call assert_equal(a, b)
+ bw!
+endfunc
+
+func Test_cmdline_complete_user_cmd()
+ command! -complete=color -nargs=1 Foo :
+ call feedkeys(":Foo \<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_equal('"Foo blue', @:)
+ call feedkeys(":Foo b\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_equal('"Foo blue', @:)
+ call feedkeys(":Foo a b\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_equal('"Foo a blue', @:)
+ call feedkeys(":Foo b\\\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_equal('"Foo b\', @:)
+ call feedkeys(":Foo b\\x\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_equal('"Foo b\x', @:)
+ delcommand Foo
+
+ redraw
+ call assert_equal('~', Screenline(&lines - 1))
+ command! FooOne :
+ command! FooTwo :
+
+ set nowildmenu
+ call feedkeys(":Foo\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_equal('"FooOne', @:)
+ call assert_equal('~', Screenline(&lines - 1))
+
+ call feedkeys(":Foo\<S-Tab>\<Home>\"\<cr>", 'tx')
+ call assert_equal('"FooTwo', @:)
+ call assert_equal('~', Screenline(&lines - 1))
+
+ delcommand FooOne
+ delcommand FooTwo
+ set wildmenu&
+endfunc
+
+func Test_complete_user_cmd()
+ command FooBar echo 'global'
+ command -buffer FooBar echo 'local'
+ call feedkeys(":Foo\<C-A>\<Home>\"\<CR>", 'tx')
+ call assert_equal('"FooBar', @:)
+
+ delcommand -buffer FooBar
+ delcommand FooBar
+endfunc
+
+func s:ScriptLocalFunction()
+ echo 'yes'
+endfunc
+
+func Test_cmdline_complete_user_func()
+ call feedkeys(":func Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"func Test_cmdline_complete_user_', @:)
+ call feedkeys(":func s:ScriptL\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"func <SNR>\d\+_ScriptLocalFunction', @:)
+
+ " g: prefix also works
+ call feedkeys(":echo g:Test_cmdline_complete_user_f\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"echo g:Test_cmdline_complete_user_func', @:)
+
+ " using g: prefix does not result in just "g:" matches from a lambda
+ let Fx = { a -> a }
+ call feedkeys(":echo g:\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"echo g:[A-Z]', @:)
+
+ " existence of script-local dict function does not break user function name
+ " completion
+ function s:a_dict_func() dict
+ endfunction
+ call feedkeys(":call Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx')
+ call assert_match('"call Test_cmdline_complete_user_', @:)
+ delfunction s:a_dict_func
+endfunc
+
+func Test_cmdline_complete_user_names()
+ if has('unix') && executable('whoami')
+ let whoami = systemlist('whoami')[0]
+ let first_letter = whoami[0]
+ if len(first_letter) > 0
+ " Trying completion of :e ~x where x is the first letter of
+ " the user name should complete to at least the user name.
+ call feedkeys(':e ~' . first_letter . "\<c-a>\<c-B>\"\<cr>", 'tx')
+ call assert_match('^"e \~.*\<' . whoami . '\>', @:)
+ endif
+ elseif has('win32')
+ " Just in case: check that the system has an Administrator account.
+ let names = system('net user')
+ if names =~ 'Administrator'
+ " Trying completion of :e ~A should complete to Administrator.
+ " There could be other names starting with "A" before Administrator.
+ call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx')
+ call assert_match('^"e \~.*Administrator', @:)
+ endif
+ else
+ throw 'Skipped: does not work on this platform'
+ endif
+endfunc
+
+func Test_cmdline_complete_bang()
+ CheckExecutable whoami
+ call feedkeys(":!whoam\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('^".*\<whoami\>', @:)
+endfunc
+
+func Test_cmdline_complete_languages()
+ let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:lc_time)
+
+ let lang = substitute(execute('language ctype'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:ctype)
+
+ let lang = substitute(execute('language collate'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:collate)
+
+ let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:lang)
+
+ call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<collate\>.*\<ctype\>.*\<messages\>.*\<time\>', @:)
+
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language messages \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language ctype \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language time \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language collate \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
+endfunc
+
+func Test_cmdline_complete_env_variable()
+ let $X_VIM_TEST_COMPLETE_ENV = 'foo'
+ call feedkeys(":edit $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"edit $X_VIM_TEST_COMPLETE_ENV', @:)
+ unlet $X_VIM_TEST_COMPLETE_ENV
+endfunc
+
+func Test_cmdline_complete_expression()
+ let g:SomeVar = 'blah'
+ for cmd in ['exe', 'echo', 'echon', 'echomsg']
+ call feedkeys(":" .. cmd .. " SomeV\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"' .. cmd .. ' SomeVar', @:)
+ call feedkeys(":" .. cmd .. " foo SomeV\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"' .. cmd .. ' foo SomeVar', @:)
+ endfor
+ unlet g:SomeVar
+endfunc
+
+" Unique function name for completion below
+func s:WeirdFunc()
+ echo 'weird'
+endfunc
+
+" Test for various command-line completion
+func Test_cmdline_complete_various()
+ " completion for a command starting with a comment
+ call feedkeys(": :|\"\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\" :|\"\<C-A>", @:)
+
+ " completion for a range followed by a comment
+ call feedkeys(":1,2\"\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"1,2\"\<C-A>", @:)
+
+ " completion for :k command
+ call feedkeys(":ka\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"ka\<C-A>", @:)
+
+ " completion for short version of the :s command
+ call feedkeys(":sI \<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"sI \<C-A>", @:)
+
+ " completion for :write command
+ call mkdir('Xcwdir', 'R')
+ call writefile(['one'], 'Xcwdir/Xfile1')
+ let save_cwd = getcwd()
+ cd Xcwdir
+ call feedkeys(":w >> \<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"w >> Xfile1", @:)
+ call chdir(save_cwd)
+
+ " completion for :w ! and :r ! commands
+ call feedkeys(":w !invalid_xyz_cmd\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"w !invalid_xyz_cmd", @:)
+ call feedkeys(":r !invalid_xyz_cmd\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"r !invalid_xyz_cmd", @:)
+
+ " completion for :>> and :<< commands
+ call feedkeys(":>>>\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\">>>\<C-A>", @:)
+ call feedkeys(":<<<\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"<<<\<C-A>", @:)
+
+ " completion for command with +cmd argument
+ call feedkeys(":buffer +/pat Xabc\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"buffer +/pat Xabc", @:)
+ call feedkeys(":buffer +/pat\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"buffer +/pat\<C-A>", @:)
+
+ " completion for a command with a trailing comment
+ call feedkeys(":ls \" comment\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"ls \" comment\<C-A>", @:)
+
+ " completion for a command with a trailing command
+ call feedkeys(":ls | ls\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"ls | ls", @:)
+
+ " completion for a command with an CTRL-V escaped argument
+ call feedkeys(":ls \<C-V>\<C-V>a\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"ls \<C-V>a\<C-A>", @:)
+
+ " completion for a command that doesn't take additional arguments
+ call feedkeys(":all abc\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"all abc\<C-A>", @:)
+
+ " completion for :wincmd with :horizontal modifier
+ call feedkeys(":horizontal wincm\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"horizontal wincmd", @:)
+
+ " completion for a command with a command modifier
+ call feedkeys(":topleft new\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"topleft new", @:)
+
+ " completion for vim9 and legacy commands
+ call feedkeys(":vim9 call strle\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"vim9 call strlen(", @:)
+ call feedkeys(":legac call strle\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"legac call strlen(", @:)
+
+ " completion for the :disassemble command
+ call feedkeys(":disas deb\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"disas debug", @:)
+ call feedkeys(":disas pro\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"disas profile", @:)
+ call feedkeys(":disas debug Test_cmdline_complete_var\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"disas debug Test_cmdline_complete_various", @:)
+ call feedkeys(":disas profile Test_cmdline_complete_var\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"disas profile Test_cmdline_complete_various", @:)
+ call feedkeys(":disas Test_cmdline_complete_var\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"disas Test_cmdline_complete_various", @:)
+
+ call feedkeys(":disas s:WeirdF\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_match('"disas <SNR>\d\+_WeirdFunc', @:)
+
+ call feedkeys(":disas \<S-Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_match('"disas <SNR>\d\+_', @:)
+ call feedkeys(":disas debug \<S-Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_match('"disas debug <SNR>\d\+_', @:)
+
+ " completion for the :match command
+ call feedkeys(":match Search /pat/\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"match Search /pat/\<C-A>", @:)
+
+ " completion for the :doautocmd command
+ call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:)
+
+ " completion of autocmd group after comma
+ call feedkeys(":doautocmd BufNew,BufEn\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"doautocmd BufNew,BufEnter", @:)
+
+ " completion of file name in :doautocmd
+ call writefile([], 'Xvarfile1', 'D')
+ call writefile([], 'Xvarfile2', 'D')
+ call feedkeys(":doautocmd BufEnter Xvarfi\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"doautocmd BufEnter Xvarfile1 Xvarfile2", @:)
+
+ " completion for the :augroup command
+ augroup XTest.test
+ augroup END
+ call feedkeys(":augroup X\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"augroup XTest.test", @:)
+
+ " group name completion in :autocmd
+ call feedkeys(":au X\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"au XTest.test", @:)
+ call feedkeys(":au XTest.test\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"au XTest.test", @:)
+
+ augroup! XTest.test
+
+ " autocmd pattern completion
+ call feedkeys(":au BufEnter *.py\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"au BufEnter *.py\t", @:)
+
+ " completion for the :unlet command
+ call feedkeys(":unlet one two\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"unlet one two", @:)
+
+ " completion for the :buffer command with curlies
+ " FIXME: what should happen on MS-Windows?
+ if !has('win32')
+ edit \{someFile}
+ call feedkeys(":buf someFile\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"buf {someFile}", @:)
+ bwipe {someFile}
+ endif
+
+ " completion for the :bdelete command
+ call feedkeys(":bdel a b c\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"bdel a b c", @:)
+
+ " completion for the :mapclear command
+ call feedkeys(":mapclear \<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"mapclear <buffer>", @:)
+
+ " completion for user defined commands with menu names
+ menu Test.foo :ls<CR>
+ com -nargs=* -complete=menu MyCmd
+ call feedkeys(":MyCmd Te\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd Test.', @:)
+ delcom MyCmd
+ unmenu Test
+
+ " completion for user defined commands with mappings
+ mapclear
+ map <F3> :ls<CR>
+ com -nargs=* -complete=mapping MyCmd
+ call feedkeys(":MyCmd <F\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd <F3> <F4>', @:)
+ mapclear
+ delcom MyCmd
+
+ " completion for :set path= with multiple backslashes
+ call feedkeys(":set path=a\\\\\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set path=a\\\ b', @:)
+
+ " completion for :set dir= with a backslash
+ call feedkeys(":set dir=a\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dir=a\ b', @:)
+
+ " completion for the :py3 commands
+ call feedkeys(":py3\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"py3 py3do py3file', @:)
+
+ " completion for the :vim9 commands
+ call feedkeys(":vim9\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"vim9cmd vim9script', @:)
+
+ " redir @" is not the start of a comment. So complete after that
+ call feedkeys(":redir @\" | cwin\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"redir @" | cwindow', @:)
+
+ " completion after a backtick
+ call feedkeys(":e `a1b2c\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e `a1b2c', @:)
+
+ " completion for :language command with an invalid argument
+ call feedkeys(":language dummy \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"language dummy \t", @:)
+
+ " completion for commands after a :global command
+ call feedkeys(":g/a\\xb/clearj\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"g/a\xb/clearjumps', @:)
+
+ " completion with ambiguous user defined commands
+ com TCmd1 echo 'TCmd1'
+ com TCmd2 echo 'TCmd2'
+ call feedkeys(":TCmd \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"TCmd ', @:)
+ delcom TCmd1
+ delcom TCmd2
+
+ " completion after a range followed by a pipe (|) character
+ call feedkeys(":1,10 | chist\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"1,10 | chistory', @:)
+
+ " completion after a :global command
+ call feedkeys(":g/a/chist\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"g/a/chistory', @:)
+ call feedkeys(":g/a\\/chist\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"g/a\\/chist\t", @:)
+
+ " use <Esc> as the 'wildchar' for completion
+ set wildchar=<Esc>
+ call feedkeys(":g/a\\xb/clearj\<Esc>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"g/a\xb/clearjumps', @:)
+ " pressing <esc> twice should cancel the command
+ call feedkeys(":chist\<Esc>\<Esc>", 'xt')
+ call assert_equal('"g/a\xb/clearjumps', @:)
+ set wildchar&
+
+ if has('unix')
+ " should be able to complete a file name that starts with a '~'.
+ call writefile([], '~Xtest')
+ call feedkeys(":e \\~X\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e \~Xtest', @:)
+ call delete('~Xtest')
+
+ " should be able to complete a file name that has a '*'
+ call writefile([], 'Xx*Yy')
+ call feedkeys(":e Xx\*\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xx\*Yy', @:)
+ call delete('Xx*Yy')
+
+ " use a literal star
+ call feedkeys(":e \\*\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e \*', @:)
+ endif
+
+ call feedkeys(":py3f\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"py3file', @:)
+endfunc
+
+" Test for 'wildignorecase'
+func Test_cmdline_wildignorecase()
+ CheckUnix
+ call writefile([], 'XTEST', 'D')
+ set wildignorecase
+ call feedkeys(":e xt\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e XTEST', @:)
+ call assert_equal(['XTEST'], getcompletion('xt', 'file'))
+ let g:Sline = ''
+ call feedkeys(":e xt\<C-d>\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e xt', @:)
+ call assert_equal('XTEST', g:Sline)
+ set wildignorecase&
+endfunc
+
+func Test_cmdline_write_alternatefile()
+ new
+ call setline('.', ['one', 'two'])
+ f foo.txt
+ new
+ f #-A
+ call assert_equal('foo.txt-A', expand('%'))
+ f #<-B.txt
+ call assert_equal('foo-B.txt', expand('%'))
+ f %<
+ call assert_equal('foo-B', expand('%'))
+ new
+ call assert_fails('f #<', 'E95:')
+ bw!
+ f foo-B.txt
+ f %<-A
+ call assert_equal('foo-B-A', expand('%'))
+ bw!
+ bw!
+endfunc
+
+func Test_cmdline_expand_cur_alt_file()
+ enew
+ file http://some.com/file.txt
+ call feedkeys(":e %\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e http://some.com/file.txt', @:)
+ edit another
+ call feedkeys(":e #\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e http://some.com/file.txt', @:)
+ bwipe
+ bwipe http://some.com/file.txt
+endfunc
+
+" using a leading backslash here
+set cpo+=C
+
+func Test_cmdline_search_range()
+ new
+ call setline(1, ['a', 'b', 'c', 'd'])
+ /d
+ 1,\/s/b/B/
+ call assert_equal('B', getline(2))
+
+ /a
+ $
+ \?,4s/c/C/
+ call assert_equal('C', getline(3))
+
+ call setline(1, ['a', 'b', 'c', 'd'])
+ %s/c/c/
+ 1,\&s/b/B/
+ call assert_equal('B', getline(2))
+
+ let @/ = 'apple'
+ call assert_fails('\/print', ['E486:.*apple'])
+
+ bwipe!
+endfunc
+
+" Test for the tick mark (') in an excmd range
+func Test_tick_mark_in_range()
+ " If only the tick is passed as a range and no command is specified, there
+ " should not be an error
+ call feedkeys(":'\<CR>", 'xt')
+ call assert_equal("'", @:)
+ call assert_fails("',print", 'E78:')
+endfunc
+
+" Test for using a line number followed by a search pattern as range
+func Test_lnum_and_pattern_as_range()
+ new
+ call setline(1, ['foo 1', 'foo 2', 'foo 3'])
+ let @" = ''
+ 2/foo/yank
+ call assert_equal("foo 3\n", @")
+ call assert_equal(1, line('.'))
+ close!
+endfunc
+
+" Tests for getcmdline(), getcmdpos() and getcmdtype()
+func Check_cmdline(cmdtype)
+ call assert_equal('MyCmd a', getcmdline())
+ call assert_equal(8, getcmdpos())
+ call assert_equal(a:cmdtype, getcmdtype())
+ return ''
+endfunc
+
+set cpo&
+
+func Test_getcmdtype()
+ call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt")
+
+ let cmdtype = ''
+ debuggreedy
+ call feedkeys(":debug echo 'test'\<CR>", "t")
+ call feedkeys("let cmdtype = \<C-R>=string(getcmdtype())\<CR>\<CR>", "t")
+ call feedkeys("cont\<CR>", "xt")
+ 0debuggreedy
+ call assert_equal('>', cmdtype)
+
+ call feedkeys("/MyCmd a\<C-R>=Check_cmdline('/')\<CR>\<Esc>", "xt")
+ call feedkeys("?MyCmd a\<C-R>=Check_cmdline('?')\<CR>\<Esc>", "xt")
+
+ call feedkeys(":call input('Answer?')\<CR>", "t")
+ call feedkeys("MyCmd a\<C-R>=Check_cmdline('@')\<CR>\<C-C>", "xt")
+
+ call feedkeys(":insert\<CR>MyCmd a\<C-R>=Check_cmdline('-')\<CR>\<Esc>", "xt")
+
+ cnoremap <expr> <F6> Check_cmdline('=')
+ call feedkeys("a\<C-R>=MyCmd a\<F6>\<Esc>\<Esc>", "xt")
+ cunmap <F6>
+
+ call assert_equal('', getcmdline())
+endfunc
+
+func Test_verbosefile()
+ set verbosefile=Xlog
+ echomsg 'foo'
+ echomsg 'bar'
+ set verbosefile=
+ let log = readfile('Xlog')
+ call assert_match("foo\nbar", join(log, "\n"))
+ call delete('Xlog')
+
+ call mkdir('Xdir', 'D')
+ call assert_fails('set verbosefile=Xdir', ['E484:.*Xdir', 'E474:'])
+endfunc
+
+func Test_verbose_option()
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ command DoSomething echo 'hello' |set ts=4 |let v = '123' |echo v
+ call feedkeys("\r", 't') " for the hit-enter prompt
+ set verbose=20
+ [SCRIPT]
+ call writefile(lines, 'XTest_verbose', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_verbose', {'rows': 12})
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":DoSomething\<CR>")
+ call VerifyScreenDump(buf, 'Test_verbose_option_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_setcmdpos()
+ func InsertTextAtPos(text, pos)
+ call assert_equal(0, setcmdpos(a:pos))
+ return a:text
+ endfunc
+
+ " setcmdpos() with position in the middle of the command line.
+ call feedkeys(":\"12\<C-R>=InsertTextAtPos('a', 3)\<CR>b\<CR>", 'xt')
+ call assert_equal('"1ab2', @:)
+
+ call feedkeys(":\"12\<C-R>\<C-R>=InsertTextAtPos('a', 3)\<CR>b\<CR>", 'xt')
+ call assert_equal('"1b2a', @:)
+
+ " setcmdpos() with position beyond the end of the command line.
+ call feedkeys(":\"12\<C-B>\<C-R>=InsertTextAtPos('a', 10)\<CR>b\<CR>", 'xt')
+ call assert_equal('"12ab', @:)
+
+ " setcmdpos() returns 1 when not editing the command line.
+ call assert_equal(1, 3->setcmdpos())
+endfunc
+
+func Test_cmdline_overstrike()
+ let encodings = ['latin1', 'utf8']
+ let encoding_save = &encoding
+
+ for e in encodings
+ exe 'set encoding=' . e
+
+ " Test overstrike in the middle of the command line.
+ call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
+ call assert_equal('"0ab1cd4', @:, e)
+
+ " Test overstrike going beyond end of command line.
+ call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cdefgh\<enter>", 'xt')
+ call assert_equal('"0ab1cdefgh', @:, e)
+
+ " Test toggling insert/overstrike a few times.
+ call feedkeys(":\"01234\<home>\<right>ab\<right>\<insert>cd\<right>\<insert>ef\<enter>", 'xt')
+ call assert_equal('"ab0cd3ef4', @:, e)
+ endfor
+
+ " Test overstrike with multi-byte characters.
+ call feedkeys(":\"テキストエディタ\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
+ call assert_equal('"テabキcdエディタ', @:, e)
+
+ let &encoding = encoding_save
+endfunc
+
+func Test_buffers_lastused()
+ " check that buffers are sorted by time when wildmode has lastused
+ call test_settime(1550020000) " middle
+ edit bufa
+ enew
+ call test_settime(1550030000) " newest
+ edit bufb
+ enew
+ call test_settime(1550010000) " oldest
+ edit bufc
+ enew
+ call test_settime(0)
+ enew
+
+ call assert_equal(['bufa', 'bufb', 'bufc'],
+ \ getcompletion('', 'buffer'))
+
+ let save_wildmode = &wildmode
+ set wildmode=full:lastused
+
+ let cap = "\<c-r>=execute('let X=getcmdline()')\<cr>"
+ call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufb', X)
+ call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufa', X)
+ call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufc', X)
+ enew
+
+ edit other
+ call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufb', X)
+ call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufa', X)
+ call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufc', X)
+ enew
+
+ let &wildmode = save_wildmode
+
+ bwipeout bufa
+ bwipeout bufb
+ bwipeout bufc
+endfunc
+
+func Test_cmdlineclear_tabenter()
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ call setline(1, range(30))
+ [SCRIPT]
+
+ call writefile(lines, 'XtestCmdlineClearTabenter', 'D')
+ let buf = RunVimInTerminal('-S XtestCmdlineClearTabenter', #{rows: 10})
+ call TermWait(buf, 25)
+ " in one tab make the command line higher with CTRL-W -
+ call term_sendkeys(buf, ":tabnew\<cr>\<C-w>-\<C-w>-gtgt")
+ call VerifyScreenDump(buf, 'Test_cmdlineclear_tabenter', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for expanding special keywords in cmdline
+func Test_cmdline_expand_special()
+ %bwipe!
+ call assert_fails('e #', 'E194:')
+ call assert_fails('e <afile>', 'E495:')
+ call assert_fails('e <abuf>', 'E496:')
+ call assert_fails('e <amatch>', 'E497:')
+
+ call writefile([], 'Xfile.cpp', 'D')
+ call writefile([], 'Xfile.java', 'D')
+ new Xfile.cpp
+ call feedkeys(":e %:r\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xfile.cpp Xfile.java', @:)
+ close
+endfunc
+
+" Test for backtick expression in the command line
+func Test_cmd_backtick()
+ %argd
+ argadd `=['a', 'b', 'c']`
+ call assert_equal(['a', 'b', 'c'], argv())
+ %argd
+
+ argadd `echo abc def`
+ call assert_equal(['abc def'], argv())
+ %argd
+endfunc
+
+" Test for the :! command
+func Test_cmd_bang()
+ CheckUnix
+
+ let lines =<< trim [SCRIPT]
+ " Test for no previous command
+ call assert_fails('!!', 'E34:')
+ set nomore
+ " Test for cmdline expansion with :!
+ call setline(1, 'foo!')
+ silent !echo <cWORD> > Xfile.out
+ call assert_equal(['foo!'], readfile('Xfile.out'))
+ " Test for using previous command
+ silent !echo \! !
+ call assert_equal(['! echo foo!'], readfile('Xfile.out'))
+ call writefile(v:errors, 'Xresult')
+ call delete('Xfile.out')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test error: "E135: *Filter* Autocommands must not change current buffer"
+func Test_cmd_bang_E135()
+ new
+ call setline(1, ['a', 'b', 'c', 'd'])
+ augroup test_cmd_filter_E135
+ au!
+ autocmd FilterReadPost * help
+ augroup END
+ call assert_fails('2,3!echo "x"', 'E135:')
+
+ augroup test_cmd_filter_E135
+ au!
+ augroup END
+ %bwipe!
+endfunc
+
+func Test_cmd_bang_args()
+ new
+ :.!
+ call assert_equal(0, v:shell_error)
+
+ " Note that below there is one space char after the '!'. This caused a
+ " shell error in the past, see https://github.com/vim/vim/issues/11495.
+ :.!
+ call assert_equal(0, v:shell_error)
+ bwipe!
+
+ CheckUnix
+ :.!pwd
+ call assert_equal(0, v:shell_error)
+ :.! pwd
+ call assert_equal(0, v:shell_error)
+
+ " Note there is one space after 'pwd'.
+ :.! pwd
+ call assert_equal(0, v:shell_error)
+
+ " Note there are two spaces after 'pwd'.
+ :.! pwd
+ call assert_equal(0, v:shell_error)
+ :.!ls ~
+ call assert_equal(0, v:shell_error)
+
+ " Note there is one space char after '~'.
+ :.!ls ~
+ call assert_equal(0, v:shell_error)
+
+ " Note there are two spaces after '~'.
+ :.!ls ~
+ call assert_equal(0, v:shell_error)
+
+ :.!echo "foo"
+ call assert_equal(getline('.'), "foo")
+ :.!echo "foo "
+ call assert_equal(getline('.'), "foo ")
+ :.!echo " foo "
+ call assert_equal(getline('.'), " foo ")
+ :.!echo " foo "
+ call assert_equal(getline('.'), " foo ")
+
+ %bwipe!
+endfunc
+
+" Test for using ~ for home directory in cmdline completion matches
+func Test_cmdline_expand_home()
+ call mkdir('Xexpdir', 'R')
+ call writefile([], 'Xexpdir/Xfile1')
+ call writefile([], 'Xexpdir/Xfile2')
+ cd Xexpdir
+ let save_HOME = $HOME
+ let $HOME = getcwd()
+ call feedkeys(":e ~/\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e ~/Xfile1 ~/Xfile2', @:)
+ let $HOME = save_HOME
+ cd ..
+endfunc
+
+" Test for using CTRL-\ CTRL-G in the command line to go back to normal mode
+" or insert mode (when 'insertmode' is set)
+func Test_cmdline_ctrl_g()
+ new
+ call setline(1, 'abc')
+ call cursor(1, 3)
+ " If command line is entered from insert mode, using C-\ C-G should back to
+ " insert mode
+ call feedkeys("i\<C-O>:\<C-\>\<C-G>xy", 'xt')
+ call assert_equal('abxyc', getline(1))
+ call assert_equal(4, col('.'))
+
+ " If command line is entered in 'insertmode', using C-\ C-G should back to
+ " 'insertmode'
+ call feedkeys(":set im\<cr>\<C-L>:\<C-\>\<C-G>12\<C-L>:set noim\<cr>", 'xt')
+ call assert_equal('ab12xyc', getline(1))
+ close!
+endfunc
+
+" Test for 'wildmode'
+func Wildmode_tests()
+ func T(a, c, p)
+ return "oneA\noneB\noneC"
+ endfunc
+ command -nargs=1 -complete=custom,T MyCmd
+
+ set nowildmenu
+ set wildmode=full,list
+ let g:Sline = ''
+ call feedkeys(":MyCmd \t\t\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('oneA oneB oneC', g:Sline)
+ call assert_equal('"MyCmd oneA', @:)
+
+ set wildmode=longest,full
+ call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd one', @:)
+ call feedkeys(":MyCmd o\t\t\t\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd oneC', @:)
+
+ set wildmode=longest
+ call feedkeys(":MyCmd one\t\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd one', @:)
+
+ set wildmode=list:longest
+ let g:Sline = ''
+ call feedkeys(":MyCmd \t\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('oneA oneB oneC', g:Sline)
+ call assert_equal('"MyCmd one', @:)
+
+ set wildmode=""
+ call feedkeys(":MyCmd \t\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd oneA', @:)
+
+ " Test for wildmode=longest with 'fileignorecase' set
+ set wildmode=longest
+ set fileignorecase
+ argadd AAA AAAA AAAAA
+ call feedkeys(":buffer a\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"buffer AAA', @:)
+ set fileignorecase&
+
+ " Test for listing files with wildmode=list
+ set wildmode=list
+ let g:Sline = ''
+ call feedkeys(":b A\t\t\<F4>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('AAA AAAA AAAAA', g:Sline)
+ call assert_equal('"b A', @:)
+
+ " when using longest completion match, matches shorter than the argument
+ " should be ignored (happens with :help)
+ set wildmode=longest,full
+ set wildmenu
+ call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"help a', @:)
+ " non existing file
+ call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e a1b2y3z4', @:)
+ set wildmenu&
+
+ " Test for longest file name completion with 'fileignorecase'
+ " On MS-Windows, file names are case insensitive.
+ if has('unix')
+ call writefile([], 'XTESTfoo', 'D')
+ call writefile([], 'Xtestbar', 'D')
+ set nofileignorecase
+ call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e XTESTfoo', @:)
+ call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xtestbar', @:)
+ set fileignorecase
+ call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xtest', @:)
+ call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xtest', @:)
+ set fileignorecase&
+ endif
+
+ %argdelete
+ delcommand MyCmd
+ delfunc T
+ set wildmode&
+ %bwipe!
+endfunc
+
+func Test_wildmode()
+ " Test with utf-8 encoding
+ call Wildmode_tests()
+
+ " Test with latin1 encoding
+ let save_encoding = &encoding
+ set encoding=latin1
+ call Wildmode_tests()
+ let &encoding = save_encoding
+endfunc
+
+func Test_custom_complete_autoload()
+ call mkdir('Xcustdir/autoload', 'pR')
+ let save_rtp = &rtp
+ exe 'set rtp=' .. getcwd() .. '/Xcustdir'
+ let lines =<< trim END
+ func vim8#Complete(a, c, p)
+ return "oneA\noneB\noneC"
+ endfunc
+ END
+ call writefile(lines, 'Xcustdir/autoload/vim8.vim')
+
+ command -nargs=1 -complete=custom,vim8#Complete MyCmd
+ set nowildmenu
+ set wildmode=full,list
+ call feedkeys(":MyCmd \<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd oneA oneB oneC', @:)
+
+ let &rtp = save_rtp
+ set wildmode& wildmenu&
+ delcommand MyCmd
+endfunc
+
+" Test for interrupting the command-line completion
+func Test_interrupt_compl()
+ func F(lead, cmdl, p)
+ if a:lead =~ 'tw'
+ call interrupt()
+ return
+ endif
+ return "one\ntwo\nthree"
+ endfunc
+ command -nargs=1 -complete=custom,F Tcmd
+
+ set nowildmenu
+ set wildmode=full
+ let interrupted = 0
+ try
+ call feedkeys(":Tcmd tw\<Tab>\<C-B>\"\<CR>", 'xt')
+ catch /^Vim:Interrupt$/
+ let interrupted = 1
+ endtry
+ call assert_equal(1, interrupted)
+
+ let interrupted = 0
+ try
+ call feedkeys(":Tcmd tw\<C-d>\<C-B>\"\<CR>", 'xt')
+ catch /^Vim:Interrupt$/
+ let interrupted = 1
+ endtry
+ call assert_equal(1, interrupted)
+
+ delcommand Tcmd
+ delfunc F
+ set wildmode&
+endfunc
+
+" Test for moving the cursor on the : command line
+func Test_cmdline_edit()
+ let str = ":one two\<C-U>"
+ let str ..= "one two\<C-W>\<C-W>"
+ let str ..= "four\<BS>\<C-H>\<Del>\<kDel>"
+ let str ..= "\<Left>five\<Right>"
+ let str ..= "\<Home>two "
+ let str ..= "\<C-Left>one "
+ let str ..= "\<C-Right> three"
+ let str ..= "\<End>\<S-Left>four "
+ let str ..= "\<S-Right> six"
+ let str ..= "\<C-B>\"\<C-E> seven\<CR>"
+ call feedkeys(str, 'xt')
+ call assert_equal("\"one two three four five six seven", @:)
+endfunc
+
+" Test for moving the cursor on the / command line in 'rightleft' mode
+func Test_cmdline_edit_rightleft()
+ CheckFeature rightleft
+ set rightleft
+ set rightleftcmd=search
+ let str = "/one two\<C-U>"
+ let str ..= "one two\<C-W>\<C-W>"
+ let str ..= "four\<BS>\<C-H>\<Del>\<kDel>"
+ let str ..= "\<Right>five\<Left>"
+ let str ..= "\<Home>two "
+ let str ..= "\<C-Right>one "
+ let str ..= "\<C-Left> three"
+ let str ..= "\<End>\<S-Right>four "
+ let str ..= "\<S-Left> six"
+ let str ..= "\<C-B>\"\<C-E> seven\<CR>"
+ call assert_fails("call feedkeys(str, 'xt')", 'E486:')
+ call assert_equal("\"one two three four five six seven", @/)
+ set rightleftcmd&
+ set rightleft&
+endfunc
+
+" Test for using <C-\>e in the command line to evaluate an expression
+func Test_cmdline_expr()
+ " Evaluate an expression from the beginning of a command line
+ call feedkeys(":abc\<C-B>\<C-\>e\"\\\"hello\"\<CR>\<CR>", 'xt')
+ call assert_equal('"hello', @:)
+
+ " Use an invalid expression for <C-\>e
+ call assert_beeps('call feedkeys(":\<C-\>einvalid\<CR>", "tx")')
+
+ " Insert literal <CTRL-\> in the command line
+ call feedkeys(":\"e \<C-\>\<C-Y>\<CR>", 'xt')
+ call assert_equal("\"e \<C-\>\<C-Y>", @:)
+endfunc
+
+" This was making the insert position negative
+func Test_cmdline_expr_register()
+ exe "sil! norm! ?\<C-\>e0\<C-R>0\<Esc>?\<C-\>e0\<CR>"
+endfunc
+
+" Test for 'imcmdline' and 'imsearch'
+" This test doesn't actually test the input method functionality.
+func Test_cmdline_inputmethod()
+ new
+ call setline(1, ['', 'abc', ''])
+ set imcmdline
+
+ call feedkeys(":\"abc\<CR>", 'xt')
+ call assert_equal("\"abc", @:)
+ call feedkeys(":\"\<C-^>abc\<C-^>\<CR>", 'xt')
+ call assert_equal("\"abc", @:)
+ call feedkeys("/abc\<CR>", 'xt')
+ call assert_equal([2, 1], [line('.'), col('.')])
+ call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ set imsearch=2
+ call cursor(1, 1)
+ call feedkeys("/abc\<CR>", 'xt')
+ call assert_equal([2, 1], [line('.'), col('.')])
+ call cursor(1, 1)
+ call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt')
+ call assert_equal([2, 1], [line('.'), col('.')])
+ set imdisable
+ call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt')
+ call assert_equal([2, 1], [line('.'), col('.')])
+ set imdisable&
+ set imsearch&
+
+ set imcmdline&
+ %bwipe!
+endfunc
+
+" Test for using CTRL-_ in the command line with 'allowrevins'
+func Test_cmdline_revins()
+ CheckNotMSWindows
+ CheckFeature rightleft
+ call feedkeys(":\"abc\<c-_>\<cr>", 'xt')
+ call assert_equal("\"abc\<c-_>", @:)
+ set allowrevins
+ call feedkeys(":\"abc\<c-_>xyz\<c-_>\<CR>", 'xt')
+ call assert_equal('"abcñèæ', @:)
+ set allowrevins&
+endfunc
+
+" Test for typing UTF-8 composing characters in the command line
+func Test_cmdline_composing_chars()
+ call feedkeys(":\"\<C-V>u3046\<C-V>u3099\<CR>", 'xt')
+ call assert_equal('"ã†ã‚™', @:)
+endfunc
+
+" test that ";" works to find a match at the start of the first line
+func Test_zero_line_search()
+ new
+ call setline(1, ["1, pattern", "2, ", "3, pattern"])
+ call cursor(1,1)
+ 0;/pattern/d
+ call assert_equal(["2, ", "3, pattern"], getline(1,'$'))
+ q!
+endfunc
+
+func Test_read_shellcmd()
+ CheckUnix
+ if executable('ls')
+ " There should be ls in the $PATH
+ call feedkeys(":r! l\<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"r! .*\<ls\>', @:)
+ endif
+
+ if executable('rm')
+ call feedkeys(":r! ++enc=utf-8 r\<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_notmatch('^"r!.*\<runtest.vim\>', @:)
+ call assert_match('^"r!.*\<rm\>', @:)
+
+ call feedkeys(":r ++enc=utf-8 !rm\<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_notmatch('^"r.*\<runtest.vim\>', @:)
+ call assert_match('^"r ++enc\S\+ !.*\<rm\>', @:)
+ endif
+endfunc
+
+" Test for going up and down the directory tree using 'wildmenu'
+func Test_wildmenu_dirstack()
+ CheckUnix
+ %bw!
+ call mkdir('Xwildmenu/dir2/dir3/dir4', 'pR')
+ call writefile([], 'Xwildmenu/file1_1.txt')
+ call writefile([], 'Xwildmenu/file1_2.txt')
+ call writefile([], 'Xwildmenu/dir2/file2_1.txt')
+ call writefile([], 'Xwildmenu/dir2/file2_2.txt')
+ call writefile([], 'Xwildmenu/dir2/dir3/file3_1.txt')
+ call writefile([], 'Xwildmenu/dir2/dir3/file3_2.txt')
+ call writefile([], 'Xwildmenu/dir2/dir3/dir4/file4_1.txt')
+ call writefile([], 'Xwildmenu/dir2/dir3/dir4/file4_2.txt')
+ set wildmenu
+
+ cd Xwildmenu/dir2/dir3/dir4
+ call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e file4_1.txt', @:)
+ call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e ../dir4/', @:)
+ call feedkeys(":e \<Tab>\<Up>\<Up>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e ../../dir3/', @:)
+ call feedkeys(":e \<Tab>\<Up>\<Up>\<Up>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e ../../../dir2/', @:)
+ call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e ../../dir3/dir4/', @:)
+ call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e ../../dir3/dir4/file4_1.txt', @:)
+ cd -
+ call feedkeys(":e Xwildmenu/\<Tab>\<Down>\<Down>\<Down>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xwildmenu/dir2/dir3/dir4/file4_1.txt', @:)
+
+ set wildmenu&
+endfunc
+
+" Test for recalling newer or older cmdline from history with <Up>, <Down>,
+" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <kPageUp>, <kPageDown>, <C-p>, or
+" <C-n>.
+func Test_recalling_cmdline()
+ CheckFeature cmdline_hist
+
+ let g:cmdlines = []
+ cnoremap <Plug>(save-cmdline) <Cmd>let g:cmdlines += [getcmdline()]<CR>
+
+ let histories = [
+ \ #{name: 'cmd', enter: ':', exit: "\<Esc>"},
+ \ #{name: 'search', enter: '/', exit: "\<Esc>"},
+ \ #{name: 'expr', enter: ":\<C-r>=", exit: "\<Esc>\<Esc>"},
+ \ #{name: 'input', enter: ":call input('')\<CR>", exit: "\<CR>"},
+ "\ TODO: {'name': 'debug', ...}
+ \]
+ let keypairs = [
+ \ #{older: "\<Up>", newer: "\<Down>", prefixmatch: v:true},
+ \ #{older: "\<S-Up>", newer: "\<S-Down>", prefixmatch: v:false},
+ \ #{older: "\<PageUp>", newer: "\<PageDown>", prefixmatch: v:false},
+ \ #{older: "\<kPageUp>", newer: "\<kPageDown>", prefixmatch: v:false},
+ \ #{older: "\<C-p>", newer: "\<C-n>", prefixmatch: v:false},
+ \]
+ let prefix = 'vi'
+ for h in histories
+ call histadd(h.name, 'vim')
+ call histadd(h.name, 'virtue')
+ call histadd(h.name, 'Virgo')
+ call histadd(h.name, 'vogue')
+ call histadd(h.name, 'emacs')
+ for k in keypairs
+ let g:cmdlines = []
+ let keyseqs = h.enter
+ \ .. prefix
+ \ .. repeat(k.older .. "\<Plug>(save-cmdline)", 2)
+ \ .. repeat(k.newer .. "\<Plug>(save-cmdline)", 2)
+ \ .. h.exit
+ call feedkeys(keyseqs, 'xt')
+ call histdel(h.name, -1) " delete the history added by feedkeys above
+ let expect = k.prefixmatch
+ \ ? ['virtue', 'vim', 'virtue', prefix]
+ \ : ['emacs', 'vogue', 'emacs', prefix]
+ call assert_equal(expect, g:cmdlines)
+ endfor
+ endfor
+
+ unlet g:cmdlines
+ cunmap <Plug>(save-cmdline)
+endfunc
+
+func Test_cmd_map_cmdlineChanged()
+ let g:log = []
+ cnoremap <F1> l<Cmd><CR>s
+ augroup test
+ autocmd!
+ autocmd CmdlineChanged : let g:log += [getcmdline()]
+ augroup END
+
+ call feedkeys(":\<F1>\<CR>", 'xt')
+ call assert_equal(['l', 'ls'], g:log)
+
+ let @b = 'b'
+ cnoremap <F1> a<C-R>b
+ let g:log = []
+ call feedkeys(":\<F1>\<CR>", 'xt')
+ call assert_equal(['a', 'ab'], g:log)
+
+ unlet g:log
+ cunmap <F1>
+ augroup test
+ autocmd!
+ augroup END
+endfunc
+
+" Test for the 'suffixes' option
+func Test_suffixes_opt()
+ call writefile([], 'Xsuffile', 'D')
+ call writefile([], 'Xsuffile.c', 'D')
+ call writefile([], 'Xsuffile.o', 'D')
+ set suffixes=
+ call feedkeys(":e Xsuffi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile Xsuffile.c Xsuffile.o', @:)
+ call feedkeys(":e Xsuffi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.c', @:)
+ set suffixes=.c
+ call feedkeys(":e Xsuffi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile Xsuffile.o Xsuffile.c', @:)
+ call feedkeys(":e Xsuffi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.o', @:)
+ set suffixes=,,
+ call feedkeys(":e Xsuffi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.c Xsuffile.o Xsuffile', @:)
+ call feedkeys(":e Xsuffi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.o', @:)
+ set suffixes&
+ " Test for getcompletion() with different patterns
+ call assert_equal(['Xsuffile', 'Xsuffile.c', 'Xsuffile.o'], getcompletion('Xsuffile', 'file'))
+ call assert_equal(['Xsuffile'], getcompletion('Xsuffile$', 'file'))
+endfunc
+
+" Test for using a popup menu for the command line completion matches
+" (wildoptions=pum)
+func Test_wildmenu_pum()
+ CheckRunVimInTerminal
+
+ let commands =<< trim [CODE]
+ set wildmenu
+ set wildoptions=pum
+ set shm+=I
+ set noruler
+ set noshowcmd
+
+ func CmdCompl(a, b, c)
+ return repeat(['aaaa'], 120)
+ endfunc
+ command -nargs=* -complete=customlist,CmdCompl Tcmd
+
+ func MyStatusLine() abort
+ return 'status'
+ endfunc
+ func SetupStatusline()
+ set statusline=%!MyStatusLine()
+ set laststatus=2
+ endfunc
+
+ func MyTabLine()
+ return 'my tab line'
+ endfunc
+ func SetupTabline()
+ set statusline=
+ set tabline=%!MyTabLine()
+ set showtabline=2
+ endfunc
+
+ func DoFeedKeys()
+ let &wildcharm = char2nr("\t")
+ call feedkeys(":edit $VIMRUNTIME/\<Tab>\<Left>\<C-U>ab\<Tab>")
+ endfunc
+ [CODE]
+ call writefile(commands, 'Xtest', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest', #{rows: 10})
+
+ call term_sendkeys(buf, ":sign \<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_01', {})
+
+ " going down the popup menu using <Down>
+ call term_sendkeys(buf, "\<Down>\<Down>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_02', {})
+
+ " going down the popup menu using <C-N>
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_03', {})
+
+ " going up the popup menu using <C-P>
+ call term_sendkeys(buf, "\<C-P>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_04', {})
+
+ " going up the popup menu using <Up>
+ call term_sendkeys(buf, "\<Up>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_05', {})
+
+ " pressing <C-E> should end completion and go back to the original match
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_06', {})
+
+ " pressing <C-Y> should select the current match and end completion
+ call term_sendkeys(buf, "\<Tab>\<C-P>\<C-P>\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_07', {})
+
+ " With 'wildmode' set to 'longest,full', completing a match should display
+ " the longest match, the wildmenu should not be displayed.
+ call term_sendkeys(buf, ":\<C-U>set wildmode=longest,full\<CR>")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":sign u\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_08', {})
+
+ " pressing <Tab> should display the wildmenu
+ call term_sendkeys(buf, "\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {})
+
+ " pressing <Tab> second time should select the next entry in the menu
+ call term_sendkeys(buf, "\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {})
+
+ call term_sendkeys(buf, ":\<C-U>set wildmode=full\<CR>")
+ " showing popup menu in different columns in the cmdline
+ call term_sendkeys(buf, ":sign define \<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_11', {})
+
+ call term_sendkeys(buf, " \<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_12', {})
+
+ call term_sendkeys(buf, " \<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_13', {})
+
+ " Directory name completion
+ call mkdir('Xnamedir/XdirA/XdirB', 'pR')
+ call writefile([], 'Xnamedir/XfileA')
+ call writefile([], 'Xnamedir/XdirA/XfileB')
+ call writefile([], 'Xnamedir/XdirA/XdirB/XfileC')
+
+ call term_sendkeys(buf, "\<C-U>e Xnamedi\<Tab>\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_14', {})
+
+ " Pressing <Right> on a directory name should go into that directory
+ call term_sendkeys(buf, "\<Right>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_15', {})
+
+ " Pressing <Left> on a directory name should go to the parent directory
+ call term_sendkeys(buf, "\<Left>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_16', {})
+
+ " Pressing <C-A> when the popup menu is displayed should list all the
+ " matches but the popup menu should still remain
+ call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-A>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_17', {})
+
+ " Pressing <C-D> when the popup menu is displayed should remove the popup
+ " menu
+ call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-D>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_18', {})
+
+ " Pressing <S-Tab> should open the popup menu with the last entry selected
+ call term_sendkeys(buf, "\<C-U>\<CR>:sign \<S-Tab>\<C-P>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_19', {})
+
+ " Pressing <Esc> should close the popup menu and cancel the cmd line
+ call term_sendkeys(buf, "\<C-U>\<CR>:sign \<Tab>\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_20', {})
+
+ " Typing a character when the popup is open, should close the popup
+ call term_sendkeys(buf, ":sign \<Tab>x")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_21', {})
+
+ " When the popup is open, entering the cmdline window should close the popup
+ call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-F>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_22', {})
+ call term_sendkeys(buf, ":q\<CR>")
+
+ " After the last popup menu item, <C-N> should show the original string
+ call term_sendkeys(buf, ":sign u\<Tab>\<C-N>\<C-N>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_23', {})
+
+ " Use the popup menu for the command name
+ call term_sendkeys(buf, "\<C-U>bu\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_24', {})
+
+ " Pressing the left arrow should remove the popup menu
+ call term_sendkeys(buf, "\<Left>\<Left>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_25', {})
+
+ " Pressing <BS> should remove the popup menu and erase the last character
+ call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<BS>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_26', {})
+
+ " Pressing <C-W> should remove the popup menu and erase the previous word
+ call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-W>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_27', {})
+
+ " Pressing <C-U> should remove the popup menu and erase the entire line
+ call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_28', {})
+
+ " Using <C-E> to cancel the popup menu and then pressing <Up> should recall
+ " the cmdline from history
+ call term_sendkeys(buf, "sign xyz\<Esc>:sign \<Tab>\<C-E>\<Up>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {})
+
+ " Check "list" still works
+ call term_sendkeys(buf, "\<C-U>set wildmode=longest,list\<CR>")
+ call term_sendkeys(buf, ":cn\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {})
+ call term_sendkeys(buf, "s")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {})
+
+ " Tests a directory name contained full-width characters.
+ call mkdir('Xnamedir/ã‚ã„ã†', 'p')
+ call writefile([], 'Xnamedir/ã‚ã„ã†/abc')
+ call writefile([], 'Xnamedir/ã‚ã„ã†/xyz')
+ call writefile([], 'Xnamedir/ã‚ã„ã†/123')
+
+ call term_sendkeys(buf, "\<C-U>set wildmode&\<CR>")
+ call term_sendkeys(buf, ":\<C-U>e Xnamedir/ã‚ã„ã†/\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_32', {})
+
+ " Pressing <C-A> when the popup menu is displayed should list all the
+ " matches and pressing a key after that should remove the popup menu
+ call term_sendkeys(buf, "\<C-U>set wildmode=full\<CR>")
+ call term_sendkeys(buf, ":sign \<Tab>\<C-A>x")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_33', {})
+
+ " Pressing <C-A> when the popup menu is displayed should list all the
+ " matches and pressing <Left> after that should move the cursor
+ call term_sendkeys(buf, "\<C-U>abc\<Esc>")
+ call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<Left>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_34', {})
+
+ " When <C-A> displays a lot of matches (screen scrolls), all the matches
+ " should be displayed correctly on the screen.
+ call term_sendkeys(buf, "\<End>\<C-U>Tcmd \<Tab>\<C-A>\<Left>\<Left>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_35', {})
+
+ " After using <C-A> to expand all the filename matches, pressing <Up>
+ " should not open the popup menu again.
+ call term_sendkeys(buf, "\<C-E>\<C-U>:cd Xnamedir/XdirA\<CR>")
+ call term_sendkeys(buf, ":e \<Tab>\<C-A>\<Up>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_36', {})
+ call term_sendkeys(buf, "\<C-E>\<C-U>:cd -\<CR>")
+
+ " After using <C-A> to expand all the matches, pressing <S-Tab> used to
+ " crash Vim
+ call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<S-Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_37', {})
+
+ " After removing the pum the command line is redrawn
+ call term_sendkeys(buf, ":edit foo\<CR>")
+ call term_sendkeys(buf, ":edit bar\<CR>")
+ call term_sendkeys(buf, ":ls\<CR>")
+ call term_sendkeys(buf, ":com\<Tab> ")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_38', {})
+ call term_sendkeys(buf, "\<C-U>\<CR>")
+
+ " Esc still works to abort the command when 'statusline' is set
+ call term_sendkeys(buf, ":call SetupStatusline()\<CR>")
+ call term_sendkeys(buf, ":si\<Tab>")
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_39', {})
+
+ " Esc still works to abort the command when 'tabline' is set
+ call term_sendkeys(buf, ":call SetupTabline()\<CR>")
+ call term_sendkeys(buf, ":si\<Tab>")
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_40', {})
+
+ " popup is cleared also when 'lazyredraw' is set
+ call term_sendkeys(buf, ":set showtabline=1 laststatus=1 lazyredraw\<CR>")
+ call term_sendkeys(buf, ":call DoFeedKeys()\<CR>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_41', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Pressing <PageDown> should scroll the menu downward
+ call term_sendkeys(buf, ":sign \<Tab>\<PageDown>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_42', {})
+ call term_sendkeys(buf, "\<PageDown>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_43', {})
+ call term_sendkeys(buf, "\<PageDown>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_44', {})
+ call term_sendkeys(buf, "\<PageDown>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_45', {})
+ call term_sendkeys(buf, "\<C-U>sign \<Tab>\<Down>\<Down>\<PageDown>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_46', {})
+
+ " Pressing <PageUp> should scroll the menu upward
+ call term_sendkeys(buf, "\<C-U>sign \<Tab>\<PageUp>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_47', {})
+ call term_sendkeys(buf, "\<PageUp>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_48', {})
+ call term_sendkeys(buf, "\<PageUp>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_49', {})
+ call term_sendkeys(buf, "\<PageUp>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_50', {})
+
+ call term_sendkeys(buf, "\<C-U>\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for wildmenumode() with the cmdline popup menu
+func Test_wildmenumode_with_pum()
+ set wildmenu
+ set wildoptions=pum
+ cnoremap <expr> <F2> wildmenumode()
+ call feedkeys(":sign \<Tab>\<F2>\<F2>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"sign define10', @:)
+ call feedkeys(":sign \<Tab>\<C-A>\<F2>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"sign define jump list place undefine unplace0', @:)
+ call feedkeys(":sign \<Tab>\<C-E>\<F2>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"sign 0', @:)
+ call feedkeys(":sign \<Tab>\<C-Y>\<F2>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"sign define0', @:)
+ set nowildmenu wildoptions&
+ cunmap <F2>
+endfunc
+
+func Test_wildmenu_with_pum_foldexpr()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['folded one', 'folded two', 'some more text'])
+ func MyFoldText()
+ return 'foo'
+ endfunc
+ set foldtext=MyFoldText() wildoptions=pum
+ normal ggzfj
+ END
+ call writefile(lines, 'Xpumfold', 'D')
+ let buf = RunVimInTerminal('-S Xpumfold', #{rows: 10})
+ call term_sendkeys(buf, ":set\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_with_pum_foldexpr_1', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_with_pum_foldexpr_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for opening the cmdline completion popup menu from the terminal window.
+" The popup menu should be positioned correctly over the status line of the
+" bottom-most window.
+func Test_wildmenu_pum_from_terminal()
+ CheckRunVimInTerminal
+ let python = PythonProg()
+ call CheckPython(python)
+
+ %bw!
+ let cmds = ['set wildmenu wildoptions=pum']
+ let pcmd = python .. ' -c "import sys; sys.stdout.write(sys.stdin.read())"'
+ call add(cmds, "call term_start('" .. pcmd .. "')")
+ call writefile(cmds, 'Xtest', 'D')
+ let buf = RunVimInTerminal('-S Xtest', #{rows: 10})
+ call term_sendkeys(buf, "\r\r\r")
+ call term_wait(buf)
+ call term_sendkeys(buf, "\<C-W>:sign \<Tab>")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_term_01', {})
+ call term_wait(buf)
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_wildmenu_pum_clear_entries()
+ CheckRunVimInTerminal
+
+ " This was using freed memory. Run in a terminal to get the pum to update.
+ let lines =<< trim END
+ set wildoptions=pum
+ set wildchar=<C-E>
+ END
+ call writefile(lines, 'XwildmenuTest', 'D')
+ let buf = RunVimInTerminal('-S XwildmenuTest', #{rows: 10})
+
+ call term_sendkeys(buf, ":\<C-E>\<C-E>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_clear_entries_1', {})
+
+ set wildoptions& wildchar&
+endfunc
+
+" Test for completion after a :substitute command followed by a pipe (|)
+" character
+func Test_cmdline_complete_substitute()
+ call feedkeys(":s | \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s | \t", @:)
+ call feedkeys(":s/ | \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s/ | \t", @:)
+ call feedkeys(":s/one | \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s/one | \t", @:)
+ call feedkeys(":s/one/ | \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s/one/ | \t", @:)
+ call feedkeys(":s/one/two | \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s/one/two | \t", @:)
+ call feedkeys(":s/one/two/ | chist\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"s/one/two/ | chistory', @:)
+ call feedkeys(":s/one/two/g \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s/one/two/g \t", @:)
+ call feedkeys(":s/one/two/g | chist\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s/one/two/g | chistory", @:)
+ call feedkeys(":s/one/t\\/ | \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"s/one/t\\/ | \t", @:)
+ call feedkeys(":s/one/t\"o/ | chist\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"s/one/t"o/ | chistory', @:)
+ call feedkeys(":s/one/t|o/ | chist\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"s/one/t|o/ | chistory', @:)
+ call feedkeys(":&\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"&\t", @:)
+endfunc
+
+" Test for the :dlist command completion
+func Test_cmdline_complete_dlist()
+ call feedkeys(":dlist 10 /pat/ a\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"dlist 10 /pat/ a\<C-A>", @:)
+ call feedkeys(":dlist 10 /pat/ \t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"dlist 10 /pat/ \t", @:)
+ call feedkeys(":dlist 10 /pa\\t/\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"dlist 10 /pa\\t/\t", @:)
+ call feedkeys(":dlist 10 /pat\\\t\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"dlist 10 /pat\\\t", @:)
+ call feedkeys(":dlist 10 /pat/ | chist\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"dlist 10 /pat/ | chistory", @:)
+endfunc
+
+" argument list (only for :argdel) fuzzy completion
+func Test_fuzzy_completion_arglist()
+ argadd change.py count.py charge.py
+ set wildoptions&
+ call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"argdel cge', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"argdel change.py charge.py', @:)
+ %argdelete
+ set wildoptions&
+endfunc
+
+" autocmd group name fuzzy completion
+func Test_fuzzy_completion_autocmd()
+ set wildoptions&
+ augroup MyFuzzyGroup
+ augroup END
+ call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"augroup mfg', @:)
+ call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"augroup MyFuzzyGroup', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"augroup MyFuzzyGroup', @:)
+ call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"augroup My*p', @:)
+ augroup! MyFuzzyGroup
+ set wildoptions&
+endfunc
+
+" buffer name fuzzy completion
+func Test_fuzzy_completion_bufname()
+ set wildoptions&
+ " Use a long name to reduce the risk of matching a random directory name
+ edit SomeRandomFileWithLetters.txt
+ enew
+ call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"b SRFWL', @:)
+ call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"b SomeRandomFileWithLetters.txt', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"b SomeRandomFileWithLetters.txt', @:)
+ call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"b S*FileWithLetters.txt', @:)
+ %bw!
+ set wildoptions&
+endfunc
+
+" buffer name (full path) fuzzy completion
+func Test_fuzzy_completion_bufname_fullpath()
+ CheckUnix
+ set wildoptions&
+ call mkdir('Xcmd/Xstate/Xfile.js', 'pR')
+ edit Xcmd/Xstate/Xfile.js
+ cd Xcmd/Xstate
+ enew
+ call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"b CmdStateFile', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match('Xcmd/Xstate/Xfile.js$', @:)
+ cd -
+ set wildoptions&
+endfunc
+
+" :behave suboptions fuzzy completion
+func Test_fuzzy_completion_behave()
+ set wildoptions&
+ call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"behave xm', @:)
+ call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"behave xterm', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"behave xterm', @:)
+ call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"behave xt*m', @:)
+ let g:Sline = ''
+ call feedkeys(":behave win\<C-D>\<F4>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('mswin', g:Sline)
+ call assert_equal('"behave win', @:)
+ set wildoptions&
+endfunc
+
+" " colorscheme name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_colorscheme()
+" endfunc
+
+" built-in command name fuzzy completion
+func Test_fuzzy_completion_cmdname()
+ set wildoptions&
+ call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sbwin', @:)
+ call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sbrewind', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sbrewind', @:)
+ call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sbr*d', @:)
+ set wildoptions&
+endfunc
+
+" " compiler name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_compiler()
+" endfunc
+
+" :cscope suboptions fuzzy completion
+func Test_fuzzy_completion_cscope()
+ CheckFeature cscope
+ set wildoptions&
+ call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cscope ret', @:)
+ call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cscope reset', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cscope reset', @:)
+ call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cscope re*t', @:)
+ set wildoptions&
+endfunc
+
+" :diffget/:diffput buffer name fuzzy completion
+func Test_fuzzy_completion_diff()
+ new SomeBuffer
+ diffthis
+ new OtherBuffer
+ diffthis
+ set wildoptions&
+ call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget sbuf', @:)
+ call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput sbuf', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget SomeBuffer', @:)
+ call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput SomeBuffer', @:)
+ %bw!
+ set wildoptions&
+endfunc
+
+" " directory name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_dirname()
+" endfunc
+
+" environment variable name fuzzy completion
+func Test_fuzzy_completion_env()
+ set wildoptions&
+ call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"echo $VUT', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"echo $VIMRUNTIME', @:)
+ set wildoptions&
+endfunc
+
+" autocmd event fuzzy completion
+func Test_fuzzy_completion_autocmd_event()
+ set wildoptions&
+ call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"autocmd BWout', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"autocmd BufWipeout', @:)
+ set wildoptions&
+endfunc
+
+" vim expression fuzzy completion
+func Test_fuzzy_completion_expr()
+ let g:PerPlaceCount = 10
+ set wildoptions&
+ call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"let c = ppc', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"let c = PerPlaceCount', @:)
+ set wildoptions&
+endfunc
+
+" " file name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_filename()
+" endfunc
+
+" " files in path fuzzy completion - NOT supported
+" func Test_fuzzy_completion_filesinpath()
+" endfunc
+
+" " filetype name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_filetype()
+" endfunc
+
+" user defined function name completion
+func Test_fuzzy_completion_userdefined_func()
+ set wildoptions&
+ call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"call Test_f_u_f', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"call Test_fuzzy_completion_userdefined_func()', @:)
+ set wildoptions&
+endfunc
+
+" <SNR> functions should be sorted to the end
+func Test_fuzzy_completion_userdefined_snr_func()
+ func s:Sendmail()
+ endfunc
+ func SendSomemail()
+ endfunc
+ func S1e2n3dmail()
+ endfunc
+ set wildoptions=fuzzy
+ call feedkeys(":call sendmail\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"call SendSomemail() S1e2n3dmail() <SNR>\d\+_Sendmail()', @:)
+ set wildoptions&
+ delfunc s:Sendmail
+ delfunc SendSomemail
+ delfunc S1e2n3dmail
+endfunc
+
+" user defined command name completion
+func Test_fuzzy_completion_userdefined_cmd()
+ set wildoptions&
+ call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"MsFeat', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"MissingFeature', @:)
+ set wildoptions&
+endfunc
+
+" " :help tag fuzzy completion - NOT supported
+" func Test_fuzzy_completion_helptag()
+" endfunc
+
+" highlight group name fuzzy completion
+func Test_fuzzy_completion_hlgroup()
+ set wildoptions&
+ call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"highlight SKey', @:)
+ call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"highlight SpecialKey', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"highlight SpecialKey', @:)
+ call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"highlight Sp*Key', @:)
+ set wildoptions&
+endfunc
+
+" :history suboptions fuzzy completion
+func Test_fuzzy_completion_history()
+ set wildoptions&
+ call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"history dg', @:)
+ call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"history search', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"history debug', @:)
+ call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"history se*h', @:)
+ set wildoptions&
+endfunc
+
+" :language locale name fuzzy completion
+func Test_fuzzy_completion_lang()
+ CheckUnix
+ set wildoptions&
+ call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"lang psx', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"lang POSIX', @:)
+ set wildoptions&
+endfunc
+
+" :mapclear buffer argument fuzzy completion
+func Test_fuzzy_completion_mapclear()
+ set wildoptions&
+ call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"mapclear buf', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"mapclear <buffer>', @:)
+ set wildoptions&
+endfunc
+
+" map name fuzzy completion
+func Test_fuzzy_completion_mapname()
+ " test regex completion works
+ set wildoptions=fuzzy
+ call feedkeys(":cnoremap <ex\<Tab> <esc> \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"cnoremap <expr> <esc> \<Tab>", @:)
+ nmap <plug>MyLongMap :p<CR>
+ call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"nmap <Plug>MyLongMap", @:)
+ call feedkeys(":nmap MLM \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"nmap MLM \t", @:)
+ call feedkeys(":nmap <F2> one two \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"nmap <F2> one two \t", @:)
+ " duplicate entries should be removed
+ vmap <plug>MyLongMap :<C-U>#<CR>
+ call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"nmap <Plug>MyLongMap", @:)
+ nunmap <plug>MyLongMap
+ vunmap <plug>MyLongMap
+ call feedkeys(":nmap ABC\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"nmap ABC\t", @:)
+ " results should be sorted by best match
+ nmap <Plug>format :
+ nmap <Plug>goformat :
+ nmap <Plug>TestFOrmat :
+ nmap <Plug>fendoff :
+ nmap <Plug>state :
+ nmap <Plug>FendingOff :
+ call feedkeys(":nmap <Plug>fo\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"nmap <Plug>format <Plug>TestFOrmat <Plug>FendingOff <Plug>goformat <Plug>fendoff", @:)
+ nunmap <Plug>format
+ nunmap <Plug>goformat
+ nunmap <Plug>TestFOrmat
+ nunmap <Plug>fendoff
+ nunmap <Plug>state
+ nunmap <Plug>FendingOff
+ set wildoptions&
+endfunc
+
+" abbreviation fuzzy completion
+func Test_fuzzy_completion_abbr()
+ set wildoptions=fuzzy
+ call feedkeys(":iabbr wait\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"iabbr <nowait>", @:)
+ iabbr WaitForCompletion WFC
+ call feedkeys(":iabbr fcl\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"iabbr WaitForCompletion", @:)
+ call feedkeys(":iabbr a1z\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"iabbr a1z\t", @:)
+
+ iunabbrev WaitForCompletion
+ set wildoptions&
+endfunc
+
+" menu name fuzzy completion
+func Test_fuzzy_completion_menu()
+ CheckFeature menu
+
+ source $VIMRUNTIME/menu.vim
+ set wildoptions&
+ call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"menu pup', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"menu PopUp.', @:)
+
+ set wildoptions&
+ source $VIMRUNTIME/delmenu.vim
+endfunc
+
+" :messages suboptions fuzzy completion
+func Test_fuzzy_completion_messages()
+ set wildoptions&
+ call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"messages clr', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"messages clear', @:)
+ set wildoptions&
+endfunc
+
+" :set option name fuzzy completion
+func Test_fuzzy_completion_option()
+ set wildoptions&
+ call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set brkopt', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set breakindentopt', @:)
+ set wildoptions&
+ call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set fixendofline', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set fixendofline', @:)
+ set wildoptions&
+endfunc
+
+" :set <term_option>
+func Test_fuzzy_completion_term_option()
+ set wildoptions&
+ call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set t_EC', @:)
+ call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set <t_EC>', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set t_EC', @:)
+ call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set <t_EC>', @:)
+ set wildoptions&
+endfunc
+
+" " :packadd directory name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_packadd()
+" endfunc
+
+" " shell command name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_shellcmd()
+" endfunc
+
+" :sign suboptions fuzzy completion
+func Test_fuzzy_completion_sign()
+ set wildoptions&
+ call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign ufe', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign undefine', @:)
+ set wildoptions&
+endfunc
+
+" :syntax suboptions fuzzy completion
+func Test_fuzzy_completion_syntax_cmd()
+ set wildoptions&
+ call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syntax kwd', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syntax keyword', @:)
+ set wildoptions&
+endfunc
+
+" syntax group name fuzzy completion
+func Test_fuzzy_completion_syntax_group()
+ set wildoptions&
+ call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syntax list mpar', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syntax list MatchParen', @:)
+ set wildoptions&
+endfunc
+
+" :syntime suboptions fuzzy completion
+func Test_fuzzy_completion_syntime()
+ CheckFeature profile
+ set wildoptions&
+ call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syntime clr', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syntime clear', @:)
+ set wildoptions&
+endfunc
+
+" " tag name fuzzy completion - NOT supported
+" func Test_fuzzy_completion_tagname()
+" endfunc
+
+" " tag name and file fuzzy completion - NOT supported
+" func Test_fuzzy_completion_tagfile()
+" endfunc
+
+" " user names fuzzy completion - how to test this functionality?
+" func Test_fuzzy_completion_username()
+" endfunc
+
+" user defined variable name fuzzy completion
+func Test_fuzzy_completion_userdefined_var()
+ let g:SomeVariable=10
+ set wildoptions&
+ call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"let SVar', @:)
+ set wildoptions=fuzzy
+ call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"let SomeVariable', @:)
+ set wildoptions&
+endfunc
+
+" Test for sorting the results by the best match
+func Test_fuzzy_completion_cmd_sort_results()
+ %bw!
+ command T123format :
+ command T123goformat :
+ command T123TestFOrmat :
+ command T123fendoff :
+ command T123state :
+ command T123FendingOff :
+ set wildoptions=fuzzy
+ call feedkeys(":T123fo\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"T123format T123TestFOrmat T123FendingOff T123goformat T123fendoff', @:)
+ delcommand T123format
+ delcommand T123goformat
+ delcommand T123TestFOrmat
+ delcommand T123fendoff
+ delcommand T123state
+ delcommand T123FendingOff
+ %bw
+ set wildoptions&
+endfunc
+
+" Test for fuzzy completion of a command with lower case letters and a number
+func Test_fuzzy_completion_cmd_alnum()
+ command Foo2Bar :
+ set wildoptions=fuzzy
+ call feedkeys(":foo2\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"Foo2Bar', @:)
+ call feedkeys(":foo\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"Foo2Bar', @:)
+ call feedkeys(":bar\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"Foo2Bar', @:)
+ delcommand Foo2Bar
+ set wildoptions&
+endfunc
+
+" Test for command completion for a command starting with 'k'
+func Test_fuzzy_completion_cmd_k()
+ command KillKillKill :
+ set wildoptions&
+ call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"killkill\<Tab>", @:)
+ set wildoptions=fuzzy
+ call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"KillKillKill', @:)
+ delcom KillKillKill
+ set wildoptions&
+endfunc
+
+" Test for fuzzy completion for user defined custom completion function
+func Test_fuzzy_completion_custom_func()
+ func Tcompl(a, c, p)
+ return "format\ngoformat\nTestFOrmat\nfendoff\nstate"
+ endfunc
+ command -nargs=* -complete=custom,Tcompl Fuzzy :
+ set wildoptions&
+ call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"Fuzzy format", @:)
+ call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"Fuzzy xy", @:)
+ call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"Fuzzy ttt", @:)
+ set wildoptions=fuzzy
+ call feedkeys(":Fuzzy \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"Fuzzy format goformat TestFOrmat fendoff state", @:)
+ call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"Fuzzy format TestFOrmat goformat fendoff", @:)
+ call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"Fuzzy xy", @:)
+ call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"Fuzzy TestFOrmat", @:)
+ delcom Fuzzy
+ set wildoptions&
+endfunc
+
+" Test for :breakadd argument completion
+func Test_cmdline_complete_breakadd()
+ call feedkeys(":breakadd \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd expr file func here", @:)
+ call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd expr", @:)
+ call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd expr", @:)
+ call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd here", @:)
+ call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd here", @:)
+ call feedkeys(":breakadd abc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd abc", @:)
+ call assert_equal(['expr', 'file', 'func', 'here'], getcompletion('', 'breakpoint'))
+ let l = getcompletion('not', 'breakpoint')
+ call assert_equal([], l)
+
+ " Test for :breakadd file [lnum] <file>
+ call writefile([], 'Xscript', 'D')
+ call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file Xscript", @:)
+ call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file Xscript", @:)
+ call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file 20 Xscript", @:)
+ call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file 20 Xscript", @:)
+ call feedkeys(":breakadd file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file 20x Xsc\t", @:)
+ call feedkeys(":breakadd file 20\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file 20\t", @:)
+ call feedkeys(":breakadd file 20x\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file 20x\t", @:)
+ call feedkeys(":breakadd file Xscript \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file Xscript ", @:)
+ call feedkeys(":breakadd file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd file X1B2C3", @:)
+
+ " Test for :breakadd func [lnum] <function>
+ func Xbreak_func()
+ endfunc
+ call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func Xbreak_func", @:)
+ call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func Xbreak_func", @:)
+ call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func 20 Xbreak_func", @:)
+ call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func 20 Xbreak_func", @:)
+ call feedkeys(":breakadd func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func 20x Xbr\t", @:)
+ call feedkeys(":breakadd func 20\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func 20\t", @:)
+ call feedkeys(":breakadd func 20x\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func 20x\t", @:)
+ call feedkeys(":breakadd func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func Xbreak_func ", @:)
+ call feedkeys(":breakadd func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd func X1B2C3", @:)
+ delfunc Xbreak_func
+
+ " Test for :breakadd expr <expression>
+ let g:Xtest_var = 10
+ call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd expr Xtest_var", @:)
+ call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd expr Xtest_var", @:)
+ call feedkeys(":breakadd expr Xtest_var \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd expr Xtest_var ", @:)
+ call feedkeys(":breakadd expr X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd expr X1B2C3", @:)
+ unlet g:Xtest_var
+
+ " Test for :breakadd here
+ call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd here Xtest", @:)
+ call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd here Xtest", @:)
+ call feedkeys(":breakadd here \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakadd here ", @:)
+endfunc
+
+" Test for :breakdel argument completion
+func Test_cmdline_complete_breakdel()
+ call feedkeys(":breakdel \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file func here", @:)
+ call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file", @:)
+ call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file", @:)
+ call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel here", @:)
+ call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel here", @:)
+ call feedkeys(":breakdel abc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel abc", @:)
+
+ " Test for :breakdel file [lnum] <file>
+ call writefile([], 'Xscript', 'D')
+ call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file Xscript", @:)
+ call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file Xscript", @:)
+ call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file 20 Xscript", @:)
+ call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file 20 Xscript", @:)
+ call feedkeys(":breakdel file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file 20x Xsc\t", @:)
+ call feedkeys(":breakdel file 20\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file 20\t", @:)
+ call feedkeys(":breakdel file 20x\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file 20x\t", @:)
+ call feedkeys(":breakdel file Xscript \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file Xscript ", @:)
+ call feedkeys(":breakdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel file X1B2C3", @:)
+
+ " Test for :breakdel func [lnum] <function>
+ func Xbreak_func()
+ endfunc
+ call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func Xbreak_func", @:)
+ call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func Xbreak_func", @:)
+ call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func 20 Xbreak_func", @:)
+ call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func 20 Xbreak_func", @:)
+ call feedkeys(":breakdel func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func 20x Xbr\t", @:)
+ call feedkeys(":breakdel func 20\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func 20\t", @:)
+ call feedkeys(":breakdel func 20x\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func 20x\t", @:)
+ call feedkeys(":breakdel func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func Xbreak_func ", @:)
+ call feedkeys(":breakdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel func X1B2C3", @:)
+ delfunc Xbreak_func
+
+ " Test for :breakdel here
+ call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel here Xtest", @:)
+ call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel here Xtest", @:)
+ call feedkeys(":breakdel here \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"breakdel here ", @:)
+endfunc
+
+" Test for :scriptnames argument completion
+func Test_cmdline_complete_scriptnames()
+ set wildmenu
+ call writefile(['let a = 1'], 'Xa1b2c3.vim', 'D')
+ source Xa1b2c3.vim
+ call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx')
+ call assert_match("\"script .*Xa1b2c3.vim$", @:)
+ call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx')
+ call assert_match("\"script .*Xa1b2c3.vim$", @:)
+ call feedkeys(":script b2c3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"script b2c3", @:)
+ call feedkeys(":script 2\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match("\"script 2\<Tab>$", @:)
+ call feedkeys(":script \<Tab>\<Left>\<Left> \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match("\"script .*Xa1b2c3.vim $", @:)
+ call feedkeys(":script \<Tab>\<Left>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"script ", @:)
+ call assert_match('Xa1b2c3.vim$', getcompletion('.*Xa1b2.*', 'scriptnames')[0])
+ call assert_equal([], getcompletion('Xa1b2', 'scriptnames'))
+ new
+ call feedkeys(":script \<Tab>\<Left>\<Left>\<CR>", 'tx')
+ call assert_equal('Xa1b2c3.vim', fnamemodify(@%, ':t'))
+ bw!
+ set wildmenu&
+endfunc
+
+" this was going over the end of IObuff
+func Test_report_error_with_composing()
+ let caught = 'no'
+ try
+ exe repeat('0', 987) .. "0\xdd\x80\xdd\x80\xdd\x80\xdd\x80"
+ catch /E492:/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+endfunc
+
+" Test for expanding 2-letter and 3-letter :substitute command arguments.
+" These commands don't accept an argument.
+func Test_cmdline_complete_substitute_short()
+ for cmd in ['sc', 'sce', 'scg', 'sci', 'scI', 'scn', 'scp', 'scl',
+ \ 'sgc', 'sge', 'sg', 'sgi', 'sgI', 'sgn', 'sgp', 'sgl', 'sgr',
+ \ 'sic', 'sie', 'si', 'siI', 'sin', 'sip', 'sir',
+ \ 'sIc', 'sIe', 'sIg', 'sIi', 'sI', 'sIn', 'sIp', 'sIl', 'sIr',
+ \ 'src', 'srg', 'sri', 'srI', 'srn', 'srp', 'srl', 'sr']
+ call feedkeys(':' .. cmd .. " \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"' .. cmd .. " \<Tab>", @:)
+ endfor
+endfunc
+
+" Test for :! shell command argument completion
+func Test_cmdline_complete_bang_cmd_argument()
+ set wildoptions=fuzzy
+ call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"!vim test_cmdline.vim', @:)
+ set wildoptions&
+ call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"!vim test_cmdline.vim', @:)
+endfunc
+
+func Check_completion()
+ call assert_equal('let a', getcmdline())
+ call assert_equal(6, getcmdpos())
+ call assert_equal(7, getcmdscreenpos())
+ call assert_equal('var', getcmdcompltype())
+ return ''
+endfunc
+
+func Test_screenpos_and_completion()
+ call feedkeys(":let a\<C-R>=Check_completion()\<CR>\<Esc>", "xt")
+endfunc
+
+func Test_recursive_register()
+ let @= = ''
+ silent! ?e/
+ let caught = 'no'
+ try
+ normal //
+ catch /E169:/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+endfunc
+
+func Test_long_error_message()
+ " the error should be truncated, not overrun IObuff
+ silent! norm Q00000000000000     000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000                                                                                                                                                                                                                        
+endfunc
+
+func Test_cmdline_redraw_tabline()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set showtabline=2
+ autocmd CmdlineEnter * set tabline=foo
+ END
+ call writefile(lines, 'Xcmdline_redraw_tabline', 'D')
+ let buf = RunVimInTerminal('-S Xcmdline_redraw_tabline', #{rows: 6})
+ call term_sendkeys(buf, ':')
+ call WaitForAssert({-> assert_match('^foo', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_wildmenu_pum_disable_while_shown()
+ set wildoptions=pum
+ set wildmenu
+ cnoremap <F2> <Cmd>set nowildmenu<CR>
+ call feedkeys(":sign \<Tab>\<F2>\<Esc>", 'tx')
+ call assert_equal(0, pumvisible())
+ cunmap <F2>
+ set wildoptions& wildmenu&
+endfunc
+
+func Test_setcmdline()
+ func SetText(text, pos)
+ autocmd CmdlineChanged * let g:cmdtype = expand('<afile>')
+ call assert_equal(0, setcmdline(a:text))
+ call assert_equal(a:text, getcmdline())
+ call assert_equal(len(a:text) + 1, getcmdpos())
+ call assert_equal(getcmdtype(), g:cmdtype)
+ unlet g:cmdtype
+ autocmd! CmdlineChanged
+
+ call assert_equal(0, setcmdline(a:text, a:pos))
+ call assert_equal(a:text, getcmdline())
+ call assert_equal(a:pos, getcmdpos())
+
+ call assert_fails('call setcmdline("' .. a:text .. '", -1)', 'E487:')
+ call assert_fails('call setcmdline({}, 0)', 'E1174:')
+ call assert_fails('call setcmdline("' .. a:text .. '", {})', 'E1210:')
+
+ return ''
+ endfunc
+
+ call feedkeys(":\<C-R>=SetText('set rtp?', 2)\<CR>\<CR>", 'xt')
+ call assert_equal('set rtp?', @:)
+
+ call feedkeys(":let g:str = input('? ')\<CR>", 't')
+ call feedkeys("\<C-R>=SetText('foo', 4)\<CR>\<CR>", 'xt')
+ call assert_equal('foo', g:str)
+ unlet g:str
+
+ delfunc SetText
+
+ " setcmdline() returns 1 when not editing the command line.
+ call assert_equal(1, 'foo'->setcmdline())
+
+ " Called in custom function
+ func CustomComplete(A, L, P)
+ call assert_equal(0, setcmdline("DoCmd "))
+ return "January\nFebruary\nMars\n"
+ endfunc
+
+ com! -nargs=* -complete=custom,CustomComplete DoCmd :
+ call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd January February Mars', @:)
+ delcom DoCmd
+ delfunc CustomComplete
+
+ " Called in <expr>
+ cnoremap <expr>a setcmdline('let foo=')
+ call feedkeys(":a\<CR>", 'tx')
+ call assert_equal('let foo=0', @:)
+ cunmap a
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cmdmods.vim b/src/testdir/test_cmdmods.vim
new file mode 100644
index 0000000..323a78e
--- /dev/null
+++ b/src/testdir/test_cmdmods.vim
@@ -0,0 +1,45 @@
+" Test for all command modifiers in
+
+def Test_cmdmods_array()
+ # Get all the command modifiers from ex_cmds.h.
+ var lines = readfile('../ex_cmds.h')->filter((_, l) => l =~ 'ex_wrongmodifier,')
+ var cmds = lines->map((_, v) => substitute(v, '.*"\(\k*\)".*', '\1', ''))
+
+ # :hide is both a command and a modifier
+ cmds->extend(['hide'])
+
+ # Get the entries of cmdmods[] in ex_docmd.c
+ edit ../ex_docmd.c
+ var top = search('^} cmdmods[') + 1
+ var bot = search('^};') - 1
+ lines = getline(top, bot)
+ var mods = lines->map((_, v) => substitute(v, '.*"\(\k*\)".*', '\1', ''))
+
+ # Add the other commands that use ex_wrongmodifier.
+ mods->extend([
+ 'endclass',
+ 'endenum',
+ 'endinterface',
+ 'public',
+ 'static',
+ 'this',
+ ])
+
+ # Check the lists are equal. Convert them to a dict to get a clearer error
+ # message.
+ var cmds_dict = {}
+ for v in cmds
+ cmds_dict[v] = 1
+ endfor
+ var mods_dict = {}
+ for v in mods
+ mods_dict[v] = 1
+ endfor
+ assert_equal(cmds_dict, mods_dict)
+
+ bwipe!
+enddef
+
+
+" vim: shiftwidth=2 sts=2 expandtab
+
diff --git a/src/testdir/test_cmdwin.vim b/src/testdir/test_cmdwin.vim
new file mode 100644
index 0000000..ad05f3e
--- /dev/null
+++ b/src/testdir/test_cmdwin.vim
@@ -0,0 +1,428 @@
+" Tests for editing the command line.
+
+source check.vim
+source screendump.vim
+
+func Test_getcmdwintype()
+ call feedkeys("q/:let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
+ call assert_equal('/', a)
+
+ call feedkeys("q?:let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
+ call assert_equal('?', a)
+
+ call feedkeys("q::let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
+ call assert_equal(':', a)
+
+ call feedkeys(":\<C-F>:let a = getcmdwintype()\<CR>:q\<CR>", 'x!')
+ call assert_equal(':', a)
+
+ call assert_equal('', getcmdwintype())
+endfunc
+
+func Test_getcmdwin_autocmd()
+ let s:seq = []
+ augroup CmdWin
+ au WinEnter * call add(s:seq, 'WinEnter ' .. win_getid())
+ au WinLeave * call add(s:seq, 'WinLeave ' .. win_getid())
+ au BufEnter * call add(s:seq, 'BufEnter ' .. bufnr())
+ au BufLeave * call add(s:seq, 'BufLeave ' .. bufnr())
+ au CmdWinEnter * call add(s:seq, 'CmdWinEnter ' .. win_getid())
+ au CmdWinLeave * call add(s:seq, 'CmdWinLeave ' .. win_getid())
+
+ let org_winid = win_getid()
+ let org_bufnr = bufnr()
+ call feedkeys("q::let a = getcmdwintype()\<CR>:let s:cmd_winid = win_getid()\<CR>:let s:cmd_bufnr = bufnr()\<CR>:q\<CR>", 'x!')
+ call assert_equal(':', a)
+ call assert_equal([
+ \ 'WinLeave ' .. org_winid,
+ \ 'WinEnter ' .. s:cmd_winid,
+ \ 'BufLeave ' .. org_bufnr,
+ \ 'BufEnter ' .. s:cmd_bufnr,
+ \ 'CmdWinEnter ' .. s:cmd_winid,
+ \ 'CmdWinLeave ' .. s:cmd_winid,
+ \ 'BufLeave ' .. s:cmd_bufnr,
+ \ 'WinLeave ' .. s:cmd_winid,
+ \ 'WinEnter ' .. org_winid,
+ \ 'BufEnter ' .. org_bufnr,
+ \ ], s:seq)
+
+ au!
+ augroup END
+endfunc
+
+func Test_cmdwin_bug()
+ let winid = win_getid()
+ sp
+ try
+ call feedkeys("q::call win_gotoid(" .. winid .. ")\<CR>:q\<CR>", 'x!')
+ catch /^Vim\%((\a\+)\)\=:E11/
+ endtry
+ bw!
+endfunc
+
+func Test_cmdwin_restore()
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ augroup vimHints | au! | augroup END
+ call setline(1, range(30))
+ 2split
+ [SCRIPT]
+ call writefile(lines, 'XTest_restore', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_restore', {'rows': 12})
+ call term_sendkeys(buf, "q:")
+ call VerifyScreenDump(buf, 'Test_cmdwin_restore_1', {})
+
+ " normal restore
+ call term_sendkeys(buf, ":q\<CR>")
+ call VerifyScreenDump(buf, 'Test_cmdwin_restore_2', {})
+
+ " restore after setting 'lines' with one window
+ call term_sendkeys(buf, ":close\<CR>")
+ call term_sendkeys(buf, "q:")
+ call term_sendkeys(buf, ":set lines=18\<CR>")
+ call term_sendkeys(buf, ":q\<CR>")
+ call VerifyScreenDump(buf, 'Test_cmdwin_restore_3', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cmdwin_no_terminal()
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', {'rows': 12})
+ call term_sendkeys(buf, ":set cmdheight=2\<CR>")
+ call term_sendkeys(buf, "q:")
+ call term_sendkeys(buf, ":let buf = term_start(['/bin/echo'], #{hidden: 1})\<CR>")
+ call VerifyScreenDump(buf, 'Test_cmdwin_no_terminal', {})
+ call term_sendkeys(buf, ":q\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cmdwin_wrong_command()
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', {'rows': 12})
+ call term_sendkeys(buf, "q:")
+ call term_sendkeys(buf, "als\<Esc>")
+ call term_sendkeys(buf, "\<C-W>k")
+ call VerifyScreenDump(buf, 'Test_cmdwin_wrong_command_1', {})
+
+ call term_sendkeys(buf, "\<C-C>")
+ call VerifyScreenDump(buf, 'Test_cmdwin_wrong_command_2', {})
+
+ call term_sendkeys(buf, ":q\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cmdwin_feedkeys()
+ " This should not generate E488
+ call feedkeys("q:\<CR>", 'x')
+ " Using feedkeys with q: only should automatically close the cmd window
+ call feedkeys('q:', 'xt')
+ call assert_equal(1, winnr('$'))
+ call assert_equal('', getcmdwintype())
+endfunc
+
+" Tests for the issues fixed in 7.4.441.
+" When 'cedit' is set to Ctrl-C, opening the command window hangs Vim
+func Test_cmdwin_cedit()
+ exe "set cedit=\<C-c>"
+ normal! :
+ call assert_equal(1, winnr('$'))
+
+ let g:cmd_wintype = ''
+ func CmdWinType()
+ let g:cmd_wintype = getcmdwintype()
+ let g:wintype = win_gettype()
+ return ''
+ endfunc
+
+ call feedkeys("\<C-c>a\<C-R>=CmdWinType()\<CR>\<CR>")
+ echo input('')
+ call assert_equal('@', g:cmd_wintype)
+ call assert_equal('command', g:wintype)
+
+ set cedit&vim
+ delfunc CmdWinType
+endfunc
+
+" Test for CmdwinEnter autocmd
+func Test_cmdwin_autocmd()
+ augroup CmdWin
+ au!
+ autocmd BufLeave * if &buftype == '' | update | endif
+ autocmd CmdwinEnter * startinsert
+ augroup END
+
+ call assert_fails('call feedkeys("q:xyz\<CR>", "xt")', 'E492:')
+ call assert_equal('xyz', @:)
+
+ augroup CmdWin
+ au!
+ augroup END
+ augroup! CmdWin
+endfunc
+
+func Test_cmdwin_jump_to_win()
+ call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:')
+ new
+ set modified
+ call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', ['E37:', 'E162:'])
+ close!
+ call feedkeys("q/:close\<CR>", "xt")
+ call assert_equal(1, winnr('$'))
+ call feedkeys("q/:exit\<CR>", "xt")
+ call assert_equal(1, winnr('$'))
+
+ " opening command window twice should fail
+ call assert_beeps('call feedkeys("q:q:\<CR>\<CR>", "xt")')
+ call assert_equal(1, winnr('$'))
+endfunc
+
+func Test_cmdwin_tabpage()
+ tabedit
+ call assert_fails("silent norm q/g :I\<Esc>", 'E11:')
+ tabclose!
+endfunc
+
+func Test_cmdwin_interrupted()
+ CheckScreendump
+
+ " aborting the :smile output caused the cmdline window to use the current
+ " buffer.
+ let lines =<< trim [SCRIPT]
+ au WinNew * smile
+ [SCRIPT]
+ call writefile(lines, 'XTest_cmdwin', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_cmdwin', {'rows': 18})
+ " open cmdwin
+ call term_sendkeys(buf, "q:")
+ call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 18))})
+ " quit more prompt for :smile command
+ call term_sendkeys(buf, "q")
+ call WaitForAssert({-> assert_match('^$', term_getline(buf, 18))})
+ " execute a simple command
+ call term_sendkeys(buf, "aecho 'done'\<CR>")
+ call VerifyScreenDump(buf, 'Test_cmdwin_interrupted', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for recursively getting multiple command line inputs
+func Test_cmdwin_multi_input()
+ call feedkeys(":\<C-R>=input('P: ')\<CR>\"cyan\<CR>\<CR>", 'xt')
+ call assert_equal('"cyan', @:)
+endfunc
+
+" Test for normal mode commands not supported in the cmd window
+func Test_cmdwin_blocked_commands()
+ call assert_fails('call feedkeys("q:\<C-T>\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-]>\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-^>\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:Q\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:Z\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<F1>\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>s\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>v\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>^\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>n\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>z\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>o\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>w\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>j\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>k\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>h\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>l\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>T\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>x\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>r\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>R\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>K\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>}\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>]\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>f\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>d\<CR>", "xt")', 'E11:')
+ call assert_fails('call feedkeys("q:\<C-W>g\<CR>", "xt")', 'E11:')
+endfunc
+
+" Close the Cmd-line window in insert mode using CTRL-C
+func Test_cmdwin_insert_mode_close()
+ %bw!
+ let s = ''
+ exe "normal q:a\<C-C>let s='Hello'\<CR>"
+ call assert_equal('Hello', s)
+ call assert_equal(1, winnr('$'))
+endfunc
+
+func Test_cmdwin_ex_mode_with_modifier()
+ " this was accessing memory after allocated text in Ex mode
+ new
+ call setline(1, ['some', 'text', 'lines'])
+ silent! call feedkeys("gQnormal vq:atopleft\<C-V>\<CR>\<CR>", 'xt')
+ bwipe!
+endfunc
+
+func s:ComplInCmdwin_GlobalCompletion(a, l, p)
+ return 'global'
+endfunc
+
+func s:ComplInCmdwin_LocalCompletion(a, l, p)
+ return 'local'
+endfunc
+
+func Test_compl_in_cmdwin()
+ set wildmenu wildchar=<Tab>
+ com! -nargs=1 -complete=command GetInput let input = <q-args>
+ com! -buffer TestCommand echo 'TestCommand'
+ let w:test_winvar = 'winvar'
+ let b:test_bufvar = 'bufvar'
+
+ " User-defined commands
+ let input = ''
+ call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('TestCommand', input)
+
+ let input = ''
+ call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('T', input)
+
+
+ com! -nargs=1 -complete=var GetInput let input = <q-args>
+ " Window-local variables
+ let input = ''
+ call feedkeys("q:iGetInput w:test_\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('w:test_winvar', input)
+
+ let input = ''
+ call feedkeys("q::GetInput w:test_\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('w:test_', input)
+
+ " Buffer-local variables
+ let input = ''
+ call feedkeys("q:iGetInput b:test_\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('b:test_bufvar', input)
+
+ let input = ''
+ call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('b:test_', input)
+
+
+ " Argument completion of buffer-local command
+ func s:ComplInCmdwin_GlobalCompletionList(a, l, p)
+ return ['global']
+ endfunc
+
+ func s:ComplInCmdwin_LocalCompletionList(a, l, p)
+ return ['local']
+ endfunc
+
+ func s:ComplInCmdwin_CheckCompletion(arg)
+ call assert_equal('local', a:arg)
+ endfunc
+
+ com! -nargs=1 -complete=custom,<SID>ComplInCmdwin_GlobalCompletion
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ com! -buffer -nargs=1 -complete=custom,<SID>ComplInCmdwin_LocalCompletion
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+
+ com! -nargs=1 -complete=customlist,<SID>ComplInCmdwin_GlobalCompletionList
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+ com! -buffer -nargs=1 -complete=customlist,<SID>ComplInCmdwin_LocalCompletionList
+ \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>)
+
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+
+ func! s:ComplInCmdwin_CheckCompletion(arg)
+ call assert_equal('global', a:arg)
+ endfunc
+ new
+ call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!')
+ quit
+
+ delfunc s:ComplInCmdwin_GlobalCompletion
+ delfunc s:ComplInCmdwin_LocalCompletion
+ delfunc s:ComplInCmdwin_GlobalCompletionList
+ delfunc s:ComplInCmdwin_LocalCompletionList
+ delfunc s:ComplInCmdwin_CheckCompletion
+
+ delcom -buffer TestCommand
+ delcom TestCommand
+ delcom GetInput
+ unlet w:test_winvar
+ unlet b:test_bufvar
+ set wildmenu& wildchar&
+endfunc
+
+func Test_cmdwin_ctrl_bsl()
+ " Using CTRL-\ CTRL-N in cmd window should close the window
+ call feedkeys("q:\<C-\>\<C-N>", 'xt')
+ call assert_equal('', getcmdwintype())
+endfunc
+
+func Test_cant_open_cmdwin_in_cmdwin()
+ try
+ call feedkeys("q:q::q\<CR>", "x!")
+ catch
+ let caught = v:exception
+ endtry
+ call assert_match('E1292:', caught)
+endfunc
+
+func Test_cmdwin_virtual_edit()
+ enew!
+ set ve=all cpo+=$
+ silent normal q/s
+
+ set ve= cpo-=$
+endfunc
+
+" Check that a :normal command can be used to stop Visual mode without side
+" effects.
+func Test_normal_escape()
+ call feedkeys("q:i\" foo\<Esc>:normal! \<C-V>\<Esc>\<CR>:\" bar\<CR>", 'ntx')
+ call assert_equal('" bar', @:)
+endfunc
+
+" This was using a pointer to a freed buffer
+func Test_cmdwin_freed_buffer_ptr()
+ " this does not work on MS-Windows because renaming an open file fails
+ CheckNotMSWindows
+
+ au BufEnter * next 0| file
+ edit 0
+ silent! norm q/
+
+ au! BufEnter
+ bwipe!
+endfunc
+
+" This was resulting in a window with negative width.
+" The test doesn't reproduce the illegal memory access though...
+func Test_cmdwin_split_often()
+ let lines = &lines
+ let columns = &columns
+ set t_WS=
+
+ try
+ set encoding=iso8859
+ set ruler
+ winsize 0 0
+ noremap 0 H
+ sil norm 0000000q:
+ catch /E36:/
+ endtry
+
+ bwipe!
+ set encoding=utf8
+ let &lines = lines
+ let &columns = columns
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_codestyle.vim b/src/testdir/test_codestyle.vim
new file mode 100644
index 0000000..ec476c0
--- /dev/null
+++ b/src/testdir/test_codestyle.vim
@@ -0,0 +1,145 @@
+" Test for checking the source code style.
+
+def Test_source_files()
+ for fname in glob('../*.[ch]', 0, 1)
+ exe 'edit ' .. fname
+
+ cursor(1, 1)
+ var lnum = search(' \t')
+ assert_equal(0, lnum, fname .. ': space before tab')
+
+ cursor(1, 1)
+ lnum = search('\s$')
+ assert_equal(0, lnum, fname .. ': trailing white space')
+
+ # some files don't stick to the Vim style rules
+ if fname =~ 'iscygpty.c'
+ continue
+ endif
+
+ # Examples in comments use "condition) {", skip them.
+ # Skip if a double quote or digit comes after the "{".
+ # Skip specific string used in os_unix.c.
+ # Also skip fold markers.
+ var skip = 'getline(".") =~ "condition) {" || getline(".") =~ "vimglob_func" || getline(".") =~ "{\"" || getline(".") =~ "{\\d" || getline(".") =~ "{{{"'
+ cursor(1, 1)
+ lnum = search(')\s*{', '', 0, 0, skip)
+ assert_equal(0, lnum, fname .. ': curly after closing paren')
+
+ cursor(1, 1)
+ # Examples in comments use double quotes.
+ skip = "getline('.') =~ '\"'"
+ # Avoid examples that contain: "} else
+ lnum = search('[^"]}\s*else', '', 0, 0, skip)
+ assert_equal(0, lnum, fname .. ': curly before "else"')
+
+ cursor(1, 1)
+ lnum = search('else\s*{', '', 0, 0, skip)
+ assert_equal(0, lnum, fname .. ': curly after "else"')
+ endfor
+
+ bwipe!
+enddef
+
+def Test_test_files()
+ for fname in glob('*.vim', 0, 1)
+ exe 'edit ' .. fname
+
+ # some files intentionally have misplaced white space
+ if fname =~ 'test_cindent.vim' || fname =~ 'test_join.vim'
+ continue
+ endif
+
+ # skip files that are known to have a space before a tab
+ if fname !~ 'test_comments.vim'
+ && fname !~ 'test_listchars.vim'
+ && fname !~ 'test_visual.vim'
+ cursor(1, 1)
+ var lnum = search(fname =~ "test_regexp_latin" ? '[^á] \t' : ' \t')
+ assert_equal(0, lnum, 'testdir/' .. fname .. ': space before tab')
+ endif
+
+ # skip files that are known to have trailing white space
+ if fname !~ 'test_cmdline.vim'
+ && fname !~ 'test_let.vim'
+ && fname !~ 'test_tagjump.vim'
+ && fname !~ 'test_vim9_cmd.vim'
+ cursor(1, 1)
+ var lnum = search(
+ fname =~ 'test_vim9_assign.vim' ? '[^=]\s$'
+ : fname =~ 'test_vim9_class.vim' ? '[^)]\s$'
+ : fname =~ 'test_vim9_script.vim' ? '[^,:3]\s$'
+ : fname =~ 'test_visual.vim' ? '[^/]\s$'
+ : '[^\\]\s$')
+ assert_equal(0, lnum, 'testdir/' .. fname .. ': trailing white space')
+ endif
+ endfor
+
+ bwipe!
+enddef
+
+def Test_help_files()
+ var lnum: number
+ set nowrapscan
+
+ for fpath in glob('../../runtime/doc/*.txt', 0, 1)
+ exe 'edit ' .. fpath
+
+ var fname = fnamemodify(fpath, ":t")
+
+ # todo.txt is for developers, it's not need a strictly check
+ # version*.txt is a history and large size, so it's not checked
+ if fname == 'todo.txt' || fname =~ 'version.*\.txt'
+ continue
+ endif
+
+ # Check for mixed tabs and spaces
+ cursor(1, 1)
+ while 1
+ lnum = search('[^/] \t')
+ if fname == 'visual.txt' && getline(lnum) =~ "STRING \tjkl"
+ || fname == 'usr_27.txt' && getline(lnum) =~ "\[^\? \t\]"
+ continue
+ endif
+ assert_equal(0, lnum, fpath .. ': space before tab')
+ if lnum == 0
+ break
+ endif
+ endwhile
+
+ # Check for unnecessary whitespace at the end of a line
+ cursor(1, 1)
+ while 1
+ lnum = search('[^/~\\]\s$')
+ # skip line that are known to have trailing white space
+ if fname == 'map.txt' && getline(lnum) =~ "unmap @@ $"
+ || fname == 'usr_12.txt' && getline(lnum) =~ "^\t/ \t$"
+ || fname == 'usr_41.txt' && getline(lnum) =~ "map <F4> o#include $"
+ || fname == 'change.txt' && getline(lnum) =~ "foobar bla $"
+ continue
+ endif
+ assert_equal(0, lnum, fpath .. ': trailing white space')
+ if lnum == 0
+ break
+ endif
+ endwhile
+
+ # TODO: Do check and fix help files
+# # Check over 80 columns
+# cursor(1, 1)
+# while 1
+# lnum = search('\%>80v.*$')
+# assert_equal(0, lnum, fpath .. ': line over 80 columns')
+# if lnum == 0
+# break
+# endif
+# endwhile
+
+ endfor
+
+ set wrapscan&vim
+ bwipe!
+enddef
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_command_count.vim b/src/testdir/test_command_count.vim
new file mode 100644
index 0000000..59e3ba0
--- /dev/null
+++ b/src/testdir/test_command_count.vim
@@ -0,0 +1,198 @@
+" Test for user command counts.
+
+func Test_command_count_0()
+ let bufnr = bufnr('%')
+ set hidden
+ set noswapfile
+
+ split DoesNotExistEver
+ let lastbuf = bufnr('$')
+ call setline(1, 'asdf')
+ quit!
+
+ command! -range -addr=loaded_buffers RangeLoadedBuffers :let lines = [<line1>, <line2>]
+ command! -range=% -addr=loaded_buffers RangeLoadedBuffersAll :let lines = [<line1>, <line2>]
+ command! -range -addr=buffers RangeBuffers :let lines = [<line1>, <line2>]
+ command! -range=% -addr=buffers RangeBuffersAll :let lines = [<line1>, <line2>]
+
+ .,$RangeLoadedBuffers
+ call assert_equal([bufnr, bufnr], lines)
+ %RangeLoadedBuffers
+ call assert_equal([bufnr, bufnr], lines)
+ RangeLoadedBuffersAll
+ call assert_equal([bufnr, bufnr], lines)
+ .,$RangeBuffers
+ call assert_equal([bufnr, lastbuf], lines)
+ %RangeBuffers
+ call assert_equal([bufnr, lastbuf], lines)
+ RangeBuffersAll
+ call assert_equal([bufnr, lastbuf], lines)
+
+ delcommand RangeLoadedBuffers
+ delcommand RangeLoadedBuffersAll
+ delcommand RangeBuffers
+ delcommand RangeBuffersAll
+
+ set hidden&
+ set swapfile&
+endfunc
+
+func Test_command_count_1()
+ silent! %argd
+ arga a b c d e
+ argdo echo "loading buffers"
+ argu 3
+ command! -range -addr=arguments RangeArguments :let lines = [<line1>, <line2>]
+ command! -range=% -addr=arguments RangeArgumentsAll :let lines = [<line1>, <line2>]
+ .-,$-RangeArguments
+ call assert_equal([2, 4], lines)
+ %RangeArguments
+ call assert_equal([1, 5], lines)
+ RangeArgumentsAll
+ call assert_equal([1, 5], lines)
+ N
+ .RangeArguments
+ call assert_equal([2, 2], lines)
+ delcommand RangeArguments
+ delcommand RangeArgumentsAll
+
+ split|split|split|split
+ 3wincmd w
+ command! -range -addr=windows RangeWindows :let lines = [<line1>, <line2>]
+ .,$RangeWindows
+ call assert_equal([3, 5], lines)
+ %RangeWindows
+ call assert_equal([1, 5], lines)
+ delcommand RangeWindows
+
+ command! -range=% -addr=windows RangeWindowsAll :let lines = [<line1>, <line2>]
+ RangeWindowsAll
+ call assert_equal([1, 5], lines)
+ delcommand RangeWindowsAll
+ only
+ blast|bd
+
+ tabe|tabe|tabe|tabe
+ normal 2gt
+ command! -range -addr=tabs RangeTabs :let lines = [<line1>, <line2>]
+ .,$RangeTabs
+ call assert_equal([2, 5], lines)
+ %RangeTabs
+ call assert_equal([1, 5], lines)
+ delcommand RangeTabs
+
+ command! -range=% -addr=tabs RangeTabsAll :let lines = [<line1>, <line2>]
+ RangeTabsAll
+ call assert_equal([1, 5], lines)
+ delcommand RangeTabsAll
+ 1tabonly
+
+ s/\n/\r\r\r\r\r/
+ 2ma<
+ $-ma>
+ command! -range=% RangeLines :let lines = [<line1>, <line2>]
+ '<,'>RangeLines
+ call assert_equal([2, 5], lines)
+ delcommand RangeLines
+
+ command! -range=% -buffer LocalRangeLines :let lines = [<line1>, <line2>]
+ '<,'>LocalRangeLines
+ call assert_equal([2, 5], lines)
+ delcommand LocalRangeLines
+endfunc
+
+func Test_command_count_2()
+ silent! %argd
+ arga a b c d
+ call assert_fails('5argu', 'E16:')
+
+ $argu
+ call assert_equal('d', expand('%:t'))
+
+ 1argu
+ call assert_equal('a', expand('%:t'))
+
+ call assert_fails('300b', 'E16:')
+
+ split|split|split|split
+ 0close
+
+ $wincmd w
+ $close
+ call assert_equal(3, winnr())
+
+ call assert_fails('$+close', 'E16:')
+
+ $tabe
+ call assert_equal(2, tabpagenr())
+
+ call assert_fails('$+tabe', 'E16:')
+
+ only!
+ e x
+ 0tabm
+ normal 1gt
+ call assert_equal('x', expand('%:t'))
+
+ tabonly!
+ only!
+endfunc
+
+func Test_command_count_3()
+ let bufnr = bufnr('%')
+ se nohidden
+ e aaa
+ let buf_aaa = bufnr('%')
+ e bbb
+ let buf_bbb = bufnr('%')
+ e ccc
+ let buf_ccc = bufnr('%')
+ exe bufnr . 'buf'
+ call assert_equal([1, 1, 1], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)])
+ exe buf_bbb . "," . buf_ccc . "bdelete"
+ call assert_equal([1, 0, 0], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)])
+ exe buf_aaa . "bdelete"
+ call assert_equal([0, 0, 0], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)])
+endfunc
+
+func Test_command_count_4()
+ %argd
+ let bufnr = bufnr('$')
+ next aa bb cc dd ee ff
+ call assert_equal(bufnr, bufnr('%'))
+
+ 3argu
+ let args = []
+ .,$-argdo call add(args, expand('%'))
+ call assert_equal(['cc', 'dd', 'ee'], args)
+
+ " create windows to get 5
+ split|split|split|split
+ 2wincmd w
+ let windows = []
+ .,$-windo call add(windows, winnr())
+ call assert_equal([2, 3, 4], windows)
+ only!
+
+ exe bufnr . 'buf'
+ let bufnr = bufnr('%')
+ let buffers = []
+ .,$-bufdo call add(buffers, bufnr('%'))
+ call assert_equal([bufnr, bufnr + 1, bufnr + 2, bufnr + 3, bufnr + 4], buffers)
+
+ exe (bufnr + 3) . 'bdel'
+ let buffers = []
+ exe (bufnr + 2) . ',' . (bufnr + 5) . "bufdo call add(buffers, bufnr('%'))"
+ call assert_equal([bufnr + 2, bufnr + 4, bufnr + 5], buffers)
+
+ " create tabpages to get 5
+ tabe|tabe|tabe|tabe
+ normal! 2gt
+ let tabpages = []
+ .,$-tabdo call add(tabpages, tabpagenr())
+ call assert_equal([2, 3, 4], tabpages)
+ tabonly!
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_comments.vim b/src/testdir/test_comments.vim
new file mode 100644
index 0000000..c34b85c
--- /dev/null
+++ b/src/testdir/test_comments.vim
@@ -0,0 +1,277 @@
+" Tests for the various flags in the 'comments' option
+
+" Test for the 'n' flag in 'comments'
+func Test_comment_nested()
+ new
+ setlocal comments=n:> fo+=ro
+ exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH"
+ exe "normal 5GOE\<C-C>6GoG"
+ let expected =<< trim END
+ > A
+ > B
+ > C
+ > D
+ >>>> E
+ >>>> F
+ >>>> G
+ >>>> H
+ END
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+" Test for the 'b' flag in 'comments'
+func Test_comment_blank()
+ new
+ setlocal comments=b:* fo+=ro
+ exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD"
+ let expected =<< trim END
+ A
+ *B
+ * C
+ * D
+ * E
+ * F
+ *G
+ H
+ END
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+" Test for the 'f' flag in 'comments' (only the first line has a comment
+" string)
+func Test_comment_firstline()
+ new
+ setlocal comments=f:- fo+=ro
+ exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>"
+ call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$'))
+ %d
+ setlocal comments=:-
+ exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>"
+ call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for the 's', 'm' and 'e' flags in 'comments'
+" Test for automatically adding comment leaders in insert mode
+func Test_comment_threepiece()
+ new
+ setlocal expandtab
+ call setline(1, ["\t/*"])
+ setlocal formatoptions=croql
+ call cursor(1, 3)
+ call feedkeys("A\<cr>\<cr>/", 'tnix')
+ call assert_equal(["\t/*", " *", " */"], getline(1, '$'))
+
+ " If a comment ends in a single line, then don't add it in the next line
+ %d
+ call setline(1, '/* line1 */')
+ call feedkeys("A\<CR>next line", 'xt')
+ call assert_equal(['/* line1 */', 'next line'], getline(1, '$'))
+
+ %d
+ " Copy the trailing indentation from the leader comment to a new line
+ setlocal autoindent noexpandtab
+ call feedkeys("a\t/*\tone\ntwo\n/", 'xt')
+ call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$'))
+ close!
+endfunc
+
+" Test for the 'r' flag in 'comments' (right align comment)
+func Test_comment_rightalign()
+ new
+ setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro
+ exe "normal i=\<C-C>o\t /***\nD\n/"
+ exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG"
+ let expected =<< trim END
+ =
+ A
+ /***
+ ** B
+ ** C
+ ** D
+ ** E
+ ** F
+ ******/
+ G
+ END
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+" Test for the 'O' flag in 'comments'
+func Test_comment_O()
+ new
+ setlocal comments=Ob:* fo+=ro
+ exe "normal i* B\nD\<C-C>kOA\<C-C>joC"
+ let expected =<< trim END
+ A
+ * B
+ * C
+ * D
+ END
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+" Test for using a multibyte character as a comment leader
+func Test_comment_multibyte_leader()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ XaY
+ XY
+ XYZ
+ X Y
+ X YZ
+ XX
+ XXa
+ XXY
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=cqm comments=n:X
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq"
+ let t =<< trim END
+ X
+ Xa
+ XaY
+ XY
+ XYZ
+ X Y
+ X YZ
+ XX
+ XXa
+ XXY
+ END
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ Xa
+ Xa
+ XY
+ XY
+ XY
+ XZ
+ X Y
+ X Y
+ X Z
+ XX
+ XXa
+ XXY
+
+ X
+ Xa
+ Xa
+ XY
+ XY
+ XY
+ XZ
+ X Y
+ X Y
+ X Z
+ XX
+ XXa
+ XXY
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& comments&
+ close!
+endfunc
+
+" Test for a space character in 'comments' setting
+func Test_comment_space()
+ new
+ setlocal comments=b:\ > fo+=ro
+ exe "normal i> B\nD\<C-C>ggOA\<C-C>joC"
+ exe "normal Go > F\nH\<C-C>kOE\<C-C>joG"
+ let expected =<< trim END
+ A
+ > B
+ C
+ D
+ > E
+ > F
+ > G
+ > H
+ END
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+" Test for formatting lines with and without comments
+func Test_comment_format_lines()
+ new
+ call setline(1, ['one', '/* two */', 'three'])
+ normal gggqG
+ call assert_equal(['one', '/* two */', 'three'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for using 'a' in 'formatoptions' with comments
+func Test_comment_autoformat()
+ new
+ setlocal formatoptions+=a
+ call feedkeys("a- one\n- two\n", 'xt')
+ call assert_equal(['- one', '- two', ''], getline(1, '$'))
+
+ %d
+ call feedkeys("a\none\n", 'xt')
+ call assert_equal(['', 'one', ''], getline(1, '$'))
+
+ setlocal formatoptions+=aw
+ %d
+ call feedkeys("aone \ntwo\n", 'xt')
+ call assert_equal(['one two', ''], getline(1, '$'))
+
+ %d
+ call feedkeys("aone\ntwo\n", 'xt')
+ call assert_equal(['one', 'two', ''], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for joining lines with comments ('j' flag in 'formatoptions')
+func Test_comment_join_lines_fo_j()
+ new
+ setlocal fo+=j comments=://
+ call setline(1, ['i++; // comment1', ' // comment2'])
+ normal J
+ call assert_equal('i++; // comment1 comment2', getline(1))
+ setlocal fo-=j
+ call setline(1, ['i++; // comment1', ' // comment2'])
+ normal J
+ call assert_equal('i++; // comment1 // comment2', getline(1))
+ " Test with nested comments
+ setlocal fo+=j comments=n:>,n:)
+ call setline(1, ['i++; > ) > ) comment1', ' > ) comment2'])
+ normal J
+ call assert_equal('i++; > ) > ) comment1 comment2', getline(1))
+ close!
+endfunc
+
+" Test for formatting lines where only the first line has a comment.
+func Test_comment_format_firstline_comment()
+ new
+ setlocal formatoptions=tcq
+ call setline(1, ['- one two', 'three'])
+ normal gggqG
+ call assert_equal(['- one two three'], getline(1, '$'))
+
+ %d
+ call setline(1, ['- one', '- two'])
+ normal gggqG
+ call assert_equal(['- one', '- two'], getline(1, '$'))
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_comparators.vim b/src/testdir/test_comparators.vim
new file mode 100644
index 0000000..69f255f
--- /dev/null
+++ b/src/testdir/test_comparators.vim
@@ -0,0 +1,13 @@
+" Test for comparators
+
+function Test_Comparators()
+ try
+ let oldisident=&isident
+ set isident+=#
+ call assert_equal(1, 1 is#1)
+ finally
+ let &isident=oldisident
+ endtry
+endfunction
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_compiler.vim b/src/testdir/test_compiler.vim
new file mode 100644
index 0000000..781bac6
--- /dev/null
+++ b/src/testdir/test_compiler.vim
@@ -0,0 +1,89 @@
+" Test the :compiler command
+
+source check.vim
+source shared.vim
+
+func Test_compiler()
+ CheckExecutable perl
+ CheckFeature quickfix
+
+ " $LANG changes the output of Perl.
+ if $LANG != ''
+ unlet $LANG
+ endif
+
+ " %:S does not work properly with 'shellslash' set
+ let save_shellslash = &shellslash
+ set noshellslash
+
+ e Xfoo.pl
+ compiler perl
+ call assert_equal('perl', b:current_compiler)
+ call assert_fails('let g:current_compiler', 'E121:')
+
+ let verbose_efm = execute('verbose set efm')
+ call assert_match('Last set from .*[/\\]compiler[/\\]perl.vim ', verbose_efm)
+
+ call setline(1, ['#!/usr/bin/perl -w', 'use strict;', 'my $foo=1'])
+ w!
+ call feedkeys(":make\<CR>\<CR>", 'tx')
+ call assert_fails('clist', 'E42:')
+
+ call setline(1, ['#!/usr/bin/perl -w', 'use strict;', '$foo=1'])
+ w!
+ call feedkeys(":make\<CR>\<CR>", 'tx')
+ let a=execute('clist')
+ call assert_match('\n \d\+ Xfoo.pl:3: Global symbol "$foo" '
+ \ . 'requires explicit package name', a)
+
+
+ let &shellslash = save_shellslash
+ call delete('Xfoo.pl')
+ bw!
+endfunc
+
+func GetCompilerNames()
+ return glob('$VIMRUNTIME/compiler/*.vim', 0, 1)
+ \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})
+ \ ->sort()
+endfunc
+
+func Test_compiler_without_arg()
+ let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
+ let a = split(execute('compiler'))
+ let exp = GetCompilerNames()
+ call assert_match(runtime .. '/compiler/' .. exp[0] .. '.vim$', a[0])
+ call assert_match(runtime .. '/compiler/' .. exp[1] .. '.vim$', a[1])
+ call assert_match(runtime .. '/compiler/' .. exp[-1] .. '.vim$', a[-1])
+endfunc
+
+" Test executing :compiler from the command line, not from a script
+func Test_compiler_commandline()
+ call system(GetVimCommandClean() .. ' --not-a-term -c "compiler gcc" -c "call writefile([b:current_compiler], ''XcompilerOut'')" -c "quit"')
+ call assert_equal(0, v:shell_error)
+ call assert_equal(["gcc"], readfile('XcompilerOut'))
+
+ call delete('XcompilerOut')
+endfunc
+
+func Test_compiler_completion()
+ let clist = GetCompilerNames()->join(' ')
+ call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('^"compiler ' .. clist .. '$', @:)
+
+ call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"compiler pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:)
+
+ call feedkeys(":compiler! p\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"compiler! pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:)
+endfunc
+
+func Test_compiler_error()
+ let g:current_compiler = 'abc'
+ call assert_fails('compiler doesnotexist', 'E666:')
+ call assert_equal('abc', g:current_compiler)
+ call assert_fails('compiler! doesnotexist', 'E666:')
+ unlet! g:current_compiler
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_conceal.vim b/src/testdir/test_conceal.vim
new file mode 100644
index 0000000..d9a7644
--- /dev/null
+++ b/src/testdir/test_conceal.vim
@@ -0,0 +1,359 @@
+" Tests for 'conceal'.
+
+source check.vim
+CheckFeature conceal
+
+source screendump.vim
+source view_util.vim
+
+func Test_conceal_two_windows()
+ CheckScreendump
+
+ let code =<< trim [CODE]
+ let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"]
+ call setline(1, lines)
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=
+ exe "normal /here\r"
+ new
+ call setline(1, lines)
+ call setline(4, "Second window")
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=nc
+ exe "normal /here\r"
+ [CODE]
+
+ call writefile(code, 'XTest_conceal', 'D')
+ " Check that cursor line is concealed
+ let buf = RunVimInTerminal('-S XTest_conceal', {})
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_01', {})
+
+ " Check that with concealed text vertical cursor movement is correct.
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_02', {})
+
+ " Check that with cursor line is not concealed
+ call term_sendkeys(buf, "j")
+ call term_sendkeys(buf, ":set concealcursor=\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_03', {})
+
+ " Check that with cursor line is not concealed when moving cursor down
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_04', {})
+
+ " Check that with cursor line is not concealed when switching windows
+ call term_sendkeys(buf, "\<C-W>\<C-W>")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_05', {})
+
+ " Check that with cursor line is only concealed in Normal mode
+ call term_sendkeys(buf, ":set concealcursor=n\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06n', {})
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06i', {})
+ call term_sendkeys(buf, "\<Esc>/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_06v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check that with cursor line is only concealed in Insert mode
+ call term_sendkeys(buf, ":set concealcursor=i\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07n', {})
+ call term_sendkeys(buf, "14|a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07i', {})
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07in', {})
+ call term_sendkeys(buf, "/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_07v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check that with cursor line is only concealed in Command mode
+ call term_sendkeys(buf, ":set concealcursor=c\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08n', {})
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08i', {})
+ call term_sendkeys(buf, "\<Esc>/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_08v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check that with cursor line is only concealed in Visual mode
+ call term_sendkeys(buf, ":set concealcursor=v\r")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09n', {})
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09i', {})
+ call term_sendkeys(buf, "\<Esc>/e")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09c', {})
+ call term_sendkeys(buf, "\<Esc>v")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_09v', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check moving the cursor while in insert mode.
+ call term_sendkeys(buf, ":set concealcursor=\r")
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_10', {})
+ call term_sendkeys(buf, "\<Down>")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_11', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Check the "o" command
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_12', {})
+ call term_sendkeys(buf, "o")
+ call VerifyScreenDump(buf, 'Test_conceal_two_windows_13', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_conceal_with_cursorline()
+ CheckScreendump
+
+ " Opens a help window, where 'conceal' is set, switches to the other window
+ " where 'cursorline' needs to be updated when the cursor moves.
+ let code =<< trim [CODE]
+ set cursorline
+ normal othis is a test
+ new
+ call setline(1, ["one", "two", "three", "four", "five"])
+ set ft=help
+ normal M
+ [CODE]
+
+ call writefile(code, 'XTest_conceal_cul', 'D')
+ let buf = RunVimInTerminal('-S XTest_conceal_cul', {})
+ call VerifyScreenDump(buf, 'Test_conceal_cul_01', {})
+
+ call term_sendkeys(buf, ":wincmd w\r")
+ call VerifyScreenDump(buf, 'Test_conceal_cul_02', {})
+
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_conceal_cul_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_conceal_with_cursorcolumn()
+ CheckScreendump
+
+ " Check that cursorcolumn and colorcolumn don't get broken in presence of
+ " wrapped lines containing concealed text
+ let code =<< trim [CODE]
+ let lines = ["one one one |hidden| one one one one one one one one",
+ \ "two two two two |hidden| here two two",
+ \ "three |hidden| three three three three three three three three"]
+ call setline(1, lines)
+ set wrap linebreak
+ set showbreak=\ >>>\
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=
+ exe "normal /here\r"
+ set cursorcolumn
+ set colorcolumn=50
+ [CODE]
+
+ call writefile(code, 'XTest_conceal_cuc', 'D')
+ let buf = RunVimInTerminal('-S XTest_conceal_cuc', {'rows': 10, 'cols': 40})
+ call VerifyScreenDump(buf, 'Test_conceal_cuc_01', {})
+
+ " move cursor to the end of line (the cursor jumps to the next screen line)
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_conceal_cuc_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_conceal_resize_term()
+ CheckScreendump
+
+ let code =<< trim [CODE]
+ call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed')
+ setl cocu=n cole=3
+ syn region CommentCodeSpan matchgroup=Comment start=/`/ end=/`/ concealends
+ normal fb
+ [CODE]
+ call writefile(code, 'XTest_conceal_resize', 'D')
+ let buf = RunVimInTerminal('-S XTest_conceal_resize', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_conceal_resize_01', {})
+
+ call win_execute(buf->win_findbuf()[0], 'wincmd +')
+ call VerifyScreenDump(buf, 'Test_conceal_resize_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_conceal_linebreak()
+ CheckScreendump
+
+ let code =<< trim [CODE]
+ vim9script
+ &wrap = true
+ &conceallevel = 2
+ &concealcursor = 'nc'
+ &linebreak = true
+ &showbreak = '+ '
+ var line: string = 'a`a`a`a`'
+ .. 'a'->repeat(&columns - 15)
+ .. ' b`b`'
+ .. 'b'->repeat(&columns - 10)
+ .. ' cccccc'
+ ['x'->repeat(&columns), '', line]->setline(1)
+ syntax region CodeSpan matchgroup=Delimiter start=/\z(`\+\)/ end=/\z1/ concealends
+ [CODE]
+ call writefile(code, 'XTest_conceal_linebreak', 'D')
+ let buf = RunVimInTerminal('-S XTest_conceal_linebreak', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_conceal_linebreak_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Tests for correct display (cursor column position) with +conceal and
+" tabulators. Need to run this test in a separate Vim instance. Otherwise the
+" screen is not updated (lazy redraw) and the cursor position is wrong.
+func Test_conceal_cursor_pos()
+ let code =<< trim [CODE]
+ :let l = ['start:', '.concealed. text', "|concealed|\ttext"]
+ :let l += ['', "\t.concealed.\ttext", "\t|concealed|\ttext", '']
+ :let l += [".a.\t.b.\t.c.\t.d.", "|a|\t|b|\t|c|\t|d|"]
+ :call append(0, l)
+ :call cursor(1, 1)
+ :" Conceal settings.
+ :set conceallevel=2
+ :set concealcursor=nc
+ :syntax match test /|/ conceal
+ :" Save current cursor position. Only works in <expr> mode, can't be used
+ :" with :normal because it moves the cursor to the command line. Thanks
+ :" to ZyX <zyx.vim@gmail.com> for the idea to use an <expr> mapping.
+ :let curpos = []
+ :nnoremap <expr> GG ":let curpos += ['".screenrow().":".screencol()."']\n"
+ :normal ztj
+ GGk
+ :" We should end up in the same column when running these commands on the
+ :" two lines.
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j
+ GGk
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j0j
+ GGk
+ :" Same for next test block.
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j
+ GGk
+ :normal ft
+ GGk
+ :normal $
+ GGk
+ :normal 0j0j
+ GGk
+ :" And check W with multiple tabs and conceals in a line.
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal $
+ GGk
+ :normal 0j
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal $
+ GGk
+ :set lbr
+ :normal $
+ GGk
+ :set list listchars=tab:>-
+ :normal 0
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal W
+ GGk
+ :normal $
+ GGk
+ :call writefile(curpos, 'Xconceal_curpos.out')
+ :q!
+
+ [CODE]
+ call writefile(code, 'XTest_conceal_curpos', 'D')
+
+ if RunVim([], [], '-s XTest_conceal_curpos')
+ call assert_equal([
+ \ '2:1', '2:17', '2:20', '3:1', '3:17', '3:20', '5:8', '5:25',
+ \ '5:28', '6:8', '6:25', '6:28', '8:1', '8:9', '8:17', '8:25',
+ \ '8:27', '9:1', '9:9', '9:17', '9:25', '9:26', '9:26', '9:1',
+ \ '9:9', '9:17', '9:25', '9:26'], readfile('Xconceal_curpos.out'))
+ endif
+
+ call delete('Xconceal_curpos.out')
+endfunc
+
+func Test_conceal_eol()
+ new!
+ setlocal concealcursor=n conceallevel=1
+ call setline(1, ["x", ""])
+ call matchaddpos('Conceal', [[2, 1, 1]], 2, -1, {'conceal': 1})
+ redraw!
+
+ call assert_notequal(screenchar(1, 1), screenchar(2, 2))
+ call assert_equal(screenattr(1, 1), screenattr(1, 2))
+ call assert_equal(screenattr(1, 2), screenattr(2, 2))
+ call assert_equal(screenattr(2, 1), screenattr(2, 2))
+
+ set list
+ redraw!
+
+ call assert_equal(screenattr(1, 1), screenattr(2, 2))
+ call assert_notequal(screenattr(1, 1), screenattr(1, 2))
+ call assert_notequal(screenattr(1, 2), screenattr(2, 1))
+
+ set nolist
+endfunc
+
+func Test_conceal_mouse_click()
+ enew!
+ set mouse=a
+ setlocal conceallevel=2 concealcursor=nc
+ syn match Concealed "this" conceal
+ hi link Concealed Search
+ call setline(1, 'conceal this click here')
+ redraw
+ call assert_equal(['conceal click here '], ScreenLines(1, 20))
+
+ " click on 'h' of "here" puts cursor there
+ call test_setmouse(1, 16)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 20, 0, 20], getcurpos())
+
+ bwipe!
+ set mouse&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_const.vim b/src/testdir/test_const.vim
new file mode 100644
index 0000000..2b79f42
--- /dev/null
+++ b/src/testdir/test_const.vim
@@ -0,0 +1,335 @@
+" Test for :const
+
+func s:noop()
+endfunc
+
+func Test_define_var_with_lock()
+ const i = 1
+ const f = 1.1
+ const s = 'vim'
+ const F = funcref('s:noop')
+ const l = [1, 2, 3]
+ const d = {'foo': 10}
+ if has('channel')
+ const j = test_null_job()
+ const c = test_null_channel()
+ endif
+ const b = v:true
+ const n = v:null
+ const bl = 0zC0FFEE
+ const here =<< trim EOS
+ hello
+ EOS
+
+ call assert_true(exists('i'))
+ call assert_true(exists('f'))
+ call assert_true(exists('s'))
+ call assert_true(exists('F'))
+ call assert_true(exists('l'))
+ call assert_true(exists('d'))
+ if has('channel')
+ call assert_true(exists('j'))
+ call assert_true(exists('c'))
+ endif
+ call assert_true(exists('b'))
+ call assert_true(exists('n'))
+ call assert_true(exists('bl'))
+ call assert_true(exists('here'))
+
+ call assert_fails('let i = 1', 'E741:')
+ call assert_fails('let f = 1.1', 'E741:')
+ call assert_fails('let s = "vim"', 'E741:')
+ call assert_fails('let F = funcref("s:noop")', 'E741:')
+ call assert_fails('let l = [1, 2, 3]', 'E741:')
+ call assert_fails('call filter(l, "v:val % 2 == 0")', 'E741:')
+ call assert_fails('let d = {"foo": 10}', 'E741:')
+ if has('channel')
+ call assert_fails('let j = test_null_job()', 'E741:')
+ call assert_fails('let c = test_null_channel()', 'E741:')
+ endif
+ call assert_fails('let b = v:true', 'E741:')
+ call assert_fails('let n = v:null', 'E741:')
+ call assert_fails('let bl = 0zC0FFEE', 'E741:')
+ call assert_fails('let here = "xxx"', 'E741:')
+
+ " Unlet
+ unlet i
+ unlet f
+ unlet s
+ unlet F
+ unlet l
+ unlet d
+ if has('channel')
+ unlet j
+ unlet c
+ endif
+ unlet b
+ unlet n
+ unlet bl
+ unlet here
+endfunc
+
+func Test_define_l_var_with_lock()
+ " With l: prefix
+ const l:i = 1
+ const l:f = 1.1
+ const l:s = 'vim'
+ const l:F = funcref('s:noop')
+ const l:l = [1, 2, 3]
+ const l:d = {'foo': 10}
+ if has('channel')
+ const l:j = test_null_job()
+ const l:c = test_null_channel()
+ endif
+ const l:b = v:true
+ const l:n = v:null
+ const l:bl = 0zC0FFEE
+ const l:here =<< trim EOS
+ hello
+ EOS
+
+ call assert_fails('let l:i = 1', 'E741:')
+ call assert_fails('let l:f = 1.1', 'E741:')
+ call assert_fails('let l:s = "vim"', 'E741:')
+ call assert_fails('let l:F = funcref("s:noop")', 'E741:')
+ call assert_fails('let l:l = [1, 2, 3]', 'E741:')
+ call assert_fails('let l:d = {"foo": 10}', 'E741:')
+ if has('channel')
+ call assert_fails('let l:j = test_null_job()', 'E741:')
+ call assert_fails('let l:c = test_null_channel()', 'E741:')
+ endif
+ call assert_fails('let l:b = v:true', 'E741:')
+ call assert_fails('let l:n = v:null', 'E741:')
+ call assert_fails('let l:bl = 0zC0FFEE', 'E741:')
+ call assert_fails('let l:here = "xxx"', 'E741:')
+
+ " Unlet
+ unlet l:i
+ unlet l:f
+ unlet l:s
+ unlet l:F
+ unlet l:l
+ unlet l:d
+ if has('channel')
+ unlet l:j
+ unlet l:c
+ endif
+ unlet l:b
+ unlet l:n
+ unlet l:bl
+ unlet l:here
+endfunc
+
+func Test_define_script_var_with_lock()
+ const s:x = 0
+ call assert_fails('let s:x = 1', 'E741:')
+ unlet s:x
+endfunc
+
+func Test_destructuring_with_lock()
+ const [a, b, c] = [1, 1.1, 'vim']
+
+ call assert_fails('let a = 1', 'E741:')
+ call assert_fails('let b = 1.1', 'E741:')
+ call assert_fails('let c = "vim"', 'E741:')
+
+ const [d; e] = [1, 1.1, 'vim']
+ call assert_fails('let d = 1', 'E741:')
+ call assert_fails('let e = [2.2, "a"]', 'E741:')
+endfunc
+
+func Test_cannot_modify_existing_variable()
+ let i = 1
+ let f = 1.1
+ let s = 'vim'
+ let F = funcref('s:noop')
+ let l = [1, 2, 3]
+ let d = {'foo': 10}
+ if has('channel')
+ let j = test_null_job()
+ let c = test_null_channel()
+ endif
+ let b = v:true
+ let n = v:null
+ let bl = 0zC0FFEE
+
+ call assert_fails('const i = 1', 'E995:')
+ call assert_fails('const f = 1.1', 'E995:')
+ call assert_fails('const s = "vim"', 'E995:')
+ call assert_fails('const F = funcref("s:noop")', 'E995:')
+ call assert_fails('const l = [1, 2, 3]', 'E995:')
+ call assert_fails('const d = {"foo": 10}', 'E995:')
+ if has('channel')
+ call assert_fails('const j = test_null_job()', 'E995:')
+ call assert_fails('const c = test_null_channel()', 'E995:')
+ endif
+ call assert_fails('const b = v:true', 'E995:')
+ call assert_fails('const n = v:null', 'E995:')
+ call assert_fails('const bl = 0zC0FFEE', 'E995:')
+ call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:')
+
+ const i2 = 1
+ const f2 = 1.1
+ const s2 = 'vim'
+ const F2 = funcref('s:noop')
+ const l2 = [1, 2, 3]
+ const d2 = {'foo': 10}
+ if has('channel')
+ const j2 = test_null_job()
+ const c2 = test_null_channel()
+ endif
+ const b2 = v:true
+ const n2 = v:null
+ const bl2 = 0zC0FFEE
+
+ call assert_fails('const i2 = 1', 'E995:')
+ call assert_fails('const f2 = 1.1', 'E995:')
+ call assert_fails('const s2 = "vim"', 'E995:')
+ call assert_fails('const F2 = funcref("s:noop")', 'E995:')
+ call assert_fails('const l2 = [1, 2, 3]', 'E995:')
+ call assert_fails('const d2 = {"foo": 10}', 'E995:')
+ if has('channel')
+ call assert_fails('const j2 = test_null_job()', 'E995:')
+ call assert_fails('const c2 = test_null_channel()', 'E995:')
+ endif
+ call assert_fails('const b2 = v:true', 'E995:')
+ call assert_fails('const n2 = v:null', 'E995:')
+ call assert_fails('const bl2 = 0zC0FFEE', 'E995:')
+ call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
+endfunc
+
+func Test_const_with_condition()
+ const x = 0
+ if 0 | const x = 1 | endif
+ call assert_equal(0, x)
+endfunc
+
+func Test_lockvar()
+ let x = 'hello'
+ lockvar x
+ call assert_fails('let x = "there"', 'E741:')
+ if 0 | unlockvar x | endif
+ call assert_fails('let x = "there"', 'E741:')
+ unlockvar x
+ let x = 'there'
+
+ if 0 | lockvar x | endif
+ let x = 'again'
+
+ let val = [1, 2, 3]
+ lockvar 0 val
+ let val[0] = 9
+ call assert_equal([9, 2, 3], val)
+ call add(val, 4)
+ call assert_equal([9, 2, 3, 4], val)
+ call assert_fails('let val = [4, 5, 6]', 'E1122:')
+
+ let l =<< trim END
+ let d = {}
+ lockvar d
+ func d.fn()
+ return 1
+ endfunc
+ END
+ let @a = l->join("\n")
+ call assert_fails('exe @a', 'E741:')
+
+ let l =<< trim END
+ let d = {}
+ let d.fn = function("min")
+ lockvar d.fn
+ func! d.fn()
+ return 1
+ endfunc
+ END
+ let @a = l->join("\n")
+ call assert_fails('exe @a', 'E741:')
+endfunc
+
+func Test_const_with_index_access()
+ let l = [1, 2, 3]
+ call assert_fails('const l[0] = 4', 'E996:')
+ call assert_fails('const l[0:1] = [1, 2]', 'E996:')
+
+ let d = {'aaa': 0}
+ call assert_fails("const d['aaa'] = 4", 'E996:')
+ call assert_fails("const d.aaa = 4", 'E996:')
+endfunc
+
+func Test_const_with_compound_assign()
+ let i = 0
+ call assert_fails('const i += 4', 'E995:')
+ call assert_fails('const i -= 4', 'E995:')
+ call assert_fails('const i *= 4', 'E995:')
+ call assert_fails('const i /= 4', 'E995:')
+ call assert_fails('const i %= 4', 'E995:')
+
+ let s = 'a'
+ call assert_fails('const s .= "b"', 'E995:')
+
+ let [a, b, c] = [1, 2, 3]
+ call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:')
+
+ let [d; e] = [1, 2, 3]
+ call assert_fails('const [d; e] += [4, 5, 6]', 'E995:')
+endfunc
+
+func Test_const_with_special_variables()
+ call assert_fails('const $FOO = "hello"', 'E996:')
+ call assert_fails('const @a = "hello"', 'E996:')
+ call assert_fails('const &filetype = "vim"', 'E996:')
+ call assert_fails('const &l:filetype = "vim"', 'E996:')
+ call assert_fails('const &g:encoding = "utf-8"', 'E996:')
+
+ call assert_fails('const [a, $CONST_FOO] = [369, "abc"]', 'E996:')
+ call assert_equal(369, a)
+ call assert_equal(v:null, getenv("CONST_FOO"))
+
+ call assert_fails('const [b; $CONST_FOO] = [246, 2, "abc"]', 'E996:')
+ call assert_equal(246, b)
+ call assert_equal(v:null, getenv("CONST_FOO"))
+endfunc
+
+func Test_const_with_eval_name()
+ let s = 'foo'
+
+ " eval name with :const should work
+ const abc_{s} = 1
+ const {s}{s} = 1
+
+ let s2 = 'abc_foo'
+ call assert_fails('const {s2} = "bar"', 'E995:')
+endfunc
+
+func Test_lock_depth_is_2()
+ " Modify list - error when changing item or adding/removing items
+ const l = [1, 2, [3, 4]]
+ call assert_fails('let l[0] = 42', 'E741:')
+ call assert_fails('let l[2][0] = 42', 'E741:')
+ call assert_fails('call add(l, 4)', 'E741:')
+ call assert_fails('unlet l[1]', 'E741:')
+
+ " Modify blob - error when changing
+ const b = 0z001122
+ call assert_fails('let b[0] = 42', 'E741:')
+
+ " Modify dict - error when changing item or adding/removing items
+ const d = {'foo': 10}
+ call assert_fails("let d['foo'] = 'hello'", 'E741:')
+ call assert_fails("let d.foo = 'hello'", 'E741:')
+ call assert_fails("let d['bar'] = 'hello'", 'E741:')
+ call assert_fails("unlet d['foo']", 'E741:')
+
+ " Modifying list or dict item contents is OK.
+ let lvar = ['a', 'b']
+ let bvar = 0z1122
+ const l2 = [0, lvar, bvar]
+ let l2[1][0] = 'c'
+ let l2[2][1] = 0x33
+ call assert_equal([0, ['c', 'b'], 0z1133], l2)
+
+ const d2 = #{a: 0, b: lvar, c: 4}
+ let d2.b[1] = 'd'
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cpoptions.vim b/src/testdir/test_cpoptions.vim
new file mode 100644
index 0000000..6ff8301
--- /dev/null
+++ b/src/testdir/test_cpoptions.vim
@@ -0,0 +1,915 @@
+" Test for the various 'cpoptions' (cpo) flags
+
+source check.vim
+source shared.vim
+source view_util.vim
+
+" Test for the 'a' flag in 'cpo'. Reading a file should set the alternate
+" file name.
+func Test_cpo_a()
+ let save_cpo = &cpo
+ call writefile(['one'], 'XfileCpoA', 'D')
+ " Wipe out all the buffers, so that the alternate file is empty
+ edit Xfoo | %bw
+ set cpo-=a
+ new
+ read XfileCpoA
+ call assert_equal('', @#)
+ %d
+ set cpo+=a
+ read XfileCpoA
+ call assert_equal('XfileCpoA', @#)
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'A' flag in 'cpo'. Writing a file should set the alternate
+" file name.
+func Test_cpo_A()
+ let save_cpo = &cpo
+ " Wipe out all the buffers, so that the alternate file is empty
+ edit Xfoo | %bw
+ set cpo-=A
+ new XcpoAfile1
+ write XcpoAfile2
+ call assert_equal('', @#)
+ %bw
+ call delete('XcpoAfile2')
+ new XcpoAfile1
+ set cpo+=A
+ write XcpoAfile2
+ call assert_equal('XcpoAfile2', @#)
+ close!
+ call delete('XcpoAfile2')
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'b' flag in 'cpo'. "\|" at the end of a map command is
+" recognized as the end of the map.
+func Test_cpo_b()
+ let save_cpo = &cpo
+ set cpo+=b
+ nnoremap <F5> :pwd\<CR>\|let i = 1
+ call assert_equal(':pwd\<CR>\', maparg('<F5>'))
+ nunmap <F5>
+ exe "nnoremap <F5> :pwd\<C-V>|let i = 1"
+ call assert_equal(':pwd|let i = 1', maparg('<F5>'))
+ nunmap <F5>
+ set cpo-=b
+ nnoremap <F5> :pwd\<CR>\|let i = 1
+ call assert_equal(':pwd\<CR>|let i = 1', maparg('<F5>'))
+ let &cpo = save_cpo
+ nunmap <F5>
+endfunc
+
+" Test for the 'B' flag in 'cpo'. A backslash in mappings, abbreviations, user
+" commands and menu commands has no special meaning.
+func Test_cpo_B()
+ let save_cpo = &cpo
+ new
+ imap <buffer> x<Bslash>k Test
+ set cpo-=B
+ iabbr <buffer> abc ab\<BS>d
+ exe "normal iabc "
+ call assert_equal('ab<BS>d ', getline(1))
+ call feedkeys(":imap <buffer> x\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"imap <buffer> x\\k', @:)
+ %d
+ set cpo+=B
+ iabbr <buffer> abc ab\<BS>d
+ exe "normal iabc "
+ call assert_equal('abd ', getline(1))
+ call feedkeys(":imap <buffer> x\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"imap <buffer> x\k', @:)
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'c' flag in 'cpo'.
+func Test_cpo_c()
+ let save_cpo = &cpo
+ set cpo+=c
+ new
+ call setline(1, ' abababababab')
+ exe "normal gg/abab\<CR>"
+ call assert_equal(3, searchcount().total)
+ set cpo-=c
+ exe "normal gg/abab\<CR>"
+ call assert_equal(5, searchcount().total)
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'C' flag in 'cpo' (line continuation)
+func Test_cpo_C()
+ let save_cpo = &cpo
+ call writefile(['let l = [', '\ 1,', '\ 2]'], 'XfileCpoC', 'D')
+ set cpo-=C
+ source XfileCpoC
+ call assert_equal([1, 2], g:l)
+ set cpo+=C
+ call assert_fails('source XfileCpoC', ['E697:', 'E10:'])
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'd' flag in 'cpo' (tags relative to the current file)
+func Test_cpo_d()
+ let save_cpo = &cpo
+ call mkdir('XdirCpoD', 'R')
+ call writefile(["one\tXfile1\t/^one$/"], 'tags', 'D')
+ call writefile(["two\tXfile2\t/^two$/"], 'XdirCpoD/tags')
+ set tags=./tags
+ set cpo-=d
+ edit XdirCpoD/Xfile
+ call assert_equal('two', taglist('.*')[0].name)
+ set cpo+=d
+ call assert_equal('one', taglist('.*')[0].name)
+
+ %bw!
+ set tags&
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'D' flag in 'cpo' (digraph after a r, f or t)
+func Test_cpo_D()
+ CheckFeature digraphs
+ let save_cpo = &cpo
+ new
+ set cpo-=D
+ call setline(1, 'abcdefgh|')
+ exe "norm! 1gg0f\<c-k>!!"
+ call assert_equal(9, col('.'))
+ set cpo+=D
+ exe "norm! 1gg0f\<c-k>!!"
+ call assert_equal(1, col('.'))
+ set cpo-=D
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'e' flag in 'cpo'
+func Test_cpo_e()
+ let save_cpo = &cpo
+ let @a='let i = 45'
+ set cpo+=e
+ call feedkeys(":@a\<CR>", 'xt')
+ call assert_equal(45, i)
+ set cpo-=e
+ call feedkeys(":@a\<CR>6\<CR>", 'xt')
+ call assert_equal(456, i)
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'E' flag in 'cpo' with yank, change, delete, etc. operators
+func Test_cpo_E()
+ new
+ call setline(1, '')
+ set cpo+=E
+ " yank an empty line
+ call assert_beeps('normal "ayl')
+ " change an empty line
+ call assert_beeps('normal lcTa')
+ call assert_beeps('normal 0c0')
+ " delete an empty line
+ call assert_beeps('normal D')
+ call assert_beeps('normal dl')
+ call assert_equal('', getline(1))
+ " change case of an empty line
+ call assert_beeps('normal gul')
+ call assert_beeps('normal gUl')
+ " replace a character
+ call assert_beeps('normal vrx')
+ " increment and decrement
+ call assert_beeps('exe "normal v\<C-A>"')
+ call assert_beeps('exe "normal v\<C-X>"')
+ set cpo-=E
+ close!
+endfunc
+
+" Test for the 'f' flag in 'cpo' (read in an empty buffer sets the file name)
+func Test_cpo_f()
+ let save_cpo = &cpo
+ new
+ set cpo-=f
+ read test_cpoptions.vim
+ call assert_equal('', @%)
+ %d
+ set cpo+=f
+ read test_cpoptions.vim
+ call assert_equal('test_cpoptions.vim', @%)
+
+ bwipe!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'F' flag in 'cpo' (write in an empty buffer sets the file name)
+func Test_cpo_F()
+ let save_cpo = &cpo
+ new
+ set cpo-=F
+ write XfileCpoF
+ call assert_equal('', @%)
+ call delete('XfileCpoF')
+ set cpo+=F
+ write XfileCpoF
+ call assert_equal('XfileCpoF', @%)
+ close!
+ call delete('XfileCpoF')
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'g' flag in 'cpo' (jump to line 1 when re-editing a file)
+func Test_cpo_g()
+ let save_cpo = &cpo
+ new test_cpoptions.vim
+ set cpo-=g
+ normal 20G
+ edit
+ call assert_equal(20, line('.'))
+ set cpo+=g
+ edit
+ call assert_equal(1, line('.'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions')
+func Test_cpo_H()
+ let save_cpo = &cpo
+ new
+ set cpo-=H
+ call setline(1, ' ')
+ normal! Ia
+ call assert_equal(' a', getline(1))
+ set cpo+=H
+ call setline(1, ' ')
+ normal! Ia
+ call assert_equal(' a ', getline(1))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" TODO: Add a test for the 'i' flag in 'cpo'
+" Interrupting the reading of a file will leave it modified.
+
+" Test for the 'I' flag in 'cpo' (deleting autoindent when using arrow keys)
+func Test_cpo_I()
+ let save_cpo = &cpo
+ new
+ setlocal autoindent
+ set cpo+=I
+ exe "normal i one\<CR>\<Up>"
+ call assert_equal(' ', getline(2))
+ set cpo-=I
+ %d
+ exe "normal i one\<CR>\<Up>"
+ call assert_equal('', getline(2))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'j' flag in 'cpo' is in the test_join.vim file.
+
+" Test for the 'J' flag in 'cpo' (two spaces after a sentence)
+func Test_cpo_J()
+ let save_cpo = &cpo
+ new
+ set cpo-=J
+ call setline(1, 'one. two! three? four."'' five.)]')
+ normal 0
+ for colnr in [6, 12, 19, 28, 34]
+ normal )
+ call assert_equal(colnr, col('.'))
+ endfor
+ for colnr in [28, 19, 12, 6, 1]
+ normal (
+ call assert_equal(colnr, col('.'))
+ endfor
+ set cpo+=J
+ normal 0
+ for colnr in [12, 28, 34]
+ normal )
+ call assert_equal(colnr, col('.'))
+ endfor
+ for colnr in [28, 12, 1]
+ normal (
+ call assert_equal(colnr, col('.'))
+ endfor
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" TODO: Add a test for the 'k' flag in 'cpo'.
+" Disable the recognition of raw key codes in mappings, abbreviations, and the
+" "to" part of menu commands.
+
+" TODO: Add a test for the 'K' flag in 'cpo'.
+" Don't wait for a key code to complete when it is halfway a mapping.
+
+" Test for the 'l' flag in 'cpo' (backslash in a [] range)
+func Test_cpo_l()
+ let save_cpo = &cpo
+ new
+ call setline(1, ['', "a\tc" .. '\t'])
+ set cpo-=l
+ exe 'normal gg/[\t]' .. "\<CR>"
+ call assert_equal([2, 8], [col('.'), virtcol('.')])
+ set cpo+=l
+ exe 'normal gg/[\t]' .. "\<CR>"
+ call assert_equal([4, 10], [col('.'), virtcol('.')])
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions')
+func Test_cpo_L()
+ let save_cpo = &cpo
+ new
+ set cpo-=L
+ call setline(1, 'abcdefghijklmnopqr')
+ exe "normal 0gR\<Tab>"
+ call assert_equal("\<Tab>ijklmnopqr", getline(1))
+ set cpo+=L
+ set list
+ call setline(1, 'abcdefghijklmnopqr')
+ exe "normal 0gR\<Tab>"
+ call assert_equal("\<Tab>cdefghijklmnopqr", getline(1))
+ set nolist
+ call setline(1, 'abcdefghijklmnopqr')
+ exe "normal 0gR\<Tab>"
+ call assert_equal("\<Tab>ijklmnopqr", getline(1))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" TODO: Add a test for the 'm' flag in 'cpo'.
+" When included, a showmatch will always wait half a second. When not
+" included, a showmatch will wait half a second or until a character is typed.
+
+" Test for the 'M' flag in 'cpo' (% with escape parenthesis)
+func Test_cpo_M()
+ let save_cpo = &cpo
+ new
+ call setline(1, ['( \( )', '\( ( \)'])
+
+ set cpo-=M
+ call cursor(1, 1)
+ normal %
+ call assert_equal(6, col('.'))
+ call cursor(1, 4)
+ call assert_beeps('normal %')
+ call cursor(2, 2)
+ normal %
+ call assert_equal(7, col('.'))
+ call cursor(2, 4)
+ call assert_beeps('normal %')
+
+ set cpo+=M
+ call cursor(1, 4)
+ normal %
+ call assert_equal(6, col('.'))
+ call cursor(1, 1)
+ call assert_beeps('normal %')
+ call cursor(2, 4)
+ normal %
+ call assert_equal(7, col('.'))
+ call cursor(2, 1)
+ call assert_beeps('normal %')
+
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'n' flag in 'cpo' (using number column for wrapped lines)
+func Test_cpo_n()
+ let save_cpo = &cpo
+ new
+ call setline(1, repeat('a', &columns))
+ setlocal number
+ set cpo-=n
+ redraw!
+ call assert_equal(' aaaa', Screenline(2))
+ set cpo+=n
+ redraw!
+ call assert_equal('aaaa', Screenline(2))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'o' flag in 'cpo' (line offset to search command)
+func Test_cpo_o()
+ let save_cpo = &cpo
+ new
+ call setline(1, ['', 'one', 'two', 'three', 'one', 'two', 'three'])
+ set cpo-=o
+ exe "normal /one/+2\<CR>"
+ normal n
+ call assert_equal(7, line('.'))
+ set cpo+=o
+ exe "normal /one/+2\<CR>"
+ normal n
+ call assert_equal(5, line('.'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'O' flag in 'cpo' (overwriting an existing file)
+func Test_cpo_O()
+ let save_cpo = &cpo
+ new XfileCpoO
+ call setline(1, 'one')
+ call writefile(['two'], 'XfileCpoO', 'D')
+ set cpo-=O
+ call assert_fails('write', 'E13:')
+ set cpo+=O
+ write
+ call assert_equal(['one'], readfile('XfileCpoO'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'p' flag in 'cpo' is in the test_lispindent.vim file.
+
+" Test for the 'P' flag in 'cpo' (appending to a file sets the current file
+" name)
+func Test_cpo_P()
+ let save_cpo = &cpo
+ call writefile([], 'XfileCpoP', 'D')
+ new
+ call setline(1, 'one')
+ set cpo+=F
+ set cpo-=P
+ write >> XfileCpoP
+ call assert_equal('', @%)
+ set cpo+=P
+ write >> XfileCpoP
+ call assert_equal('XfileCpoP', @%)
+
+ bwipe!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'q' flag in 'cpo' (joining multiple lines)
+func Test_cpo_q()
+ let save_cpo = &cpo
+ new
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ set cpo-=q
+ normal gg4J
+ call assert_equal(14, col('.'))
+ %d
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ set cpo+=q
+ normal gg4J
+ call assert_equal(4, col('.'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'r' flag in 'cpo' (redo command with a search motion)
+func Test_cpo_r()
+ let save_cpo = &cpo
+ new
+ call setline(1, repeat(['one two three four'], 2))
+ set cpo-=r
+ exe "normal ggc/two\<CR>abc "
+ let @/ = 'three'
+ normal 2G.
+ call assert_equal('abc two three four', getline(2))
+ %d
+ call setline(1, repeat(['one two three four'], 2))
+ set cpo+=r
+ exe "normal ggc/two\<CR>abc "
+ let @/ = 'three'
+ normal 2G.
+ call assert_equal('abc three four', getline(2))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'R' flag in 'cpo' (clear marks after a filter command)
+func Test_cpo_R()
+ CheckUnix
+ let save_cpo = &cpo
+ new
+ call setline(1, ['three', 'one', 'two'])
+ set cpo-=R
+ 3mark r
+ %!sort
+ call assert_equal(3, line("'r"))
+ %d
+ call setline(1, ['three', 'one', 'two'])
+ set cpo+=R
+ 3mark r
+ %!sort
+ call assert_equal(0, line("'r"))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" TODO: Add a test for the 's' flag in 'cpo'.
+" Set buffer options when entering the buffer for the first time. If not
+" present the options are set when the buffer is created.
+
+" Test for the 'S' flag in 'cpo' (copying buffer options)
+func Test_cpo_S()
+ let save_cpo = &cpo
+ new Xfile1
+ set noautoindent
+ new Xfile2
+ set cpo-=S
+ set autoindent
+ wincmd p
+ call assert_equal(0, &autoindent)
+ wincmd p
+ call assert_equal(1, &autoindent)
+ set cpo+=S
+ wincmd p
+ call assert_equal(1, &autoindent)
+ set noautoindent
+ wincmd p
+ call assert_equal(0, &autoindent)
+ wincmd t
+ close!
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 't' flag in 'cpo' is in the test_tagjump.vim file.
+
+" Test for the 'u' flag in 'cpo' (Vi-compatible undo)
+func Test_cpo_u()
+ let save_cpo = &cpo
+ new
+ set cpo-=u
+ exe "normal iabc\<C-G>udef\<C-G>ughi"
+ normal uu
+ call assert_equal('abc', getline(1))
+ %d
+ set cpo+=u
+ exe "normal iabc\<C-G>udef\<C-G>ughi"
+ normal uu
+ call assert_equal('abcdefghi', getline(1))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" TODO: Add a test for the 'v' flag in 'cpo'.
+" Backspaced characters remain visible on the screen in Insert mode.
+
+" Test for the 'w' flag in 'cpo' ('cw' on a blank character changes only one
+" character)
+func Test_cpo_w()
+ let save_cpo = &cpo
+ new
+ set cpo+=w
+ call setline(1, 'here are some words')
+ norm! 1gg0elcwZZZ
+ call assert_equal('hereZZZ are some words', getline('.'))
+ norm! 1gg2elcWYYY
+ call assert_equal('hereZZZ areYYY some words', getline('.'))
+ set cpo-=w
+ call setline(1, 'here are some words')
+ norm! 1gg0elcwZZZ
+ call assert_equal('hereZZZare some words', getline('.'))
+ norm! 1gg2elcWYYY
+ call assert_equal('hereZZZare someYYYwords', getline('.'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'W' flag in 'cpo' is in the test_writefile.vim file
+
+" Test for the 'x' flag in 'cpo' (Esc on command-line executes command)
+func Test_cpo_x()
+ let save_cpo = &cpo
+ set cpo-=x
+ let i = 1
+ call feedkeys(":let i=10\<Esc>", 'xt')
+ call assert_equal(1, i)
+ set cpo+=x
+ call feedkeys(":let i=10\<Esc>", 'xt')
+ call assert_equal(10, i)
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'X' flag in 'cpo' ('R' with a count)
+func Test_cpo_X()
+ let save_cpo = &cpo
+ new
+ call setline(1, 'aaaaaa')
+ set cpo-=X
+ normal gg4Rx
+ call assert_equal('xxxxaa', getline(1))
+ normal ggRy
+ normal 4.
+ call assert_equal('yyyyaa', getline(1))
+ call setline(1, 'aaaaaa')
+ set cpo+=X
+ normal gg4Rx
+ call assert_equal('xxxxaaaaa', getline(1))
+ normal ggRy
+ normal 4.
+ call assert_equal('yyyyxxxaaaaa', getline(1))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'y' flag in 'cpo' (repeating a yank command)
+func Test_cpo_y()
+ let save_cpo = &cpo
+ new
+ call setline(1, ['one', 'two'])
+ set cpo-=y
+ normal ggyy
+ normal 2G.
+ call assert_equal("one\n", @")
+ %d
+ call setline(1, ['one', 'two'])
+ set cpo+=y
+ normal ggyy
+ normal 2G.
+ call assert_equal("two\n", @")
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the 'Z' flag in 'cpo' (write! resets 'readonly')
+func Test_cpo_Z()
+ let save_cpo = &cpo
+ call writefile([], 'XfileCpoZ', 'D')
+ new XfileCpoZ
+ setlocal readonly
+ set cpo-=Z
+ write!
+ call assert_equal(0, &readonly)
+ set cpo+=Z
+ setlocal readonly
+ write!
+ call assert_equal(1, &readonly)
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '!' flag in 'cpo' is in the test_normal.vim file
+
+" Test for displaying dollar when changing text ('$' flag in 'cpoptions')
+func Test_cpo_dollar()
+ new
+ let g:Line = ''
+ func SaveFirstLine()
+ let g:Line = Screenline(1)
+ return ''
+ endfunc
+ inoremap <expr> <buffer> <F2> SaveFirstLine()
+ call test_override('redraw_flag', 1)
+ set cpo+=$
+ call setline(1, 'one two three')
+ redraw!
+ exe "normal c2w\<F2>vim"
+ call assert_equal('one tw$ three', g:Line)
+ call assert_equal('vim three', getline(1))
+ set cpo-=$
+ call test_override('ALL', 0)
+ delfunc SaveFirstLine
+ %bw!
+endfunc
+
+" Test for the '%' flag in 'cpo' (parenthesis matching inside strings)
+func Test_cpo_percent()
+ let save_cpo = &cpo
+ new
+ call setline(1, ' if (strcmp("ab)cd(", s))')
+ set cpo-=%
+ normal 8|%
+ call assert_equal(28, col('.'))
+ normal 15|%
+ call assert_equal(27, col('.'))
+ normal 27|%
+ call assert_equal(15, col('.'))
+ call assert_beeps("normal 19|%")
+ call assert_beeps("normal 22|%")
+ set cpo+=%
+ normal 8|%
+ call assert_equal(28, col('.'))
+ normal 15|%
+ call assert_equal(19, col('.'))
+ normal 27|%
+ call assert_equal(22, col('.'))
+ normal 19|%
+ call assert_equal(15, col('.'))
+ normal 22|%
+ call assert_equal(27, col('.'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for cursor movement with '-' in 'cpoptions'
+func Test_cpo_minus()
+ new
+ call setline(1, ['foo', 'bar', 'baz'])
+ let save_cpo = &cpo
+ set cpo+=-
+ call assert_beeps('normal 10j')
+ call assert_equal(1, line('.'))
+ normal G
+ call assert_beeps('normal 10k')
+ call assert_equal(3, line('.'))
+ call assert_fails(10, 'E16:')
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '+' flag in 'cpo' ('write file' command resets the 'modified'
+" flag)
+func Test_cpo_plus()
+ let save_cpo = &cpo
+ call writefile([], 'XfileCpoPlus', 'D')
+ new XfileCpoPlus
+ call setline(1, 'foo')
+ write X1
+ call assert_equal(1, &modified)
+ set cpo+=+
+ write X2
+ call assert_equal(0, &modified)
+ close!
+ call delete('X1')
+ call delete('X2')
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '*' flag in 'cpo' (':*' is same as ':@')
+func Test_cpo_star()
+ let save_cpo = &cpo
+ let x = 0
+ new
+ set cpo-=*
+ let @a = 'let x += 1'
+ call assert_fails('*a', 'E20:')
+ set cpo+=*
+ *a
+ call assert_equal(1, x)
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '<' flag in 'cpo' is in the test_mapping.vim file
+
+" Test for the '>' flag in 'cpo' (use a new line when appending to a register)
+func Test_cpo_gt()
+ let save_cpo = &cpo
+ new
+ call setline(1, 'one two')
+ set cpo-=>
+ let @r = ''
+ normal gg"Rye
+ normal "Rye
+ call assert_equal("oneone", @r)
+ set cpo+=>
+ let @r = ''
+ normal gg"Rye
+ normal "Rye
+ call assert_equal("\none\none", @r)
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the ';' flag in 'cpo'
+" Test for t,f,F,T movement commands and 'cpo-;' setting
+func Test_cpo_semicolon()
+ let save_cpo = &cpo
+ new
+ call append(0, ["aaa two three four", " zzz", "yyy ",
+ \ "bbb yee yoo four", "ccc two three four",
+ \ "ddd yee yoo four"])
+ set cpo-=;
+ 1
+ normal! 0tt;D
+ 2
+ normal! 0fz;D
+ 3
+ normal! $Fy;D
+ 4
+ normal! $Ty;D
+ set cpo+=;
+ 5
+ normal! 0tt;;D
+ 6
+ normal! $Ty;;D
+
+ call assert_equal('aaa two', getline(1))
+ call assert_equal(' z', getline(2))
+ call assert_equal('y', getline(3))
+ call assert_equal('bbb y', getline(4))
+ call assert_equal('ccc', getline(5))
+ call assert_equal('ddd yee y', getline(6))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '#' flag in 'cpo' (count before 'D', 'o' and 'O' operators)
+func Test_cpo_hash()
+ let save_cpo = &cpo
+ new
+ set cpo-=#
+ call setline(1, ['one', 'two', 'three'])
+ normal gg2D
+ call assert_equal(['three'], getline(1, '$'))
+ normal gg2ofour
+ call assert_equal(['three', 'four', 'four'], getline(1, '$'))
+ normal gg2Otwo
+ call assert_equal(['two', 'two', 'three', 'four', 'four'], getline(1, '$'))
+ %d
+ set cpo+=#
+ call setline(1, ['one', 'two', 'three'])
+ normal gg2D
+ call assert_equal(['', 'two', 'three'], getline(1, '$'))
+ normal gg2oone
+ call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
+ normal gg2Ozero
+ call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '&' flag in 'cpo'. The swap file is kept when a buffer is still
+" loaded and ':preserve' is used.
+func Test_cpo_ampersand()
+ call writefile(['one'], 'XfileCpoAmp', 'D')
+ let after =<< trim [CODE]
+ set cpo+=&
+ preserve
+ qall
+ [CODE]
+ if RunVim([], after, 'XfileCpoAmp')
+ call assert_equal(1, filereadable('.XfileCpoAmp.swp'))
+ call delete('.XfileCpoAmp.swp')
+ endif
+endfunc
+
+" Test for the '\' flag in 'cpo' (backslash in a [] range in a search pattern)
+func Test_cpo_backslash()
+ let save_cpo = &cpo
+ new
+ call setline(1, ['', " \\-string"])
+ set cpo-=\
+ exe 'normal gg/[ \-]' .. "\<CR>n"
+ call assert_equal(3, col('.'))
+ set cpo+=\
+ exe 'normal gg/[ \-]' .. "\<CR>n"
+ call assert_equal(2, col('.'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '/' flag in 'cpo' is in the test_substitute.vim file
+
+" Test for the '{' flag in 'cpo' (the "{" and "}" commands stop at a {
+" character at the start of a line)
+func Test_cpo_brace()
+ let save_cpo = &cpo
+ new
+ call setline(1, ['', '{', ' int i;', '}', ''])
+ set cpo-={
+ normal gg}
+ call assert_equal(5, line('.'))
+ normal G{
+ call assert_equal(1, line('.'))
+ set cpo+={
+ normal gg}
+ call assert_equal(2, line('.'))
+ normal G{
+ call assert_equal(2, line('.'))
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" Test for the '.' flag in 'cpo' (:cd command fails if the current buffer is
+" modified)
+func Test_cpo_dot()
+ let save_cpo = &cpo
+ new Xfoo
+ call setline(1, 'foo')
+ let save_dir = getcwd()
+ set cpo+=.
+
+ " :cd should fail when buffer is modified and 'cpo' contains dot.
+ call assert_fails('cd ..', 'E747:')
+ call assert_equal(save_dir, getcwd())
+
+ " :cd with exclamation mark should succeed.
+ cd! ..
+ call assert_notequal(save_dir, getcwd())
+
+ " :cd should succeed when buffer has been written.
+ w!
+ exe 'cd ' .. fnameescape(save_dir)
+ call assert_equal(save_dir, getcwd())
+
+ call delete('Xfoo')
+ set cpo&
+ close!
+ let &cpo = save_cpo
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_crypt.vim b/src/testdir/test_crypt.vim
new file mode 100644
index 0000000..d1c645f
--- /dev/null
+++ b/src/testdir/test_crypt.vim
@@ -0,0 +1,288 @@
+" Tests for encryption.
+
+source check.vim
+CheckFeature cryptv
+
+" Use the xxd command from:
+" 1: $XXDPROG if set and it is executable
+" 2: the ../xxd directory if the executable is found there
+if !empty($XXDPROG) && executable($XXDPROG)
+ let s:xxd_cmd = $XXDPROG
+elseif executable('..\xxd\xxd.exe')
+ " we're on MS-Windows
+ let s:xxd_cmd = '..\xxd\xxd.exe'
+elseif executable('../xxd/xxd')
+ " we're on something like Unix
+ let s:xxd_cmd = '../xxd/xxd'
+else
+ " looks like xxd wasn't build (yet)
+ let s:xxd_cmd = ''
+endif
+
+func Common_head_only(text)
+ " This was crashing Vim
+ split Xtest.txt
+ call setline(1, a:text)
+ wq
+ call feedkeys(":split Xtest.txt\<CR>foobar\<CR>", "tx")
+ call delete('Xtest.txt')
+ call assert_match('VimCrypt', getline(1))
+ bwipe!
+endfunc
+
+func Test_head_only_2()
+ call Common_head_only('VimCrypt~02!abc')
+endfunc
+
+func Test_head_only_3()
+ call Common_head_only('VimCrypt~03!abc')
+endfunc
+
+func Test_head_only_4()
+ CheckFeature sodium
+ call Common_head_only('VimCrypt~04!abc')
+endfunc
+
+func Crypt_uncrypt(method)
+ exe "set cryptmethod=" . a:method
+ " If the blowfish test fails 'cryptmethod' will be 'zip' now.
+ call assert_equal(a:method, &cryptmethod)
+
+ split Xtest.txt
+ let text = ['01234567890123456789012345678901234567',
+ \ 'line 2 foo bar blah',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx']
+ call setline(1, text)
+ call feedkeys(":X\<CR>foobar\<CR>foobar\<CR>", 'xt')
+ call assert_equal('*****', &key)
+ w!
+ bwipe!
+ call feedkeys(":split Xtest.txt\<CR>foobar\<CR>", 'xt')
+ call assert_equal(text, getline(1, 3))
+ set key= cryptmethod&
+ bwipe!
+ call delete('Xtest.txt')
+endfunc
+
+func Test_crypt_zip()
+ call Crypt_uncrypt('zip')
+endfunc
+
+func Test_crypt_blowfish()
+ call Crypt_uncrypt('blowfish')
+endfunc
+
+func Test_crypt_blowfish2()
+ call Crypt_uncrypt('blowfish2')
+endfunc
+
+func Test_crypt_sodium()
+ CheckFeature sodium
+ call Crypt_uncrypt('xchacha20')
+endfunc
+
+func Uncrypt_stable(method, crypted_text, key, uncrypted_text)
+ split Xtest.txt
+ set bin noeol key= fenc=latin1
+ exe "set cryptmethod=" . a:method
+ call setline(1, a:crypted_text)
+ w!
+ bwipe!
+ set nobin
+ call feedkeys(":split Xtest.txt\<CR>" . a:key . "\<CR>", 'xt')
+ call assert_equal(a:uncrypted_text, getline(1, len(a:uncrypted_text)))
+ bwipe!
+ call delete('Xtest.txt')
+ set key=
+endfunc
+
+func Uncrypt_stable_xxd(method, hex, key, uncrypted_text)
+ if empty(s:xxd_cmd)
+ throw 'Skipped: xxd program missing'
+ endif
+ " use xxd to write the binary content
+ call system(s:xxd_cmd .. ' -r >Xtest.txt', a:hex)
+ call feedkeys(":split Xtest.txt\<CR>" . a:key . "\<CR>", 'xt')
+ call assert_equal(a:uncrypted_text, getline(1, len(a:uncrypted_text)))
+ bwipe!
+ call delete('Xtest.txt')
+ set key=
+endfunc
+
+func Test_uncrypt_zip()
+ call Uncrypt_stable('zip', "VimCrypt~01!\u0006\u001clV'\u00de}Mg\u00a0\u00ea\u00a3V\u00a9\u00e7\u0007E#3\u008e2U\u00e9\u0097", "foofoo", ["1234567890", "aábbccddeëff"])
+endfunc
+
+func Test_uncrypt_blowfish()
+ call Uncrypt_stable('blowfish', "VimCrypt~02!k)\u00be\u0017\u0097#\u0016\u00ddS\u009c\u00f5=\u00ba\u00e0\u00c8#\u00a5M\u00b4\u0086J\u00c3A\u00cd\u00a5M\u00b4\u0086!\u0080\u0015\u009b\u00f5\u000f\u00e1\u00d2\u0019\u0082\u0016\u0098\u00f7\u000d\u00da", "barbar", ["asdfasdfasdf", "0001112223333"])
+endfunc
+
+func Test_uncrypt_blowfish2a()
+ call Uncrypt_stable('blowfish', "VimCrypt~03!\u001e\u00d1N\u00e3;\u00d3\u00c0\u00a0^C)\u0004\u00f7\u007f.\u00b6\u00abF\u000eS\u0019\u00e0\u008b6\u00d2[T\u00cb\u00a7\u0085\u00d8\u00be9\u000b\u00812\u000bQ\u00b3\u00cc@\u0097\u000f\u00df\u009a\u00adIv\u00aa.\u00d8\u00c9\u00ee\u009e`\u00bd$\u00af%\u00d0", "barburp", ["abcdefghijklmnopqrstuvwxyz", "!@#$%^&*()_+=-`~"])
+endfunc
+
+func Test_uncrypt_blowfish2()
+ call Uncrypt_stable('blowfish2', "VimCrypt~03!\u001e\u00d1N\u00e3;\u00d3\u00c0\u00a0^C)\u0004\u00f7\u007f.\u00b6\u00abF\u000eS\u0019\u00e0\u008b6\u00d2[T\u00cb\u00a7\u0085\u00d8\u00be9\u000b\u00812\u000bQ\u00b3\u00cc@\u0097\u000f\u00df\u009a\u00adIv\u00aa.\u00d8\u00c9\u00ee\u009e`\u00bd$\u00af%\u00d0", "barburp", ["abcdefghijklmnopqrstuvwxyz", "!@#$%^&*()_+=-`~"])
+endfunc
+
+func Test_uncrypt_xchacha20()
+ CheckFeature sodium
+ let hex = ['00000000: 5669 6d43 7279 7074 7e30 3421 6b7d e607 vimCrypt~04!k}..',
+ \ '00000010: 4ea4 e99f 923e f67f 7b59 a80d 3bca 2f06 N....>..{Y..;./.',
+ \ '00000020: fa11 b951 8d09 0dc9 470f e7cf 8b90 4310 ...Q....G.....C.',
+ \ '00000030: 653b b83b e493 378b 0390 0e38 f912 626b e;.;..7....8..bk',
+ \ '00000040: a02e 4697 0254 2625 2d8e 3a0b 784b e89c ..F..T&%-.:.xK..',
+ \ '00000050: 0c67 a975 3c17 9319 8ffd 1463 7783 a1f3 .g.u<......cw...',
+ \ '00000060: d917 dcb3 8b3e ecd7 c7d4 086b 6059 7ead .....>.....k`Y~.',
+ \ '00000070: 9b07 f96b 5c1b 4d08 cd91 f208 5221 7484 ...k\.M.....R!t.',
+ \ '00000080: 72be 0136 84a1 d3 r..6...']
+ " the file should be in latin1 encoding, this makes sure that readfile()
+ " retries several times converting the multi-byte characters
+ call Uncrypt_stable_xxd('xchacha20', hex, "sodium_crypt", ["abcdefghijklmnopqrstuvwxyzäöü", "ZZZ_äüöÄÜÖ_!@#$%^&*()_+=-`~"])
+endfunc
+
+func Test_uncrypt_xchacha20_invalid()
+ CheckFeature sodium
+
+ " load an invalid encrypted file and verify it can be decrypted with an
+ " error message
+ try
+ call feedkeys(":split samples/crypt_sodium_invalid.txt\<CR>sodium\<CR>", 'xt')
+ call assert_false(1, 'should not happen')
+ catch
+ call assert_exception('pre-mature')
+ endtry
+ call assert_match("Note: Encryption of swapfile not supported, disabling swap file", execute(':5messages'))
+
+ call assert_equal(0, &swapfile)
+ call assert_equal("xchacha20", &cryptmethod)
+ call assert_equal('311111111111111111111111', getline('$'))
+ bw!
+endfunc
+
+func Test_uncrypt_xchacha20_2()
+ CheckFeature sodium
+
+ sp Xcrypt_sodium.txt
+ " Create a larger file, so that Vim will write in several blocks
+ call setline(1, range(1,4000))
+ call assert_equal(1, &swapfile)
+ set cryptmethod=xchacha20
+ call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
+ " swapfile disabled
+ call assert_equal(0, &swapfile)
+ call assert_match("Note: Encryption of swapfile not supported, disabling swap file", execute(':messages'))
+ w!
+ " encrypted using xchacha20
+ call assert_match("\[xchacha20\]", execute(':messages'))
+ bw!
+ call feedkeys(":sp Xcrypt_sodium.txt\<CR>sodium\<CR>", 'xt')
+ " successfully decrypted
+ call assert_equal(range(1, 4000)->map( {_, v -> string(v)}), getline(1,'$'))
+ set key=
+ w! ++ff=unix
+ " encryption removed (on MS-Windows the .* matches [unix])
+ call assert_match('"Xcrypt_sodium.txt".*4000L, 18893B written', execute(':message'))
+ bw!
+ call delete('Xcrypt_sodium.txt')
+ set cryptmethod&vim
+endfunc
+
+func Test_uncrypt_xchacha20_3_persistent_undo()
+ CheckFeature sodium
+ CheckFeature persistent_undo
+
+ sp Xcrypt_sodium_undo.txt
+ set cryptmethod=xchacha20 undofile
+ call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
+ call assert_equal(1, &undofile)
+ let ufile=undofile(@%)
+ call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
+ call cursor(1, 1)
+
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ w!
+ call assert_equal(0, &undofile)
+ bw!
+ call feedkeys(":sp Xcrypt_sodium_undo.txt\<CR>sodium\<CR>", 'xt')
+ " should fail
+ norm! u
+ call assert_match('Already at oldest change', execute(':1mess'))
+ call assert_fails('verbose rundo ' .. fnameescape(ufile), 'E822')
+ bw!
+ set undolevels& cryptmethod& undofile&
+ call delete('Xcrypt_sodium_undo.txt')
+endfunc
+
+func Test_encrypt_xchacha20_missing()
+ if has("sodium")
+ return
+ endif
+ sp Xcrypt_sodium_undo.txt
+ call assert_fails(':set cryptmethod=xchacha20', 'E474')
+ bw!
+ set cm&
+endfunc
+
+func Test_uncrypt_unknown_method()
+ split Xuncrypt_unknown.txt
+ set bin noeol key= fenc=latin1
+ call setline(1, "VimCrypt~93!\u001e\u00d1")
+ w!
+ bwipe!
+ set nobin
+ call assert_fails(":split Xuncrypt_unknown.txt", 'E821:')
+
+ bwipe!
+ call delete('Xuncrypt_unknown.txt')
+ set key=
+endfunc
+
+func Test_crypt_key_mismatch()
+ set cryptmethod=blowfish
+
+ split Xtest.txt
+ call setline(1, 'nothing')
+ call feedkeys(":X\<CR>foobar\<CR>nothing\<CR>", 'xt')
+ call assert_match("Keys don't match!", execute(':2messages'))
+ call assert_equal('', &key)
+ call feedkeys("\<CR>\<CR>", 'xt')
+
+ set cryptmethod&
+ bwipe!
+endfunc
+
+func Test_crypt_set_key_changes_buffer()
+
+ new Xtest1.txt
+ call setline(1, 'nothing')
+ set cryptmethod=blowfish2
+ call feedkeys(":X\<CR>foobar\<CR>foobar\<CR>", 'xt')
+ call assert_fails(":q", "E37:")
+ w
+ set key=anotherkey
+ call assert_fails(":bw")
+ w
+ call feedkeys(":X\<CR>foobar\<CR>foobar\<CR>", 'xt')
+ call assert_fails(":bw")
+ w
+ let winnr = winnr()
+ wincmd p
+ call setwinvar(winnr, '&key', 'yetanotherkey')
+ wincmd p
+ call assert_fails(":bw")
+ w
+
+ set cryptmethod&
+ set key=
+ bwipe!
+ call delete('Xtest1.txt')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cscope.vim b/src/testdir/test_cscope.vim
new file mode 100644
index 0000000..b2ecdcc
--- /dev/null
+++ b/src/testdir/test_cscope.vim
@@ -0,0 +1,337 @@
+" Test for cscope commands.
+
+source check.vim
+CheckFeature cscope
+CheckFeature quickfix
+CheckExecutable cscope
+
+func CscopeSetupOrClean(setup)
+ if a:setup
+ noa sp ../memfile_test.c
+ saveas! Xmemfile_test.c
+ call system('cscope -bk -fXcscope.out Xmemfile_test.c')
+ call system('cscope -bk -fXcscope2.out Xmemfile_test.c')
+ cscope add Xcscope.out
+ set cscopequickfix=s-,g-,d-,c-,t-,e-,f-,i-,a-
+ else
+ cscope kill -1
+ for file in ['Xcscope.out', 'Xcscope2.out', 'Xmemfile_test.c']
+ call delete(file)
+ endfo
+ endif
+endfunc
+
+func Test_cscopeWithCscopeConnections()
+ call CscopeSetupOrClean(1)
+ " Test: E568: duplicate cscope database not added
+ try
+ set nocscopeverbose
+ cscope add Xcscope.out
+ set cscopeverbose
+ catch
+ call assert_report('exception thrown')
+ endtry
+ call assert_fails('cscope add', 'E560:')
+ call assert_fails('cscope add Xcscope.out', 'E568:')
+ call assert_fails('cscope add doesnotexist.out', 'E563:')
+ if has('unix')
+ call assert_fails('cscope add /dev/null', 'E564:')
+ endif
+
+ " Test: Find this C-Symbol
+ for cmd in ['cs find s main', 'cs find 0 main']
+ let a = execute(cmd)
+ " Test where it moves the cursor
+ call assert_equal('main(void)', getline('.'))
+ " Test the output of the :cs command
+ call assert_match('\n(1 of 1): <<main>> main(void )', a)
+ endfor
+
+ " Test: Find this definition
+ for cmd in ['cs find g test_mf_hash',
+ \ 'cs find 1 test_mf_hash',
+ \ 'cs find 1 test_mf_hash'] " leading space ignored.
+ exe cmd
+ call assert_equal(['', '/*', ' * Test mf_hash_*() functions.', ' */', ' static void', 'test_mf_hash(void)', '{'], getline(line('.')-5, line('.')+1))
+ endfor
+
+ " Test: Find functions called by this function
+ for cmd in ['cs find d test_mf_hash', 'cs find 2 test_mf_hash']
+ let a = execute(cmd)
+ call assert_match('\n(1 of 42): <<mf_hash_init>> mf_hash_init(&ht);', a)
+ call assert_equal(' mf_hash_init(&ht);', getline('.'))
+ endfor
+
+ " Test: Find functions calling this function
+ for cmd in ['cs find c test_mf_hash', 'cs find 3 test_mf_hash']
+ let a = execute(cmd)
+ call assert_match('\n(1 of 1): <<main>> test_mf_hash();', a)
+ call assert_equal(' test_mf_hash();', getline('.'))
+ endfor
+
+ " Test: Find this text string
+ for cmd in ['cs find t Bram', 'cs find 4 Bram']
+ let a = execute(cmd)
+ call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
+ call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
+ endfor
+
+ " Test: Find this egrep pattern
+ " test all matches returned by cscope
+ for cmd in ['cs find e ^\#includ.', 'cs find 6 ^\#includ.']
+ let a = execute(cmd)
+ call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a)
+ call assert_equal('#include <assert.h>', getline('.'))
+ cnext
+ call assert_equal('#include "main.c"', getline('.'))
+ cnext
+ call assert_equal('#include "memfile.c"', getline('.'))
+ call assert_fails('cnext', 'E553:')
+ endfor
+
+ " Test: Find the same egrep pattern using lcscope this time.
+ let a = execute('lcs find e ^\#includ.')
+ call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a)
+ call assert_equal('#include <assert.h>', getline('.'))
+ lnext
+ call assert_equal('#include "main.c"', getline('.'))
+ lnext
+ call assert_equal('#include "memfile.c"', getline('.'))
+ call assert_fails('lnext', 'E553:')
+
+ " Test: Find this file
+ for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c']
+ enew
+ let a = execute(cmd)
+ call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+B')
+ call assert_equal('Xmemfile_test.c', @%)
+ endfor
+
+ " Test: Find files #including this file
+ for cmd in ['cs find i assert.h', 'cs find 8 assert.h']
+ enew
+ let a = execute(cmd)
+ let alines = split(a, '\n', 1)
+ call assert_equal('', alines[0])
+ call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+B')
+ call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2])
+ call assert_equal('#include <assert.h>', getline('.'))
+ endfor
+
+ " Test: Invalid find command
+ call assert_fails('cs find', 'E560:')
+ call assert_fails('cs find x', 'E560:')
+
+ " Test: Find places where this symbol is assigned a value
+ " this needs a cscope >= 15.8
+ " unfortunately, Travis has cscope version 15.7
+ let cscope_version = systemlist('cscope --version')[0]
+ let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?'))
+ if cs_version >= 15.8
+ for cmd in ['cs find a item', 'cs find 9 item']
+ let a = execute(cmd)
+ call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = LALLOC_CLEAR_ONE(mf_hashitem_T);'], split(a, '\n', 1))
+ call assert_equal(' item = LALLOC_CLEAR_ONE(mf_hashitem_T);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ endfor
+ endif
+
+ " Test: leading whitespace is not removed for cscope find text
+ let a = execute('cscope find t test_mf_hash')
+ call assert_equal(['', '(1 of 1): <<<unknown>>> test_mf_hash();'], split(a, '\n', 1))
+ call assert_equal(' test_mf_hash();', getline('.'))
+
+ " Test: test with scscope
+ let a = execute('scs find t Bram')
+ call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
+ call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
+
+ " Test: cscope help
+ for cmd in ['cs', 'cs help', 'cs xxx']
+ let a = execute(cmd)
+ call assert_match('^cscope commands:\n', a)
+ call assert_match('\nadd :', a)
+ call assert_match('\nfind :', a)
+ call assert_match('\nhelp : Show this message', a)
+ call assert_match('\nkill : Kill a connection', a)
+ call assert_match('\nreset: Reinit all connections', a)
+ call assert_match('\nshow : Show connections', a)
+ endfor
+ let a = execute('scscope help')
+ call assert_match('This cscope command does not support splitting the window\.', a)
+
+ " Test: reset connections
+ let a = execute('cscope reset')
+ call assert_match('\nAdded cscope database.*Xcscope.out (#0)', a)
+ call assert_match('\nAll cscope databases reset', a)
+
+ " Test: cscope show
+ let a = execute('cscope show')
+ call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
+
+ " Test: cstag and 'csto' option
+ set csto=0
+ let a = execute('cstag TEST_COUNT')
+ call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a)
+ call assert_equal('#define TEST_COUNT 50000', getline('.'))
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
+ set csto=1
+ let a = execute('cstag index_to_key')
+ call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a)
+ call assert_equal('#define index_to_key(i) ((i) ^ 15167)', getline('.'))
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
+ call assert_fails('cstag', 'E562:')
+ let save_tags = &tags
+ set tags=
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
+ let a = execute('cstag index_to_key')
+ call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a)
+ let &tags = save_tags
+
+ " Test: 'cst' option
+ set nocst
+ call assert_fails('tag TEST_COUNT', 'E433:')
+ set cst
+ let a = execute('tag TEST_COUNT')
+ call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a)
+ call assert_equal('#define TEST_COUNT 50000', getline('.'))
+ let a = execute('tags')
+ call assert_match('1 1 TEST_COUNT\s\+\d\+\s\+#define index_to_key', a)
+
+ " Test: 'cscoperelative'
+ call mkdir('Xcscoperelative')
+ cd Xcscoperelative
+ let a = execute('cs find g test_mf_hash')
+ call assert_notequal('test_mf_hash(void)', getline('.'))
+ set cscoperelative
+ let a = execute('cs find g test_mf_hash')
+ call assert_equal('test_mf_hash(void)', getline('.'))
+ set nocscoperelative
+ cd ..
+ call delete('Xcscoperelative', 'd')
+
+ " Test: E259: no match found
+ call assert_fails('cscope find g DOES_NOT_EXIST', 'E259:')
+
+ " Test: this should trigger call to cs_print_tags()
+ " Unclear how to check result though, we just exercise the code.
+ set cst cscopequickfix=s0
+ call feedkeys(":cs find s main\<CR>", 't')
+
+ " Test: cscope kill
+ call assert_fails('cscope kill', 'E560:')
+ call assert_fails('cscope kill 2', 'E261:')
+ call assert_fails('cscope kill xxx', 'E261:')
+
+ let a = execute('cscope kill 0')
+ call assert_match('cscope connection 0 closed', a)
+
+ cscope add Xcscope.out
+ let a = execute('cscope kill Xcscope.out')
+ call assert_match('cscope connection Xcscope.out closed', a)
+
+ cscope add Xcscope.out .
+ let a = execute('cscope kill -1')
+ call assert_match('cscope connection .*Xcscope.out closed', a)
+ let a = execute('cscope kill -1')
+ call assert_equal('', a)
+
+ " Test: 'csprg' option invalid command
+ call assert_equal('cscope', &csprg)
+ set csprg=doesnotexist
+ call assert_fails('cscope add Xcscope2.out', 'E609:')
+ set csprg=cscope
+
+ " Test: multiple cscope connections
+ cscope add Xcscope.out
+ cscope add Xcscope2.out . -C
+ let a = execute('cscope show')
+ call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
+ call assert_match('\n 1 \d\+.*Xcscope2.out\s*\.', a)
+
+ " Test: test Ex command line completion
+ call feedkeys(":cs \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cs add find help kill reset show', @:)
+
+ call feedkeys(":scs \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"scs find', @:)
+
+ call feedkeys(":cs find \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cs find a c d e f g i s t', @:)
+
+ call feedkeys(":cs kill \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cs kill -1 0 1', @:)
+
+ call feedkeys(":cs add Xcscope\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cs add Xcscope.out Xcscope2.out', @:)
+
+ " Test: cscope_connection()
+ call assert_equal(cscope_connection(), 1)
+ call assert_equal(cscope_connection(0, 'out'), 1)
+ call assert_equal(cscope_connection(0, 'xxx'), 1)
+
+ call assert_equal(cscope_connection(1, 'out'), 1)
+ call assert_equal(cscope_connection(1, 'xxx'), 0)
+
+ call assert_equal(cscope_connection(2, 'out'), 0)
+ call assert_equal(cscope_connection(2, getcwd() .. '/Xcscope.out', 1), 1)
+
+ call assert_equal(cscope_connection(3, 'xxx', '..'), 0)
+ call assert_equal(cscope_connection(3, 'out', 'xxx'), 0)
+ call assert_equal(cscope_connection(3, 'out', '.'), 1)
+
+ call assert_equal(cscope_connection(4, 'out', '.'), 0)
+
+ call assert_equal(cscope_connection(5, 'out'), 0)
+ call assert_equal(cscope_connection(-1, 'out'), 0)
+
+ call CscopeSetupOrClean(0)
+endfunc
+
+" Test ":cs add {dir}" (add the {dir}/cscope.out database)
+func Test_cscope_add_dir()
+ call mkdir('Xcscopedir', 'pD')
+
+ " Cscope doesn't handle symlinks, so this needs to be resolved in case a
+ " shadow directory is being used.
+ let memfile = resolve('../memfile_test.c')
+ call system('cscope -bk -fXcscopedir/cscope.out ' . memfile)
+
+ cs add Xcscopedir
+ let a = execute('cscope show')
+ let lines = split(a, "\n", 1)
+ call assert_equal(3, len(lines))
+ call assert_equal(' # pid database name prepend path', lines[0])
+ call assert_equal('', lines[1])
+ call assert_match('^ 0 \d\+.*Xcscopedir/cscope.out\s\+<none>$', lines[2])
+
+ cs kill -1
+ call delete('Xcscopedir/cscope.out')
+ call assert_fails('cs add Xcscopedir', 'E563:')
+endfunc
+
+func Test_cscopequickfix()
+ set cscopequickfix=s-,g-,d+,c-,t+,e-,f0,i-,a-
+ call assert_equal('s-,g-,d+,c-,t+,e-,f0,i-,a-', &cscopequickfix)
+
+ call assert_fails('set cscopequickfix=x-', 'E474:')
+ call assert_fails('set cscopequickfix=s', 'E474:')
+ call assert_fails('set cscopequickfix=s7', 'E474:')
+ call assert_fails('set cscopequickfix=s-a', 'E474:')
+endfunc
+
+func Test_withoutCscopeConnection()
+ call assert_equal(cscope_connection(), 0)
+
+ call assert_fails('cscope find s main', 'E567:')
+ let a = execute('cscope show')
+ call assert_match('no cscope connections', a)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cursor_func.vim b/src/testdir/test_cursor_func.vim
new file mode 100644
index 0000000..d74255e
--- /dev/null
+++ b/src/testdir/test_cursor_func.vim
@@ -0,0 +1,493 @@
+" Tests for cursor() and other functions that get/set the cursor position
+
+source check.vim
+
+func Test_wrong_arguments()
+ call assert_fails('call cursor(1. 3)', 'E474:')
+ call assert_fails('call cursor(test_null_list())', 'E474:')
+endfunc
+
+func Test_move_cursor()
+ new
+ call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
+
+ call cursor([1, 1, 0, 1])
+ call assert_equal([1, 1, 0, 1], getcurpos()[1:])
+ call cursor([4, 3, 0, 3])
+ call assert_equal([4, 3, 0, 3], getcurpos()[1:])
+
+ call cursor(2, 2)
+ call assert_equal([2, 2, 0, 2], getcurpos()[1:])
+ " line number zero keeps the line number
+ call cursor(0, 1)
+ call assert_equal([2, 1, 0, 1], getcurpos()[1:])
+ " col number zero keeps the column
+ call cursor(3, 0)
+ call assert_equal([3, 1, 0, 1], getcurpos()[1:])
+ " below last line goes to last line
+ eval [9, 1]->cursor()
+ call assert_equal([4, 1, 0, 1], getcurpos()[1:])
+ " pass string arguments
+ call cursor('3', '3')
+ call assert_equal([3, 3, 0, 3], getcurpos()[1:])
+
+ call setline(1, ["\<TAB>"])
+ call cursor(1, 1, 1)
+ call assert_equal([1, 1, 1], getcurpos()[1:3])
+
+ call assert_fails('call cursor(-1, -1)', 'E475:')
+
+ quit!
+endfunc
+
+func Test_curswant_maxcol()
+ new
+ call setline(1, 'foo')
+
+ " Test that after "$" command curswant is set to the same value as v:maxcol.
+ normal! 1G$
+ call assert_equal(v:maxcol, getcurpos()[4])
+ call assert_equal(v:maxcol, winsaveview().curswant)
+
+ quit!
+endfunc
+
+" Very short version of what matchparen does.
+function s:Highlight_Matching_Pair()
+ let save_cursor = getcurpos()
+ eval save_cursor->setpos('.')
+endfunc
+
+func Test_curswant_with_autocommand()
+ new
+ call setline(1, ['func()', '{', '}', '----'])
+ autocmd! CursorMovedI * call s:Highlight_Matching_Pair()
+ call test_override("char_avail", 1)
+ exe "normal! 3Ga\<Down>X\<Esc>"
+ call test_override("char_avail", 0)
+ call assert_equal('-X---', getline(4))
+ autocmd! CursorMovedI *
+ quit!
+endfunc
+
+" Tests for behavior of curswant with cursorcolumn/line
+func Test_curswant_with_cursorcolumn()
+ new
+ call setline(1, ['01234567', ''])
+ exe "normal! ggf6j"
+ call assert_equal(6, winsaveview().curswant)
+ set cursorcolumn
+ call assert_equal(6, winsaveview().curswant)
+ quit!
+endfunc
+
+func Test_curswant_with_cursorline()
+ new
+ call setline(1, ['01234567', ''])
+ exe "normal! ggf6j"
+ call assert_equal(6, winsaveview().curswant)
+ set cursorline
+ call assert_equal(6, winsaveview().curswant)
+ quit!
+endfunc
+
+func Test_screenpos()
+ rightbelow new
+ rightbelow 20vsplit
+ call setline(1, ["\tsome text", "long wrapping line here", "next line"])
+ redraw
+ let winid = win_getid()
+ let [winrow, wincol] = win_screenpos(winid)
+ call assert_equal({'row': winrow,
+ \ 'col': wincol + 0,
+ \ 'curscol': wincol + 7,
+ \ 'endcol': wincol + 7}, winid->screenpos(1, 1))
+ call assert_equal({'row': winrow,
+ \ 'col': wincol + 13,
+ \ 'curscol': wincol + 13,
+ \ 'endcol': wincol + 13}, winid->screenpos(1, 7))
+ call assert_equal({'row': winrow + 2,
+ \ 'col': wincol + 1,
+ \ 'curscol': wincol + 1,
+ \ 'endcol': wincol + 1}, screenpos(winid, 2, 22))
+ setlocal number
+ call assert_equal({'row': winrow + 3,
+ \ 'col': wincol + 9,
+ \ 'curscol': wincol + 9,
+ \ 'endcol': wincol + 9}, screenpos(winid, 2, 22))
+
+ let wininfo = getwininfo(winid)[0]
+ call setline(3, ['x']->repeat(wininfo.height))
+ call setline(line('$') + 1, 'x'->repeat(wininfo.width * 3))
+ setlocal nonumber display=lastline so=0
+ exe "normal G\<C-Y>\<C-Y>"
+ redraw
+ call assert_equal({'row': winrow + wininfo.height - 1,
+ \ 'col': wincol + 7,
+ \ 'curscol': wincol + 7,
+ \ 'endcol': wincol + 7}, winid->screenpos(line('$'), 8))
+ call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0},
+ \ winid->screenpos(line('$'), 22))
+
+ close
+ call assert_equal({}, screenpos(999, 1, 1))
+
+ bwipe!
+ set display&
+
+ call assert_equal(#{col: 1, row: 1, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1))
+ nmenu WinBar.TEST :
+ call assert_equal(#{col: 1, row: 2, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1))
+ nunmenu WinBar.TEST
+endfunc
+
+func Test_screenpos_fold()
+ CheckFeature folding
+
+ enew!
+ call setline(1, range(10))
+ 3,5fold
+ redraw
+ call assert_equal(2, screenpos(1, 2, 1).row)
+ call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 3, 1))
+ call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 4, 1))
+ call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 5, 1))
+ setlocal number
+ call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 3, 1))
+ call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 4, 1))
+ call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 5, 1))
+ call assert_equal(4, screenpos(1, 6, 1).row)
+ bwipe!
+endfunc
+
+func Test_screenpos_diff()
+ CheckFeature diff
+
+ enew!
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
+ vnew
+ call setline(1, ['a', 'b', 'c', 'g', 'h', 'i'])
+ windo diffthis
+ wincmd w
+ call assert_equal(#{col: 3, row: 7, endcol: 3, curscol: 3}, screenpos(0, 4, 1))
+
+ windo diffoff
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_screenpos_number()
+ rightbelow new
+ rightbelow 73vsplit
+ call setline (1, repeat('x', 66))
+ setlocal number
+ redraw
+ let winid = win_getid()
+ let [winrow, wincol] = win_screenpos(winid)
+ let pos = screenpos(winid, 1, 66)
+ call assert_equal(winrow, pos.row)
+ call assert_equal(wincol + 66 + 3, pos.col)
+
+ call assert_fails('echo screenpos(0, 2, 1)', 'E966:')
+
+ close
+ bwipe!
+endfunc
+
+" Save the visual start character position
+func SaveVisualStartCharPos()
+ call add(g:VisualStartPos, getcharpos('v'))
+ return ''
+endfunc
+
+" Save the current cursor character position in insert mode
+func SaveInsertCurrentCharPos()
+ call add(g:InsertCurrentPos, getcharpos('.'))
+ return ''
+endfunc
+
+" Test for the getcharpos() function
+func Test_getcharpos()
+ call assert_fails('call getcharpos({})', 'E731:')
+ call assert_equal([0, 0, 0, 0], getcharpos(0))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678', ' │ x'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal([0, 1, 1, 0], getcharpos('.'))
+ call assert_equal([0, 5, 1, 0], getcharpos('$'))
+ normal 2G6l
+ call assert_equal([0, 2, 7, 0], getcharpos('.'))
+ normal 3G$
+ call assert_equal([0, 3, 1, 0], getcharpos('.'))
+ normal 4G$
+ call assert_equal([0, 4, 9, 0], getcharpos('.'))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal([0, 2, 8, 0], getcharpos("'m"))
+ delmarks m
+ call assert_equal([0, 0, 0, 0], getcharpos("'m"))
+
+ " Check mark does not move
+ normal 5Gfxma
+ call assert_equal([0, 5, 5, 0], getcharpos("'a"))
+ call assert_equal([0, 5, 5, 0], getcharpos("'a"))
+ call assert_equal([0, 5, 5, 0], getcharpos("'a"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharPos()
+ let g:VisualStartPos = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([[0, 2, 7, 0], [0, 2, 10, 0], [0, 2, 5, 0]], g:VisualStartPos)
+ call assert_equal([0, 2, 9, 0], getcharpos('v'))
+ let g:VisualStartPos = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 3, 1, 0], [0, 3, 2, 0]], g:VisualStartPos)
+ let g:VisualStartPos = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos)
+ vunmap <F3>
+
+ " Test for getting the position in insert mode with the cursor after the
+ " last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharPos()
+ let g:InsertCurrentPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 2, 10, 0], [0, 3, 2, 0], [0, 4, 10, 0],
+ \ [0, 2, 7, 0]], g:InsertCurrentPos)
+ iunmap <F3>
+
+ %bw!
+endfunc
+
+" Test for the setcharpos() function
+func Test_setcharpos()
+ call assert_equal(-1, setcharpos('.', test_null_list()))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ call setcharpos('.', [0, 1, 1, 0])
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 4, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 1, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 20, 0])
+ call assert_equal([4, 9], [line('.'), col('.')])
+
+ " Test for mark
+ delmarks m
+ call setcharpos("'m", [0, 2, 9, 0])
+ normal `m
+ call assert_equal([2, 11], [line('.'), col('.')])
+ " unload the buffer and try to set the mark
+ let bnr = bufnr()
+ enew!
+ call assert_equal(-1, setcharpos("'m", [bnr, 2, 2, 0]))
+
+ %bw!
+ call assert_equal(-1, setcharpos('.', [10, 3, 1, 0]))
+endfunc
+
+func SaveVisualStartCharCol()
+ call add(g:VisualStartCol, charcol('v'))
+ return ''
+endfunc
+
+func SaveInsertCurrentCharCol()
+ call add(g:InsertCurrentCol, charcol('.'))
+ return ''
+endfunc
+
+" Test for the charcol() function
+func Test_charcol()
+ call assert_fails('call charcol({})', 'E1222:')
+ call assert_fails('call charcol(".", [])', 'E1210:')
+ call assert_fails('call charcol(0)', 'E1222:')
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal(1, charcol('.'))
+ call assert_equal(1, charcol('$'))
+ normal 2G6l
+ call assert_equal(7, charcol('.'))
+ call assert_equal(10, charcol('$'))
+ normal 3G$
+ call assert_equal(1, charcol('.'))
+ call assert_equal(2, charcol('$'))
+ normal 4G$
+ call assert_equal(9, charcol('.'))
+ call assert_equal(10, charcol('$'))
+
+ " Test for [lnum, '$']
+ call assert_equal(1, charcol([1, '$']))
+ call assert_equal(10, charcol([2, '$']))
+ call assert_equal(2, charcol([3, '$']))
+ call assert_equal(0, charcol([5, '$']))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal(8, charcol("'m"))
+ delmarks m
+ call assert_equal(0, charcol("'m"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharCol()
+ let g:VisualStartCol = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([7, 10, 5], g:VisualStartCol)
+ call assert_equal(9, charcol('v'))
+ let g:VisualStartCol = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([1, 2], g:VisualStartCol)
+ let g:VisualStartCol = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([1, 1], g:VisualStartCol)
+ vunmap <F3>
+
+ " Test for getting the column number in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCurrentCharCol()
+ let g:InsertCurrentCol = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol)
+ iunmap <F3>
+
+ " Test for getting the column number in another window.
+ let winid = win_getid()
+ new
+ call win_execute(winid, 'normal 1G')
+ call assert_equal(1, charcol('.', winid))
+ call assert_equal(1, charcol('$', winid))
+ call win_execute(winid, 'normal 2G6l')
+ call assert_equal(7, charcol('.', winid))
+ call assert_equal(10, charcol('$', winid))
+
+ " calling from another tab page also works
+ tabnew
+ call assert_equal(7, charcol('.', winid))
+ call assert_equal(10, charcol('$', winid))
+ tabclose
+
+ " unknown window ID
+ call assert_equal(0, charcol('.', 10001))
+
+ %bw!
+endfunc
+
+func SaveInsertCursorCharPos()
+ call add(g:InsertCursorPos, getcursorcharpos('.'))
+ return ''
+endfunc
+
+" Test for getcursorcharpos()
+func Test_getcursorcharpos()
+ call assert_equal(getcursorcharpos(), getcursorcharpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999))
+
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal 1G9l
+ call assert_equal([0, 1, 1, 0, 1], getcursorcharpos())
+ normal 2G9l
+ call assert_equal([0, 2, 9, 0, 14], getcursorcharpos())
+ normal 3G9l
+ call assert_equal([0, 3, 1, 0, 1], getcursorcharpos())
+ normal 4G9l
+ call assert_equal([0, 4, 9, 0, 9], getcursorcharpos())
+
+ " Test for getting the cursor position in insert mode with the cursor after
+ " the last character in a line
+ inoremap <expr> <F3> SaveInsertCursorCharPos()
+ let g:InsertCursorPos = []
+ exe "normal 1GA\<F3>"
+ exe "normal 2GA\<F3>"
+ exe "normal 3GA\<F3>"
+ exe "normal 4GA\<F3>"
+ exe "normal 2G6li\<F3>"
+ call assert_equal([[0, 1, 1, 0, 1], [0, 2, 10, 0, 15], [0, 3, 2, 0, 2],
+ \ [0, 4, 10, 0, 10], [0, 2, 7, 0, 12]], g:InsertCursorPos)
+ iunmap <F3>
+
+ let winid = win_getid()
+ normal 2G5l
+ wincmd w
+ call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid))
+ %bw!
+endfunc
+
+" Test for setcursorcharpos()
+func Test_setcursorcharpos()
+ call assert_fails('call setcursorcharpos(test_null_list())', 'E474:')
+ call assert_fails('call setcursorcharpos([1])', 'E474:')
+ call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:')
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal G
+ call setcursorcharpos([1, 1])
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ call setcursorcharpos([2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcursorcharpos([0, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcursorcharpos(0, 7, 0)
+ call assert_equal([2, 9], [line('.'), col('.')])
+
+ call setcursorcharpos(3, 4)
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([3, 1])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 0, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 20])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos([100, 100, 100, 100])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos('$', 1)
+ call assert_equal([4, 1], [line('.'), col('.')])
+
+ %bw!
+endfunc
+
+" Test for virtcol2col()
+func Test_virtcol2col()
+ new
+ call setline(1, ["a\tb\tc"])
+ call assert_equal(1, virtcol2col(0, 1, 1))
+ call assert_equal(2, virtcol2col(0, 1, 2))
+ call assert_equal(2, virtcol2col(0, 1, 8))
+ call assert_equal(3, virtcol2col(0, 1, 9))
+ call assert_equal(4, virtcol2col(0, 1, 10))
+ call assert_equal(4, virtcol2col(0, 1, 16))
+ call assert_equal(5, virtcol2col(0, 1, 17))
+ call assert_equal(-1, virtcol2col(10, 1, 1))
+ call assert_equal(-1, virtcol2col(0, 10, 1))
+ call assert_equal(-1, virtcol2col(0, -1, 1))
+ call assert_equal(-1, virtcol2col(0, 1, -1))
+ call assert_equal(5, virtcol2col(0, 1, 20))
+ call assert_fails('echo virtcol2col("0", 1, 20)', 'E1210:')
+ call assert_fails('echo virtcol2col(0, "1", 20)', 'E1210:')
+ call assert_fails('echo virtcol2col(0, 1, "1")', 'E1210:')
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_cursorline.vim b/src/testdir/test_cursorline.vim
new file mode 100644
index 0000000..f4c58de
--- /dev/null
+++ b/src/testdir/test_cursorline.vim
@@ -0,0 +1,327 @@
+" Test for cursorline and cursorlineopt
+
+source check.vim
+source screendump.vim
+
+func s:screen_attr(lnum) abort
+ return map(range(1, 8), 'screenattr(a:lnum, v:val)')
+endfunc
+
+func s:test_windows(h, w) abort
+ call NewWindow(a:h, a:w)
+endfunc
+
+func s:close_windows() abort
+ call CloseWindow()
+endfunc
+
+func s:new_hi() abort
+ redir => save_hi
+ silent! hi CursorLineNr
+ redir END
+ let save_hi = join(split(substitute(save_hi, '\s*xxx\s*', ' ', ''), "\n"), '')
+ exe 'hi' save_hi 'ctermbg=0 guibg=Black'
+ return save_hi
+endfunc
+
+func Test_cursorline_highlight1()
+ let save_hi = s:new_hi()
+ try
+ call s:test_windows(10, 20)
+ call setline(1, repeat(['aaaa'], 10))
+ redraw
+ let attr01 = s:screen_attr(1)
+ call assert_equal(repeat([attr01[0]], 8), attr01)
+
+ setl number numberwidth=4
+ redraw
+ let attr11 = s:screen_attr(1)
+ call assert_equal(repeat([attr11[0]], 4), attr11[0:3])
+ call assert_equal(repeat([attr11[4]], 4), attr11[4:7])
+ call assert_notequal(attr11[0], attr11[4])
+
+ setl cursorline
+ redraw
+ let attr21 = s:screen_attr(1)
+ let attr22 = s:screen_attr(2)
+ call assert_equal(repeat([attr21[0]], 4), attr21[0:3])
+ call assert_equal(repeat([attr21[4]], 4), attr21[4:7])
+ call assert_equal(attr11, attr22)
+ call assert_notequal(attr22, attr21)
+
+ setl nocursorline relativenumber
+ redraw
+ let attr31 = s:screen_attr(1)
+ call assert_equal(attr22[0:3], attr31[0:3])
+ call assert_equal(attr11[4:7], attr31[4:7])
+
+ call s:close_windows()
+ finally
+ exe 'hi' save_hi
+ endtry
+endfunc
+
+func Test_cursorline_highlight2()
+ CheckOption cursorlineopt
+
+ let save_hi = s:new_hi()
+ try
+ call s:test_windows(10, 20)
+ call setline(1, repeat(['aaaa'], 10))
+ redraw
+ let attr0 = s:screen_attr(1)
+ call assert_equal(repeat([attr0[0]], 8), attr0)
+
+ setl number
+ redraw
+ let attr1 = s:screen_attr(1)
+ call assert_notequal(attr0[0:3], attr1[0:3])
+ call assert_equal(attr0[0:3], attr1[4:7])
+
+ setl cursorline cursorlineopt=both
+ redraw
+ let attr2 = s:screen_attr(1)
+ call assert_notequal(attr1[0:3], attr2[0:3])
+ call assert_notequal(attr1[4:7], attr2[4:7])
+
+ setl cursorlineopt=line
+ redraw
+ let attr3 = s:screen_attr(1)
+ call assert_equal(attr1[0:3], attr3[0:3])
+ call assert_equal(attr2[4:7], attr3[4:7])
+
+ setl cursorlineopt=number
+ redraw
+ let attr4 = s:screen_attr(1)
+ call assert_equal(attr2[0:3], attr4[0:3])
+ call assert_equal(attr1[4:7], attr4[4:7])
+
+ setl nonumber
+ redraw
+ let attr5 = s:screen_attr(1)
+ call assert_equal(attr0, attr5)
+
+ call s:close_windows()
+ finally
+ exe 'hi' save_hi
+ endtry
+endfunc
+
+func Test_cursorline_screenline()
+ CheckScreendump
+ CheckOption cursorlineopt
+
+ let filename='Xcursorline'
+ let lines = []
+
+ let file_content =<< trim END
+ 1 foooooooo ar einsâ€zwei drei vier fünf sechs sieben acht un zehn elf zwöfl dreizehn v ierzehn fünfzehn
+ 2 foooooooo bar eins zwei drei vier fünf sechs sieben
+ 3 foooooooo bar eins zwei drei vier fünf sechs sieben
+ 4 foooooooo bar eins zwei drei vier fünf sechs sieben
+ END
+ let lines1 =<< trim END1
+ set nocp
+ set display=lastline
+ set cursorlineopt=screenline cursorline nu wrap sbr=>
+ hi CursorLineNr ctermfg=blue
+ 25vsp
+ END1
+ let lines2 =<< trim END2
+ call cursor(1,1)
+ END2
+ call extend(lines, lines1)
+ call extend(lines, ["call append(0, ".. string(file_content).. ')'])
+ call extend(lines, lines2)
+ call writefile(lines, filename, 'D')
+ " basic test
+ let buf = RunVimInTerminal('-S '. filename, #{rows: 20})
+ call VerifyScreenDump(buf, 'Test_'. filename. '_1', {})
+ call term_sendkeys(buf, "fagj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_2', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_3', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_4', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_5', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_6', {})
+ " test with set list and cursorlineopt containing number
+ call term_sendkeys(buf, "gg0")
+ call term_sendkeys(buf, ":set list cursorlineopt+=number listchars=space:-\<cr>")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_7', {})
+ call term_sendkeys(buf, "fagj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_8', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_9', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_10', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_11', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_12', {})
+ if exists("+foldcolumn") && exists("+signcolumn") && exists("+breakindent")
+ " test with set foldcolumn signcolumn and breakindent
+ call term_sendkeys(buf, "gg0")
+ call term_sendkeys(buf, ":set breakindent foldcolumn=2 signcolumn=yes\<cr>")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_13', {})
+ call term_sendkeys(buf, "fagj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_14', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_15', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_16', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_17', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_18', {})
+ call term_sendkeys(buf, ":set breakindent& foldcolumn& signcolumn&\<cr>")
+ endif
+ " showbreak should not be highlighted with CursorLine when 'number' is off
+ call term_sendkeys(buf, "gg0")
+ call term_sendkeys(buf, ":set list cursorlineopt=screenline listchars=space:-\<cr>")
+ call term_sendkeys(buf, ":set nonumber\<cr>")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_19', {})
+ call term_sendkeys(buf, "fagj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_20', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_21', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_22', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_23', {})
+ call term_sendkeys(buf, "gj")
+ call VerifyScreenDump(buf, 'Test_'. filename. '_24', {})
+ call term_sendkeys(buf, ":set list& cursorlineopt& listchars&\<cr>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cursorline_redraw()
+ CheckScreendump
+ CheckOption cursorlineopt
+
+ let textlines =<< END
+ When the option is a list of flags, {value} must be
+ exactly as they appear in the option. Remove flags
+ one by one to avoid problems.
+ Also see |:set-args| above.
+
+The {option} arguments to ":set" may be repeated. For example: >
+ :set ai nosi sw=3 ts=3
+If you make an error in one of the arguments, an error message will be given
+and the following arguments will be ignored.
+
+ *:set-verbose*
+When 'verbose' is non-zero, displaying an option value will also tell where it
+was last set. Example: >
+ :verbose set shiftwidth cindent?
+< shiftwidth=4 ~
+ Last set from modeline line 1 ~
+ cindent ~
+ Last set from /usr/local/share/vim/vim60/ftplugin/c.vim line 30 ~
+This is only done when specific option values are requested, not for ":verbose
+set all" or ":verbose set" without an argument.
+When the option was set by hand there is no "Last set" message.
+When the option was set while executing a function, user command or
+END
+ call writefile(textlines, 'Xtextfile', 'D')
+
+ let script =<< trim END
+ set cursorline scrolloff=2
+ normal 12G
+ END
+ call writefile(script, 'Xscript', 'D')
+
+ let buf = RunVimInTerminal('-S Xscript Xtextfile', #{rows: 20, cols: 40})
+ call VerifyScreenDump(buf, 'Test_cursorline_redraw_1', {})
+ call term_sendkeys(buf, "zt")
+ call TermWait(buf)
+ call term_sendkeys(buf, "\<C-U>")
+ call VerifyScreenDump(buf, 'Test_cursorline_redraw_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cursorline_callback()
+ CheckScreendump
+ CheckFeature timers
+
+ let lines =<< trim END
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set cursorline
+ call cursor(4, 1)
+
+ func Func(timer)
+ call cursor(2, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ END
+ call writefile(lines, 'Xcul_timer', 'D')
+
+ let buf = RunVimInTerminal('-S Xcul_timer', #{rows: 8})
+ call TermWait(buf, 310)
+ call VerifyScreenDump(buf, 'Test_cursorline_callback_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cursorline_screenline_update()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, repeat('xyz ', 30))
+ set cursorline cursorlineopt=screenline
+ inoremap <F2> <Cmd>call cursor(1, 1)<CR>
+ END
+ call writefile(lines, 'Xcul_screenline', 'D')
+
+ let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8})
+ call term_sendkeys(buf, "A")
+ call VerifyScreenDump(buf, 'Test_cursorline_screenline_1', {})
+ call term_sendkeys(buf, "\<F2>")
+ call VerifyScreenDump(buf, 'Test_cursorline_screenline_2', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cursorline_cursorbind_horizontal_scroll()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, 'aa bb cc dd ee ff gg hh ii jj kk ll mm' ..
+ \ ' nn oo pp qq rr ss tt uu vv ww xx yy zz')
+ set nowrap
+ " The following makes the cursor apparent on the screen dump
+ set sidescroll=1 cursorcolumn
+ " add empty lines, required for cursorcolumn
+ call append(1, ['','','',''])
+ 20vsp
+ windo :set cursorbind
+ END
+ call writefile(lines, 'Xhor_scroll', 'D')
+
+ let buf = RunVimInTerminal('-S Xhor_scroll', #{rows: 8})
+ call term_sendkeys(buf, "20l")
+ call VerifyScreenDump(buf, 'Test_hor_scroll_1', {})
+ call term_sendkeys(buf, "10l")
+ call VerifyScreenDump(buf, 'Test_hor_scroll_2', {})
+ call term_sendkeys(buf, ":windo :set cursorline\<cr>")
+ call term_sendkeys(buf, "0")
+ call term_sendkeys(buf, "20l")
+ call VerifyScreenDump(buf, 'Test_hor_scroll_3', {})
+ call term_sendkeys(buf, "10l")
+ call VerifyScreenDump(buf, 'Test_hor_scroll_4', {})
+ call term_sendkeys(buf, ":windo :set nocursorline nocursorcolumn\<cr>")
+ call term_sendkeys(buf, "0")
+ call term_sendkeys(buf, "40l")
+ call VerifyScreenDump(buf, 'Test_hor_scroll_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_curswant.vim b/src/testdir/test_curswant.vim
new file mode 100644
index 0000000..7637850
--- /dev/null
+++ b/src/testdir/test_curswant.vim
@@ -0,0 +1,25 @@
+" Tests for curswant not changing when setting an option
+
+func Test_curswant()
+ new
+ call append(0, ['1234567890', '12345'])
+
+ normal! ggf8j
+ call assert_equal(7, winsaveview().curswant)
+ let &tabstop=&tabstop
+ call assert_equal(4, winsaveview().curswant)
+
+ normal! ggf8j
+ call assert_equal(7, winsaveview().curswant)
+ let &timeoutlen=&timeoutlen
+ call assert_equal(7, winsaveview().curswant)
+
+ normal! ggf8j
+ call assert_equal(7, winsaveview().curswant)
+ let &ttimeoutlen=&ttimeoutlen
+ call assert_equal(7, winsaveview().curswant)
+
+ enew!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_debugger.vim b/src/testdir/test_debugger.vim
new file mode 100644
index 0000000..2b405d9
--- /dev/null
+++ b/src/testdir/test_debugger.vim
@@ -0,0 +1,1531 @@
+" Tests for the Vim script debug commands
+
+source shared.vim
+source screendump.vim
+source check.vim
+
+CheckRunVimInTerminal
+
+func CheckCWD()
+ " Check that the longer lines don't wrap due to the length of the script name
+ " in cwd
+ let script_len = len( getcwd() .. '/Xtest1.vim' )
+ let longest_line = len( 'Breakpoint in "" line 1' )
+ if script_len > ( 75 - longest_line )
+ throw 'Skipped: Your CWD has too many characters'
+ endif
+endfunc
+command! -nargs=0 -bar CheckCWD call CheckCWD()
+
+" "options" argument can contain:
+" 'msec' - time to wait for a match
+" 'match' - "pattern" to use "lines" as pattern instead of text
+func CheckDbgOutput(buf, lines, options = {})
+ " Verify the expected output
+ let lnum = 20 - len(a:lines)
+ let msec = get(a:options, 'msec', 1000)
+ for l in a:lines
+ if get(a:options, 'match', 'equal') ==# 'pattern'
+ call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, msec)
+ else
+ call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, msec)
+ endif
+ let lnum += 1
+ endfor
+endfunc
+
+" Run a Vim debugger command
+" If the expected output argument is supplied, then check for it.
+func RunDbgCmd(buf, cmd, ...)
+ call term_sendkeys(a:buf, a:cmd . "\r")
+ call TermWait(a:buf)
+
+ if a:0 != 0
+ let options = #{match: 'equal'}
+ if a:0 > 1
+ call extend(options, a:2)
+ endif
+ call CheckDbgOutput(a:buf, a:1, options)
+ endif
+endfunc
+
+" Debugger tests
+func Test_Debugger()
+ " Create a Vim script with some functions
+ let lines =<< trim END
+ func Foo()
+ let var1 = 1
+ let var2 = Bar(var1) + 9
+ return var2
+ endfunc
+ func Bar(var)
+ let var1 = 2 + a:var
+ let var2 = Bazz(var1) + 4
+ return var2
+ endfunc
+ func Bazz(var)
+ try
+ let var1 = 3 + a:var
+ let var3 = "another var"
+ let var3 = "value2"
+ catch
+ let var4 = "exception"
+ endtry
+ return var1
+ endfunc
+ def Vim9Func()
+ for cmd in ['confirm', 'xxxxxxx']
+ for _ in [1, 2]
+ echo cmd
+ endfor
+ endfor
+ enddef
+ END
+ call writefile(lines, 'XtestDebug.vim', 'D')
+
+ " Start Vim in a terminal
+ let buf = RunVimInTerminal('-S XtestDebug.vim', {})
+
+ " Start the Vim debugger
+ call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])
+
+ " Create a few stack frames by stepping through functions
+ call RunDbgCmd(buf, 'step', ['line 1: let var1 = 1'])
+ call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bar(var1) + 9'])
+ call RunDbgCmd(buf, 'step', ['line 1: let var1 = 2 + a:var'])
+ call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bazz(var1) + 4'])
+ call RunDbgCmd(buf, 'step', ['line 1: try'])
+ call RunDbgCmd(buf, 'step', ['line 2: let var1 = 3 + a:var'])
+ call RunDbgCmd(buf, 'step', ['line 3: let var3 = "another var"'])
+
+ " check backtrace
+ call RunDbgCmd(buf, 'backtrace', [
+ \ ' 2 function Foo[2]',
+ \ ' 1 Bar[2]',
+ \ '->0 Bazz',
+ \ 'line 3: let var3 = "another var"'])
+
+ " Check variables in different stack frames
+ call RunDbgCmd(buf, 'echo var1', ['6'])
+
+ call RunDbgCmd(buf, 'up')
+ call RunDbgCmd(buf, 'back', [
+ \ ' 2 function Foo[2]',
+ \ '->1 Bar[2]',
+ \ ' 0 Bazz',
+ \ 'line 3: let var3 = "another var"'])
+ call RunDbgCmd(buf, 'echo var1', ['3'])
+
+ call RunDbgCmd(buf, 'u')
+ call RunDbgCmd(buf, 'bt', [
+ \ '->2 function Foo[2]',
+ \ ' 1 Bar[2]',
+ \ ' 0 Bazz',
+ \ 'line 3: let var3 = "another var"'])
+ call RunDbgCmd(buf, 'echo var1', ['1'])
+
+ " Undefined variables
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'frame 2')
+ call RunDbgCmd(buf, 'echo var3', [
+ \ 'Error detected while processing function Foo[2]..Bar[2]..Bazz:',
+ \ 'line 4:',
+ \ 'E121: Undefined variable: var3'])
+
+ " var3 is defined in this level with some other value
+ call RunDbgCmd(buf, 'fr 0')
+ call RunDbgCmd(buf, 'echo var3', ['another var'])
+
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, '')
+ call RunDbgCmd(buf, '')
+ call RunDbgCmd(buf, '')
+ call RunDbgCmd(buf, '')
+ call RunDbgCmd(buf, 'step', [
+ \ 'function Foo[2]..Bar',
+ \ 'line 3: End of function'])
+ call RunDbgCmd(buf, 'up')
+
+ " Undefined var2
+ call RunDbgCmd(buf, 'echo var2', [
+ \ 'Error detected while processing function Foo[2]..Bar:',
+ \ 'line 3:',
+ \ 'E121: Undefined variable: var2'])
+
+ " Var2 is defined with 10
+ call RunDbgCmd(buf, 'down')
+ call RunDbgCmd(buf, 'echo var2', ['10'])
+
+ " Backtrace movements
+ call RunDbgCmd(buf, 'b', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ " next command cannot go down, we are on bottom
+ call RunDbgCmd(buf, 'down', ['frame is zero'])
+ call RunDbgCmd(buf, 'up')
+
+ " next command cannot go up, we are on top
+ call RunDbgCmd(buf, 'up', ['frame at highest level: 1'])
+ call RunDbgCmd(buf, 'where', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ " fil is not frame or finish, it is file
+ call RunDbgCmd(buf, 'fil', ['"[No Name]" --No lines in buffer--'])
+
+ " relative backtrace movement
+ call RunDbgCmd(buf, 'fr -1')
+ call RunDbgCmd(buf, 'frame', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ call RunDbgCmd(buf, 'fr +1')
+ call RunDbgCmd(buf, 'fram', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ " go beyond limits does not crash
+ call RunDbgCmd(buf, 'fr 100', ['frame at highest level: 1'])
+ call RunDbgCmd(buf, 'fra', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ call RunDbgCmd(buf, 'frame -40', ['frame is zero'])
+ call RunDbgCmd(buf, 'fram', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ " final result 19
+ call RunDbgCmd(buf, 'cont', ['19'])
+
+ " breakpoints tests
+
+ " Start a debug session, so that reading the last line from the terminal
+ " works properly.
+ call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])
+
+ " No breakpoints
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ " Place some breakpoints
+ call RunDbgCmd(buf, 'breaka func Bar')
+ call RunDbgCmd(buf, 'breaklis', [' 1 func Bar line 1'])
+ call RunDbgCmd(buf, 'breakadd func 3 Bazz')
+ call RunDbgCmd(buf, 'breaklist', [' 1 func Bar line 1',
+ \ ' 2 func Bazz line 3'])
+
+ " Check whether the breakpoints are hit
+ call RunDbgCmd(buf, 'cont', [
+ \ 'Breakpoint in "Bar" line 1',
+ \ 'function Foo[2]..Bar',
+ \ 'line 1: let var1 = 2 + a:var'])
+ call RunDbgCmd(buf, 'cont', [
+ \ 'Breakpoint in "Bazz" line 3',
+ \ 'function Foo[2]..Bar[2]..Bazz',
+ \ 'line 3: let var3 = "another var"'])
+
+ " Delete the breakpoints
+ call RunDbgCmd(buf, 'breakd 1')
+ call RunDbgCmd(buf, 'breakli', [' 2 func Bazz line 3'])
+ call RunDbgCmd(buf, 'breakdel func 3 Bazz')
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ call RunDbgCmd(buf, 'cont')
+
+ " Make sure the breakpoints are removed
+ call RunDbgCmd(buf, ':echo Foo()', ['19'])
+
+ " Delete a non-existing breakpoint
+ call RunDbgCmd(buf, ':breakdel 2', ['E161: Breakpoint not found: 2'])
+
+ " Expression breakpoint
+ call RunDbgCmd(buf, ':breakadd func 2 Bazz')
+ call RunDbgCmd(buf, ':echo Bazz(1)', [
+ \ 'Entering Debug mode. Type "cont" to continue.',
+ \ 'function Bazz',
+ \ 'line 2: let var1 = 3 + a:var'])
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'breaka expr var3')
+ call RunDbgCmd(buf, 'breakl', [' 3 func Bazz line 2',
+ \ ' 4 expr var3'])
+ call RunDbgCmd(buf, 'cont', ['Breakpoint in "Bazz" line 5',
+ \ 'Oldval = "''another var''"',
+ \ 'Newval = "''value2''"',
+ \ 'function Bazz',
+ \ 'line 5: catch'])
+
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ " Check for error cases
+ call RunDbgCmd(buf, 'breakadd abcd', [
+ \ 'Error detected while processing function Bazz:',
+ \ 'line 5:',
+ \ 'E475: Invalid argument: abcd'])
+ call RunDbgCmd(buf, 'breakadd func', ['E475: Invalid argument: func'])
+ call RunDbgCmd(buf, 'breakadd func 2', ['E475: Invalid argument: func 2'])
+ call RunDbgCmd(buf, 'breaka func a()', ['E475: Invalid argument: func a()'])
+ call RunDbgCmd(buf, 'breakd abcd', ['E475: Invalid argument: abcd'])
+ call RunDbgCmd(buf, 'breakd func', ['E475: Invalid argument: func'])
+ call RunDbgCmd(buf, 'breakd func a()', ['E475: Invalid argument: func a()'])
+ call RunDbgCmd(buf, 'breakd func a', ['E161: Breakpoint not found: func a'])
+ call RunDbgCmd(buf, 'breakd expr', ['E475: Invalid argument: expr'])
+ call RunDbgCmd(buf, 'breakd expr x', ['E161: Breakpoint not found: expr x'])
+
+ " finish the current function
+ call RunDbgCmd(buf, 'finish', [
+ \ 'function Bazz',
+ \ 'line 8: End of function'])
+ call RunDbgCmd(buf, 'cont')
+
+ " Test for :next
+ call RunDbgCmd(buf, ':debug echo Bar(1)')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'next')
+ call RunDbgCmd(buf, '', [
+ \ 'function Bar',
+ \ 'line 3: return var2'])
+ call RunDbgCmd(buf, 'c')
+
+ " Test for :interrupt
+ call RunDbgCmd(buf, ':debug echo Bazz(1)')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'interrupt', [
+ \ 'Exception thrown: Vim:Interrupt',
+ \ 'function Bazz',
+ \ 'line 5: catch'])
+ call RunDbgCmd(buf, 'c')
+
+ " Test showing local variable in :def function
+ call RunDbgCmd(buf, ':breakadd func 2 Vim9Func')
+ call RunDbgCmd(buf, ':call Vim9Func()', ['line 2: for _ in [1, 2]'])
+ call RunDbgCmd(buf, 'next', ['line 2: for _ in [1, 2]'])
+ call RunDbgCmd(buf, 'echo cmd', ['confirm'])
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'cont')
+
+ " Test for :quit
+ call RunDbgCmd(buf, ':debug echo Foo()')
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'breakadd func 3 Foo')
+ call RunDbgCmd(buf, 'breakadd func 3 Bazz')
+ call RunDbgCmd(buf, 'cont', [
+ \ 'Breakpoint in "Bazz" line 3',
+ \ 'function Foo[2]..Bar[2]..Bazz',
+ \ 'line 3: let var3 = "another var"'])
+ call RunDbgCmd(buf, 'quit', [
+ \ 'Breakpoint in "Foo" line 3',
+ \ 'function Foo',
+ \ 'line 3: return var2'])
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'quit')
+ call RunDbgCmd(buf, 'enew! | only!')
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_Debugger_breakadd()
+ " Tests for :breakadd file and :breakadd here
+ " Breakpoints should be set before sourcing the file
+
+ let lines =<< trim END
+ let var1 = 10
+ let var2 = 20
+ let var3 = 30
+ let var4 = 40
+ END
+ call writefile(lines, 'XdebugBreakadd.vim', 'D')
+
+ " Start Vim in a terminal
+ let buf = RunVimInTerminal('XdebugBreakadd.vim', {})
+ call RunDbgCmd(buf, ':breakadd file 2 XdebugBreakadd.vim')
+ call RunDbgCmd(buf, ':4 | breakadd here')
+ call RunDbgCmd(buf, ':source XdebugBreakadd.vim', ['line 2: let var2 = 20'])
+ call RunDbgCmd(buf, 'cont', ['line 4: let var4 = 40'])
+ call RunDbgCmd(buf, 'cont')
+
+ call StopVimInTerminal(buf)
+
+ %bw!
+
+ call assert_fails('breakadd here', 'E32:')
+ call assert_fails('breakadd file Xtest.vim /\)/', 'E55:')
+endfunc
+
+" Test for expression breakpoint set using ":breakadd expr <expr>"
+func Test_Debugger_breakadd_expr()
+ CheckCWD
+
+ let lines =<< trim END
+ let g:Xtest_var += 1
+ END
+ call writefile(lines, 'XdebugBreakExpr.vim', 'D')
+
+ " Start Vim in a terminal
+ let buf = RunVimInTerminal('XdebugBreakExpr.vim', {})
+ call RunDbgCmd(buf, ':let g:Xtest_var = 10')
+ call RunDbgCmd(buf, ':breakadd expr g:Xtest_var')
+ call RunDbgCmd(buf, ':source %')
+ let expected =<< trim eval END
+ Oldval = "10"
+ Newval = "11"
+ {fnamemodify('XdebugBreakExpr.vim', ':p')}
+ line 1: let g:Xtest_var += 1
+ END
+ call RunDbgCmd(buf, ':source %', expected)
+ call RunDbgCmd(buf, 'cont')
+ let expected =<< trim eval END
+ Oldval = "11"
+ Newval = "12"
+ {fnamemodify('XdebugBreakExpr.vim', ':p')}
+ line 1: let g:Xtest_var += 1
+ END
+ call RunDbgCmd(buf, ':source %', expected)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+def Test_Debugger_breakadd_vim9_expr()
+ var lines =<< trim END
+ vim9script
+ func g:EarlyFunc()
+ endfunc
+ breakadd expr DoesNotExist()
+ func g:LaterFunc()
+ endfunc
+ breakdel *
+ END
+ writefile(lines, 'XdebugBreak9expr.vim', 'D')
+
+ # Start Vim in a terminal
+ var buf = g:RunVimInTerminal('-S XdebugBreak9expr.vim', {wait_for_ruler: 0})
+ call g:TermWait(buf, g:RunningWithValgrind() ? 1000 : 50)
+
+ # Despite the failure the functions are defined
+ g:RunDbgCmd(buf, ':function g:EarlyFunc',
+ ['function EarlyFunc()', 'endfunction'], {match: 'pattern'})
+ g:RunDbgCmd(buf, ':function g:LaterFunc',
+ ['function LaterFunc()', 'endfunction'], {match: 'pattern'})
+
+ call g:StopVimInTerminal(buf)
+enddef
+
+def Test_Debugger_break_at_return()
+ var lines =<< trim END
+ vim9script
+ def g:GetNum(): number
+ return 1
+ + 2
+ + 3
+ enddef
+ breakadd func GetNum
+ END
+ writefile(lines, 'XdebugBreakRet.vim', 'D')
+
+ # Start Vim in a terminal
+ var buf = g:RunVimInTerminal('-S XdebugBreakRet.vim', {wait_for_ruler: 0})
+ call g:TermWait(buf, g:RunningWithValgrind() ? 1000 : 50)
+
+ g:RunDbgCmd(buf, ':call GetNum()',
+ ['line 1: return 1 + 2 + 3'], {match: 'pattern'})
+
+ call g:StopVimInTerminal(buf)
+enddef
+
+func Test_Backtrace_Through_Source()
+ CheckCWD
+ let file1 =<< trim END
+ func SourceAnotherFile()
+ source Xtest2.vim
+ endfunc
+
+ func CallAFunction()
+ call SourceAnotherFile()
+ call File2Function()
+ endfunc
+
+ func GlobalFunction()
+ call CallAFunction()
+ endfunc
+ END
+ call writefile(file1, 'Xtest1.vim', 'D')
+
+ let file2 =<< trim END
+ func DoAThing()
+ echo "DoAThing"
+ endfunc
+
+ func File2Function()
+ call DoAThing()
+ endfunc
+
+ call File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest1.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug call GlobalFunction()',
+ \ ['cmd: call GlobalFunction()'])
+ call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
+
+ call RunDbgCmd(buf, 'backtrace', ['>backtrace',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: call CallAFunction()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+
+ call RunDbgCmd(buf, 'backtrace', ['>backtrace',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[1]',
+ \ '->0 SourceAnotherFile',
+ \ 'line 1: source Xtest2.vim'])
+
+ " Step into the 'source' command. Note that we print the full trace all the
+ " way though the source command.
+ call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()'])
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up', [ 'frame at highest level: 3' ] )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
+
+ " step until we have another meaningful trace
+ call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
+ call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 9: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 5 function GlobalFunction[1]',
+ \ ' 4 CallAFunction[1]',
+ \ ' 3 SourceAnotherFile[1]',
+ \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
+ \ ' 1 function File2Function[1]',
+ \ '->0 DoAThing',
+ \ 'line 1: echo "DoAThing"'])
+
+ " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 function GlobalFunction[1]',
+ \ '->0 CallAFunction',
+ \ 'line 2: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[2]',
+ \ '->0 File2Function',
+ \ 'line 1: call DoAThing()'])
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_Backtrace_Autocmd()
+ CheckCWD
+ let file1 =<< trim END
+ func SourceAnotherFile()
+ source Xtest2.vim
+ endfunc
+
+ func CallAFunction()
+ call SourceAnotherFile()
+ call File2Function()
+ endfunc
+
+ func GlobalFunction()
+ call CallAFunction()
+ endfunc
+
+ au User TestGlobalFunction :call GlobalFunction() | echo "Done"
+ END
+ call writefile(file1, 'Xtest1.vim', 'D')
+
+ let file2 =<< trim END
+ func DoAThing()
+ echo "DoAThing"
+ endfunc
+
+ func File2Function()
+ call DoAThing()
+ endfunc
+
+ call File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest1.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug doautocmd User TestGlobalFunction',
+ \ ['cmd: doautocmd User TestGlobalFunction'])
+ call RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"'])
+
+ " At this point the only thing in the stack is the autocommand
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->0 User Autocommands for "TestGlobalFunction"',
+ \ 'cmd: call GlobalFunction() | echo "Done"'])
+
+ " And now we're back into the call stack
+ call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 User Autocommands for "TestGlobalFunction"',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: call CallAFunction()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()'])
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 User Autocommands for "TestGlobalFunction"',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[1]',
+ \ '->0 SourceAnotherFile',
+ \ 'line 1: source Xtest2.vim'])
+
+ " Step into the 'source' command. Note that we print the full trace all the
+ " way though the source command.
+ call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()'])
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'up', [ 'frame at highest level: 4' ] )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ '->3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ '->2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ '->1 SourceAnotherFile[1]',
+ \ ' 0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down' )
+ call RunDbgCmd( buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 1: func DoAThing()' ] )
+
+ call RunDbgCmd( buf, 'down', [ 'frame is zero' ] )
+
+ " step until we have another meaningful trace
+ call RunDbgCmd(buf, 'step', ['line 5: func File2Function()'])
+ call RunDbgCmd(buf, 'step', ['line 9: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 4 User Autocommands for "TestGlobalFunction"',
+ \ ' 3 function GlobalFunction[1]',
+ \ ' 2 CallAFunction[1]',
+ \ ' 1 SourceAnotherFile[1]',
+ \ '->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ 'line 9: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 6 User Autocommands for "TestGlobalFunction"',
+ \ ' 5 function GlobalFunction[1]',
+ \ ' 4 CallAFunction[1]',
+ \ ' 3 SourceAnotherFile[1]',
+ \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]',
+ \ ' 1 function File2Function[1]',
+ \ '->0 DoAThing',
+ \ 'line 1: echo "DoAThing"'])
+
+ " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 10: End of sourced file'])
+ call RunDbgCmd(buf, 'step', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'step', ['line 2: call File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 2 User Autocommands for "TestGlobalFunction"',
+ \ ' 1 function GlobalFunction[1]',
+ \ '->0 CallAFunction',
+ \ 'line 2: call File2Function()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 User Autocommands for "TestGlobalFunction"',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[2]',
+ \ '->0 File2Function',
+ \ 'line 1: call DoAThing()'])
+
+
+ " Now unwind so that we get back to the original autocommand (and the second
+ " cmd echo "Done")
+ call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 3 User Autocommands for "TestGlobalFunction"',
+ \ ' 2 function GlobalFunction[1]',
+ \ ' 1 CallAFunction[2]',
+ \ '->0 File2Function',
+ \ 'line 1: End of function'])
+
+ call RunDbgCmd(buf, 'finish', ['line 2: End of function'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 2 User Autocommands for "TestGlobalFunction"',
+ \ ' 1 function GlobalFunction[1]',
+ \ '->0 CallAFunction',
+ \ 'line 2: End of function'])
+
+ call RunDbgCmd(buf, 'finish', ['line 1: End of function'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 User Autocommands for "TestGlobalFunction"',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: End of function'])
+
+ call RunDbgCmd(buf, 'step', ['cmd: echo "Done"'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->0 User Autocommands for "TestGlobalFunction"',
+ \ 'cmd: echo "Done"'])
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_Backtrace_CmdLine()
+ CheckCWD
+ let file1 =<< trim END
+ func SourceAnotherFile()
+ source Xtest2.vim
+ endfunc
+
+ func CallAFunction()
+ call SourceAnotherFile()
+ call File2Function()
+ endfunc
+
+ func GlobalFunction()
+ call CallAFunction()
+ endfunc
+
+ au User TestGlobalFunction :call GlobalFunction() | echo "Done"
+ END
+ call writefile(file1, 'Xtest1.vim', 'D')
+
+ let file2 =<< trim END
+ func DoAThing()
+ echo "DoAThing"
+ endfunc
+
+ func File2Function()
+ call DoAThing()
+ endfunc
+
+ call File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim', 'D')
+
+ let buf = RunVimInTerminal(
+ \ '-S Xtest1.vim -c "debug call GlobalFunction()"',
+ \ {'wait_for_ruler': 0})
+
+ " Need to wait for the vim-in-terminal to be ready.
+ " With valgrind this can take quite long.
+ call CheckDbgOutput(buf, ['command line',
+ \ 'cmd: call GlobalFunction()'], #{msec: 5000})
+
+ " At this point the only thing in the stack is the cmdline
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ '->0 command line',
+ \ 'cmd: call GlobalFunction()'])
+
+ " And now we're back into the call stack
+ call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '>backtrace',
+ \ ' 1 command line',
+ \ '->0 function GlobalFunction',
+ \ 'line 1: call CallAFunction()'])
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_Backtrace_DefFunction()
+ CheckCWD
+ let file1 =<< trim END
+ vim9script
+ import './Xtest2.vim' as imp
+
+ def SourceAnotherFile()
+ source Xtest2.vim
+ enddef
+
+ def CallAFunction()
+ SourceAnotherFile()
+ imp.File2Function()
+ enddef
+
+ def g:GlobalFunction()
+ var some = "some var"
+ CallAFunction()
+ enddef
+
+ defcompile
+ END
+ call writefile(file1, 'Xtest1.vim', 'D')
+
+ let file2 =<< trim END
+ vim9script
+
+ def DoAThing(): number
+ var a = 100 * 2
+ a += 3
+ return a
+ enddef
+
+ export def File2Function()
+ DoAThing()
+ enddef
+
+ defcompile
+ File2Function()
+ END
+ call writefile(file2, 'Xtest2.vim', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest1.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug call GlobalFunction()',
+ \ ['cmd: call GlobalFunction()'])
+
+ call RunDbgCmd(buf, 'step', ['line 1: var some = "some var"'])
+ call RunDbgCmd(buf, 'step', ['line 2: CallAFunction()'])
+ call RunDbgCmd(buf, 'echo some', ['some var'])
+
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V->0 function GlobalFunction',
+ \ '\Vline 2: CallAFunction()',
+ \ ],
+ \ #{match: 'pattern'})
+
+ call RunDbgCmd(buf, 'step', ['line 1: SourceAnotherFile()'])
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+ " Repeated line, because we first are in the compiled function before the
+ " EXEC and then in do_cmdline() before the :source command.
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+ call RunDbgCmd(buf, 'step', ['line 1: vim9script'])
+ call RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number'])
+ call RunDbgCmd(buf, 'step', ['line 9: export def File2Function()'])
+ call RunDbgCmd(buf, 'step', ['line 13: defcompile'])
+ call RunDbgCmd(buf, 'step', ['line 14: File2Function()'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 function GlobalFunction[2]',
+ \ '\V 2 <SNR>\.\*_CallAFunction[1]',
+ \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
+ \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ '\Vline 14: File2Function()'],
+ \ #{match: 'pattern'})
+
+ " Don't step into compiled functions...
+ call RunDbgCmd(buf, 'next', ['line 15: End of sourced file'])
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 function GlobalFunction[2]',
+ \ '\V 2 <SNR>\.\*_CallAFunction[1]',
+ \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
+ \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
+ \ '\Vline 15: End of sourced file'],
+ \ #{match: 'pattern'})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_DefFunction_expr()
+ CheckCWD
+ let file3 =<< trim END
+ vim9script
+ g:someVar = "foo"
+ def g:ChangeVar()
+ g:someVar = "bar"
+ echo "changed"
+ enddef
+ defcompile
+ END
+ call writefile(file3, 'Xtest3.vim', 'D')
+ let buf = RunVimInTerminal('-S Xtest3.vim', {})
+
+ call RunDbgCmd(buf, ':breakadd expr g:someVar')
+ call RunDbgCmd(buf, ':call g:ChangeVar()', ['Oldval = "''foo''"', 'Newval = "''bar''"', 'function ChangeVar', 'line 2: echo "changed"'])
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_debug_def_and_legacy_function()
+ CheckCWD
+ let file =<< trim END
+ vim9script
+ def g:SomeFunc()
+ echo "here"
+ echo "and"
+ echo "there"
+ breakadd func 2 LocalFunc
+ LocalFunc()
+ enddef
+
+ def LocalFunc()
+ echo "first"
+ echo "second"
+ breakadd func LegacyFunc
+ LegacyFunc()
+ enddef
+
+ func LegacyFunc()
+ echo "legone"
+ echo "legtwo"
+ endfunc
+
+ breakadd func 2 g:SomeFunc
+ END
+ call writefile(file, 'XtestDebug.vim', 'D')
+
+ let buf = RunVimInTerminal('-S XtestDebug.vim', {})
+
+ call RunDbgCmd(buf,':call SomeFunc()', ['line 2: echo "and"'])
+ call RunDbgCmd(buf,'next', ['line 3: echo "there"'])
+ call RunDbgCmd(buf,'next', ['line 4: breakadd func 2 LocalFunc'])
+
+ " continue, next breakpoint is in LocalFunc()
+ call RunDbgCmd(buf,'cont', ['line 2: echo "second"'])
+
+ " continue, next breakpoint is in LegacyFunc()
+ call RunDbgCmd(buf,'cont', ['line 1: echo "legone"'])
+
+ call RunDbgCmd(buf, 'cont')
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_debug_def_function()
+ CheckCWD
+ let file =<< trim END
+ vim9script
+ def g:Func()
+ var n: number
+ def Closure(): number
+ return n + 3
+ enddef
+ n += Closure()
+ echo 'result: ' .. n
+ enddef
+
+ def g:FuncWithArgs(text: string, nr: number, ...items: list<number>)
+ echo text .. nr
+ for it in items
+ echo it
+ endfor
+ echo "done"
+ enddef
+
+ def g:FuncWithDict()
+ var d = {
+ a: 1,
+ b: 2,
+ }
+ # comment
+ def Inner()
+ eval 1 + 2
+ enddef
+ enddef
+
+ def g:FuncComment()
+ # comment
+ echo "first"
+ .. "one"
+ # comment
+ echo "second"
+ enddef
+
+ def g:FuncForLoop()
+ eval 1 + 2
+ for i in [11, 22, 33]
+ eval i + 2
+ endfor
+ echo "done"
+ enddef
+
+ def g:FuncWithSplitLine()
+ eval 1 + 2
+ | eval 2 + 3
+ enddef
+ END
+ call writefile(file, 'Xtest.vim', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug call Func()',
+ \ ['cmd: call Func()'])
+ call RunDbgCmd(buf, 'next', ['result: 3'])
+ call term_sendkeys(buf, "\r")
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf,
+ \ ':debug call FuncWithArgs("asdf", 42, 1, 2, 3)',
+ \ ['cmd: call FuncWithArgs("asdf", 42, 1, 2, 3)'])
+ call RunDbgCmd(buf, 'step', ['line 1: echo text .. nr'])
+ call RunDbgCmd(buf, 'echo text', ['asdf'])
+ call RunDbgCmd(buf, 'echo nr', ['42'])
+ call RunDbgCmd(buf, 'echo items', ['[1, 2, 3]'])
+ call RunDbgCmd(buf, 'step', ['asdf42', 'function FuncWithArgs', 'line 2: for it in items'])
+ call RunDbgCmd(buf, 'step', ['function FuncWithArgs', 'line 2: for it in items'])
+ call RunDbgCmd(buf, 'echo it', ['0'])
+ call RunDbgCmd(buf, 'step', ['line 3: echo it'])
+ call RunDbgCmd(buf, 'echo it', ['1'])
+ call RunDbgCmd(buf, 'step', ['1', 'function FuncWithArgs', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'step', ['line 2: for it in items'])
+ call RunDbgCmd(buf, 'echo it', ['1'])
+ call RunDbgCmd(buf, 'step', ['line 3: echo it'])
+ call RunDbgCmd(buf, 'step', ['2', 'function FuncWithArgs', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'step', ['line 2: for it in items'])
+ call RunDbgCmd(buf, 'echo it', ['2'])
+ call RunDbgCmd(buf, 'step', ['line 3: echo it'])
+ call RunDbgCmd(buf, 'step', ['3', 'function FuncWithArgs', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'step', ['line 2: for it in items'])
+ call RunDbgCmd(buf, 'step', ['line 5: echo "done"'])
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf,
+ \ ':debug call FuncWithDict()',
+ \ ['cmd: call FuncWithDict()'])
+ call RunDbgCmd(buf, 'step', ['line 1: var d = { a: 1, b: 2, }'])
+ call RunDbgCmd(buf, 'step', ['line 6: def Inner()'])
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf, ':breakadd func 1 FuncComment')
+ call RunDbgCmd(buf, ':call FuncComment()', ['function FuncComment', 'line 2: echo "first" .. "one"'])
+ call RunDbgCmd(buf, ':breakadd func 3 FuncComment')
+ call RunDbgCmd(buf, 'cont', ['function FuncComment', 'line 5: echo "second"'])
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf, ':breakadd func 2 FuncForLoop')
+ call RunDbgCmd(buf, ':call FuncForLoop()', ['function FuncForLoop', 'line 2: for i in [11, 22, 33]'])
+ call RunDbgCmd(buf, 'step', ['line 2: for i in [11, 22, 33]'])
+ call RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 3: eval i + 2'])
+ call RunDbgCmd(buf, 'echo i', ['11'])
+ call RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 2: for i in [11, 22, 33]'])
+ call RunDbgCmd(buf, 'next', ['line 3: eval i + 2'])
+ call RunDbgCmd(buf, 'echo i', ['22'])
+
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf, ':breakadd func FuncWithSplitLine')
+ call RunDbgCmd(buf, ':call FuncWithSplitLine()', ['function FuncWithSplitLine', 'line 1: eval 1 + 2 | eval 2 + 3'])
+
+ call RunDbgCmd(buf, 'cont')
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_debug_def_function_with_lambda()
+ CheckCWD
+ let lines =<< trim END
+ vim9script
+ def g:Func()
+ var s = 'a'
+ ['b']->map((_, v) => s)
+ echo "done"
+ enddef
+ breakadd func 2 g:Func
+ END
+ call writefile(lines, 'XtestLambda.vim', 'D')
+
+ let buf = RunVimInTerminal('-S XtestLambda.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':call g:Func()',
+ \ ['function Func', 'line 2: [''b'']->map((_, v) => s)'])
+ call RunDbgCmd(buf,
+ \ 'next',
+ \ ['function Func', 'line 3: echo "done"'])
+
+ call RunDbgCmd(buf, 'cont')
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_debug_backtrace_level()
+ CheckCWD
+ let lines =<< trim END
+ let s:file1_var = 'file1'
+ let g:global_var = 'global'
+
+ func s:File1Func( arg )
+ let s:file1_var .= a:arg
+ let local_var = s:file1_var .. ' test1'
+ let g:global_var .= local_var
+ source Xtest2.vim
+ endfunc
+
+ call s:File1Func( 'arg1' )
+ END
+ call writefile(lines, 'Xtest1.vim', 'D')
+
+ let lines =<< trim END
+ let s:file2_var = 'file2'
+
+ func s:File2Func( arg )
+ let s:file2_var .= a:arg
+ let local_var = s:file2_var .. ' test2'
+ let g:global_var .= local_var
+ endfunc
+
+ call s:File2Func( 'arg2' )
+ END
+ call writefile(lines, 'Xtest2.vim', 'D')
+
+ let file1 = getcwd() .. '/Xtest1.vim'
+ let file2 = getcwd() .. '/Xtest2.vim'
+
+ " set a breakpoint and source file1.vim
+ let buf = RunVimInTerminal(
+ \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim',
+ \ #{wait_for_ruler: 0})
+
+ call CheckDbgOutput(buf, [
+ \ 'Breakpoint in "' .. file1 .. '" line 1',
+ \ 'Entering Debug mode. Type "cont" to continue.',
+ \ 'command line..script ' .. file1,
+ \ 'line 1: let s:file1_var = ''file1'''
+ \ ], #{msec: 5000})
+
+ " step through the initial declarations
+ call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] )
+ call RunDbgCmd(buf, 'step', [ 'line 4: func s:File1Func( arg )' ] )
+ call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
+ call RunDbgCmd(buf, 'echo global_var', [ 'global' ] )
+
+ " step in to the first function
+ call RunDbgCmd(buf, 'step', [ 'line 11: call s:File1Func( ''arg1'' )' ] )
+ call RunDbgCmd(buf, 'step', [ 'line 1: let s:file1_var .= a:arg' ] )
+ call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
+ call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
+ call RunDbgCmd(buf,
+ \'echo global_var',
+ \[ 'E121: Undefined variable: global_var' ] )
+ call RunDbgCmd(buf,
+ \'echo local_var',
+ \[ 'E121: Undefined variable: local_var' ] )
+ call RunDbgCmd(buf,
+ \'echo l:local_var',
+ \[ 'E121: Undefined variable: l:local_var' ] )
+
+ " backtrace up
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 2 command line',
+ \ '\V 1 script ' .. file1 .. '[11]',
+ \ '\V->0 function <SNR>\.\*_File1Func',
+ \ '\Vline 1: let s:file1_var .= a:arg',
+ \ ],
+ \ #{ match: 'pattern' } )
+ call RunDbgCmd(buf, 'up', [ '>up' ] )
+
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 2 command line',
+ \ '\V->1 script ' .. file1 .. '[11]',
+ \ '\V 0 function <SNR>\.\*_File1Func',
+ \ '\Vline 1: let s:file1_var .= a:arg',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ " Expression evaluation in the script frame (not the function frame)
+ " FIXME: Unexpected in this scope (a: should not be visible)
+ call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] )
+ call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] )
+ " FIXME: Unexpected in this scope (global should be found)
+ call RunDbgCmd(buf,
+ \'echo global_var',
+ \[ 'E121: Undefined variable: global_var' ] )
+ call RunDbgCmd(buf,
+ \'echo local_var',
+ \[ 'E121: Undefined variable: local_var' ] )
+ call RunDbgCmd(buf,
+ \'echo l:local_var',
+ \[ 'E121: Undefined variable: l:local_var' ] )
+
+
+ " step while backtraced jumps to the latest frame
+ call RunDbgCmd(buf, 'step', [
+ \ 'line 2: let local_var = s:file1_var .. '' test1''' ] )
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 2 command line',
+ \ '\V 1 script ' .. file1 .. '[11]',
+ \ '\V->0 function <SNR>\.\*_File1Func',
+ \ '\Vline 2: let local_var = s:file1_var .. '' test1''',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ call RunDbgCmd(buf, 'step', [ 'line 3: let g:global_var .= local_var' ] )
+ call RunDbgCmd(buf, 'echo local_var', [ 'file1arg1 test1' ] )
+ call RunDbgCmd(buf, 'echo l:local_var', [ 'file1arg1 test1' ] )
+
+ call RunDbgCmd(buf, 'step', [ 'line 4: source Xtest2.vim' ] )
+ call RunDbgCmd(buf, 'step', [ 'line 1: let s:file2_var = ''file2''' ] )
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 command line',
+ \ '\V 2 script ' .. file1 .. '[11]',
+ \ '\V 1 function <SNR>\.\*_File1Func[4]',
+ \ '\V->0 script ' .. file2,
+ \ '\Vline 1: let s:file2_var = ''file2''',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ " Expression evaluation in the script frame file2 (not the function frame)
+ call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
+ call RunDbgCmd(buf,
+ \ 'echo s:file1_var',
+ \ [ 'E121: Undefined variable: s:file1_var' ] )
+ call RunDbgCmd(buf, 'echo g:global_var', [ 'globalfile1arg1 test1' ] )
+ call RunDbgCmd(buf, 'echo global_var', [ 'globalfile1arg1 test1' ] )
+ call RunDbgCmd(buf,
+ \'echo local_var',
+ \[ 'E121: Undefined variable: local_var' ] )
+ call RunDbgCmd(buf,
+ \'echo l:local_var',
+ \[ 'E121: Undefined variable: l:local_var' ] )
+ call RunDbgCmd(buf,
+ \ 'echo s:file2_var',
+ \ [ 'E121: Undefined variable: s:file2_var' ] )
+
+ call RunDbgCmd(buf, 'step', [ 'line 3: func s:File2Func( arg )' ] )
+ call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
+
+ " Up the stack to the other script context
+ call RunDbgCmd(buf, 'up')
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 command line',
+ \ '\V 2 script ' .. file1 .. '[11]',
+ \ '\V->1 function <SNR>\.\*_File1Func[4]',
+ \ '\V 0 script ' .. file2,
+ \ '\Vline 3: func s:File2Func( arg )',
+ \ ],
+ \ #{ match: 'pattern' } )
+ " FIXME: Unexpected. Should see the a: and l: dicts from File1Func
+ call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] )
+ call RunDbgCmd(buf,
+ \ 'echo l:local_var',
+ \ [ 'E121: Undefined variable: l:local_var' ] )
+
+ call RunDbgCmd(buf, 'up')
+ call RunDbgCmd(buf, 'backtrace', [
+ \ '\V>backtrace',
+ \ '\V 3 command line',
+ \ '\V->2 script ' .. file1 .. '[11]',
+ \ '\V 1 function <SNR>\.\*_File1Func[4]',
+ \ '\V 0 script ' .. file2,
+ \ '\Vline 3: func s:File2Func( arg )',
+ \ ],
+ \ #{ match: 'pattern' } )
+
+ " FIXME: Unexpected (wrong script vars are used)
+ call RunDbgCmd(buf,
+ \ 'echo s:file1_var',
+ \ [ 'E121: Undefined variable: s:file1_var' ] )
+ call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
+
+ call RunDbgCmd(buf, 'cont')
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for setting a breakpoint on a :endif where the :if condition is false
+" and then quit the script. This should generate an interrupt.
+func Test_breakpt_endif_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ if v:false
+ let g:Xpath ..= 'b'
+ endif
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on a :else where the :if condition is false
+" and then quit the script. This should generate an interrupt.
+func Test_breakpt_else_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ if v:false
+ let g:Xpath ..= 'b'
+ else
+ invalid_command
+ endif
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on a :endwhile where the :while condition is
+" false and then quit the script. This should generate an interrupt.
+func Test_breakpt_endwhile_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ while v:false
+ let g:Xpath ..= 'b'
+ endwhile
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on a script local function
+func Test_breakpt_scriptlocal_func()
+ let g:Xpath = ''
+ func s:G()
+ let g:Xpath ..= 'a'
+ endfunc
+
+ let funcname = expand("<SID>") .. "G"
+ exe "breakadd func 1 " .. funcname
+ debuggreedy
+ redir => output
+ call feedkeys(":call " .. funcname .. "()\<CR>c\<CR>", "xt")
+ redir END
+ 0debuggreedy
+ call assert_match('Breakpoint in "' .. funcname .. '" line 1', output)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ exe "delfunc " .. funcname
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_delete.vim b/src/testdir/test_delete.vim
new file mode 100644
index 0000000..b05c27d
--- /dev/null
+++ b/src/testdir/test_delete.vim
@@ -0,0 +1,110 @@
+" Test for delete().
+
+source check.vim
+
+func Test_file_delete()
+ split Xfdelfile
+ call setline(1, ['a', 'b'])
+ wq
+ call assert_equal(['a', 'b'], readfile('Xfdelfile'))
+ call assert_equal(0, delete('Xfdelfile'))
+ call assert_fails('call readfile("Xfdelfile")', 'E484:')
+ call assert_equal(-1, delete('Xfdelfile'))
+ bwipe Xfdelfile
+endfunc
+
+func Test_dir_delete()
+ call mkdir('Xdirdel')
+ call assert_true(isdirectory('Xdirdel'))
+ call assert_equal(0, delete('Xdirdel', 'd'))
+ call assert_false(isdirectory('Xdirdel'))
+ call assert_equal(-1, delete('Xdirdel', 'd'))
+endfunc
+
+func Test_recursive_delete()
+ call mkdir('Xrecdel')
+ call mkdir('Xrecdel/subdir')
+ call mkdir('Xrecdel/empty')
+ split Xrecdel/Xfile
+ call setline(1, ['a', 'b'])
+ w
+ w Xrecdel/subdir/Xfile
+ close
+ call assert_true(isdirectory('Xrecdel'))
+ call assert_equal(['a', 'b'], readfile('Xrecdel/Xfile'))
+ call assert_true(isdirectory('Xrecdel/subdir'))
+ call assert_equal(['a', 'b'], readfile('Xrecdel/subdir/Xfile'))
+ call assert_true('Xrecdel/empty'->isdirectory())
+ call assert_equal(0, delete('Xrecdel', 'rf'))
+ call assert_false(isdirectory('Xrecdel'))
+ call assert_equal(-1, delete('Xrecdel', 'd'))
+ bwipe Xrecdel/Xfile
+ bwipe Xrecdel/subdir/Xfile
+endfunc
+
+func Test_symlink_delete()
+ CheckUnix
+ split Xslfile
+ call setline(1, ['a', 'b'])
+ wq
+ silent !ln -s Xslfile Xdellink
+ " Delete the link, not the file
+ call assert_equal(0, delete('Xdellink'))
+ call assert_equal(-1, delete('Xdellink'))
+ call assert_equal(0, delete('Xslfile'))
+ bwipe Xslfile
+endfunc
+
+func Test_symlink_dir_delete()
+ CheckUnix
+ call mkdir('Xsymdir')
+ silent !ln -s Xsymdir Xdirlink
+ call assert_true(isdirectory('Xsymdir'))
+ call assert_true(isdirectory('Xdirlink'))
+ " Delete the link, not the directory
+ call assert_equal(0, delete('Xdirlink'))
+ call assert_equal(-1, delete('Xdirlink'))
+ call assert_equal(0, delete('Xsymdir', 'd'))
+endfunc
+
+func Test_symlink_recursive_delete()
+ CheckUnix
+ call mkdir('Xrecdir3')
+ call mkdir('Xrecdir3/subdir')
+ call mkdir('Xrecdir4')
+ split Xrecdir3/Xfile
+ call setline(1, ['a', 'b'])
+ w
+ w Xrecdir3/subdir/Xfile
+ w Xrecdir4/Xfile
+ close
+ silent !ln -s ../Xrecdir4 Xrecdir3/Xreclink
+
+ call assert_true(isdirectory('Xrecdir3'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir3/Xfile'))
+ call assert_true(isdirectory('Xrecdir3/subdir'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir3/subdir/Xfile'))
+ call assert_true(isdirectory('Xrecdir4'))
+ call assert_true(isdirectory('Xrecdir3/Xreclink'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir4/Xfile'))
+
+ call assert_equal(0, delete('Xrecdir3', 'rf'))
+ call assert_false(isdirectory('Xrecdir3'))
+ call assert_equal(-1, delete('Xrecdir3', 'd'))
+ " symlink is deleted, not the directory it points to
+ call assert_true(isdirectory('Xrecdir4'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir4/Xfile'))
+ call assert_equal(0, delete('Xrecdir4/Xfile'))
+ call assert_equal(0, delete('Xrecdir4', 'd'))
+
+ bwipe Xrecdir3/Xfile
+ bwipe Xrecdir3/subdir/Xfile
+ bwipe Xrecdir4/Xfile
+endfunc
+
+func Test_delete_errors()
+ call assert_fails('call delete('''')', 'E474:')
+ call assert_fails('call delete(''foo'', 0)', 'E15:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_diffmode.vim b/src/testdir/test_diffmode.vim
new file mode 100644
index 0000000..d9ffbb3
--- /dev/null
+++ b/src/testdir/test_diffmode.vim
@@ -0,0 +1,1630 @@
+" Tests for diff mode
+
+source shared.vim
+source screendump.vim
+source check.vim
+
+func Test_diff_fold_sync()
+ enew!
+ let g:update_count = 0
+ au DiffUpdated * let g:update_count += 1
+
+ let l = range(50)
+ call setline(1, l)
+ diffthis
+ let winone = win_getid()
+ new
+ let l[25] = 'diff'
+ call setline(1, l)
+ diffthis
+ let wintwo = win_getid()
+ " line 15 is inside the closed fold
+ call assert_equal(19, foldclosedend(10))
+ call win_gotoid(winone)
+ call assert_equal(19, foldclosedend(10))
+ " open the fold
+ normal zv
+ call assert_equal(-1, foldclosedend(10))
+ " fold in other window must have opened too
+ call win_gotoid(wintwo)
+ call assert_equal(-1, foldclosedend(10))
+
+ " cursor position is in sync
+ normal 23G
+ call win_gotoid(winone)
+ call assert_equal(23, getcurpos()[1])
+
+ " depending on how redraw is done DiffUpdated may be triggered once or twice
+ call assert_inrange(1, 2, g:update_count)
+ au! DiffUpdated
+
+ windo diffoff
+ close!
+ set nomodified
+endfunc
+
+func Test_vert_split()
+ set diffopt=filler
+ call Common_vert_split()
+ set diffopt&
+endfunc
+
+func Test_vert_split_internal()
+ set diffopt=internal,filler
+ call Common_vert_split()
+ set diffopt&
+endfunc
+
+func Common_vert_split()
+ " Disable the title to avoid xterm keeping the wrong one.
+ set notitle noicon
+ new
+ let l = ['1 aa', '2 bb', '3 cc', '4 dd', '5 ee']
+ call setline(1, l)
+ w! Xtest
+ normal dd
+ $
+ put
+ normal kkrXoxxx
+ w! Xtest2
+ file Nop
+ normal ggoyyyjjjozzzz
+ set foldmethod=marker foldcolumn=4
+ call assert_equal(0, &diff)
+ call assert_equal('marker', &foldmethod)
+ call assert_equal(4, &foldcolumn)
+ call assert_equal(0, &scrollbind)
+ call assert_equal(0, &cursorbind)
+ call assert_equal(1, &wrap)
+
+ vert diffsplit Xtest
+ vert diffsplit Xtest2
+ call assert_equal(1, &diff)
+ call assert_equal('diff', &foldmethod)
+ call assert_equal(2, &foldcolumn)
+ call assert_equal(1, &scrollbind)
+ call assert_equal(1, &cursorbind)
+ call assert_equal(0, &wrap)
+
+ let diff_fdm = &fdm
+ let diff_fdc = &fdc
+ " repeat entering diff mode here to see if this saves the wrong settings
+ diffthis
+ " jump to second window for a moment to have filler line appear at start of
+ " first window
+ wincmd w
+ normal gg
+ wincmd p
+ normal gg
+ call assert_equal(2, winline())
+ normal j
+ call assert_equal(4, winline())
+ normal j
+ call assert_equal(5, winline())
+ normal j
+ call assert_equal(6, winline())
+ normal j
+ call assert_equal(8, winline())
+ normal j
+ call assert_equal(9, winline())
+
+ wincmd w
+ normal gg
+ call assert_equal(1, winline())
+ normal j
+ call assert_equal(2, winline())
+ normal j
+ call assert_equal(4, winline())
+ normal j
+ call assert_equal(5, winline())
+ normal j
+ call assert_equal(8, winline())
+
+ wincmd w
+ normal gg
+ call assert_equal(2, winline())
+ normal j
+ call assert_equal(3, winline())
+ normal j
+ call assert_equal(4, winline())
+ normal j
+ call assert_equal(5, winline())
+ normal j
+ call assert_equal(6, winline())
+ normal j
+ call assert_equal(7, winline())
+ normal j
+ call assert_equal(8, winline())
+
+ " Test diffoff
+ diffoff!
+ 1wincmd w
+ let &diff = 1
+ let &fdm = diff_fdm
+ let &fdc = diff_fdc
+ 4wincmd w
+ diffoff!
+ 1wincmd w
+ call assert_equal(0, &diff)
+ call assert_equal('marker', &foldmethod)
+ call assert_equal(4, &foldcolumn)
+ call assert_equal(0, &scrollbind)
+ call assert_equal(0, &cursorbind)
+ call assert_equal(1, &wrap)
+
+ wincmd w
+ call assert_equal(0, &diff)
+ call assert_equal('marker', &foldmethod)
+ call assert_equal(4, &foldcolumn)
+ call assert_equal(0, &scrollbind)
+ call assert_equal(0, &cursorbind)
+ call assert_equal(1, &wrap)
+
+ wincmd w
+ call assert_equal(0, &diff)
+ call assert_equal('marker', &foldmethod)
+ call assert_equal(4, &foldcolumn)
+ call assert_equal(0, &scrollbind)
+ call assert_equal(0, &cursorbind)
+ call assert_equal(1, &wrap)
+
+ call delete('Xtest')
+ call delete('Xtest2')
+ windo bw!
+endfunc
+
+func Test_filler_lines()
+ " Test that diffing shows correct filler lines
+ enew!
+ put =range(4,10)
+ 1d _
+ vnew
+ put =range(1,10)
+ 1d _
+ windo diffthis
+ wincmd h
+ call assert_equal(1, line('w0'))
+ unlet! diff_fdm diff_fdc
+ windo diffoff
+ bwipe!
+ enew!
+endfunc
+
+func Test_diffget_diffput()
+ enew!
+ let l = range(50)
+ call setline(1, l)
+ call assert_fails('diffget', 'E99:')
+ diffthis
+ call assert_fails('diffget', 'E100:')
+ new
+ let l[10] = 'one'
+ let l[20] = 'two'
+ let l[30] = 'three'
+ let l[40] = 'four'
+ call setline(1, l)
+ diffthis
+ call assert_equal('one', getline(11))
+ 11diffget
+ call assert_equal('10', getline(11))
+ 21diffput
+ wincmd w
+ call assert_equal('two', getline(21))
+ normal 31Gdo
+ call assert_equal('three', getline(31))
+ call assert_equal('40', getline(41))
+ normal 41Gdp
+ wincmd w
+ call assert_equal('40', getline(41))
+ new
+ diffthis
+ call assert_fails('diffget', 'E101:')
+
+ windo diffoff
+ %bwipe!
+endfunc
+
+" Test putting two changes from one buffer to another
+func Test_diffput_two()
+ new a
+ let win_a = win_getid()
+ call setline(1, range(1, 10))
+ diffthis
+ new b
+ let win_b = win_getid()
+ call setline(1, range(1, 10))
+ 8del
+ 5del
+ diffthis
+ call win_gotoid(win_a)
+ %diffput
+ call win_gotoid(win_b)
+ call assert_equal(map(range(1, 10), 'string(v:val)'), getline(1, '$'))
+ bwipe! a
+ bwipe! b
+endfunc
+
+" Test for :diffget/:diffput with a range that is inside a diff chunk
+func Test_diffget_diffput_range()
+ call setline(1, range(1, 10))
+ new
+ call setline(1, range(11, 20))
+ windo diffthis
+ 3,5diffget
+ call assert_equal(['13', '14', '15'], getline(3, 5))
+ call setline(1, range(1, 10))
+ 4,8diffput
+ wincmd p
+ call assert_equal(['13', '4', '5', '6', '7', '8', '19'], getline(3, 9))
+ %bw!
+endfunc
+
+" Test for :diffget/:diffput with an empty buffer and a non-empty buffer
+func Test_diffget_diffput_empty_buffer()
+ %d _
+ new
+ call setline(1, 'one')
+ windo diffthis
+ diffget
+ call assert_equal(['one'], getline(1, '$'))
+ %d _
+ diffput
+ wincmd p
+ call assert_equal([''], getline(1, '$'))
+ %bw!
+endfunc
+
+" :diffput and :diffget completes names of buffers which
+" are in diff mode and which are different than current buffer.
+" No completion when the current window is not in diff mode.
+func Test_diffget_diffput_completion()
+ e Xdiff1 | diffthis
+ botright new Xdiff2
+ botright new Xdiff3 | split | diffthis
+ botright new Xdiff4 | diffthis
+
+ wincmd t
+ call assert_equal('Xdiff1', bufname('%'))
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput Xdiff3 Xdiff4', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget Xdiff3 Xdiff4', @:)
+ call assert_equal(['Xdiff3', 'Xdiff4'], getcompletion('', 'diff_buffer'))
+
+ " Xdiff2 is not in diff mode, so no completion for :diffput, :diffget
+ wincmd j
+ call assert_equal('Xdiff2', bufname('%'))
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput ', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget ', @:)
+ call assert_equal([], getcompletion('', 'diff_buffer'))
+
+ " Xdiff3 is split in 2 windows, only the top one is in diff mode.
+ " So completion of :diffput :diffget only happens in the top window.
+ wincmd j
+ call assert_equal('Xdiff3', bufname('%'))
+ call assert_equal(1, &diff)
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput Xdiff1 Xdiff4', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget Xdiff1 Xdiff4', @:)
+ call assert_equal(['Xdiff1', 'Xdiff4'], getcompletion('', 'diff_buffer'))
+
+ wincmd j
+ call assert_equal('Xdiff3', bufname('%'))
+ call assert_equal(0, &diff)
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput ', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget ', @:)
+ call assert_equal([], getcompletion('', 'diff_buffer'))
+
+ wincmd j
+ call assert_equal('Xdiff4', bufname('%'))
+ call feedkeys(":diffput \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffput Xdiff1 Xdiff3', @:)
+ call feedkeys(":diffget \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"diffget Xdiff1 Xdiff3', @:)
+ call assert_equal(['Xdiff1', 'Xdiff3'], getcompletion('', 'diff_buffer'))
+
+ %bwipe
+endfunc
+
+func Test_dp_do_buffer()
+ e! one
+ let bn1=bufnr('%')
+ let l = range(60)
+ call setline(1, l)
+ diffthis
+
+ new two
+ let l[10] = 'one'
+ let l[20] = 'two'
+ let l[30] = 'three'
+ let l[40] = 'four'
+ let l[50] = 'five'
+ call setline(1, l)
+ diffthis
+
+ " dp and do with invalid buffer number.
+ 11
+ call assert_fails('norm 99999dp', 'E102:')
+ call assert_fails('norm 99999do', 'E102:')
+ call assert_fails('diffput non_existing_buffer', 'E94:')
+ call assert_fails('diffget non_existing_buffer', 'E94:')
+
+ " dp and do with valid buffer number.
+ call assert_equal('one', getline('.'))
+ exe 'norm ' . bn1 . 'do'
+ call assert_equal('10', getline('.'))
+ 21
+ call assert_equal('two', getline('.'))
+ diffget one
+ call assert_equal('20', getline('.'))
+
+ 31
+ exe 'norm ' . bn1 . 'dp'
+ 41
+ diffput one
+ wincmd w
+ 31
+ call assert_equal('three', getline('.'))
+ 41
+ call assert_equal('four', getline('.'))
+
+ " dp and do with buffer number which is not in diff mode.
+ new not_in_diff_mode
+ let bn3=bufnr('%')
+ wincmd w
+ 51
+ call assert_fails('exe "norm" . bn3 . "dp"', 'E103:')
+ call assert_fails('exe "norm" . bn3 . "do"', 'E103:')
+ call assert_fails('diffput not_in_diff_mode', 'E94:')
+ call assert_fails('diffget not_in_diff_mode', 'E94:')
+
+ windo diffoff
+ %bwipe!
+endfunc
+
+func Test_do_lastline()
+ e! one
+ call setline(1, ['1','2','3','4','5','6'])
+ diffthis
+
+ new two
+ call setline(1, ['2','4','5'])
+ diffthis
+
+ 1
+ norm dp]c
+ norm dp]c
+ wincmd w
+ call assert_equal(4, line('$'))
+ norm G
+ norm do
+ call assert_equal(3, line('$'))
+
+ windo diffoff
+ %bwipe!
+endfunc
+
+func Test_diffoff()
+ enew!
+ call setline(1, ['Two', 'Three'])
+ redraw
+ let normattr = screenattr(1, 1)
+ diffthis
+ botright vert new
+ call setline(1, ['One', '', 'Two', 'Three'])
+ diffthis
+ redraw
+ call assert_notequal(normattr, 1->screenattr(1))
+ diffoff!
+ redraw
+ call assert_equal(normattr, screenattr(1, 1))
+ bwipe!
+ bwipe!
+endfunc
+
+func Common_icase_test()
+ edit one
+ call setline(1, ['One', 'Two', 'Three', 'Four', 'Fi#ve'])
+ redraw
+ let normattr = screenattr(1, 1)
+ diffthis
+
+ botright vert new two
+ call setline(1, ['one', 'TWO', 'Three ', 'Four', 'fI=VE'])
+ diffthis
+
+ redraw
+ call assert_equal(normattr, screenattr(1, 1))
+ call assert_equal(normattr, screenattr(2, 1))
+ call assert_notequal(normattr, screenattr(3, 1))
+ call assert_equal(normattr, screenattr(4, 1))
+
+ let dtextattr = screenattr(5, 3)
+ call assert_notequal(dtextattr, screenattr(5, 1))
+ call assert_notequal(dtextattr, screenattr(5, 5))
+
+ diffoff!
+ %bwipe!
+endfunc
+
+func Test_diffopt_icase()
+ set diffopt=icase,foldcolumn:0
+ call Common_icase_test()
+ set diffopt&
+endfunc
+
+func Test_diffopt_icase_internal()
+ set diffopt=icase,foldcolumn:0,internal
+ call Common_icase_test()
+ set diffopt&
+endfunc
+
+func Common_iwhite_test()
+ edit one
+ " Difference in trailing spaces and amount of spaces should be ignored,
+ " but not other space differences.
+ call setline(1, ["One \t", 'Two', 'Three', 'one two', 'one two', 'Four'])
+ redraw
+ let normattr = screenattr(1, 1)
+ diffthis
+
+ botright vert new two
+ call setline(1, ["One\t ", "Two\t ", 'Three', 'one two', 'onetwo', ' Four'])
+ diffthis
+
+ redraw
+ call assert_equal(normattr, screenattr(1, 1))
+ call assert_equal(normattr, screenattr(2, 1))
+ call assert_equal(normattr, screenattr(3, 1))
+ call assert_equal(normattr, screenattr(4, 1))
+ call assert_notequal(normattr, screenattr(5, 1))
+ call assert_notequal(normattr, screenattr(6, 1))
+
+ diffoff!
+ %bwipe!
+endfunc
+
+func Test_diffopt_iwhite()
+ set diffopt=iwhite,foldcolumn:0
+ call Common_iwhite_test()
+ set diffopt&
+endfunc
+
+func Test_diffopt_iwhite_internal()
+ set diffopt=internal,iwhite,foldcolumn:0
+ call Common_iwhite_test()
+ set diffopt&
+endfunc
+
+func Test_diffopt_context()
+ enew!
+ call setline(1, ['1', '2', '3', '4', '5', '6', '7'])
+ diffthis
+ new
+ call setline(1, ['1', '2', '3', '4', '5x', '6', '7'])
+ diffthis
+
+ set diffopt=context:2
+ call assert_equal('+-- 2 lines: 1', foldtextresult(1))
+ set diffopt=internal,context:2
+ call assert_equal('+-- 2 lines: 1', foldtextresult(1))
+
+ set diffopt=context:1
+ call assert_equal('+-- 3 lines: 1', foldtextresult(1))
+ set diffopt=internal,context:1
+ call assert_equal('+-- 3 lines: 1', foldtextresult(1))
+
+ diffoff!
+ %bwipe!
+ set diffopt&
+endfunc
+
+func Test_diffopt_horizontal()
+ set diffopt=internal,horizontal
+ diffsplit
+
+ call assert_equal(&columns, winwidth(1))
+ call assert_equal(&columns, winwidth(2))
+ call assert_equal(&lines, winheight(1) + winheight(2) + 3)
+ call assert_inrange(0, 1, winheight(1) - winheight(2))
+
+ set diffopt&
+ diffoff!
+ %bwipe
+endfunc
+
+func Test_diffopt_vertical()
+ set diffopt=internal,vertical
+ diffsplit
+
+ call assert_equal(&lines - 2, winheight(1))
+ call assert_equal(&lines - 2, winheight(2))
+ call assert_equal(&columns, winwidth(1) + winwidth(2) + 1)
+ call assert_inrange(0, 1, winwidth(1) - winwidth(2))
+
+ set diffopt&
+ diffoff!
+ %bwipe
+endfunc
+
+func Test_diffopt_hiddenoff()
+ set diffopt=internal,filler,foldcolumn:0,hiddenoff
+ e! one
+ call setline(1, ['Two', 'Three'])
+ redraw
+ let normattr = screenattr(1, 1)
+ diffthis
+ botright vert new two
+ call setline(1, ['One', 'Four'])
+ diffthis
+ redraw
+ call assert_notequal(normattr, screenattr(1, 1))
+ set hidden
+ close
+ redraw
+ " should not diffing with hidden buffer two while 'hiddenoff' is enabled
+ call assert_equal(normattr, screenattr(1, 1))
+
+ bwipe!
+ bwipe!
+ set hidden& diffopt&
+endfunc
+
+func Test_diffoff_hidden()
+ set diffopt=internal,filler,foldcolumn:0
+ e! one
+ call setline(1, ['Two', 'Three'])
+ redraw
+ let normattr = screenattr(1, 1)
+ diffthis
+ botright vert new two
+ call setline(1, ['One', 'Four'])
+ diffthis
+ redraw
+ call assert_notequal(normattr, screenattr(1, 1))
+ set hidden
+ close
+ redraw
+ " diffing with hidden buffer two
+ call assert_notequal(normattr, screenattr(1, 1))
+ diffoff
+ redraw
+ call assert_equal(normattr, screenattr(1, 1))
+ diffthis
+ redraw
+ " still diffing with hidden buffer two
+ call assert_notequal(normattr, screenattr(1, 1))
+ diffoff!
+ redraw
+ call assert_equal(normattr, screenattr(1, 1))
+ diffthis
+ redraw
+ " no longer diffing with hidden buffer two
+ call assert_equal(normattr, screenattr(1, 1))
+
+ bwipe!
+ bwipe!
+ set hidden& diffopt&
+endfunc
+
+func Test_setting_cursor()
+ new Xtest1
+ put =range(1,90)
+ wq
+ new Xtest2
+ put =range(1,100)
+ wq
+
+ tabe Xtest2
+ $
+ diffsp Xtest1
+ tabclose
+
+ call delete('Xtest1')
+ call delete('Xtest2')
+endfunc
+
+func Test_diff_move_to()
+ new
+ call setline(1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ diffthis
+ vnew
+ call setline(1, [1, '2x', 3, 4, 4, 5, '6x', 7, '8x', 9, '10x'])
+ diffthis
+ norm ]c
+ call assert_equal(2, line('.'))
+ norm 3]c
+ call assert_equal(9, line('.'))
+ norm 10]c
+ call assert_equal(11, line('.'))
+ norm [c
+ call assert_equal(9, line('.'))
+ norm 2[c
+ call assert_equal(5, line('.'))
+ norm 10[c
+ call assert_equal(2, line('.'))
+ %bwipe!
+endfunc
+
+func Test_diffexpr()
+ CheckExecutable diff
+
+ func DiffExpr()
+ " Prepend some text to check diff type detection
+ call writefile(['warning', ' message'], v:fname_out)
+ silent exe '!diff ' . v:fname_in . ' ' . v:fname_new . '>>' . v:fname_out
+ endfunc
+ set diffexpr=DiffExpr()
+ set diffopt=foldcolumn:0
+
+ enew!
+ call setline(1, ['one', 'two', 'three'])
+ redraw
+ let normattr = screenattr(1, 1)
+ diffthis
+
+ botright vert new
+ call setline(1, ['one', 'two', 'three.'])
+ diffthis
+
+ redraw
+ call assert_equal(normattr, screenattr(1, 1))
+ call assert_equal(normattr, screenattr(2, 1))
+ call assert_notequal(normattr, screenattr(3, 1))
+ diffoff!
+
+ " Try using a non-existing function for 'diffexpr'.
+ set diffexpr=NewDiffFunc()
+ call assert_fails('windo diffthis', ['E117:', 'E97:'])
+ diffoff!
+
+ " Using a script-local function
+ func s:NewDiffExpr()
+ endfunc
+ set diffexpr=s:NewDiffExpr()
+ call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr)
+ set diffexpr=<SID>NewDiffExpr()
+ call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr)
+
+ %bwipe!
+ set diffexpr& diffopt&
+ delfunc DiffExpr
+ delfunc s:NewDiffExpr
+endfunc
+
+func Test_diffpatch()
+ " The patch program on MS-Windows may fail or hang.
+ CheckExecutable patch
+ CheckUnix
+ new
+ insert
+***************
+*** 1,3 ****
+ 1
+! 2
+ 3
+--- 1,4 ----
+ 1
+! 2x
+ 3
++ 4
+.
+ saveas! Xpatch
+ bwipe!
+ new
+ call assert_fails('diffpatch Xpatch', 'E816:')
+
+ for name in ['Xpatch', 'Xpatch$HOME', 'Xpa''tch']
+ call setline(1, ['1', '2', '3'])
+ if name != 'Xpatch'
+ call rename('Xpatch', name)
+ endif
+ exe 'diffpatch ' . escape(name, '$')
+ call assert_equal(['1', '2x', '3', '4'], getline(1, '$'))
+ if name != 'Xpatch'
+ call rename(name, 'Xpatch')
+ endif
+ bwipe!
+ endfor
+
+ call delete('Xpatch')
+ bwipe!
+endfunc
+
+func Test_diff_too_many_buffers()
+ for i in range(1, 8)
+ exe "new Xtest" . i
+ diffthis
+ endfor
+ new Xtest9
+ call assert_fails('diffthis', 'E96:')
+ %bwipe!
+endfunc
+
+func Test_diff_nomodifiable()
+ new
+ call setline(1, [1, 2, 3, 4])
+ setl nomodifiable
+ diffthis
+ vnew
+ call setline(1, ['1x', 2, 3, 3, 4])
+ diffthis
+ call assert_fails('norm dp', 'E793:')
+ setl nomodifiable
+ call assert_fails('norm do', 'E21:')
+ %bwipe!
+endfunc
+
+func Test_diff_hlID()
+ new
+ call setline(1, [1, 2, 3])
+ diffthis
+ vnew
+ call setline(1, ['1x', 2, 'x', 3])
+ diffthis
+ redraw
+
+ call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("")
+
+ call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange")
+ call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText")
+ call diff_hlID(2, 1)->synIDattr("name")->assert_equal("")
+ call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd")
+ eval 4->diff_hlID(1)->synIDattr("name")->assert_equal("")
+
+ wincmd w
+ call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange")
+ call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "")
+ call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "")
+
+ %bwipe!
+endfunc
+
+func Test_diff_filler()
+ new
+ call setline(1, [1, 2, 3, 'x', 4])
+ diffthis
+ vnew
+ call setline(1, [1, 2, 'y', 'y', 3, 4])
+ diffthis
+ redraw
+
+ call assert_equal([0, 0, 0, 0, 0, 0, 0, 1, 0], map(range(-1, 7), 'v:val->diff_filler()'))
+ wincmd w
+ call assert_equal([0, 0, 0, 0, 2, 0, 0, 0], map(range(-1, 6), 'diff_filler(v:val)'))
+
+ %bwipe!
+endfunc
+
+func Test_diff_lastline()
+ enew!
+ only!
+ call setline(1, ['This is a ', 'line with five ', 'rows'])
+ diffthis
+ botright vert new
+ call setline(1, ['This is', 'a line with ', 'four rows'])
+ diffthis
+ 1
+ call feedkeys("Je a\<CR>", 'tx')
+ call feedkeys("Je a\<CR>", 'tx')
+ let w1lines = winline()
+ wincmd w
+ $
+ let w2lines = winline()
+ call assert_equal(w2lines, w1lines)
+ bwipe!
+ bwipe!
+endfunc
+
+func WriteDiffFiles(buf, list1, list2)
+ call writefile(a:list1, 'Xdifile1')
+ call writefile(a:list2, 'Xdifile2')
+ if a:buf
+ call term_sendkeys(a:buf, ":checktime\<CR>")
+ endif
+endfunc
+
+" Verify a screendump with both the internal and external diff.
+func VerifyBoth(buf, dumpfile, extra)
+ " trailing : for leaving the cursor on the command line
+ for cmd in [":set diffopt=filler" . a:extra . "\<CR>:", ":set diffopt+=internal\<CR>:"]
+ call term_sendkeys(a:buf, cmd)
+ if VerifyScreenDump(a:buf, a:dumpfile, {}, cmd =~ 'internal' ? 'internal' : 'external')
+ " don't let the next iteration overwrite the "failed" file.
+ return
+ endif
+ endfor
+
+ " also test unified diff
+ call term_sendkeys(a:buf, ":call SetupUnified()\<CR>:")
+ call term_sendkeys(a:buf, ":redraw!\<CR>:")
+ call VerifyScreenDump(a:buf, a:dumpfile, {}, 'unified')
+ call term_sendkeys(a:buf, ":call StopUnified()\<CR>:")
+endfunc
+
+" Verify a screendump with the internal diff only.
+func VerifyInternal(buf, dumpfile, extra)
+ call term_sendkeys(a:buf, ":diffupdate!\<CR>")
+ " trailing : for leaving the cursor on the command line
+ call term_sendkeys(a:buf, ":set diffopt=internal,filler" . a:extra . "\<CR>:")
+ call VerifyScreenDump(a:buf, a:dumpfile, {})
+endfunc
+
+func Test_diff_screen()
+ let g:test_is_flaky = 1
+ CheckScreendump
+ CheckFeature menu
+
+ let lines =<< trim END
+ func UnifiedDiffExpr()
+ " Prepend some text to check diff type detection
+ call writefile(['warning', ' message'], v:fname_out)
+ silent exe '!diff -U0 ' .. v:fname_in .. ' ' .. v:fname_new .. '>>' .. v:fname_out
+ endfunc
+ func SetupUnified()
+ set diffexpr=UnifiedDiffExpr()
+ diffupdate
+ endfunc
+ func StopUnified()
+ set diffexpr=
+ endfunc
+ END
+ call writefile(lines, 'XdiffSetup', 'D')
+
+ " clean up already existing swap files, just in case
+ call delete('.Xdifile1.swp')
+ call delete('.Xdifile2.swp')
+
+ " Test 1: Add a line in beginning of file 2
+ call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ let buf = RunVimInTerminal('-d -S XdiffSetup Xdifile1 Xdifile2', {})
+ " Set autoread mode, so that Vim won't complain once we re-write the test
+ " files
+ call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
+
+ call VerifyBoth(buf, 'Test_diff_01', '')
+
+ " Test 2: Add a line in beginning of file 1
+ call WriteDiffFiles(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_02', '')
+
+ " Test 3: Add a line at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_03', '')
+
+ " Test 4: Add a line at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_04', '')
+
+ " Test 5: Add a line in the middle of file 2, remove on at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_05', '')
+
+ " Test 6: Add a line in the middle of file 1, remove on at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_06', '')
+
+ " Variants on test 6 with different context settings
+ call term_sendkeys(buf, ":set diffopt+=context:2\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.2', {})
+ call term_sendkeys(buf, ":set diffopt-=context:2\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:1\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.1', {})
+ call term_sendkeys(buf, ":set diffopt-=context:1\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:0\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.0', {})
+ call term_sendkeys(buf, ":set diffopt-=context:0\<cr>")
+
+ " Test 7 - 9: Test normal/patience/histogram diff algorithm
+ call WriteDiffFiles(buf, ['#include <stdio.h>', '', '// Frobs foo heartily', 'int frobnitz(int foo)', '{',
+ \ ' int i;', ' for(i = 0; i < 10; i++)', ' {', ' printf("Your answer is: ");',
+ \ ' printf("%d\n", foo);', ' }', '}', '', 'int fact(int n)', '{', ' if(n > 1)', ' {',
+ \ ' return fact(n-1) * n;', ' }', ' return 1;', '}', '', 'int main(int argc, char **argv)',
+ \ '{', ' frobnitz(fact(10));', '}'],
+ \ ['#include <stdio.h>', '', 'int fib(int n)', '{', ' if(n > 2)', ' {',
+ \ ' return fib(n-1) + fib(n-2);', ' }', ' return 1;', '}', '', '// Frobs foo heartily',
+ \ 'int frobnitz(int foo)', '{', ' int i;', ' for(i = 0; i < 10; i++)', ' {',
+ \ ' printf("%d\n", foo);', ' }', '}', '',
+ \ 'int main(int argc, char **argv)', '{', ' frobnitz(fib(10));', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_07', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_08', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:histogram\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_09', {})
+
+ " Test 10-11: normal/indent-heuristic
+ call term_sendkeys(buf, ":set diffopt&vim\<cr>")
+ call WriteDiffFiles(buf, ['', ' def finalize(values)', '', ' values.each do |v|', ' v.finalize', ' end'],
+ \ ['', ' def finalize(values)', '', ' values.each do |v|', ' v.prepare', ' end', '',
+ \ ' values.each do |v|', ' v.finalize', ' end'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_10', {})
+
+ " Leave trailing : at commandline!
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'one')
+ " shouldn't matter, if indent-algorithm comes before or after the algorithm
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic,algorithm:patience\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'two')
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience,indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'three')
+
+ " Test 12: diff the same file
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_12', '')
+
+ " Test 13: diff an empty file
+ call WriteDiffFiles(buf, [], [])
+ call VerifyBoth(buf, 'Test_diff_13', '')
+
+ " Test 14: test diffopt+=icase
+ call WriteDiffFiles(buf, ['a', 'b', 'cd'], ['A', 'b', 'cDe'])
+ call VerifyBoth(buf, 'Test_diff_14', " diffopt+=filler diffopt+=icase")
+
+ " Test 15-16: test diffopt+=iwhite
+ call WriteDiffFiles(buf, ['int main()', '{', ' printf("Hello, World!");', ' return 0;', '}'],
+ \ ['int main()', '{', ' if (0)', ' {', ' printf("Hello, World!");', ' return 0;', ' }', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt&vim diffopt+=filler diffopt+=iwhite\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_15', {})
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_16', {})
+
+ " Test 17: test diffopt+=iblank
+ call WriteDiffFiles(buf, ['a', ' ', 'cd', 'ef', 'xxx'], ['a', 'cd', '', 'ef', 'yyy'])
+ call VerifyInternal(buf, 'Test_diff_17', " diffopt+=iblank")
+
+ " Test 18: test diffopt+=iblank,iwhite / iwhiteall / iwhiteeol
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhite")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteall")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteeol
+ call WriteDiffFiles(buf, ['a ', 'x', 'cd', 'ef', 'xx xx', 'foo', 'bar'], ['a', 'x', 'c d', ' ef', 'xx xx', 'foo', '', 'bar'])
+ call VerifyInternal(buf, 'Test_diff_19', " diffopt+=iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteall
+ call VerifyInternal(buf, 'Test_diff_20', " diffopt+=iwhiteall")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xdifile1')
+ call delete('Xdifile2')
+endfunc
+
+func Test_diff_with_scroll_and_change()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 15))
+ vnew
+ call setline(1, range(9, 15))
+ windo diffthis
+ wincmd h
+ exe "normal Gl5\<C-E>"
+ END
+ call writefile(lines, 'Xtest_scroll_change', 'D')
+ let buf = RunVimInTerminal('-S Xtest_scroll_change', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_scroll_change_01', {})
+
+ call term_sendkeys(buf, "ax\<Esc>")
+ call VerifyScreenDump(buf, 'Test_diff_scroll_change_02', {})
+
+ call term_sendkeys(buf, "\<C-W>lay\<Esc>")
+ call VerifyScreenDump(buf, 'Test_diff_scroll_change_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_diff_with_cursorline()
+ CheckScreendump
+
+ call writefile([
+ \ 'hi CursorLine ctermbg=red ctermfg=white',
+ \ 'set cursorline',
+ \ 'call setline(1, ["foo","foo","foo","bar"])',
+ \ 'vnew',
+ \ 'call setline(1, ["bee","foo","foo","baz"])',
+ \ 'windo diffthis',
+ \ '2wincmd w',
+ \ ], 'Xtest_diff_cursorline', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_cursorline', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_with_cursorline_01', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_with_cursorline_02', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_with_cursorline_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_diff_with_cursorline_number()
+ CheckScreendump
+
+ let lines =<< trim END
+ hi CursorLine ctermbg=red ctermfg=white
+ hi CursorLineNr ctermbg=white ctermfg=black cterm=underline
+ set cursorline number
+ call setline(1, ["baz", "foo", "foo", "bar"])
+ 2
+ vnew
+ call setline(1, ["foo", "foo", "bar"])
+ windo diffthis
+ 1wincmd w
+ END
+ call writefile(lines, 'Xtest_diff_cursorline_number', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_cursorline_number', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_01', {})
+ call term_sendkeys(buf, ":set cursorlineopt=number\r")
+ call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_diff_with_cursorline_breakindent()
+ CheckScreendump
+
+ call writefile([
+ \ 'hi CursorLine ctermbg=red ctermfg=white',
+ \ 'set noequalalways wrap diffopt=followwrap cursorline breakindent',
+ \ '50vnew',
+ \ 'call setline(1, [" "," "," "," "])',
+ \ 'exe "norm 20Afoo\<Esc>j20Afoo\<Esc>j20Afoo\<Esc>j20Abar\<Esc>"',
+ \ 'vnew',
+ \ 'call setline(1, [" "," "," "," "])',
+ \ 'exe "norm 20Abee\<Esc>j20Afoo\<Esc>j20Afoo\<Esc>j20Abaz\<Esc>"',
+ \ 'windo diffthis',
+ \ '2wincmd w',
+ \ ], 'Xtest_diff_cursorline_breakindent', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_cursorline_breakindent', {})
+
+ call term_sendkeys(buf, "gg0")
+ call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_01', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_02', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_03', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_with_cul_bri_04', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_diff_with_syntax()
+ CheckScreendump
+
+ let lines =<< trim END
+ void doNothing() {
+ int x = 0;
+ char *s = "hello";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram1.c', 'D')
+ let lines =<< trim END
+ void doSomething() {
+ int x = 0;
+ char *s = "there";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram2.c', 'D')
+
+ let lines =<< trim END
+ edit Xprogram1.c
+ diffsplit Xprogram2.c
+ END
+ call writefile(lines, 'Xtest_diff_syntax', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_syntax', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_syntax_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_diff_of_diff()
+ CheckScreendump
+ CheckFeature rightleft
+
+ call writefile([
+ \ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])',
+ \ 'vnew',
+ \ 'call setline(1, ["aa","bb","cc"])',
+ \ 'windo diffthis',
+ \ '1wincmd w',
+ \ 'setlocal number',
+ \ ], 'Xtest_diff_diff', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_diff', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_of_diff_01', {})
+
+ call term_sendkeys(buf, ":set rightleft\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_of_diff_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func CloseoffSetup()
+ enew
+ call setline(1, ['one', 'two', 'three'])
+ diffthis
+ new
+ call setline(1, ['one', 'tow', 'three'])
+ diffthis
+ call assert_equal(1, &diff)
+ only!
+endfunc
+
+func Test_diff_closeoff()
+ " "closeoff" included by default: last diff win gets 'diff' reset'
+ call CloseoffSetup()
+ call assert_equal(0, &diff)
+ enew!
+
+ " "closeoff" excluded: last diff win keeps 'diff' set'
+ set diffopt-=closeoff
+ call CloseoffSetup()
+ call assert_equal(1, &diff)
+ diffoff!
+ enew!
+endfunc
+
+func Test_diff_followwrap()
+ new
+ set diffopt+=followwrap
+ set wrap
+ diffthis
+ call assert_equal(1, &wrap)
+ diffoff
+ set nowrap
+ diffthis
+ call assert_equal(0, &wrap)
+ diffoff
+ set diffopt&
+ bwipe!
+endfunc
+
+func Test_diff_maintains_change_mark()
+ func DiffMaintainsChangeMark()
+ enew!
+ call setline(1, ['a', 'b', 'c', 'd'])
+ diffthis
+ new
+ call setline(1, ['a', 'b', 'c', 'e'])
+ " Set '[ and '] marks
+ 2,3yank
+ call assert_equal([2, 3], [line("'["), line("']")])
+ " Verify they aren't affected by the implicit diff
+ diffthis
+ call assert_equal([2, 3], [line("'["), line("']")])
+ " Verify they aren't affected by an explicit diff
+ diffupdate
+ call assert_equal([2, 3], [line("'["), line("']")])
+ bwipe!
+ bwipe!
+ endfunc
+
+ set diffopt-=internal
+ call DiffMaintainsChangeMark()
+ set diffopt+=internal
+ call DiffMaintainsChangeMark()
+
+ set diffopt&
+ delfunc DiffMaintainsChangeMark
+endfunc
+
+" Test for 'patchexpr'
+func Test_patchexpr()
+ let g:patch_args = []
+ func TPatch()
+ call add(g:patch_args, readfile(v:fname_in))
+ call add(g:patch_args, readfile(v:fname_diff))
+ call writefile(['output file'], v:fname_out)
+ endfunc
+ set patchexpr=TPatch()
+
+ call writefile(['input file'], 'Xinput', 'D')
+ call writefile(['diff file'], 'Xdiff', 'D')
+ %bwipe!
+ edit Xinput
+ diffpatch Xdiff
+ call assert_equal('output file', getline(1))
+ call assert_equal('Xinput.new', bufname())
+ call assert_equal(2, winnr('$'))
+ call assert_true(&diff)
+
+ " Using a script-local function
+ func s:NewPatchExpr()
+ endfunc
+ set patchexpr=s:NewPatchExpr()
+ call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr)
+ set patchexpr=<SID>NewPatchExpr()
+ call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr)
+
+ set patchexpr&
+ delfunc TPatch
+ delfunc s:NewPatchExpr
+ %bwipe!
+endfunc
+
+func Test_diff_rnu()
+ CheckScreendump
+
+ let content =<< trim END
+ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
+ vnew
+ call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
+ windo diffthis
+ setlocal number rnu foldcolumn=0
+ END
+ call writefile(content, 'Xtest_diff_rnu', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_rnu', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_rnu_01', {})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_rnu_02', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_rnu_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_diff_multilineconceal()
+ new
+ diffthis
+
+ new
+ call matchadd('Conceal', 'a\nb', 9, -1, {'conceal': 'Y'})
+ set cole=2 cocu=n
+ call setline(1, ["a", "b"])
+ diffthis
+ redraw
+endfunc
+
+func Test_diff_and_scroll()
+ " this was causing an ml_get error
+ set ls=2
+ for i in range(winheight(0) * 2)
+ call setline(i, i < winheight(0) - 10 ? i : i + 10)
+ endfor
+ vnew
+ for i in range(winheight(0)*2 + 10)
+ call setline(i, i < winheight(0) - 10 ? 0 : i)
+ endfor
+ diffthis
+ wincmd p
+ diffthis
+ execute 'normal ' . winheight(0) . "\<C-d>"
+
+ bwipe!
+ bwipe!
+ set ls&
+endfunc
+
+func Test_diff_filler_cursorcolumn()
+ CheckScreendump
+
+ let content =<< trim END
+ call setline(1, ['aa', 'bb', 'cc'])
+ vnew
+ call setline(1, ['aa', 'cc'])
+ windo diffthis
+ wincmd p
+ setlocal cursorcolumn foldcolumn=0
+ norm! gg0
+ redraw!
+ END
+ call writefile(content, 'Xtest_diff_cuc', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_cuc', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_cuc_01', {})
+
+ call term_sendkeys(buf, "l")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_02', {})
+ call term_sendkeys(buf, "0j")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_03', {})
+ call term_sendkeys(buf, "l")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_04', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for adding/removing lines inside diff chunks, between diff chunks
+" and before diff chunks
+func Test_diff_modify_chunks()
+ enew!
+ let w2_id = win_getid()
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
+ new
+ let w1_id = win_getid()
+ call setline(1, ['a', '2', '3', 'd', 'e', 'f', '7', '8', 'i'])
+ windo diffthis
+
+ " remove a line between two diff chunks and create a new diff chunk
+ call win_gotoid(w2_id)
+ 5d
+ call win_gotoid(w1_id)
+ call diff_hlID(5, 1)->synIDattr('name')->assert_equal('DiffAdd')
+
+ " add a line between two diff chunks
+ call win_gotoid(w2_id)
+ normal! 4Goe
+ call win_gotoid(w1_id)
+ call diff_hlID(4, 1)->synIDattr('name')->assert_equal('')
+ call diff_hlID(5, 1)->synIDattr('name')->assert_equal('')
+
+ " remove all the lines in a diff chunk.
+ call win_gotoid(w2_id)
+ 7,8d
+ call win_gotoid(w1_id)
+ let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
+ call assert_equal(['', 'DiffText', 'DiffText', '', '', '', 'DiffAdd',
+ \ 'DiffAdd', ''], hl)
+
+ " remove lines from one diff chunk to just before the next diff chunk
+ call win_gotoid(w2_id)
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
+ 2,6d
+ call win_gotoid(w1_id)
+ let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
+ call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', 'DiffAdd',
+ \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl)
+
+ " remove lines just before the top of a diff chunk
+ call win_gotoid(w2_id)
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
+ 5,6d
+ call win_gotoid(w1_id)
+ let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
+ call assert_equal(['', 'DiffText', 'DiffText', '', 'DiffText', 'DiffText',
+ \ 'DiffAdd', 'DiffAdd', ''], hl)
+
+ " remove line after the end of a diff chunk
+ call win_gotoid(w2_id)
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
+ 4d
+ call win_gotoid(w1_id)
+ let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
+ call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', '', '', 'DiffText',
+ \ 'DiffText', ''], hl)
+
+ " remove lines starting from the end of one diff chunk and ending inside
+ " another diff chunk
+ call win_gotoid(w2_id)
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
+ 4,7d
+ call win_gotoid(w1_id)
+ let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
+ call assert_equal(['', 'DiffText', 'DiffText', 'DiffText', 'DiffAdd',
+ \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl)
+
+ " removing the only remaining diff chunk should make the files equal
+ call win_gotoid(w2_id)
+ call setline(1, ['a', '2', '3', 'x', 'd', 'e', 'f', 'x', '7', '8', 'i'])
+ 8d
+ let hl = range(1, 10)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
+ call assert_equal(['', '', '', 'DiffAdd', '', '', '', '', '', ''], hl)
+ call win_gotoid(w2_id)
+ 4d
+ call win_gotoid(w1_id)
+ let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')})
+ call assert_equal(['', '', '', '', '', '', '', '', ''], hl)
+
+ %bw!
+endfunc
+
+func Test_diff_binary()
+ CheckScreendump
+
+ let content =<< trim END
+ call setline(1, ['a', 'b', "c\n", 'd', 'e', 'f', 'g'])
+ vnew
+ call setline(1, ['A', 'b', 'c', 'd', 'E', 'f', 'g'])
+ windo diffthis
+ wincmd p
+ norm! gg0
+ redraw!
+ END
+ call writefile(content, 'Xtest_diff_bin', 'D')
+ let buf = RunVimInTerminal('-S Xtest_diff_bin', {})
+
+ " Test using internal diff
+ call VerifyScreenDump(buf, 'Test_diff_bin_01', {})
+
+ " Test using internal diff and case folding
+ call term_sendkeys(buf, ":set diffopt+=icase\<cr>")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_bin_02', {})
+ " Test using external diff
+ call term_sendkeys(buf, ":set diffopt=filler\<cr>")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_bin_03', {})
+ " Test using external diff and case folding
+ call term_sendkeys(buf, ":set diffopt=filler,icase\<cr>")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_bin_04', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ set diffopt&vim
+endfunc
+
+" Test for using the 'zi' command to invert 'foldenable' in diff windows (test
+" for the issue fixed by patch 6.2.317)
+func Test_diff_foldinvert()
+ %bw!
+ edit Xdoffile1
+ new Xdoffile2
+ new Xdoffile3
+ windo diffthis
+ " open a non-diff window
+ botright new
+ 1wincmd w
+ call assert_true(getwinvar(1, '&foldenable'))
+ call assert_true(getwinvar(2, '&foldenable'))
+ call assert_true(getwinvar(3, '&foldenable'))
+ normal zi
+ call assert_false(getwinvar(1, '&foldenable'))
+ call assert_false(getwinvar(2, '&foldenable'))
+ call assert_false(getwinvar(3, '&foldenable'))
+ normal zi
+ call assert_true(getwinvar(1, '&foldenable'))
+ call assert_true(getwinvar(2, '&foldenable'))
+ call assert_true(getwinvar(3, '&foldenable'))
+
+ " If the current window has 'noscrollbind', then 'zi' should not change
+ " 'foldenable' in other windows.
+ 1wincmd w
+ set noscrollbind
+ normal zi
+ call assert_false(getwinvar(1, '&foldenable'))
+ call assert_true(getwinvar(2, '&foldenable'))
+ call assert_true(getwinvar(3, '&foldenable'))
+
+ " 'zi' should not change the 'foldenable' for windows with 'noscrollbind'
+ 1wincmd w
+ set scrollbind
+ normal zi
+ call setwinvar(2, '&scrollbind', v:false)
+ normal zi
+ call assert_false(getwinvar(1, '&foldenable'))
+ call assert_true(getwinvar(2, '&foldenable'))
+ call assert_false(getwinvar(3, '&foldenable'))
+
+ %bw!
+ set scrollbind&
+endfunc
+
+" This was scrolling for 'cursorbind' but 'scrollbind' is more important
+func Test_diff_scroll()
+ CheckScreendump
+
+ let left =<< trim END
+ line 1
+ line 2
+ line 3
+ line 4
+
+ // Common block
+ // one
+ // containing
+ // four lines
+
+ // Common block
+ // two
+ // containing
+ // four lines
+ END
+ call writefile(left, 'Xleft', 'D')
+ let right =<< trim END
+ line 1
+ line 2
+ line 3
+ line 4
+
+ Lorem
+ ipsum
+ dolor
+ sit
+ amet,
+ consectetur
+ adipiscing
+ elit.
+ Etiam
+ luctus
+ lectus
+ sodales,
+ dictum
+
+ // Common block
+ // one
+ // containing
+ // four lines
+
+ Vestibulum
+ tincidunt
+ aliquet
+ nulla.
+
+ // Common block
+ // two
+ // containing
+ // four lines
+ END
+ call writefile(right, 'Xright', 'D')
+ let buf = RunVimInTerminal('-d Xleft Xright', {'rows': 12})
+ call term_sendkeys(buf, "\<C-W>\<C-W>jjjj")
+ call VerifyScreenDump(buf, 'Test_diff_scroll_1', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_scroll_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" This was trying to update diffs for a buffer being closed
+func Test_diff_only()
+ silent! lfile
+ set diff
+ lopen
+ norm o
+ silent! norm o
+
+ set nodiff
+ %bwipe!
+endfunc
+
+" This was causing invalid diff block values
+" FIXME: somehow this causes a valgrind error when run directly but not when
+" run as a test.
+func Test_diff_manipulations()
+ set diff
+ split 0
+ sil! norm R doobdeuR doobdeuR doobdeu
+
+ set nodiff
+ %bwipe!
+endfunc
+
+" This was causing the line number in the diff block to go below one.
+" FIXME: somehow this causes a valgrind error when run directly but not when
+" run as a test.
+func Test_diff_put_and_undo()
+ set diff
+ next 0
+ split 00
+ sil! norm o0gguudpo0ggJuudp
+
+ bwipe!
+ bwipe!
+ set nodiff
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_digraph.vim b/src/testdir/test_digraph.vim
new file mode 100644
index 0000000..a8a55a7
--- /dev/null
+++ b/src/testdir/test_digraph.vim
@@ -0,0 +1,607 @@
+" Tests for digraphs
+
+source check.vim
+CheckFeature digraphs
+source term_util.vim
+
+func Put_Dig(chars)
+ exe "norm! o\<c-k>".a:chars
+endfu
+
+func Put_Dig_BS(char1, char2)
+ exe "norm! o".a:char1."\<bs>".a:char2
+endfu
+
+func Test_digraphs()
+ new
+ call Put_Dig("00")
+ call assert_equal("∞", getline('.'))
+ " not a digraph
+ call Put_Dig("el")
+ call assert_equal("l", getline('.'))
+ call Put_Dig("ht")
+ call assert_equal("þ", getline('.'))
+ " digraph "ab" is the same as "ba"
+ call Put_Dig("ab")
+ call Put_Dig("ba")
+ call assert_equal(["ã°","ã°"], getline(line('.')-1,line('.')))
+ " Euro sign
+ call Put_Dig("e=")
+ call Put_Dig("=e")
+ call Put_Dig("Eu")
+ call Put_Dig("uE")
+ call assert_equal(['е']+repeat(["€"],3), getline(line('.')-3,line('.')))
+ " Rouble sign
+ call Put_Dig("R=")
+ call Put_Dig("=R")
+ call Put_Dig("=P")
+ call Put_Dig("P=")
+ call assert_equal(['Р']+repeat(["₽"],2)+['П'], getline(line('.')-3,line('.')))
+ " Not a digraph
+ call Put_Dig("a\<bs>")
+ call Put_Dig("\<bs>a")
+ call assert_equal(["<BS>", "<BS>a"], getline(line('.')-1,line('.')))
+ " Grave
+ call Put_Dig("a!")
+ call Put_Dig("!e")
+ call Put_Dig("b!") " not defined
+ call assert_equal(["à", "è", "!"], getline(line('.')-2,line('.')))
+ " Acute accent
+ call Put_Dig("a'")
+ call Put_Dig("'e")
+ call Put_Dig("b'") " not defined
+ call assert_equal(["á", "é", "'"], getline(line('.')-2,line('.')))
+ " Circumflex
+ call Put_Dig("a>")
+ call Put_Dig(">e")
+ call Put_Dig("b>") " not defined
+ call assert_equal(['â', 'ê', '>'], getline(line('.')-2,line('.')))
+ " Tilde
+ call Put_Dig("o~")
+ call Put_Dig("~u") " not defined
+ call Put_Dig("z~") " not defined
+ call assert_equal(['õ', 'u', '~'], getline(line('.')-2,line('.')))
+ " Tilde
+ call Put_Dig("o?")
+ call Put_Dig("?u")
+ call Put_Dig("z?") " not defined
+ call assert_equal(['õ', 'ũ', '?'], getline(line('.')-2,line('.')))
+ " Macron
+ call Put_Dig("o-")
+ call Put_Dig("-u")
+ call Put_Dig("z-") " not defined
+ call assert_equal(['Å', 'Å«', '-'], getline(line('.')-2,line('.')))
+ " Breve
+ call Put_Dig("o(")
+ call Put_Dig("(u")
+ call Put_Dig("z(") " not defined
+ call assert_equal(['Å', 'Å­', '('], getline(line('.')-2,line('.')))
+ " Dot above
+ call Put_Dig("b.")
+ call Put_Dig(".e")
+ call Put_Dig("a.") " not defined
+ call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
+ " Diaeresis
+ call Put_Dig("a:")
+ call Put_Dig(":u")
+ call Put_Dig("b:") " not defined
+ call assert_equal(['ä', 'ü', ':'], getline(line('.')-2,line('.')))
+ " Cedilla
+ call Put_Dig("',")
+ call Put_Dig(",C")
+ call Put_Dig("b,") " not defined
+ call assert_equal(['¸', 'Ç', ','], getline(line('.')-2,line('.')))
+ " Underline
+ call Put_Dig("B_")
+ call Put_Dig("_t")
+ call Put_Dig("a_") " not defined
+ call assert_equal(['Ḇ', 'ṯ', '_'], getline(line('.')-2,line('.')))
+ " Stroke
+ call Put_Dig("j/")
+ call Put_Dig("/l")
+ call Put_Dig("b/") " not defined
+ call assert_equal(['/', 'Å‚', '/'], getline(line('.')-2,line('.')))
+ " Double acute
+ call Put_Dig('O"')
+ call Put_Dig('"y')
+ call Put_Dig('b"') " not defined
+ call assert_equal(['Å', 'ÿ', '"'], getline(line('.')-2,line('.')))
+ " Ogonek
+ call Put_Dig('u;')
+ call Put_Dig(';E')
+ call Put_Dig('b;') " not defined
+ call assert_equal(['ų', 'Ę', ';'], getline(line('.')-2,line('.')))
+ " Caron
+ call Put_Dig('u<')
+ call Put_Dig('<E')
+ call Put_Dig('b<') " not defined
+ call assert_equal(['Ç”', 'Äš', '<'], getline(line('.')-2,line('.')))
+ " Ring above
+ call Put_Dig('u0')
+ call Put_Dig('0E') " not defined
+ call Put_Dig('b0') " not defined
+ call assert_equal(['ů', 'E', '0'], getline(line('.')-2,line('.')))
+ " Hook
+ call Put_Dig('u2')
+ call Put_Dig('2E')
+ call Put_Dig('b2') " not defined
+ call assert_equal(['ủ', 'Ẻ', '2'], getline(line('.')-2,line('.')))
+ " Horn
+ call Put_Dig('u9')
+ call Put_Dig('9E') " not defined
+ call Put_Dig('b9') " not defined
+ call assert_equal(['Æ°', 'E', '9'], getline(line('.')-2,line('.')))
+ " Cyrillic
+ call Put_Dig('u=')
+ call Put_Dig('=b')
+ call Put_Dig('=_')
+ call assert_equal(['у', 'б', '〓'], getline(line('.')-2,line('.')))
+ " Greek
+ call Put_Dig('u*')
+ call Put_Dig('*b')
+ call Put_Dig('*_')
+ call assert_equal(['υ', 'β', '々'], getline(line('.')-2,line('.')))
+ " Greek/Cyrillic special
+ call Put_Dig('u%')
+ call Put_Dig('%b') " not defined
+ call Put_Dig('%_') " not defined
+ call assert_equal(['Ï', 'b', '_'], getline(line('.')-2,line('.')))
+ " Arabic
+ call Put_Dig('u+')
+ call Put_Dig('+b')
+ call Put_Dig('+_') " japanese industrial symbol
+ call assert_equal(['+', 'ب', '〄'], getline(line('.')-2,line('.')))
+ " Hebrew
+ call Put_Dig('Q+')
+ call Put_Dig('+B')
+ call Put_Dig('+X')
+ call assert_equal(['ק', 'ב', 'ח'], getline(line('.')-2,line('.')))
+ " Latin
+ call Put_Dig('a3')
+ call Put_Dig('A3')
+ call Put_Dig('3X')
+ call assert_equal(['Ç£', 'Ç¢', 'X'], getline(line('.')-2,line('.')))
+ " Bopomofo
+ call Put_Dig('a4')
+ call Put_Dig('A4')
+ call Put_Dig('4X')
+ call assert_equal(['ã„š', '4', 'X'], getline(line('.')-2,line('.')))
+ " Hiragana
+ call Put_Dig('a5')
+ call Put_Dig('A5')
+ call Put_Dig('5X')
+ call assert_equal(['ã‚', 'ã', 'X'], getline(line('.')-2,line('.')))
+ " Katakana
+ call Put_Dig('a6')
+ call Put_Dig('A6')
+ call Put_Dig('6X')
+ call assert_equal(['ã‚¡', 'ã‚¢', 'X'], getline(line('.')-2,line('.')))
+ " Superscripts
+ call Put_Dig('1S')
+ call Put_Dig('2S')
+ call Put_Dig('3S')
+ call assert_equal(['¹', '²', '³'], getline(line('.')-2,line('.')))
+ " Subscripts
+ call Put_Dig('1s')
+ call Put_Dig('2s')
+ call Put_Dig('3s')
+ call assert_equal(['â‚', 'â‚‚', '₃'], getline(line('.')-2,line('.')))
+ " Eszet (only lowercase)
+ call Put_Dig("ss")
+ call Put_Dig("SS") " start of string
+ call assert_equal(["ß", "˜"], getline(line('.')-1,line('.')))
+ " High bit set
+ call Put_Dig("a ")
+ call Put_Dig(" A")
+ call assert_equal(['á', 'Ã'], getline(line('.')-1,line('.')))
+ " Escape is not part of a digraph
+ call Put_Dig("a\<esc>")
+ call Put_Dig("\<esc>A")
+ call assert_equal(['', 'A'], getline(line('.')-1,line('.')))
+ " define some custom digraphs
+ " old: 00 ∞
+ " old: el l
+ digraph 00 9216
+ digraph el 0252
+ call Put_Dig("00")
+ call Put_Dig("el")
+ " Reset digraphs
+ digraph 00 8734
+ digraph el 108
+ call Put_Dig("00")
+ call Put_Dig("el")
+ call assert_equal(['â€', 'ü', '∞', 'l'], getline(line('.')-3,line('.')))
+ call assert_fails('exe "digraph a\<Esc> 100"', 'E104:')
+ call assert_fails('exe "digraph \<Esc>a 100"', 'E104:')
+ call assert_fails('digraph xy z', 'E39:')
+ call assert_fails('digraph x', 'E1214:')
+ bw!
+endfunc
+
+func Test_digraphs_option()
+ " reset whichwrap option, so that testing <esc><bs>A works,
+ " without moving up a line
+ set digraph ww=
+ new
+ call Put_Dig_BS("0","0")
+ call assert_equal("∞", getline('.'))
+ " not a digraph
+ call Put_Dig_BS("e","l")
+ call assert_equal("l", getline('.'))
+ call Put_Dig_BS("h","t")
+ call assert_equal("þ", getline('.'))
+ " digraph "ab" is the same as "ba"
+ call Put_Dig_BS("a","b")
+ call Put_Dig_BS("b","a")
+ call assert_equal(["ã°","ã°"], getline(line('.')-1,line('.')))
+ " Euro sign
+ call Put_Dig_BS("e","=")
+ call Put_Dig_BS("=","e")
+ call Put_Dig_BS("E","u")
+ call Put_Dig_BS("u","E")
+ call assert_equal(['е']+repeat(["€"],3), getline(line('.')-3,line('.')))
+ " Rouble sign
+ call Put_Dig_BS("R","=")
+ call Put_Dig_BS("=","R")
+ call Put_Dig_BS("=","P")
+ call Put_Dig_BS("P","=")
+ call assert_equal(['Р']+repeat(["₽"],2)+['П'], getline(line('.')-3,line('.')))
+ " Not a digraph: this is different from <c-k>!
+ call Put_Dig_BS("a","\<bs>")
+ call Put_Dig_BS("\<bs>","a")
+ call assert_equal(['','a'], getline(line('.')-1,line('.')))
+ " Grave
+ call Put_Dig_BS("a","!")
+ call Put_Dig_BS("!","e")
+ call Put_Dig_BS("b","!") " not defined
+ call assert_equal(["à", "è", "!"], getline(line('.')-2,line('.')))
+ " Acute accent
+ call Put_Dig_BS("a","'")
+ call Put_Dig_BS("'","e")
+ call Put_Dig_BS("b","'") " not defined
+ call assert_equal(["á", "é", "'"], getline(line('.')-2,line('.')))
+ " Cicumflex
+ call Put_Dig_BS("a",">")
+ call Put_Dig_BS(">","e")
+ call Put_Dig_BS("b",">") " not defined
+ call assert_equal(['â', 'ê', '>'], getline(line('.')-2,line('.')))
+ " Tilde
+ call Put_Dig_BS("o","~")
+ call Put_Dig_BS("~","u") " not defined
+ call Put_Dig_BS("z","~") " not defined
+ call assert_equal(['õ', 'u', '~'], getline(line('.')-2,line('.')))
+ " Tilde
+ call Put_Dig_BS("o","?")
+ call Put_Dig_BS("?","u")
+ call Put_Dig_BS("z","?") " not defined
+ call assert_equal(['õ', 'ũ', '?'], getline(line('.')-2,line('.')))
+ " Macron
+ call Put_Dig_BS("o","-")
+ call Put_Dig_BS("-","u")
+ call Put_Dig_BS("z","-") " not defined
+ call assert_equal(['Å', 'Å«', '-'], getline(line('.')-2,line('.')))
+ " Breve
+ call Put_Dig_BS("o","(")
+ call Put_Dig_BS("(","u")
+ call Put_Dig_BS("z","(") " not defined
+ call assert_equal(['Å', 'Å­', '('], getline(line('.')-2,line('.')))
+ " Dot above
+ call Put_Dig_BS("b",".")
+ call Put_Dig_BS(".","e")
+ call Put_Dig_BS("a",".") " not defined
+ call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.')))
+ " Diaeresis
+ call Put_Dig_BS("a",":")
+ call Put_Dig_BS(":","u")
+ call Put_Dig_BS("b",":") " not defined
+ call assert_equal(['ä', 'ü', ':'], getline(line('.')-2,line('.')))
+ " Cedilla
+ call Put_Dig_BS("'",",")
+ call Put_Dig_BS(",","C")
+ call Put_Dig_BS("b",",") " not defined
+ call assert_equal(['¸', 'Ç', ','], getline(line('.')-2,line('.')))
+ " Underline
+ call Put_Dig_BS("B","_")
+ call Put_Dig_BS("_","t")
+ call Put_Dig_BS("a","_") " not defined
+ call assert_equal(['Ḇ', 'ṯ', '_'], getline(line('.')-2,line('.')))
+ " Stroke
+ call Put_Dig_BS("j","/")
+ call Put_Dig_BS("/","l")
+ call Put_Dig_BS("b","/") " not defined
+ call assert_equal(['/', 'Å‚', '/'], getline(line('.')-2,line('.')))
+ " Double acute
+ call Put_Dig_BS('O','"')
+ call Put_Dig_BS('"','y')
+ call Put_Dig_BS('b','"') " not defined
+ call assert_equal(['Å', 'ÿ', '"'], getline(line('.')-2,line('.')))
+ " Ogonek
+ call Put_Dig_BS('u',';')
+ call Put_Dig_BS(';','E')
+ call Put_Dig_BS('b',';') " not defined
+ call assert_equal(['ų', 'Ę', ';'], getline(line('.')-2,line('.')))
+ " Caron
+ call Put_Dig_BS('u','<')
+ call Put_Dig_BS('<','E')
+ call Put_Dig_BS('b','<') " not defined
+ call assert_equal(['Ç”', 'Äš', '<'], getline(line('.')-2,line('.')))
+ " Ring above
+ call Put_Dig_BS('u','0')
+ call Put_Dig_BS('0','E') " not defined
+ call Put_Dig_BS('b','0') " not defined
+ call assert_equal(['ů', 'E', '0'], getline(line('.')-2,line('.')))
+ " Hook
+ call Put_Dig_BS('u','2')
+ call Put_Dig_BS('2','E')
+ call Put_Dig_BS('b','2') " not defined
+ call assert_equal(['ủ', 'Ẻ', '2'], getline(line('.')-2,line('.')))
+ " Horn
+ call Put_Dig_BS('u','9')
+ call Put_Dig_BS('9','E') " not defined
+ call Put_Dig_BS('b','9') " not defined
+ call assert_equal(['Æ°', 'E', '9'], getline(line('.')-2,line('.')))
+ " Cyrillic
+ call Put_Dig_BS('u','=')
+ call Put_Dig_BS('=','b')
+ call Put_Dig_BS('=','_')
+ call assert_equal(['у', 'б', '〓'], getline(line('.')-2,line('.')))
+ " Greek
+ call Put_Dig_BS('u','*')
+ call Put_Dig_BS('*','b')
+ call Put_Dig_BS('*','_')
+ call assert_equal(['υ', 'β', '々'], getline(line('.')-2,line('.')))
+ " Greek/Cyrillic special
+ call Put_Dig_BS('u','%')
+ call Put_Dig_BS('%','b') " not defined
+ call Put_Dig_BS('%','_') " not defined
+ call assert_equal(['Ï', 'b', '_'], getline(line('.')-2,line('.')))
+ " Arabic
+ call Put_Dig_BS('u','+')
+ call Put_Dig_BS('+','b')
+ call Put_Dig_BS('+','_') " japanese industrial symbol
+ call assert_equal(['+', 'ب', '〄'], getline(line('.')-2,line('.')))
+ " Hebrew
+ call Put_Dig_BS('Q','+')
+ call Put_Dig_BS('+','B')
+ call Put_Dig_BS('+','X')
+ call assert_equal(['ק', 'ב', 'ח'], getline(line('.')-2,line('.')))
+ " Latin
+ call Put_Dig_BS('a','3')
+ call Put_Dig_BS('A','3')
+ call Put_Dig_BS('3','X')
+ call assert_equal(['Ç£', 'Ç¢', 'X'], getline(line('.')-2,line('.')))
+ " Bopomofo
+ call Put_Dig_BS('a','4')
+ call Put_Dig_BS('A','4')
+ call Put_Dig_BS('4','X')
+ call assert_equal(['ã„š', '4', 'X'], getline(line('.')-2,line('.')))
+ " Hiragana
+ call Put_Dig_BS('a','5')
+ call Put_Dig_BS('A','5')
+ call Put_Dig_BS('5','X')
+ call assert_equal(['ã‚', 'ã', 'X'], getline(line('.')-2,line('.')))
+ " Katakana
+ call Put_Dig_BS('a','6')
+ call Put_Dig_BS('A','6')
+ call Put_Dig_BS('6','X')
+ call assert_equal(['ã‚¡', 'ã‚¢', 'X'], getline(line('.')-2,line('.')))
+ " Superscripts
+ call Put_Dig_BS('1','S')
+ call Put_Dig_BS('2','S')
+ call Put_Dig_BS('3','S')
+ call assert_equal(['¹', '²', '³'], getline(line('.')-2,line('.')))
+ " Subscripts
+ call Put_Dig_BS('1','s')
+ call Put_Dig_BS('2','s')
+ call Put_Dig_BS('3','s')
+ call assert_equal(['â‚', 'â‚‚', '₃'], getline(line('.')-2,line('.')))
+ " Eszet (only lowercase)
+ call Put_Dig_BS("s","s")
+ call Put_Dig_BS("S","S") " start of string
+ call assert_equal(["ß", "˜"], getline(line('.')-1,line('.')))
+ " High bit set (different from <c-k>)
+ call Put_Dig_BS("a"," ")
+ call Put_Dig_BS(" ","A")
+ call assert_equal([' ', 'A'], getline(line('.')-1,line('.')))
+ " Escape is not part of a digraph (different from <c-k>)
+ call Put_Dig_BS("a","\<esc>")
+ call Put_Dig_BS("\<esc>","A")
+ call assert_equal(['', ''], getline(line('.')-1,line('.')))
+ " define some custom digraphs
+ " old: 00 ∞
+ " old: el l
+ digraph 00 9216
+ digraph el 0252
+ call Put_Dig_BS("0","0")
+ call Put_Dig_BS("e","l")
+ " Reset digraphs
+ digraph 00 8734
+ digraph el 108
+ call Put_Dig_BS("0","0")
+ call Put_Dig_BS("e","l")
+ call assert_equal(['â€', 'ü', '∞', 'l'], getline(line('.')-3,line('.')))
+ set nodigraph ww&vim
+ bw!
+endfunc
+
+func Test_digraphs_output()
+ new
+ let out = execute(':digraph')
+ call assert_equal('Eu € 8364', matchstr(out, '\C\<Eu\D*8364\>'))
+ call assert_equal('=e € 8364', matchstr(out, '\C=e\D*8364\>'))
+ call assert_equal('=R ₽ 8381', matchstr(out, '\C=R\D*8381\>'))
+ call assert_equal('=P ₽ 8381', matchstr(out, '\C=P\D*8381\>'))
+ call assert_equal('o: ö 246', matchstr(out, '\C\<o:\D*246\>'))
+ call assert_equal('v4 ㄪ 12586', matchstr(out, '\C\<v4\D*12586\>'))
+ call assert_equal("'0 Ëš 730", matchstr(out, '\C''0\D*730\>'))
+ call assert_equal('Z% Ж 1046', matchstr(out, '\C\<Z%\D*1046\>'))
+ call assert_equal('u- Å« 363', matchstr(out, '\C\<u-\D*363\>'))
+ call assert_equal('SH ^A 1', matchstr(out, '\C\<SH\D*1\>'))
+ call assert_notmatch('Latin supplement', out)
+
+ let out_bang_without_custom = execute(':digraph!')
+ digraph lt 60
+ let out_bang_with_custom = execute(':digraph!')
+ call assert_notmatch('lt', out_bang_without_custom)
+ call assert_match("^\n"
+ \ .. "NU ^@ 10 .*\n"
+ \ .. "Latin supplement\n"
+ \ .. "!I ¡ 161 .*\n"
+ \ .. ".*\n"
+ \ .. 'Custom\n.*\<lt < 60\>', out_bang_with_custom)
+ bw!
+endfunc
+
+func Test_loadkeymap()
+ CheckFeature keymap
+ new
+ set keymap=czech
+ set iminsert=0
+ call feedkeys("o|\<c-^>|01234567890|\<esc>", 'tx')
+ call assert_equal("|'é+ěšÄřžýáíé'", getline('.'))
+ " reset keymap and encoding option
+ set keymap=
+ bw!
+endfunc
+
+func Test_digraph_cmndline()
+ " Create digraph on commandline
+ call feedkeys(":\"\<c-k>Eu\<cr>", 'xt')
+ call assert_equal('"€', @:)
+
+ " Canceling a CTRL-K on the cmdline
+ call feedkeys(":\"a\<c-k>\<esc>b\<cr>", 'xt')
+ call assert_equal('"ab', @:)
+endfunc
+
+func Test_show_digraph()
+ new
+ call Put_Dig("e=")
+ call assert_equal("\n<е> 1077, Hex 0435, Oct 2065, Digr e=", execute('ascii'))
+ bwipe!
+endfunc
+
+func Test_show_digraph_cp1251()
+ new
+ set encoding=cp1251
+ call Put_Dig("='")
+ call assert_equal("\n<\xfa> <|z> <M-z> 250, Hex fa, Oct 372, Digr ='", execute('ascii'))
+ set encoding=utf-8
+ bwipe!
+endfunc
+
+" Test for error in a keymap file
+func Test_loadkeymap_error()
+ CheckFeature keymap
+ call assert_fails('loadkeymap', 'E105:')
+ call writefile(['loadkeymap', 'a'], 'Xkeymap', 'D')
+ call assert_fails('source Xkeymap', 'E791:')
+endfunc
+
+" Test for the characters displayed on the screen when entering a digraph
+func Test_entering_digraph()
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('', {'rows': 6})
+ call term_sendkeys(buf, "i\<C-K>")
+ call TermWait(buf)
+ call assert_equal('?', term_getline(buf, 1))
+ call term_sendkeys(buf, "1")
+ call TermWait(buf)
+ call assert_equal('1', term_getline(buf, 1))
+ call term_sendkeys(buf, "2")
+ call TermWait(buf)
+ call assert_equal('½', term_getline(buf, 1))
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_digraph_set_function()
+ new
+ call digraph_set('aa', 'ã‚')
+ call Put_Dig('aa')
+ call assert_equal('ã‚', getline('$'))
+ call digraph_set(' i', 'ã„')
+ call Put_Dig(' i')
+ call assert_equal('ã„', getline('$'))
+ call digraph_set(' ', 'ã†')
+ call Put_Dig(' ')
+ call assert_equal('ã†', getline('$'))
+
+ eval 'aa'->digraph_set('ãˆ')
+ call Put_Dig('aa')
+ call assert_equal('ãˆ', getline('$'))
+
+ call assert_fails('call digraph_set("aaa", "ã‚")', 'E1214: Digraph must be just two characters: aaa')
+ call assert_fails('call digraph_set("b", "ã‚")', 'E1214: Digraph must be just two characters: b')
+ call assert_fails('call digraph_set("ã‚", "ã‚")', 'E1214: Digraph must be just two characters: ã‚')
+ call assert_fails('call digraph_set("aa", "ã‚ã‚")', 'E1215: Digraph must be one character: ã‚ã‚')
+ call assert_fails('call digraph_set("aa", "ã‹" .. nr2char(0x3099))', 'E1215: Digraph must be one character: ã‹' .. nr2char(0x3099))
+ call assert_fails('call digraph_set(test_null_string(), "ã„")', 'E1214: Digraph must be just two characters')
+ call assert_fails('call digraph_set("aa", 0z10)', 'E976: Using a Blob as a String')
+ bwipe!
+endfunc
+
+func Test_digraph_get_function()
+ " Built-in digraphs
+ call assert_equal('∞', digraph_get('00'))
+
+ " User-defined digraphs
+ call digraph_set('aa', 'ã‚')
+ call digraph_set(' i', 'ã„')
+ call digraph_set(' ', 'ã†')
+ call assert_equal('ã‚', digraph_get('aa'))
+ call assert_equal('ã‚', 'aa'->digraph_get())
+ call assert_equal('ã„', digraph_get(' i'))
+ call assert_equal('ã†', digraph_get(' '))
+ call assert_fails('call digraph_get("aaa")', 'E1214: Digraph must be just two characters: aaa')
+ call assert_fails('call digraph_get("b")', 'E1214: Digraph must be just two characters: b')
+ call assert_fails('call digraph_get(test_null_string())', 'E1214: Digraph must be just two characters:')
+ call assert_fails('call digraph_get(0z10)', 'E976: Using a Blob as a String')
+endfunc
+
+func Test_digraph_get_function_encode()
+ CheckFeature iconv
+
+ let testcases = {
+ \'00': '∞',
+ \'aa': 'ã‚',
+ \}
+ for [key, ch] in items(testcases)
+ call digraph_set(key, ch)
+ set encoding=japan
+ call assert_equal(iconv(ch, 'utf-8', 'japan'), digraph_get(key))
+ set encoding=utf-8
+ endfor
+endfunc
+
+func Test_digraph_setlist_function()
+ call digraph_setlist([['aa', 'ã'], ['bb', 'ã']])
+ call assert_equal('ã', digraph_get('aa'))
+ call assert_equal('ã', digraph_get('bb'))
+
+ call assert_fails('call digraph_setlist([[]])', 'E1216:')
+ call assert_fails('call digraph_setlist([["aa", "b", "cc"]])', 'E1216:')
+ call assert_fails('call digraph_setlist([["ã‚", "ã‚"]])', 'E1214: Digraph must be just two characters: ã‚')
+ call assert_fails('call digraph_setlist([test_null_list()])', 'E1216:')
+ call assert_fails('call digraph_setlist({})', 'E1216:')
+ call assert_fails('call digraph_setlist([{}])', 'E1216:')
+ call assert_true(digraph_setlist(test_null_list()))
+endfunc
+
+func Test_digraph_getlist_function()
+ " Make sure user-defined digraphs are defined
+ call digraph_setlist([['aa', 'ã'], ['bb', 'ã']])
+
+ for pair in digraph_getlist(1)
+ call assert_equal(digraph_get(pair[0]), pair[1])
+ endfor
+
+ " We don't know how many digraphs are registered before, so check the number
+ " of digraphs returned.
+ call assert_equal(digraph_getlist()->len(), digraph_getlist(0)->len())
+ call assert_notequal((digraph_getlist()->len()), digraph_getlist(1)->len())
+
+ call assert_fails('call digraph_getlist(0z12)', 'E974: Using a Blob as a Number')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_display.vim b/src/testdir/test_display.vim
new file mode 100644
index 0000000..abf1cd0
--- /dev/null
+++ b/src/testdir/test_display.vim
@@ -0,0 +1,471 @@
+" Test for displaying stuff
+
+if !has('gui_running') && has('unix')
+ set term=ansi
+endif
+
+source view_util.vim
+source check.vim
+source screendump.vim
+
+func Test_display_foldcolumn()
+ CheckFeature folding
+
+ new
+ vnew
+ vert resize 25
+ call assert_equal(25, winwidth(winnr()))
+ set isprint=@
+
+ 1put='e more noise blah blah‚ more stuff here'
+
+ let expect = [
+ \ "e more noise blah blah<82",
+ \ "> more stuff here "
+ \ ]
+
+ call cursor(2, 1)
+ norm! zt
+ let lines = ScreenLines([1,2], winwidth(0))
+ call assert_equal(expect, lines)
+ set fdc=2
+ let lines = ScreenLines([1,2], winwidth(0))
+ let expect = [
+ \ " e more noise blah blah<",
+ \ " 82> more stuff here "
+ \ ]
+ call assert_equal(expect, lines)
+
+ quit!
+ quit!
+endfunc
+
+func Test_display_foldtext_mbyte()
+ CheckFeature folding
+
+ call NewWindow(10, 40)
+ call append(0, range(1,20))
+ exe "set foldmethod=manual foldtext=foldtext() fillchars=fold:\u2500,vert:\u2502 fdc=2"
+ call cursor(2, 1)
+ norm! zf13G
+ let lines=ScreenLines([1,3], winwidth(0)+1)
+ let expect=[
+ \ " 1 \u2502",
+ \ "+ +-- 12 lines: 2". repeat("\u2500", 23). "\u2502",
+ \ " 14 \u2502",
+ \ ]
+ call assert_equal(expect, lines)
+
+ set fillchars=fold:-,vert:\|
+ let lines=ScreenLines([1,3], winwidth(0)+1)
+ let expect=[
+ \ " 1 |",
+ \ "+ +-- 12 lines: 2". repeat("-", 23). "|",
+ \ " 14 |",
+ \ ]
+ call assert_equal(expect, lines)
+
+ set foldtext& fillchars& foldmethod& fdc&
+ bw!
+endfunc
+
+" check that win_ins_lines() and win_del_lines() work when t_cs is empty.
+func Test_scroll_without_region()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ set t_cs=
+ set laststatus=2
+ END
+ call writefile(lines, 'Xtestscroll', 'D')
+ let buf = RunVimInTerminal('-S Xtestscroll', #{rows: 10})
+
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_1', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_2', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_3', {})
+
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":set laststatus=0\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_4', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_5', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_6', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_display_listchars_precedes()
+ call NewWindow(10, 10)
+ " Need a physical line that wraps over the complete
+ " window size
+ call append(0, repeat('aaa aaa aa ', 10))
+ call append(1, repeat(['bbb bbb bbb bbb'], 2))
+ " remove blank trailing line
+ $d
+ set list nowrap
+ call cursor(1, 1)
+ " move to end of line and scroll 2 characters back
+ norm! $2zh
+ let lines=ScreenLines([1,4], winwidth(0)+1)
+ let expect = [
+ \ " aaa aa $ |",
+ \ "$ |",
+ \ "$ |",
+ \ "~ |",
+ \ ]
+ call assert_equal(expect, lines)
+ set list listchars+=precedes:< nowrap
+ call cursor(1, 1)
+ " move to end of line and scroll 2 characters back
+ norm! $2zh
+ let lines = ScreenLines([1,4], winwidth(0)+1)
+ let expect = [
+ \ "<aaa aa $ |",
+ \ "< |",
+ \ "< |",
+ \ "~ |",
+ \ ]
+ call assert_equal(expect, lines)
+ set wrap
+ call cursor(1, 1)
+ " the complete line should be displayed in the window
+ norm! $
+
+ let lines = ScreenLines([1,10], winwidth(0)+1)
+ let expect = [
+ \ "<aaa aaa a|",
+ \ "a aaa aaa |",
+ \ "aa aaa aaa|",
+ \ " aa aaa aa|",
+ \ "a aa aaa a|",
+ \ "aa aa aaa |",
+ \ "aaa aa aaa|",
+ \ " aaa aa aa|",
+ \ "a aaa aa a|",
+ \ "aa aaa aa |",
+ \ ]
+ call assert_equal(expect, lines)
+ set list& listchars& wrap&
+ bw!
+endfunc
+
+" Check that win_lines() works correctly with the number_only parameter=TRUE
+" should break early to optimize cost of drawing, but needs to make sure
+" that the number column is correctly highlighted.
+func Test_scroll_CursorLineNr_update()
+ CheckScreendump
+
+ let lines =<< trim END
+ hi CursorLineNr ctermfg=73 ctermbg=236
+ set nu rnu cursorline cursorlineopt=number
+ exe ":norm! o\<esc>110ia\<esc>"
+ END
+ let filename = 'Xdrawscreen'
+ call writefile(lines, filename, 'D')
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 5, cols: 50})
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_winline_rnu', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" check a long file name does not result in the hit-enter prompt
+func Test_edit_long_file_name()
+ CheckScreendump
+
+ let longName = 'x'->repeat(min([&columns, 255]))
+ call writefile([], longName, 'D')
+ let buf = RunVimInTerminal('-N -u NONE ' .. longName, #{rows: 8})
+
+ call VerifyScreenDump(buf, 'Test_long_file_name_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_unprintable_fileformats()
+ CheckScreendump
+
+ call writefile(["unix\r", "two"], 'Xunix.txt', 'D')
+ call writefile(["mac\r", "two"], 'Xmac.txt', 'D')
+ let lines =<< trim END
+ edit Xunix.txt
+ split Xmac.txt
+ edit ++ff=mac
+ END
+ let filename = 'Xunprintable'
+ call writefile(lines, filename, 'D')
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 9, cols: 50})
+ call VerifyScreenDump(buf, 'Test_display_unprintable_01', {})
+ call term_sendkeys(buf, "\<C-W>\<C-W>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_display_unprintable_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for scrolling that modifies buffer during visual block
+func Test_visual_block_scroll()
+ CheckScreendump
+
+ let lines =<< trim END
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set scrolloff=1
+ call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
+ call cursor(5, 1)
+ END
+
+ let filename = 'Xvisualblockmodifiedscroll'
+ call writefile(lines, filename, 'D')
+
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 7})
+ call term_sendkeys(buf, "V\<C-D>\<C-D>")
+
+ call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for clearing paren highlight when switching buffers
+func Test_matchparen_clear_highlight()
+ CheckScreendump
+
+ let lines =<< trim END
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set hidden
+ call setline(1, ['()'])
+ normal 0
+
+ func OtherBuffer()
+ enew
+ exe "normal iaa\<Esc>0"
+ endfunc
+ END
+ call writefile(lines, 'XMatchparenClear', 'D')
+ let buf = RunVimInTerminal('-S XMatchparenClear', #{rows: 5})
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
+
+ call term_sendkeys(buf, ":call OtherBuffer()\<CR>:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
+
+ call term_sendkeys(buf, "\<C-^>:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
+
+ call term_sendkeys(buf, "\<C-^>:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_display_scroll_at_topline()
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', #{cols: 20})
+ call term_sendkeys(buf, ":call setline(1, repeat('a', 21))\<CR>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "O\<Esc>")
+ call VerifyScreenDump(buf, 'Test_display_scroll_at_topline', #{rows: 4})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_display_scroll_update_visual()
+ CheckScreendump
+
+ let lines =<< trim END
+ set scrolloff=0
+ call setline(1, repeat(['foo'], 10))
+ call sign_define('foo', { 'text': '>' })
+ call sign_place(1, 'bar', 'foo', bufnr(), { 'lnum': 2 })
+ call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 })
+ autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif
+ END
+ call writefile(lines, 'XupdateVisual.vim', 'D')
+
+ let buf = RunVimInTerminal('-S XupdateVisual.vim', #{rows: 8, cols: 60})
+ call term_sendkeys(buf, "VG7kk")
+ call VerifyScreenDump(buf, 'Test_display_scroll_update_visual', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for 'eob' (EndOfBuffer) item in 'fillchars'
+func Test_eob_fillchars()
+ " default value
+ call assert_match('eob:\~', &fillchars)
+ " invalid values
+ call assert_fails(':set fillchars=eob:', 'E474:')
+ call assert_fails(':set fillchars=eob:xy', 'E474:')
+ call assert_fails(':set fillchars=eob:\255', 'E474:')
+ call assert_fails(':set fillchars=eob:<ff>', 'E474:')
+ call assert_fails(":set fillchars=eob:\x01", 'E474:')
+ call assert_fails(':set fillchars=eob:\\x01', 'E474:')
+ " default is ~
+ new
+ redraw
+ call assert_equal('~', Screenline(2))
+ set fillchars=eob:+
+ redraw
+ call assert_equal('+', Screenline(2))
+ set fillchars=eob:\
+ redraw
+ call assert_equal(' ', nr2char(screenchar(2, 1)))
+ set fillchars&
+ close
+endfunc
+
+" Test for 'foldopen', 'foldclose' and 'foldsep' in 'fillchars'
+func Test_fold_fillchars()
+ new
+ set fdc=2 foldenable foldmethod=manual
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ 2,4fold
+ " First check for the default setting for a closed fold
+ let lines = ScreenLines([1, 3], 8)
+ let expected = [
+ \ ' one ',
+ \ '+ +-- 3',
+ \ ' five '
+ \ ]
+ call assert_equal(expected, lines)
+ normal 2Gzo
+ " check the characters for an open fold
+ let lines = ScreenLines([1, 5], 8)
+ let expected = [
+ \ ' one ',
+ \ '- two ',
+ \ '| three ',
+ \ '| four ',
+ \ ' five '
+ \ ]
+ call assert_equal(expected, lines)
+
+ " change the setting
+ set fillchars=vert:\|,fold:-,eob:~,foldopen:[,foldclose:],foldsep:-
+
+ " check the characters for an open fold
+ let lines = ScreenLines([1, 5], 8)
+ let expected = [
+ \ ' one ',
+ \ '[ two ',
+ \ '- three ',
+ \ '- four ',
+ \ ' five '
+ \ ]
+ call assert_equal(expected, lines)
+
+ " check the characters for a closed fold
+ normal 2Gzc
+ let lines = ScreenLines([1, 3], 8)
+ let expected = [
+ \ ' one ',
+ \ '] +-- 3',
+ \ ' five '
+ \ ]
+ call assert_equal(expected, lines)
+
+ %bw!
+ set fillchars& fdc& foldmethod& foldenable&
+endfunc
+
+func Test_local_fillchars()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['window 1']->repeat(3))
+ setlocal fillchars=stl:1,stlnc:a,vert:=,eob:x
+ vnew
+ call setline(1, ['window 2']->repeat(3))
+ setlocal fillchars=stl:2,stlnc:b,vert:+,eob:y
+ new
+ wincmd J
+ call setline(1, ['window 3']->repeat(3))
+ setlocal fillchars=stl:3,stlnc:c,vert:<,eob:z
+ vnew
+ call setline(1, ['window 4']->repeat(3))
+ setlocal fillchars=stl:4,stlnc:d,vert:>,eob:o
+ END
+ call writefile(lines, 'Xdisplayfillchars', 'D')
+ let buf = RunVimInTerminal('-S Xdisplayfillchars', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_display_fillchars_1', {})
+
+ call term_sendkeys(buf, ":wincmd k\r")
+ call VerifyScreenDump(buf, 'Test_display_fillchars_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_display_linebreak_breakat()
+ new
+ vert resize 25
+ let _breakat = &breakat
+ setl signcolumn=yes linebreak breakat=) showbreak=+\
+ call setline(1, repeat('x', winwidth(0) - 2) .. ')abc')
+ let lines = ScreenLines([1, 2], 25)
+ let expected = [
+ \ ' xxxxxxxxxxxxxxxxxxxxxxx',
+ \ ' + )abc '
+ \ ]
+ call assert_equal(expected, lines)
+ %bw!
+ let &breakat=_breakat
+endfunc
+
+func Run_Test_display_lastline(euro)
+ let lines =<< trim END
+ call setline(1, ['aaa', 'b'->repeat(200)])
+ set display=truncate
+
+ vsplit
+ 100wincmd <
+ END
+ if a:euro != ''
+ let lines[2] = 'set fillchars=vert:\|,lastline:€'
+ endif
+ call writefile(lines, 'XdispLastline', 'D')
+ let buf = RunVimInTerminal('-S XdispLastline', #{rows: 10})
+ call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}1', {})
+
+ call term_sendkeys(buf, ":set display=lastline\<CR>")
+ call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}2', {})
+
+ call term_sendkeys(buf, ":100wincmd >\<CR>")
+ call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}3', {})
+
+ call term_sendkeys(buf, ":set display=truncate\<CR>")
+ call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}4', {})
+
+ call term_sendkeys(buf, ":close\<CR>")
+ call term_sendkeys(buf, ":3split\<CR>")
+ call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}5', {})
+
+ call term_sendkeys(buf, ":close\<CR>")
+ call term_sendkeys(buf, ":2vsplit\<CR>")
+ call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}6', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_display_lastline()
+ CheckScreendump
+
+ call Run_Test_display_lastline('')
+ call Run_Test_display_lastline('euro_')
+
+ call assert_fails(':set fillchars=lastline:', 'E474:')
+ call assert_fails(':set fillchars=lastline:〇', 'E474:')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_edit.vim b/src/testdir/test_edit.vim
new file mode 100644
index 0000000..79e3c3f
--- /dev/null
+++ b/src/testdir/test_edit.vim
@@ -0,0 +1,2109 @@
+" Test for edit functions
+
+if exists("+t_kD")
+ let &t_kD="[3;*~"
+endif
+
+source check.vim
+
+" Needed for testing basic rightleft: Test_edit_rightleft
+source view_util.vim
+
+" Needs to come first until the bug in getchar() is
+" fixed: https://groups.google.com/d/msg/vim_dev/fXL9yme4H4c/bOR-U6_bAQAJ
+func Test_edit_00b()
+ new
+ call setline(1, ['abc '])
+ inoreabbr <buffer> h here some more
+ call cursor(1, 4)
+ " <c-l> expands the abbreviation and ends insertmode
+ call feedkeys(":set im\<cr> h\<c-l>:set noim\<cr>", 'tix')
+ call assert_equal(['abc here some more '], getline(1,'$'))
+ iunabbr <buffer> h
+ bw!
+endfunc
+
+func Test_edit_01()
+ " set for Travis CI?
+ " set nocp noesckeys
+ new
+ " 1) empty buffer
+ call assert_equal([''], getline(1,'$'))
+ " 2) delete in an empty line
+ call feedkeys("i\<del>\<esc>", 'tnix')
+ call assert_equal([''], getline(1,'$'))
+ %d
+ " 3) delete one character
+ call setline(1, 'a')
+ call feedkeys("i\<del>\<esc>", 'tnix')
+ call assert_equal([''], getline(1,'$'))
+ %d
+ " 4) delete a multibyte character
+ call setline(1, "\u0401")
+ call feedkeys("i\<del>\<esc>", 'tnix')
+ call assert_equal([''], getline(1,'$'))
+ %d
+ " 5.1) delete linebreak with 'bs' option containing eol
+ let _bs=&bs
+ set bs=eol
+ call setline(1, ["abc def", "ghi jkl"])
+ call cursor(1, 1)
+ call feedkeys("A\<del>\<esc>", 'tnix')
+ call assert_equal(['abc defghi jkl'], getline(1, 2))
+ %d
+ " 5.2) delete linebreak with backspace option w/out eol
+ set bs=
+ call setline(1, ["abc def", "ghi jkl"])
+ call cursor(1, 1)
+ call feedkeys("A\<del>\<esc>", 'tnix')
+ call assert_equal(["abc def", "ghi jkl"], getline(1, 2))
+ let &bs=_bs
+ bw!
+endfunc
+
+func Test_edit_02()
+ " Change cursor position in InsertCharPre command
+ new
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ fu! DoIt(...)
+ call cursor(1, 4)
+ if len(a:000)
+ let v:char=a:1
+ endif
+ endfu
+ au InsertCharPre <buffer> :call DoIt('y')
+ call feedkeys("ix\<esc>", 'tnix')
+ call assert_equal(['abcy'], getline(1, '$'))
+ " Setting <Enter> in InsertCharPre
+ au! InsertCharPre <buffer> :call DoIt("\n")
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ call feedkeys("ix\<esc>", 'tnix')
+ call assert_equal(['abc', ''], getline(1, '$'))
+ %d
+ au! InsertCharPre
+ " Change cursor position in InsertEnter command
+ " 1) when setting v:char, keeps changed cursor position
+ au! InsertEnter <buffer> :call DoIt('y')
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ call feedkeys("ix\<esc>", 'tnix')
+ call assert_equal(['abxc'], getline(1, '$'))
+ " 2) when not setting v:char, restores changed cursor position
+ au! InsertEnter <buffer> :call DoIt()
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ call feedkeys("ix\<esc>", 'tnix')
+ call assert_equal(['xabc'], getline(1, '$'))
+ au! InsertEnter
+ delfu DoIt
+ bw!
+endfunc
+
+func Test_edit_03()
+ " Change cursor after <c-o> command to end of line
+ new
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ call feedkeys("i\<c-o>$y\<esc>", 'tnix')
+ call assert_equal(['abcy'], getline(1, '$'))
+ %d
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ call feedkeys("i\<c-o>80|y\<esc>", 'tnix')
+ call assert_equal(['abcy'], getline(1, '$'))
+ %d
+ call setline(1, 'abc')
+ call feedkeys("Ad\<c-o>:s/$/efg/\<cr>hij", 'tnix')
+ call assert_equal(['hijabcdefg'], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_edit_04()
+ " test for :stopinsert
+ new
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ call feedkeys("i\<c-o>:stopinsert\<cr>$", 'tnix')
+ call feedkeys("aX\<esc>", 'tnix')
+ call assert_equal(['abcX'], getline(1, '$'))
+ %d
+ bw!
+endfunc
+
+func Test_edit_05()
+ " test for folds being opened
+ new
+ call setline(1, ['abcX', 'abcX', 'zzzZ'])
+ call cursor(1, 1)
+ set foldmethod=manual foldopen+=insert
+ " create fold for those two lines
+ norm! Vjzf
+ call feedkeys("$ay\<esc>", 'tnix')
+ call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$'))
+ %d
+ call setline(1, ['abcX', 'abcX', 'zzzZ'])
+ call cursor(1, 1)
+ set foldmethod=manual foldopen-=insert
+ " create fold for those two lines
+ norm! Vjzf
+ call feedkeys("$ay\<esc>", 'tnix')
+ call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$'))
+ %d
+ bw!
+endfunc
+
+func Test_edit_06()
+ " Test in diff mode
+ CheckFeature diff
+ CheckExecutable diff
+ new
+ call setline(1, ['abc', 'xxx', 'yyy'])
+ vnew
+ call setline(1, ['abc', 'zzz', 'xxx', 'yyy'])
+ wincmd p
+ diffthis
+ wincmd p
+ diffthis
+ wincmd p
+ call cursor(2, 1)
+ norm! zt
+ call feedkeys("Ozzz\<esc>", 'tnix')
+ call assert_equal(['abc', 'zzz', 'xxx', 'yyy'], getline(1,'$'))
+ bw!
+ bw!
+endfunc
+
+func Test_edit_07()
+ " 1) Test with completion <c-l> when popupmenu is visible
+ new
+ call setline(1, 'J')
+
+ func! ListMonths()
+ call complete(col('.')-1, ['January', 'February', 'March',
+ \ 'April', 'May', 'June', 'July', 'August', 'September',
+ \ 'October', 'November', 'December'])
+ return ''
+ endfunc
+ inoremap <buffer> <F5> <C-R>=ListMonths()<CR>
+
+ call feedkeys("A\<f5>\<c-p>". repeat("\<down>", 6)."\<c-l>\<down>\<c-l>\<cr>", 'tx')
+ call assert_equal(['July'], getline(1,'$'))
+ " 1) Test completion when InsertCharPre kicks in
+ %d
+ call setline(1, 'J')
+ fu! DoIt()
+ if v:char=='u'
+ let v:char='an'
+ endif
+ endfu
+ au InsertCharPre <buffer> :call DoIt()
+ call feedkeys("A\<f5>\<c-p>u\<cr>\<c-l>\<cr>", 'tx')
+ call assert_equal(["Jan\<c-l>",''], 1->getline('$'))
+ %d
+ call setline(1, 'J')
+ call feedkeys("A\<f5>\<c-p>u\<down>\<c-l>\<cr>", 'tx')
+ call assert_equal(["January"], 1->getline('$'))
+
+ delfu ListMonths
+ delfu DoIt
+ iunmap <buffer> <f5>
+ bw!
+endfunc
+
+func Test_edit_08()
+ " reset insertmode from i_ctrl-r_=
+ let g:bufnr = bufnr('%')
+ new
+ call setline(1, ['abc'])
+ call cursor(1, 4)
+ call feedkeys(":set im\<cr>ZZZ\<c-r>=setbufvar(g:bufnr,'&im', 0)\<cr>",'tnix')
+ call assert_equal(['abZZZc'], getline(1,'$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ call assert_false(0, '&im')
+ bw!
+ unlet g:bufnr
+endfunc
+
+func Test_edit_09()
+ " test i_CTRL-\ combinations
+ new
+ call setline(1, ['abc', 'def', 'ghi'])
+ call cursor(1, 1)
+ " 1) CTRL-\ CTLR-N
+ call feedkeys(":set im\<cr>\<c-\>\<c-n>ccABC\<c-l>", 'txin')
+ call assert_equal(['ABC', 'def', 'ghi'], getline(1,'$'))
+ call setline(1, ['ABC', 'def', 'ghi'])
+ " 2) CTRL-\ CTLR-G
+ call feedkeys("j0\<c-\>\<c-g>ZZZ\<cr>\<c-l>", 'txin')
+ call assert_equal(['ABC', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
+ call feedkeys("I\<c-\>\<c-g>YYY\<c-l>", 'txin')
+ call assert_equal(['ABC', 'ZZZ', 'YYYdef', 'ghi'], getline(1,'$'))
+ set noinsertmode
+ " 3) CTRL-\ CTRL-O
+ call setline(1, ['ABC', 'ZZZ', 'def', 'ghi'])
+ call cursor(1, 1)
+ call feedkeys("A\<c-o>ix", 'txin')
+ call assert_equal(['ABxC', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
+ call feedkeys("A\<c-\>\<c-o>ix", 'txin')
+ call assert_equal(['ABxCx', 'ZZZ', 'def', 'ghi'], getline(1,'$'))
+ " 4) CTRL-\ a (should be inserted literally, not special after <c-\>
+ call setline(1, ['ABC', 'ZZZ', 'def', 'ghi'])
+ call cursor(1, 1)
+ call feedkeys("A\<c-\>a", 'txin')
+ call assert_equal(["ABC\<c-\>a", 'ZZZ', 'def', 'ghi'], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_edit_11()
+ " Test that indenting kicks in
+ new
+ set cindent
+ call setline(1, ['{', '', ''])
+ call cursor(2, 1)
+ call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
+ call cursor(3, 1)
+ call feedkeys("\<Insert>/* comment */", 'tnix')
+ call assert_equal(['{', "\<tab>int c;", "/* comment */"], getline(1, '$'))
+ " added changed cindentkeys slightly
+ set cindent cinkeys+=*/
+ call setline(1, ['{', '', ''])
+ call cursor(2, 1)
+ call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
+ call cursor(3, 1)
+ call feedkeys("i/* comment */", 'tnix')
+ call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */"], getline(1, '$'))
+ set cindent cinkeys+==end
+ call feedkeys("oend\<cr>\<esc>", 'tnix')
+ call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */", "\tend", ''], getline(1, '$'))
+ set cinkeys-==end
+ %d
+ " Use indentexpr instead of cindenting
+ func! Do_Indent()
+ if v:lnum == 3
+ return 3*shiftwidth()
+ else
+ return 2*shiftwidth()
+ endif
+ endfunc
+ setl indentexpr=Do_Indent() indentkeys+=*/
+ call setline(1, ['{', '', ''])
+ call cursor(2, 1)
+ call feedkeys("i\<c-f>int c;\<esc>", 'tnix')
+ call cursor(3, 1)
+ call feedkeys("i/* comment */", 'tnix')
+ call assert_equal(['{', "\<tab>\<tab>int c;", "\<tab>\<tab>\<tab>/* comment */"], getline(1, '$'))
+ set cinkeys&vim indentkeys&vim
+ set nocindent indentexpr=
+ delfu Do_Indent
+ bw!
+endfunc
+
+func Test_edit_11_indentexpr()
+ " Test that indenting kicks in
+ new
+ " Use indentexpr instead of cindenting
+ func! Do_Indent()
+ let pline=prevnonblank(v:lnum)
+ if empty(getline(v:lnum))
+ if getline(pline) =~ 'if\|then'
+ return shiftwidth()
+ else
+ return 0
+ endif
+ else
+ return 0
+ endif
+ endfunc
+ setl indentexpr=Do_Indent() indentkeys+=0=then,0=fi
+ call setline(1, ['if [ $this ]'])
+ call cursor(1, 1)
+ call feedkeys("othen\<cr>that\<cr>fi", 'tnix')
+ call assert_equal(['if [ $this ]', "then", "\<tab>that", "fi"], getline(1, '$'))
+ set cinkeys&vim indentkeys&vim
+ set nocindent indentexpr=
+ delfu Do_Indent
+
+ " Using a script-local function
+ func s:NewIndentExpr()
+ endfunc
+ set indentexpr=s:NewIndentExpr()
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
+ set indentexpr=<SID>NewIndentExpr()
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
+ setlocal indentexpr=
+ setglobal indentexpr=s:NewIndentExpr()
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
+ call assert_equal('', &indentexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
+ bw!
+ setglobal indentexpr=<SID>NewIndentExpr()
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &g:indentexpr)
+ call assert_equal('', &indentexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr)
+ bw!
+ set indentexpr&
+
+ bw!
+endfunc
+
+" Test changing indent in replace mode
+func Test_edit_12()
+ new
+ call setline(1, ["\tabc", "\tdef"])
+ call cursor(2, 4)
+ call feedkeys("R^\<c-d>", 'tnix')
+ call assert_equal(["\tabc", "def"], getline(1, '$'))
+ call assert_equal([0, 2, 2, 0], '.'->getpos())
+ %d
+ call setline(1, ["\tabc", "\t\tdef"])
+ call cursor(2, 2)
+ call feedkeys("R^\<c-d>", 'tnix')
+ call assert_equal(["\tabc", "def"], getline(1, '$'))
+ call assert_equal([0, 2, 1, 0], getpos('.'))
+ %d
+ call setline(1, ["\tabc", "\t\tdef"])
+ call cursor(2, 2)
+ call feedkeys("R\<c-t>", 'tnix')
+ call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
+ call assert_equal([0, 2, 2, 0], getpos('.'))
+ bw!
+ 10vnew
+ call setline(1, ["\tabc", "\t\tdef"])
+ call cursor(2, 2)
+ call feedkeys("R\<c-t>", 'tnix')
+ call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
+ call assert_equal([0, 2, 2, 0], getpos('.'))
+ %d
+ set sw=4
+ call setline(1, ["\tabc", "\t\tdef"])
+ call cursor(2, 2)
+ call feedkeys("R\<c-t>\<c-t>", 'tnix')
+ call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
+ call assert_equal([0, 2, 2, 0], getpos('.'))
+ %d
+ call setline(1, ["\tabc", "\t\tdef"])
+ call cursor(2, 2)
+ call feedkeys("R\<c-t>\<c-t>", 'tnix')
+ call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$'))
+ call assert_equal([0, 2, 2, 0], getpos('.'))
+ set sw&
+
+ " In replace mode, after hitting enter in a line with tab characters,
+ " pressing backspace should restore the tab characters.
+ %d
+ setlocal autoindent backspace=2
+ call setline(1, "\tone\t\ttwo")
+ exe "normal ggRred\<CR>six" .. repeat("\<BS>", 8)
+ call assert_equal(["\tone\t\ttwo"], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_edit_13()
+ " Test smartindenting
+ new
+ set smartindent autoindent
+ call setline(1, ["\tabc"])
+ call feedkeys("A {\<cr>more\<cr>}\<esc>", 'tnix')
+ call assert_equal(["\tabc {", "\t\tmore", "\t}"], getline(1, '$'))
+ set smartindent& autoindent&
+ bwipe!
+
+ " Test autoindent removing indent of blank line.
+ new
+ call setline(1, ' foo bar baz')
+ set autoindent
+ exe "normal 0eea\<CR>\<CR>\<Esc>"
+ call assert_equal(" foo bar", getline(1))
+ call assert_equal("", getline(2))
+ call assert_equal(" baz", getline(3))
+ set autoindent&
+
+ " pressing <C-U> to erase line should keep the indent with 'autoindent'
+ set backspace=2 autoindent
+ %d
+ exe "normal i\tone\<CR>three\<C-U>two"
+ call assert_equal(["\tone", "\ttwo"], getline(1, '$'))
+ set backspace& autoindent&
+
+ bwipe!
+endfunc
+
+" Test for autoindent removing indent when insert mode is stopped. Some parts
+" of the code is exercised only when interactive mode is used. So use Vim in a
+" terminal.
+func Test_autoindent_remove_indent()
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('-N Xarifile', {'rows': 6, 'cols' : 20})
+ call TermWait(buf)
+ call term_sendkeys(buf, ":set autoindent\n")
+ " leaving insert mode in a new line with indent added by autoindent, should
+ " remove the indent.
+ call term_sendkeys(buf, "i\<Tab>foo\<CR>\<Esc>")
+ " Need to delay for some time, otherwise the code in getchar.c will not be
+ " exercised.
+ call TermWait(buf, 50)
+ " when a line is wrapped and the cursor is at the start of the second line,
+ " leaving insert mode, should move the cursor back to the first line.
+ call term_sendkeys(buf, "o" .. repeat('x', 20) .. "\<Esc>")
+ " Need to delay for some time, otherwise the code in getchar.c will not be
+ " exercised.
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":w\n")
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+ call assert_equal(["\tfoo", '', repeat('x', 20)], readfile('Xarifile'))
+ call delete('Xarifile')
+endfunc
+
+func Test_edit_CR()
+ " Test for <CR> in insert mode
+ " basically only in quickfix mode it's tested, the rest
+ " has been taken care of by other tests
+ CheckFeature quickfix
+ botright new
+ call writefile(range(1, 10), 'Xqflist.txt', 'D')
+ call setqflist([{'filename': 'Xqflist.txt', 'lnum': 2}])
+ copen
+ set modifiable
+ call feedkeys("A\<cr>", 'tnix')
+ call assert_equal('Xqflist.txt', bufname(''))
+ call assert_equal(2, line('.'))
+ cclose
+ botright new
+ call setloclist(0, [{'filename': 'Xqflist.txt', 'lnum': 10}])
+ lopen
+ set modifiable
+ call feedkeys("A\<cr>", 'tnix')
+ call assert_equal('Xqflist.txt', bufname(''))
+ call assert_equal(10, line('.'))
+ call feedkeys("A\<Enter>", 'tnix')
+ call feedkeys("A\<kEnter>", 'tnix')
+ call feedkeys("A\n", 'tnix')
+ call feedkeys("A\r", 'tnix')
+ call assert_equal(map(range(1, 10), 'string(v:val)') + ['', '', '', ''], getline(1, '$'))
+
+ bw!
+ lclose
+endfunc
+
+func Test_edit_CTRL_()
+ CheckFeature rightleft
+ " disabled for Windows builds, why?
+ CheckNotMSWindows
+ let _encoding=&encoding
+ set encoding=utf-8
+ " Test for CTRL-_
+ new
+ call setline(1, ['abc'])
+ call cursor(1, 1)
+ call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
+ call assert_equal(["\<C-_>xyzabc"], getline(1, '$'))
+ call assert_false(&revins)
+ set ari
+ call setline(1, ['abc'])
+ call cursor(1, 1)
+ call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
+ call assert_equal(["æèñabc"], getline(1, '$'))
+ call assert_true(&revins)
+ call setline(1, ['abc'])
+ call cursor(1, 1)
+ call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
+ call assert_equal(["xyzabc"], getline(1, '$'))
+ call assert_false(&revins)
+ set noari
+ let &encoding=_encoding
+ bw!
+endfunc
+
+" needs to come first, to have the @. register empty
+func Test_edit_00a_CTRL_A()
+ " Test pressing CTRL-A
+ new
+ call setline(1, repeat([''], 5))
+ call cursor(1, 1)
+ try
+ call feedkeys("A\<NUL>", 'tnix')
+ catch /^Vim\%((\a\+)\)\=:E29/
+ call assert_true(1, 'E29 error caught')
+ endtry
+ call cursor(1, 1)
+ call feedkeys("Afoobar \<esc>", 'tnix')
+ call cursor(2, 1)
+ call feedkeys("A\<c-a>more\<esc>", 'tnix')
+ call cursor(3, 1)
+ call feedkeys("A\<NUL>and more\<esc>", 'tnix')
+ call assert_equal(['foobar ', 'foobar more', 'foobar morend more', '', ''], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_edit_CTRL_EY()
+ " Ctrl-E/ Ctrl-Y in insert mode completion to scroll
+ 10new
+ call setline(1, range(1, 100))
+ call cursor(30, 1)
+ norm! z.
+ call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix')
+ call assert_equal(30, winsaveview()['topline'])
+ call assert_equal([0, 30, 2, 0], getpos('.'))
+ call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix')
+ call feedkeys("A\<c-x>".repeat("\<c-y>", 10), 'tnix')
+ call assert_equal(21, winsaveview()['topline'])
+ call assert_equal([0, 30, 2, 0], getpos('.'))
+ bw!
+endfunc
+
+func Test_edit_CTRL_G()
+ new
+ call setline(1, ['foobar', 'foobar', 'foobar'])
+ call cursor(2, 4)
+ call feedkeys("ioooooooo\<c-g>k\<c-r>.\<esc>", 'tnix')
+ call assert_equal(['foooooooooobar', 'foooooooooobar', 'foobar'], getline(1, '$'))
+ call assert_equal([0, 1, 11, 0], getpos('.'))
+ call feedkeys("i\<c-g>k\<esc>", 'tnix')
+ call assert_equal([0, 1, 10, 0], getpos('.'))
+ call cursor(2, 4)
+ call feedkeys("i\<c-g>jzzzz\<esc>", 'tnix')
+ call assert_equal(['foooooooooobar', 'foooooooooobar', 'foozzzzbar'], getline(1, '$'))
+ call assert_equal([0, 3, 7, 0], getpos('.'))
+ call feedkeys("i\<c-g>j\<esc>", 'tnix')
+ call assert_equal([0, 3, 6, 0], getpos('.'))
+ call assert_nobeep("normal! i\<c-g>\<esc>")
+ bw!
+endfunc
+
+func Test_edit_CTRL_I()
+ " Tab in completion mode
+ let path=expand("%:p:h")
+ new
+ call setline(1, [path. "/", ''])
+ call feedkeys("Arunt\<c-x>\<c-f>\<tab>\<cr>\<esc>", 'tnix')
+ call assert_match('runtest\.vim', getline(1))
+ %d
+ call writefile(['one', 'two', 'three'], 'Xinclude.txt', 'D')
+ let include='#include Xinclude.txt'
+ call setline(1, [include, ''])
+ call cursor(2, 1)
+ call feedkeys("A\<c-x>\<tab>\<cr>\<esc>", 'tnix')
+ call assert_equal([include, 'one', ''], getline(1, '$'))
+ call feedkeys("2ggC\<c-x>\<tab>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal([include, 'two', ''], getline(1, '$'))
+ call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal([include, 'three', ''], getline(1, '$'))
+ call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal([include, '', ''], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_edit_CTRL_K()
+ " Test pressing CTRL-K (basically only dictionary completion and digraphs
+ " the rest is already covered
+ call writefile(['A', 'AA', 'AAA', 'AAAA'], 'Xdictionary.txt', 'D')
+ set dictionary=Xdictionary.txt
+ new
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<cr>\<esc>", 'tnix')
+ call assert_equal(['AA', ''], getline(1, '$'))
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal(['AAA'], getline(1, '$'))
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal(['AAAA'], getline(1, '$'))
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal(['A'], getline(1, '$'))
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal(['AA'], getline(1, '$'))
+
+ " press an unexpected key after dictionary completion
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<c-]>\<cr>\<esc>", 'tnix')
+ call assert_equal(['AA', ''], getline(1, '$'))
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<c-s>\<cr>\<esc>", 'tnix')
+ call assert_equal(["AA\<c-s>", ''], getline(1, '$'))
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-k>\<c-f>\<cr>\<esc>", 'tnix')
+ call assert_equal(["AA\<c-f>", ''], getline(1, '$'))
+
+ set dictionary=
+ %d
+ call setline(1, 'A')
+ call cursor(1, 1)
+ let v:testing = 1
+ try
+ call feedkeys("A\<c-x>\<c-k>\<esc>", 'tnix')
+ catch
+ " error sleeps 2 seconds, when v:testing is not set
+ let v:testing = 0
+ endtry
+
+ call test_override("char_avail", 1)
+ set showcmd
+ %d
+ call feedkeys("A\<c-k>a:\<esc>", 'tnix')
+ call assert_equal(['ä'], getline(1, '$'))
+ call test_override("char_avail", 0)
+ set noshowcmd
+
+ bw!
+endfunc
+
+func Test_edit_CTRL_L()
+ " Test Ctrl-X Ctrl-L (line completion)
+ new
+ set complete=.
+ call setline(1, ['one', 'two', 'three', '', '', '', ''])
+ call cursor(4, 1)
+ call feedkeys("A\<c-x>\<c-l>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-n>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-p>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<c-p>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
+ set complete=
+ call cursor(5, 1)
+ call feedkeys("A\<c-x>\<c-l>\<c-p>\<c-n>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 'three', "\<c-l>\<c-p>\<c-n>", '', ''], getline(1, '$'))
+ set complete&
+ %d
+ if has("conceal") && has("syntax")
+ call setline(1, ['foo', 'bar', 'foobar'])
+ call test_override("char_avail", 1)
+ set conceallevel=2 concealcursor=n
+ syn on
+ syn match ErrorMsg "^bar"
+ call matchadd("Conceal", 'oo', 10, -1, {'conceal': 'X'})
+ func! DoIt()
+ let g:change=1
+ endfunc
+ au! TextChangedI <buffer> :call DoIt()
+
+ call cursor(2, 1)
+ call assert_false(exists("g:change"))
+ call feedkeys("A \<esc>", 'tnix')
+ call assert_equal(['foo', 'bar ', 'foobar'], getline(1, '$'))
+ call assert_equal(1, g:change)
+
+ call test_override("char_avail", 0)
+ call clearmatches()
+ syn off
+ au! TextChangedI
+ delfu DoIt
+ unlet! g:change
+ endif
+ bw!
+endfunc
+
+func Test_edit_CTRL_N()
+ " Check keyword completion
+ for e in ['latin1', 'utf-8']
+ exe 'set encoding=' .. e
+ new
+ set complete=.
+ call setline(1, ['INFER', 'loWER', '', '', ])
+ call cursor(3, 1)
+ call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix")
+ call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$'), e)
+ %d
+ call setline(1, ['INFER', 'loWER', '', '', ])
+ call cursor(3, 1)
+ set ignorecase infercase
+ call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix")
+ call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$'), e)
+ set noignorecase noinfercase
+ %d
+ call setline(1, ['one word', 'two word'])
+ exe "normal! Goo\<C-P>\<C-X>\<C-P>"
+ call assert_equal('one word', getline(3))
+ %d
+ set complete&
+ bw!
+ endfor
+endfunc
+
+func Test_edit_CTRL_O()
+ " Check for CTRL-O in insert mode
+ new
+ inoreabbr <buffer> h here some more
+ call setline(1, ['abc', 'def'])
+ call cursor(1, 1)
+ " Ctrl-O after an abbreviation
+ exe "norm A h\<c-o>:set nu\<cr> text"
+ call assert_equal(['abc here some more text', 'def'], getline(1, '$'))
+ call assert_true(&nu)
+ set nonu
+ iunabbr <buffer> h
+ " Ctrl-O at end of line with 've'=onemore
+ call cursor(1, 1)
+ call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix')
+ call assert_equal([0, 1, 23, 0], g:a)
+ call cursor(1, 1)
+ set ve=onemore
+ call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix')
+ call assert_equal([0, 1, 24, 0], g:a)
+ set ve=
+ unlet! g:a
+ bw!
+endfunc
+
+func Test_edit_CTRL_R()
+ " Insert Register
+ new
+ call test_override("ALL", 1)
+ set showcmd
+ call feedkeys("AFOOBAR eins zwei\<esc>", 'tnix')
+ call feedkeys("O\<c-r>.", 'tnix')
+ call feedkeys("O\<c-r>=10*500\<cr>\<esc>", 'tnix')
+ call feedkeys("O\<c-r>=getreg('=', 1)\<cr>\<esc>", 'tnix')
+ call assert_equal(["getreg('=', 1)", '5000', "FOOBAR eins zwei", "FOOBAR eins zwei"], getline(1, '$'))
+ call test_override("ALL", 0)
+ set noshowcmd
+ bw!
+endfunc
+
+func Test_edit_CTRL_S()
+ " Test pressing CTRL-S (basically only spellfile completion)
+ " the rest is already covered
+ new
+ if !has("spell")
+ call setline(1, 'vim')
+ call feedkeys("A\<c-x>ss\<cr>\<esc>", 'tnix')
+ call assert_equal(['vims', ''], getline(1, '$'))
+ bw!
+ return
+ endif
+ call setline(1, 'vim')
+ " spell option not yet set
+ try
+ call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix')
+ catch /^Vim\%((\a\+)\)\=:E756/
+ call assert_true(1, 'error caught')
+ endtry
+ call assert_equal(['vim', ''], getline(1, '$'))
+ %d
+ setl spell spelllang=en
+ call setline(1, 'vim')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix')
+ call assert_equal(['Vim', ''], getline(1, '$'))
+ %d
+ call setline(1, 'vim')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-s>\<down>\<cr>\<esc>", 'tnix')
+ call assert_equal(['Aim'], getline(1, '$'))
+ %d
+ call setline(1, 'vim')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix')
+ call assert_equal(['vim', ''], getline(1, '$'))
+ %d
+ " empty buffer
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix')
+ call assert_equal(['', ''], getline(1, '$'))
+ setl nospell
+ bw!
+endfunc
+
+func Test_edit_CTRL_T()
+ " Check for CTRL-T and CTRL-X CTRL-T in insert mode
+ " 1) increase indent
+ new
+ call setline(1, "abc")
+ call cursor(1, 1)
+ call feedkeys("A\<c-t>xyz", 'tnix')
+ call assert_equal(["\<tab>abcxyz"], getline(1, '$'))
+ " 2) also when paste option is set
+ set paste
+ call setline(1, "abc")
+ call cursor(1, 1)
+ call feedkeys("A\<c-t>xyz", 'tnix')
+ call assert_equal(["\<tab>abcxyz"], getline(1, '$'))
+ set nopaste
+ " CTRL-X CTRL-T (thesaurus complete)
+ call writefile(['angry furious mad enraged'], 'Xthesaurus', 'D')
+ set thesaurus=Xthesaurus
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix')
+ call assert_equal(['mad', ''], getline(1, '$'))
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['angry', ''], getline(1, '$'))
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['furious', ''], getline(1, '$'))
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['enraged', ''], getline(1, '$'))
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['mad', ''], getline(1, '$'))
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['mad', ''], getline(1, '$'))
+ " Using <c-p> <c-n> when 'complete' is empty
+ set complete=
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['angry', ''], getline(1, '$'))
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-p>\<cr>\<esc>", 'tnix')
+ call assert_equal(['mad', ''], getline(1, '$'))
+ set complete&
+
+ set thesaurus=
+ %d
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ let v:testing = 1
+ try
+ call feedkeys("A\<c-x>\<c-t>\<esc>", 'tnix')
+ catch
+ " error sleeps 2 seconds, when v:testing is not set
+ let v:testing = 0
+ endtry
+ call assert_equal(['mad'], getline(1, '$'))
+ bw!
+endfunc
+
+" Test thesaurus completion with different encodings
+func Test_thesaurus_complete_with_encoding()
+ call writefile(['angry furious mad enraged'], 'Xthesaurus', 'D')
+ set thesaurus=Xthesaurus
+ for e in ['latin1', 'utf-8']
+ exe 'set encoding=' .. e
+ new
+ call setline(1, 'mad')
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix')
+ call assert_equal(['mad', ''], getline(1, '$'))
+ bw!
+ endfor
+ set thesaurus=
+endfunc
+
+" Test 'thesaurusfunc'
+func MyThesaurus(findstart, base)
+ let mythesaurus = [
+ \ #{word: "happy",
+ \ synonyms: "cheerful,blissful,flying high,looking good,peppy"},
+ \ #{word: "kind",
+ \ synonyms: "amiable,bleeding-heart,heart in right place"}]
+ if a:findstart
+ " locate the start of the word
+ let line = getline('.')
+ let start = col('.') - 1
+ while start > 0 && line[start - 1] =~ '\a'
+ let start -= 1
+ endwhile
+ return start
+ else
+ " find strings matching with "a:base"
+ let res = []
+ for w in mythesaurus
+ if w.word =~ '^' . a:base
+ call add(res, w.word)
+ call extend(res, split(w.synonyms, ","))
+ endif
+ endfor
+ return res
+ endif
+endfunc
+
+func Test_thesaurus_func()
+ new
+ set thesaurus=notused
+ set thesaurusfunc=NotUsed
+ setlocal thesaurusfunc=MyThesaurus
+ call setline(1, "an ki")
+ call cursor(1, 1)
+ call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['an amiable', ''], getline(1, '$'))
+
+ setlocal thesaurusfunc=NonExistingFunc
+ call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')
+
+ setlocal thesaurusfunc=
+ set thesaurusfunc=NonExistingFunc
+ call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')
+ %bw!
+
+ set thesaurusfunc=
+ set thesaurus=
+endfunc
+
+func Test_edit_CTRL_U()
+ " Test 'completefunc'
+ new
+ " -1, -2 and -3 are special return values
+ let g:special=0
+ fun! CompleteMonths(findstart, base)
+ if a:findstart
+ " locate the start of the word
+ return g:special
+ else
+ " find months matching with "a:base"
+ let res = []
+ for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")
+ if m =~ '^\c'.a:base
+ call add(res, {'word': m, 'abbr': m.' Month', 'icase': 0})
+ endif
+ endfor
+ return {'words': res, 'refresh': 'always'}
+ endif
+ endfun
+ set completefunc=CompleteMonths
+ call setline(1, ['', ''])
+ call cursor(1, 1)
+ call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
+ call assert_equal(['X', '', ''], getline(1, '$'))
+ %d
+ let g:special=-1
+ call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
+ call assert_equal(['XJan', ''], getline(1, '$'))
+ %d
+ let g:special=-2
+ call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
+ call assert_equal(['X', ''], getline(1, '$'))
+ %d
+ let g:special=-3
+ call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
+ call assert_equal(['X', ''], getline(1, '$'))
+ %d
+ let g:special=0
+ call feedkeys("AM\<c-x>\<c-u>\<cr>\<esc>", 'tnix')
+ call assert_equal(['Mar', ''], getline(1, '$'))
+ %d
+ call feedkeys("AM\<c-x>\<c-u>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['May', ''], getline(1, '$'))
+ %d
+ call feedkeys("AM\<c-x>\<c-u>\<c-n>\<c-n>\<cr>\<esc>", 'tnix')
+ call assert_equal(['M', ''], getline(1, '$'))
+ delfu CompleteMonths
+ %d
+ try
+ call feedkeys("A\<c-x>\<c-u>", 'tnix')
+ call assert_fails(1, 'unknown completion function')
+ catch /^Vim\%((\a\+)\)\=:E117/
+ call assert_true(1, 'E117 error caught')
+ endtry
+ set completefunc=
+ bw!
+endfunc
+
+func Test_edit_completefunc_delete()
+ func CompleteFunc(findstart, base)
+ if a:findstart == 1
+ return col('.') - 1
+ endif
+ normal dd
+ return ['a', 'b']
+ endfunc
+ new
+ set completefunc=CompleteFunc
+ call setline(1, ['', 'abcd', ''])
+ 2d
+ call assert_fails("normal 2G$a\<C-X>\<C-U>", 'E565:')
+ bwipe!
+endfunc
+
+
+func Test_edit_CTRL_Z()
+ " Ctrl-Z when insertmode is not set inserts it literally
+ new
+ call setline(1, 'abc')
+ call feedkeys("A\<c-z>\<esc>", 'tnix')
+ call assert_equal(["abc\<c-z>"], getline(1,'$'))
+ bw!
+ " TODO: How to Test Ctrl-Z in insert mode, e.g. suspend?
+endfunc
+
+func Test_edit_DROP()
+ CheckFeature dnd
+ new
+ call setline(1, ['abc def ghi'])
+ call cursor(1, 1)
+ try
+ call feedkeys("i\<Drop>\<Esc>", 'tnix')
+ call assert_fails(1, 'Invalid register name')
+ catch /^Vim\%((\a\+)\)\=:E353/
+ call assert_true(1, 'error caught')
+ endtry
+ bw!
+endfunc
+
+func Test_edit_CTRL_V()
+ new
+ call setline(1, ['abc'])
+ call cursor(2, 1)
+
+ " force some redraws
+ set showmode showcmd
+ call test_override('char_avail', 1)
+
+ call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
+ call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
+
+ if has("rightleft") && exists("+rl")
+ set rl
+ call setline(1, ['abc'])
+ call cursor(2, 1)
+ call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
+ call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
+ set norl
+ endif
+
+ set noshowmode showcmd
+ call test_override('char_avail', 0)
+
+ " No modifiers should be applied to the char typed using i_CTRL-V_digit.
+ call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix')
+ call assert_equal('LcL<C-F2>πjπ<M-F3>', getline(2))
+
+ if has('osx')
+ " A char with a modifier should not be a valid char for i_CTRL-V_digit.
+ call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix')
+ call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3))
+ endif
+
+ bw!
+endfunc
+
+func Test_edit_F1()
+ CheckFeature quickfix
+
+ " Pressing <f1>
+ new
+ call feedkeys(":set im\<cr>\<f1>\<c-l>", 'tnix')
+ set noinsertmode
+ call assert_equal('help', &buftype)
+ bw
+ bw
+endfunc
+
+func Test_edit_F21()
+ " Pressing <f21>
+ " sends a netbeans command
+ CheckFeature netbeans_intg
+ new
+ " I have no idea what this is supposed to do :)
+ call feedkeys("A\<F21>\<F1>\<esc>", 'tnix')
+ bw
+endfunc
+
+func Test_edit_HOME_END()
+ " Test Home/End Keys
+ new
+ set foldopen+=hor
+ call setline(1, ['abc', 'def'])
+ call cursor(1, 1)
+ call feedkeys("AX\<Home>Y\<esc>", 'tnix')
+ call cursor(2, 1)
+ call feedkeys("iZ\<End>Y\<esc>", 'tnix')
+ call assert_equal(['YabcX', 'ZdefY'], getline(1, '$'))
+
+ set foldopen-=hor
+ bw!
+endfunc
+
+func Test_edit_INS()
+ " Test for Pressing <Insert>
+ new
+ call setline(1, ['abc', 'def'])
+ call cursor(1, 1)
+ call feedkeys("i\<Insert>ZYX>", 'tnix')
+ call assert_equal(['ZYX>', 'def'], getline(1, '$'))
+ call setline(1, ['abc', 'def'])
+ call cursor(1, 1)
+ call feedkeys("i\<Insert>Z\<Insert>YX>", 'tnix')
+ call assert_equal(['ZYX>bc', 'def'], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_edit_LEFT_RIGHT()
+ " Left, Shift-Left, Right, Shift-Right
+ new
+ call setline(1, ['abc def ghi', 'ABC DEF GHI', 'ZZZ YYY XXX'])
+ let _ww=&ww
+ set ww=
+ call cursor(2, 1)
+ call feedkeys("i\<left>\<esc>", 'tnix')
+ call assert_equal([0, 2, 1, 0], getpos('.'))
+ " Is this a bug, <s-left> does not respect whichwrap option
+ call feedkeys("i\<s-left>\<esc>", 'tnix')
+ call assert_equal([0, 1, 8, 0], getpos('.'))
+ call feedkeys("i". repeat("\<s-left>", 3). "\<esc>", 'tnix')
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ call feedkeys("i\<right>\<esc>", 'tnix')
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ call feedkeys("i\<right>\<right>\<esc>", 'tnix')
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+ call feedkeys("A\<right>\<esc>", 'tnix')
+ call assert_equal([0, 1, 11, 0], getpos('.'))
+ call feedkeys("A\<s-right>\<esc>", 'tnix')
+ call assert_equal([0, 2, 1, 0], getpos('.'))
+ call feedkeys("i\<s-right>\<esc>", 'tnix')
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ call cursor(3, 11)
+ call feedkeys("A\<right>\<esc>", 'tnix')
+ call feedkeys("A\<s-right>\<esc>", 'tnix')
+ call assert_equal([0, 3, 11, 0], getpos('.'))
+ call cursor(2, 11)
+ " <S-Right> does not respect 'whichwrap' option
+ call feedkeys("A\<s-right>\<esc>", 'tnix')
+ call assert_equal([0, 3, 1, 0], getpos('.'))
+ " Check motion when 'whichwrap' contains cursor keys for insert mode
+ set ww+=[,]
+ call cursor(2, 1)
+ call feedkeys("i\<left>\<esc>", 'tnix')
+ call assert_equal([0, 1, 11, 0], getpos('.'))
+ call cursor(2, 11)
+ call feedkeys("A\<right>\<esc>", 'tnix')
+ call assert_equal([0, 3, 1, 0], getpos('.'))
+ call cursor(2, 11)
+ call feedkeys("A\<s-right>\<esc>", 'tnix')
+ call assert_equal([0, 3, 1, 0], getpos('.'))
+ let &ww = _ww
+ bw!
+endfunc
+
+func Test_edit_MOUSE()
+ " This is a simple test, since we not really using the mouse here
+ CheckFeature mouse
+ 10new
+ call setline(1, range(1, 100))
+ call cursor(1, 1)
+ call assert_equal(1, line('w0'))
+ call assert_equal(10, line('w$'))
+ set mouse=a
+ " One scroll event moves three lines.
+ call feedkeys("A\<ScrollWheelDown>\<esc>", 'tnix')
+ call assert_equal(4, line('w0'))
+ call assert_equal(13, line('w$'))
+ " This should move by one page down.
+ call feedkeys("A\<S-ScrollWheelDown>\<esc>", 'tnix')
+ call assert_equal(14, line('w0'))
+ set nostartofline
+ " Another page down.
+ call feedkeys("A\<C-ScrollWheelDown>\<esc>", 'tnix')
+ call assert_equal(24, line('w0'))
+
+ call assert_equal([0, 24, 2, 0], getpos('.'))
+ call test_setmouse(4, 3)
+ call feedkeys("A\<LeftMouse>\<esc>", 'tnix')
+ call assert_equal([0, 27, 2, 0], getpos('.'))
+ set mousemodel=extend
+ call test_setmouse(5, 3)
+ call feedkeys("A\<RightMouse>\<esc>\<esc>", 'tnix')
+ call assert_equal([0, 28, 2, 0], getpos('.'))
+ set mousemodel&
+ call cursor(1, 100)
+ norm! zt
+ " this should move by a screen up, but when the test
+ " is run, it moves up to the top of the buffer...
+ call feedkeys("A\<ScrollWheelUp>\<esc>", 'tnix')
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ call cursor(1, 30)
+ norm! zt
+ call feedkeys("A\<S-ScrollWheelUp>\<esc>", 'tnix')
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ call cursor(1, 30)
+ norm! zt
+ call feedkeys("A\<C-ScrollWheelUp>\<esc>", 'tnix')
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ %d
+ call setline(1, repeat(["12345678901234567890"], 100))
+ call cursor(2, 1)
+ call feedkeys("A\<ScrollWheelRight>\<esc>", 'tnix')
+ call assert_equal([0, 2, 20, 0], getpos('.'))
+ call feedkeys("A\<ScrollWheelLeft>\<esc>", 'tnix')
+ call assert_equal([0, 2, 20, 0], getpos('.'))
+ call feedkeys("A\<S-ScrollWheelRight>\<esc>", 'tnix')
+ call assert_equal([0, 2, 20, 0], getpos('.'))
+ call feedkeys("A\<S-ScrollWheelLeft>\<esc>", 'tnix')
+ call assert_equal([0, 2, 20, 0], getpos('.'))
+ call feedkeys("A\<C-ScrollWheelRight>\<esc>", 'tnix')
+ call assert_equal([0, 2, 20, 0], getpos('.'))
+ call feedkeys("A\<C-ScrollWheelLeft>\<esc>", 'tnix')
+ call assert_equal([0, 2, 20, 0], getpos('.'))
+ set mouse& startofline
+ bw!
+endfunc
+
+func Test_edit_PAGEUP_PAGEDOWN()
+ 10new
+ call setline(1, repeat(['abc def ghi'], 30))
+ call cursor(1, 1)
+ call feedkeys("i\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 9, 1, 0], getpos('.'))
+ call feedkeys("i\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 17, 1, 0], getpos('.'))
+ call feedkeys("i\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 25, 1, 0], getpos('.'))
+ call feedkeys("i\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 30, 1, 0], getpos('.'))
+ call feedkeys("i\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 30, 1, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 29, 1, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 21, 1, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 13, 1, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 5, 11, 0], getpos('.'))
+ " <S-Up> is the same as <PageUp>
+ " <S-Down> is the same as <PageDown>
+ call cursor(1, 1)
+ call feedkeys("i\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 9, 1, 0], getpos('.'))
+ call feedkeys("i\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 17, 1, 0], getpos('.'))
+ call feedkeys("i\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 25, 1, 0], getpos('.'))
+ call feedkeys("i\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 30, 1, 0], getpos('.'))
+ call feedkeys("i\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 30, 1, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 29, 1, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 21, 1, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 13, 1, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 5, 11, 0], getpos('.'))
+ set nostartofline
+ call cursor(30, 11)
+ norm! zt
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 29, 11, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 21, 11, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 13, 11, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 5, 11, 0], getpos('.'))
+ call feedkeys("A\<PageUp>\<esc>", 'tnix')
+ call assert_equal([0, 5, 11, 0], getpos('.'))
+ call cursor(1, 1)
+ call feedkeys("A\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 9, 11, 0], getpos('.'))
+ call feedkeys("A\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 17, 11, 0], getpos('.'))
+ call feedkeys("A\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 25, 11, 0], getpos('.'))
+ call feedkeys("A\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 30, 11, 0], getpos('.'))
+ call feedkeys("A\<PageDown>\<esc>", 'tnix')
+ call assert_equal([0, 30, 11, 0], getpos('.'))
+ " <S-Up> is the same as <PageUp>
+ " <S-Down> is the same as <PageDown>
+ call cursor(30, 11)
+ norm! zt
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 29, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 21, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 13, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 5, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Up>\<esc>", 'tnix')
+ call assert_equal([0, 5, 11, 0], getpos('.'))
+ call cursor(1, 1)
+ call feedkeys("A\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 9, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 17, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 25, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 30, 11, 0], getpos('.'))
+ call feedkeys("A\<S-Down>\<esc>", 'tnix')
+ call assert_equal([0, 30, 11, 0], getpos('.'))
+ bw!
+endfunc
+
+func Test_edit_forbidden()
+ new
+ " 1) edit in the sandbox is not allowed
+ call setline(1, 'a')
+ com! Sandbox :sandbox call feedkeys("i\<del>\<esc>", 'tnix')
+ call assert_fails(':Sandbox', 'E48:')
+ com! Sandbox :sandbox exe "norm! i\<del>"
+ call assert_fails(':Sandbox', 'E48:')
+ delcom Sandbox
+ call assert_equal(['a'], getline(1,'$'))
+
+ " 2) edit with textlock set
+ fu! DoIt()
+ call feedkeys("i\<del>\<esc>", 'tnix')
+ endfu
+ au InsertCharPre <buffer> :call DoIt()
+ try
+ call feedkeys("ix\<esc>", 'tnix')
+ call assert_fails(1, 'textlock')
+ catch /^Vim\%((\a\+)\)\=:E565/ " catch E565: not allowed here
+ endtry
+ " TODO: Might be a bug: should x really be inserted here
+ call assert_equal(['xa'], getline(1, '$'))
+ delfu DoIt
+ try
+ call feedkeys("ix\<esc>", 'tnix')
+ call assert_fails(1, 'unknown function')
+ catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function
+ endtry
+ au! InsertCharPre
+
+ " 3) edit when completion is shown
+ fun! Complete(findstart, base)
+ if a:findstart
+ return col('.')
+ else
+ call feedkeys("i\<del>\<esc>", 'tnix')
+ return []
+ endif
+ endfun
+ set completefunc=Complete
+ try
+ call feedkeys("i\<c-x>\<c-u>\<esc>", 'tnix')
+ call assert_fails(1, 'change in complete function')
+ catch /^Vim\%((\a\+)\)\=:E565/ " catch E565
+ endtry
+ delfu Complete
+ set completefunc=
+
+ if has("rightleft") && exists("+fkmap")
+ " 4) 'R' when 'fkmap' and 'revins' is set.
+ set revins fkmap
+ try
+ normal Ri
+ call assert_fails(1, "R with 'fkmap' and 'ri' set")
+ catch
+ finally
+ set norevins nofkmap
+ endtry
+ endif
+ bw!
+endfunc
+
+func Test_edit_rightleft()
+ " Cursor in rightleft mode moves differently
+ CheckFeature rightleft
+ call NewWindow(10, 20)
+ call setline(1, ['abc', 'def', 'ghi'])
+ call cursor(1, 2)
+ set rightleft
+ " Screen looks as expected
+ let lines = ScreenLines([1, 4], winwidth(0))
+ let expect = [
+ \" cba",
+ \" fed",
+ \" ihg",
+ \" ~"]
+ call assert_equal(join(expect, "\n"), join(lines, "\n"))
+ " 2) right moves to the left
+ call feedkeys("i\<right>\<esc>x", 'txin')
+ call assert_equal(['bc', 'def', 'ghi'], getline(1,'$'))
+ call cursor(1, 2)
+ call feedkeys("i\<s-right>\<esc>", 'txin')
+ call cursor(1, 2)
+ call feedkeys("i\<c-right>\<esc>", 'txin')
+ " Screen looks as expected
+ let lines = ScreenLines([1, 4], winwidth(0))
+ let expect = [
+ \" cb",
+ \" fed",
+ \" ihg",
+ \" ~"]
+ call assert_equal(join(expect, "\n"), join(lines, "\n"))
+ " 2) left moves to the right
+ call setline(1, ['abc', 'def', 'ghi'])
+ call cursor(1, 2)
+ call feedkeys("i\<left>\<esc>x", 'txin')
+ call assert_equal(['ac', 'def', 'ghi'], getline(1,'$'))
+ call cursor(1, 2)
+ call feedkeys("i\<s-left>\<esc>", 'txin')
+ call cursor(1, 2)
+ call feedkeys("i\<c-left>\<esc>", 'txin')
+ " Screen looks as expected
+ let lines = ScreenLines([1, 4], winwidth(0))
+ let expect = [
+ \" ca",
+ \" fed",
+ \" ihg",
+ \" ~"]
+ call assert_equal(join(expect, "\n"), join(lines, "\n"))
+ %d _
+ call test_override('redraw_flag', 1)
+ call test_override('char_avail', 1)
+ call feedkeys("a\<C-V>x41", "xt")
+ redraw!
+ call assert_equal(repeat(' ', 19) .. 'A', Screenline(1))
+ call test_override('ALL', 0)
+ set norightleft
+ bw!
+endfunc
+
+func Test_edit_complete_very_long_name()
+ " Long directory names only work on Unix.
+ CheckUnix
+
+ let dirname = getcwd() . "/Xlongdir"
+ let longdirname = dirname . repeat('/' . repeat('d', 255), 4)
+ try
+ call mkdir(longdirname, 'pR')
+ catch /E739:/
+ " Long directory name probably not supported.
+ call delete(dirname, 'rf')
+ return
+ endtry
+
+ " Try to get the Vim window position before setting 'columns', so that we can
+ " move the window back to where it was.
+ let winposx = getwinposx()
+ let winposy = getwinposy()
+
+ if winposx >= 0 && winposy >= 0 && !has('gui_running')
+ " We did get the window position, but xterm may report the wrong numbers.
+ " Move the window to the reported position and compute any offset.
+ exe 'winpos ' . winposx . ' ' . winposy
+ sleep 100m
+ let x = getwinposx()
+ if x >= 0
+ let winposx += winposx - x
+ endif
+ let y = getwinposy()
+ if y >= 0
+ let winposy += winposy - y
+ endif
+ endif
+
+ let save_columns = &columns
+ " Need at least about 1100 columns to reproduce the problem.
+ set columns=2000
+ set noswapfile
+
+ let longfilename = longdirname . '/' . repeat('a', 255)
+ call writefile(['Totum', 'Table'], longfilename)
+ new
+ exe "next Xnofile " . longfilename
+ exe "normal iT\<C-N>"
+
+ bwipe!
+ exe 'bwipe! ' . longfilename
+ let &columns = save_columns
+ if winposx >= 0 && winposy >= 0
+ exe 'winpos ' . winposx . ' ' . winposy
+ endif
+ set swapfile&
+endfunc
+
+func Test_edit_backtick()
+ next a\`b c
+ call assert_equal('a`b', expand('%'))
+ next
+ call assert_equal('c', expand('%'))
+ call assert_equal('a\`b c', expand('##'))
+endfunc
+
+func Test_edit_quit()
+ edit foo.txt
+ split
+ new
+ call setline(1, 'hello')
+ 3wincmd w
+ redraw!
+ call assert_fails('1q', 'E37:')
+ bwipe! foo.txt
+ only
+endfunc
+
+func Test_edit_alt()
+ " Keeping the cursor line didn't happen when the first line has indent.
+ new
+ call setline(1, [' one', 'two', 'three'])
+ w XAltFile
+ $
+ call assert_equal(3, line('.'))
+ e Xother
+ e #
+ call assert_equal(3, line('.'))
+
+ bwipe XAltFile
+ call delete('XAltFile')
+endfunc
+
+func Test_edit_InsertLeave()
+ new
+ au InsertLeavePre * let g:did_au_pre = 1
+ au InsertLeave * let g:did_au = 1
+ let g:did_au_pre = 0
+ let g:did_au = 0
+ call feedkeys("afoo\<Esc>", 'tx')
+ call assert_equal(1, g:did_au_pre)
+ call assert_equal(1, g:did_au)
+ call assert_equal('foo', getline(1))
+
+ let g:did_au_pre = 0
+ let g:did_au = 0
+ call feedkeys("Sbar\<C-C>", 'tx')
+ call assert_equal(1, g:did_au_pre)
+ call assert_equal(0, g:did_au)
+ call assert_equal('bar', getline(1))
+
+ inoremap x xx<Esc>
+ let g:did_au_pre = 0
+ let g:did_au = 0
+ call feedkeys("Saax", 'tx')
+ call assert_equal(1, g:did_au_pre)
+ call assert_equal(1, g:did_au)
+ call assert_equal('aaxx', getline(1))
+
+ inoremap x xx<C-C>
+ let g:did_au_pre = 0
+ let g:did_au = 0
+ call feedkeys("Sbbx", 'tx')
+ call assert_equal(1, g:did_au_pre)
+ call assert_equal(0, g:did_au)
+ call assert_equal('bbxx', getline(1))
+
+ bwipe!
+ au! InsertLeave InsertLeavePre
+ iunmap x
+endfunc
+
+func Test_edit_InsertLeave_undo()
+ new XtestUndo
+ set undofile
+ au InsertLeave * wall
+ exe "normal ofoo\<Esc>"
+ call assert_equal(2, line('$'))
+ normal u
+ call assert_equal(1, line('$'))
+
+ bwipe!
+ au! InsertLeave
+ call delete('XtestUndo')
+ call delete(undofile('XtestUndo'))
+ set undofile&
+endfunc
+
+" Test for inserting characters using CTRL-V followed by a number.
+func Test_edit_special_chars()
+ new
+
+ let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
+
+ exe "normal " . t
+ call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2))
+
+ close!
+endfunc
+
+func Test_edit_startinsert()
+ new
+ set backspace+=start
+ call setline(1, 'foobar')
+ call feedkeys("A\<C-U>\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ call setline(1, 'foobar')
+ call feedkeys(":startinsert!\<CR>\<C-U>\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ set backspace&
+ bwipe!
+endfunc
+
+" Test for :startreplace and :startgreplace
+func Test_edit_startreplace()
+ new
+ call setline(1, 'abc')
+ call feedkeys("l:startreplace\<CR>xyz\e", 'xt')
+ call assert_equal('axyz', getline(1))
+ call feedkeys("0:startreplace!\<CR>abc\e", 'xt')
+ call assert_equal('axyzabc', getline(1))
+ call setline(1, "a\tb")
+ call feedkeys("0l:startgreplace\<CR>xyz\e", 'xt')
+ call assert_equal("axyz\tb", getline(1))
+ call feedkeys("0i\<C-R>=execute('startreplace')\<CR>12\e", 'xt')
+ call assert_equal("12axyz\tb", getline(1))
+ close!
+endfunc
+
+func Test_edit_noesckeys()
+ CheckNotGui
+ new
+
+ " <Left> moves cursor when 'esckeys' is set
+ exe "set t_kl=\<Esc>OD"
+ set esckeys
+ call feedkeys("axyz\<Esc>ODX", "xt")
+ call assert_equal("xyXz", getline(1))
+
+ " <Left> exits Insert mode when 'esckeys' is off
+ set noesckeys
+ call setline(1, '')
+ call feedkeys("axyz\<Esc>ODX", "xt")
+ call assert_equal(["DX", "xyz"], getline(1, 2))
+
+ bwipe!
+ set esckeys
+endfunc
+
+" Test for running an invalid ex command in insert mode using CTRL-O
+func Test_edit_ctrl_o_invalid_cmd()
+ new
+ set showmode showcmd
+ " Avoid a sleep of 3 seconds. Zero might have side effects.
+ call test_override('ui_delay', 50)
+ let caught_e492 = 0
+ try
+ call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt")
+ catch /E492:/
+ let caught_e492 = 1
+ endtry
+ call assert_equal(1, caught_e492)
+ call assert_equal('abc', getline(1))
+ set showmode& showcmd&
+ call test_override('ui_delay', 0)
+ close!
+endfunc
+
+" Test for editing a file with a very long name
+func Test_edit_illegal_filename()
+ CheckEnglish
+ new
+ redir => msg
+ exe 'edit ' . repeat('f', 5000)
+ redir END
+ call assert_match("Illegal file name$", split(msg, "\n")[0])
+ close!
+endfunc
+
+" Test for editing a directory
+func Test_edit_is_a_directory()
+ CheckEnglish
+ let dirname = getcwd() . "/Xeditdir"
+ call mkdir(dirname, 'p')
+
+ new
+ redir => msg
+ exe 'edit' dirname
+ redir END
+ call assert_match("is a directory$", split(msg, "\n")[0])
+ bwipe!
+
+ let dirname .= '/'
+
+ new
+ redir => msg
+ exe 'edit' dirname
+ redir END
+ call assert_match("is a directory$", split(msg, "\n")[0])
+ bwipe!
+
+ call delete(dirname, 'rf')
+endfunc
+
+" Test for editing a file using invalid file encoding
+func Test_edit_invalid_encoding()
+ CheckEnglish
+ call writefile([], 'Xinvfile', 'D')
+ redir => msg
+ new ++enc=axbyc Xinvfile
+ redir END
+ call assert_match('\[NOT converted\]', msg)
+ close!
+endfunc
+
+" Test for the "charconvert" option
+func Test_edit_charconvert()
+ CheckEnglish
+ call writefile(['one', 'two'], 'Xccfile', 'D')
+
+ " set 'charconvert' to a non-existing function
+ set charconvert=NonExitingFunc()
+ new
+ let caught_e117 = v:false
+ try
+ redir => msg
+ edit ++enc=axbyc Xccfile
+ catch /E117:/
+ let caught_e117 = v:true
+ finally
+ redir END
+ endtry
+ call assert_true(caught_e117)
+ call assert_equal(['one', 'two'], getline(1, '$'))
+ call assert_match("Conversion with 'charconvert' failed", msg)
+ close!
+ set charconvert&
+
+ " 'charconvert' function doesn't create a output file
+ func Cconv1()
+ endfunc
+ set charconvert=Cconv1()
+ new
+ redir => msg
+ edit ++enc=axbyc Xccfile
+ redir END
+ call assert_equal(['one', 'two'], getline(1, '$'))
+ call assert_match("can't read output of 'charconvert'", msg)
+ close!
+ delfunc Cconv1
+ set charconvert&
+
+ " 'charconvert' function to convert to upper case
+ func Cconv2()
+ let data = readfile(v:fname_in)
+ call map(data, 'toupper(v:val)')
+ call writefile(data, v:fname_out)
+ endfunc
+ set charconvert=Cconv2()
+ new Xccfile
+ write ++enc=ucase Xccfile1
+ call assert_equal(['ONE', 'TWO'], readfile('Xccfile1'))
+ call delete('Xccfile1')
+ close!
+ delfunc Cconv2
+ set charconvert&
+
+ " 'charconvert' function removes the input file
+ func Cconv3()
+ call delete(v:fname_in)
+ endfunc
+ set charconvert=Cconv3()
+ new
+ call assert_fails('edit ++enc=lcase Xccfile', 'E202:')
+ call assert_equal([''], getline(1, '$'))
+ close!
+ delfunc Cconv3
+ set charconvert&
+endfunc
+
+" Test for editing a file without read permission
+func Test_edit_file_no_read_perm()
+ CheckUnix
+ CheckNotRoot
+
+ call writefile(['one', 'two'], 'Xnrpfile', 'D')
+ call setfperm('Xnrpfile', '-w-------')
+ new
+ redir => msg
+ edit Xnrpfile
+ redir END
+ call assert_equal(1, &readonly)
+ call assert_equal([''], getline(1, '$'))
+ call assert_match('\[Permission Denied\]', msg)
+ close!
+endfunc
+
+" Using :edit without leaving 'insertmode' should not cause Insert mode to be
+" re-entered immediately after <C-L>
+func Test_edit_insertmode_ex_edit()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set insertmode noruler
+ inoremap <C-B> <Cmd>edit Xfoo<CR>
+ END
+ call writefile(lines, 'Xtest_edit_insertmode_ex_edit', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6, wait_for_ruler: 0})
+ " Somehow when using valgrind "INSERT" does not show up unless we send
+ " something to the terminal.
+ for i in range(30)
+ if term_getline(buf, 6) =~ 'INSERT'
+ break
+ endif
+ call term_sendkeys(buf, "-")
+ sleep 100m
+ endfor
+ call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, 6))})
+ call term_sendkeys(buf, "\<C-B>\<C-L>")
+ call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Pressing escape in 'insertmode' should beep
+" FIXME: Execute this later, when using valgrind it makes the next test
+" Test_edit_insertmode_ex_edit() fail.
+func Test_z_edit_insertmode_esc_beeps()
+ new
+ set insertmode
+ call assert_beeps("call feedkeys(\"one\<Esc>\", 'xt')")
+ set insertmode&
+ " unsupported "CTRL-G l" command should beep in insert mode.
+ call assert_beeps("normal i\<C-G>l")
+ bwipe!
+endfunc
+
+" Test for 'hkmap' and 'hkmapp'
+func Test_edit_hkmap()
+ CheckFeature rightleft
+ if has('win32') && !has('gui')
+ throw 'Skipped: fails on the MS-Windows terminal version'
+ endif
+ new
+
+ set revins hkmap
+ let str = 'abcdefghijklmnopqrstuvwxyz'
+ let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ let str ..= '`/'',.;'
+ call feedkeys('i' .. str, 'xt')
+ let expected = "óõú,.;"
+ let expected ..= "ZYXWVUTSRQPONMLKJIHGFEDCBA"
+ let expected ..= "æèñ'äåàãø/ôíîöêìçïéòë÷âáðù"
+ call assert_equal(expected, getline(1))
+
+ %d
+ set revins hkmap hkmapp
+ let str = 'abcdefghijklmnopqrstuvwxyz'
+ let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ call feedkeys('i' .. str, 'xt')
+ let expected = "õYXWVUTSRQóOïíLKJIHGFEDêBA"
+ let expected ..= "öòXùåèúæø'ôñðîì÷çéäâóǟãëáà"
+ call assert_equal(expected, getline(1))
+
+ set revins& hkmap& hkmapp&
+ close!
+endfunc
+
+" Test for 'allowrevins' and using CTRL-_ in insert mode
+func Test_edit_allowrevins()
+ CheckFeature rightleft
+ new
+ set allowrevins
+ call feedkeys("iABC\<C-_>DEF\<C-_>GHI", 'xt')
+ call assert_equal('ABCFEDGHI', getline(1))
+ set allowrevins&
+ close!
+endfunc
+
+" Test for inserting a register in insert mode using CTRL-R
+func Test_edit_insert_reg()
+ new
+ let g:Line = ''
+ func SaveFirstLine()
+ let g:Line = Screenline(1)
+ return 'r'
+ endfunc
+ inoremap <expr> <buffer> <F2> SaveFirstLine()
+ call test_override('redraw_flag', 1)
+ call test_override('char_avail', 1)
+ let @r = 'sample'
+ call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt")
+ call assert_equal('"', g:Line)
+ call test_override('ALL', 0)
+ close!
+endfunc
+
+" When a character is inserted at the last position of the last line in a
+" window, the window contents should be scrolled one line up. If the top line
+" is part of a fold, then the entire fold should be scrolled up.
+func Test_edit_lastline_scroll()
+ new
+ let h = winheight(0)
+ let lines = ['one', 'two', 'three']
+ let lines += repeat(['vim'], h - 4)
+ call setline(1, lines)
+ call setline(h, repeat('x', winwidth(0) - 1))
+ call feedkeys("GAx", 'xt')
+ redraw!
+ call assert_equal(h - 1, winline())
+ call assert_equal(2, line('w0'))
+
+ " scroll with a fold
+ 1,2fold
+ normal gg
+ call setline(h + 1, repeat('x', winwidth(0) - 1))
+ call feedkeys("GAx", 'xt')
+ redraw!
+ call assert_equal(h - 1, winline())
+ call assert_equal(3, line('w0'))
+
+ close!
+endfunc
+
+func Test_edit_browse()
+ " in the GUI this opens a file picker, we only test the terminal behavior
+ CheckNotGui
+
+ " ":browse xxx" checks for the FileExplorer augroup and assumes editing "."
+ " works then.
+ augroup FileExplorer
+ au!
+ augroup END
+
+ " When the USE_FNAME_CASE is defined this used to cause a crash.
+ browse enew
+ bwipe!
+
+ browse split
+ bwipe!
+endfunc
+
+func Test_read_invalid()
+ set encoding=latin1
+ " This was not properly checking for going past the end.
+ call assert_fails('r`=', 'E484')
+ set encoding=utf-8
+endfunc
+
+" Test for the 'revins' option
+func Test_edit_revins()
+ CheckFeature rightleft
+ new
+ set revins
+ exe "normal! ione\ttwo three"
+ call assert_equal("eerht owt\teno", getline(1))
+ call setline(1, "one\ttwo three")
+ normal! gg$bi a
+ call assert_equal("one\ttwo a three", getline(1))
+ exe "normal! $bi\<BS>\<BS>"
+ call assert_equal("one\ttwo a ree", getline(1))
+ exe "normal! 0wi\<C-W>"
+ call assert_equal("one\t a ree", getline(1))
+ exe "normal! 0wi\<C-U>"
+ call assert_equal("one\t ", getline(1))
+ " newline in insert mode starts at the end of the line
+ call setline(1, 'one two three')
+ exe "normal! wi\nfour"
+ call assert_equal(['one two three', 'ruof'], getline(1, '$'))
+ set revins&
+ bw!
+endfunc
+
+" Test for getting the character of the line below after "p"
+func Test_edit_put_CTRL_E()
+ set encoding=latin1
+ new
+ let @" = ''
+ sil! norm orggRx
+ sil! norm pr
+ call assert_equal(['r', 'r'], getline(1, 2))
+ bwipe!
+ set encoding=utf-8
+endfunc
+
+" Test toggling of input method. See :help i_CTRL-^
+func Test_edit_CTRL_hat()
+ CheckFeature xim
+
+ " FIXME: test fails with Motif GUI.
+ " test also fails when running in the GUI.
+ CheckFeature gui_gtk
+ CheckNotGui
+
+ new
+
+ call assert_equal(0, &iminsert)
+ call feedkeys("i\<C-^>", 'xt')
+ call assert_equal(2, &iminsert)
+ call feedkeys("i\<C-^>", 'xt')
+ call assert_equal(0, &iminsert)
+
+ bwipe!
+endfunc
+
+" Weird long file name was going over the end of NameBuff
+func Test_edit_overlong_file_name()
+ CheckUnix
+
+ file 0000000000000000000000000000
+ file %%%%%%%%%%%%%%%%%%%%%%%%%%
+ file %%%%%%
+ set readonly
+ set ls=2
+
+ redraw!
+ set noreadonly ls&
+ bwipe!
+endfunc
+
+func Test_edit_shift_bs()
+ CheckMSWindows
+
+ " FIXME: this works interactively, but the test fails
+ throw 'Skipped: Shift-Backspace Test not working correctly :('
+
+ " Need to run this in Win32 Terminal, do not use CheckRunVimInTerminal
+ if !has("terminal")
+ return
+ endif
+
+ " Shift Backspace should work like Backspace in insert mode
+ let lines =<< trim END
+ call setline(1, ['abc'])
+ END
+ call writefile(lines, 'Xtest_edit_shift_bs', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest_edit_shift_bs', #{rows: 3})
+ call term_sendkeys(buf, "A\<S-BS>-\<esc>")
+ call TermWait(buf, 50)
+ call assert_equal('ab-', term_getline(buf, 1))
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_environ.vim b/src/testdir/test_environ.vim
new file mode 100644
index 0000000..b5dbdce
--- /dev/null
+++ b/src/testdir/test_environ.vim
@@ -0,0 +1,89 @@
+" Test for environment variables.
+
+scriptencoding utf-8
+
+source check.vim
+
+func Test_environ()
+ unlet! $TESTENV
+ call assert_equal(0, has_key(environ(), 'TESTENV'))
+ let $TESTENV = 'foo'
+ call assert_equal(1, has_key(environ(), 'TESTENV'))
+ let $TESTENV = 'ã“ã‚“ã«ã¡ã‚'
+ call assert_equal('ã“ã‚“ã«ã¡ã‚', environ()['TESTENV'])
+endfunc
+
+func Test_getenv()
+ unlet! $TESTENV
+ call assert_equal(v:null, 'TESTENV'->getenv())
+ let $TESTENV = 'foo'
+ call assert_equal('foo', getenv('TESTENV'))
+endfunc
+
+func Test_setenv()
+ unlet! $TESTENV
+ eval 'foo'->setenv('TEST ENV')
+ call assert_equal('foo', getenv('TEST ENV'))
+ call setenv('TEST ENV', v:null)
+ call assert_equal(v:null, getenv('TEST ENV'))
+endfunc
+
+func Test_special_env()
+ " The value for $HOME is cached internally by Vim, ensure the value is up to
+ " date.
+ let orig_ENV = $HOME
+
+ let $HOME = 'foo'
+ call assert_equal('foo', expand('~'))
+ " old $HOME value is kept until a new one is set
+ unlet $HOME
+ call assert_equal('foo', expand('~'))
+
+ call setenv('HOME', 'bar')
+ call assert_equal('bar', expand('~'))
+ " old $HOME value is kept until a new one is set
+ call setenv('HOME', v:null)
+ call assert_equal('bar', expand('~'))
+
+ let $HOME = orig_ENV
+endfunc
+
+func Test_external_env()
+ call setenv('FOO', 'HelloWorld')
+ if has('win32')
+ let result = system('echo %FOO%')
+ else
+ let result = system('echo $FOO')
+ endif
+ let result = substitute(result, '[ \r\n]', '', 'g')
+ call assert_equal('HelloWorld', result)
+
+ call setenv('FOO', v:null)
+ if has('win32')
+ let result = system('set | findstr "^FOO="')
+ else
+ let result = system('env | grep ^FOO=')
+ endif
+ call assert_equal('', result)
+endfunc
+
+func Test_mac_locale()
+ CheckFeature osxdarwin
+
+ " If $LANG is not set then the system locale will be used.
+ " Run Vim after unsetting all the locale environmental vars, and capture the
+ " output of :lang.
+ let lang_results = system("unset LANG; unset LC_MESSAGES; unset LC_CTYPE; " ..
+ \ shellescape(v:progpath) ..
+ \ " --clean -esX -c 'redir @a' -c 'lang' -c 'put a' -c 'print' -c 'qa!' ")
+
+ " Check that:
+ " 1. The locale is the form of <locale>.UTF-8.
+ " 2. Check that fourth item (LC_NUMERIC) is properly set to "C".
+ " Example match: "en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8"
+ call assert_match('"\([a-zA-Z_]\+\.UTF-8/\)\{3}C\(/[a-zA-Z_]\+\.UTF-8\)\{2}"',
+ \ lang_results,
+ \ "Default locale should have UTF-8 encoding set, and LC_NUMERIC set to 'C'")
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_erasebackword.vim b/src/testdir/test_erasebackword.vim
new file mode 100644
index 0000000..b917e66
--- /dev/null
+++ b/src/testdir/test_erasebackword.vim
@@ -0,0 +1,22 @@
+" Test for i_CTRL-W
+
+func Test_erasebackword()
+ enew
+
+ exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>"
+ call assert_equal(' wwwã“ã‚“ã«ã¡ã‚世界ワールド', getline('.'))
+ exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>"
+ call assert_equal(' wwwã“ã‚“ã«ã¡ã‚世界', getline('.'))
+ exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>"
+ call assert_equal(' wwwã“ã‚“ã«ã¡ã‚', getline('.'))
+ exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>"
+ call assert_equal(' www', getline('.'))
+ exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>"
+ call assert_equal(' ', getline('.'))
+ exe "normal o wwwã“ã‚“ã«ã¡ã‚世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>\<C-W>"
+ call assert_equal('', getline('.'))
+
+ enew!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_escaped_glob.vim b/src/testdir/test_escaped_glob.vim
new file mode 100644
index 0000000..f81961b
--- /dev/null
+++ b/src/testdir/test_escaped_glob.vim
@@ -0,0 +1,35 @@
+" Test whether glob()/globpath() return correct results with certain escaped
+" characters.
+
+source check.vim
+
+func SetUp()
+ " consistent sorting of file names
+ set nofileignorecase
+endfunction
+
+function Test_glob()
+ " This test fails on Windows because of the special characters in the
+ " filenames. Disable the test on non-Unix systems for now.
+ CheckUnix
+
+ " Execute these commands in the sandbox, so that using the shell fails.
+ " Setting 'shell' to an invalid name causes a memory leak.
+ sandbox call assert_equal("", glob('Xxx\{'))
+ sandbox call assert_equal("", 'Xxx\$'->glob())
+ w! Xxx\{
+ w! Xxx\$
+ sandbox call assert_equal("Xxx{", glob('Xxx\{'))
+ sandbox call assert_equal("Xxx$", glob('Xxx\$'))
+ call delete('Xxx{')
+ call delete('Xxx$')
+endfunction
+
+function Test_globpath()
+ sandbox call assert_equal("sautest/autoload/globone.vim\nsautest/autoload/globtwo.vim",
+ \ globpath('sautest/autoload', 'glob*.vim'))
+ sandbox call assert_equal(['sautest/autoload/globone.vim', 'sautest/autoload/globtwo.vim'],
+ \ 'glob*.vim'->globpath('sautest/autoload', 0, 1))
+endfunction
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim
new file mode 100644
index 0000000..46ec12d
--- /dev/null
+++ b/src/testdir/test_eval_stuff.vim
@@ -0,0 +1,705 @@
+" Tests for various eval things.
+
+source view_util.vim
+source shared.vim
+
+function s:foo() abort
+ try
+ return [] == 0
+ catch
+ return 1
+ endtry
+endfunction
+
+func Test_catch_return_with_error()
+ call assert_equal(1, s:foo())
+endfunc
+
+func Test_nocatch_restore_silent_emsg()
+ silent! try
+ throw 1
+ catch
+ endtry
+ echoerr 'wrong again'
+ call assert_equal('wrong again', ScreenLine(&lines))
+endfunc
+
+func Test_mkdir_p()
+ call mkdir('Xmkdir/nested', 'pR')
+ call assert_true(isdirectory('Xmkdir/nested'))
+ try
+ " Trying to make existing directories doesn't error
+ call mkdir('Xmkdir', 'p')
+ call mkdir('Xmkdir/nested', 'p')
+ catch /E739:/
+ call assert_report('mkdir(..., "p") failed for an existing directory')
+ endtry
+ " 'p' doesn't suppress real errors
+ call writefile([], 'Xmkdirfile', 'D')
+ call assert_fails('call mkdir("Xmkdirfile", "p")', 'E739:')
+
+ call assert_equal(0, mkdir(test_null_string()))
+ call assert_fails('call mkdir([])', 'E730:')
+ call assert_fails('call mkdir("abc", [], [])', 'E745:')
+endfunc
+
+func DoMkdirDel(name)
+ call mkdir(a:name, 'pD')
+ call assert_true(isdirectory(a:name))
+endfunc
+
+func DoMkdirDelAddFile(name)
+ call mkdir(a:name, 'pD')
+ call assert_true(isdirectory(a:name))
+ call writefile(['text'], a:name .. '/file')
+endfunc
+
+func DoMkdirDelRec(name)
+ call mkdir(a:name, 'pR')
+ call assert_true(isdirectory(a:name))
+endfunc
+
+func DoMkdirDelRecAddFile(name)
+ call mkdir(a:name, 'pR')
+ call assert_true(isdirectory(a:name))
+ call writefile(['text'], a:name .. '/file')
+endfunc
+
+func Test_mkdir_defer_del()
+ " Xtopdir/tmp is created thus deleted, not Xtopdir itself
+ call mkdir('Xtopdir', 'R')
+ call DoMkdirDel('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
+
+ " Deletion fails because "tmp" contains "sub"
+ call DoMkdirDel('Xtopdir/tmp/sub')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_true(isdirectory('Xtopdir/tmp'))
+ call delete('Xtopdir/tmp', 'rf')
+
+ " Deletion fails because "tmp" contains "file"
+ call DoMkdirDelAddFile('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_true(isdirectory('Xtopdir/tmp'))
+ call assert_true(filereadable('Xtopdir/tmp/file'))
+ call delete('Xtopdir/tmp', 'rf')
+
+ " Xtopdir/tmp is created thus deleted, not Xtopdir itself
+ call DoMkdirDelRec('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
+
+ " Deletion works even though "tmp" contains "sub"
+ call DoMkdirDelRec('Xtopdir/tmp/sub')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
+
+ " Deletion works even though "tmp" contains "file"
+ call DoMkdirDelRecAddFile('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
+endfunc
+
+func Test_line_continuation()
+ let array = [5,
+ "\ ignore this
+ \ 6,
+ "\ more to ignore
+ "\ more moreto ignore
+ \ ]
+ "\ and some more
+ call assert_equal([5, 6], array)
+endfunc
+
+func Test_E963()
+ " These commands used to cause an internal error prior to vim 8.1.0563
+ let v_e = v:errors
+ let v_o = v:oldfiles
+ call assert_fails("let v:errors=''", 'E963:')
+ call assert_equal(v_e, v:errors)
+ call assert_fails("let v:oldfiles=''", 'E963:')
+ call assert_equal(v_o, v:oldfiles)
+endfunc
+
+func Test_for_invalid()
+ call assert_fails("for x in 99", 'E1098:')
+ call assert_fails("for x in function('winnr')", 'E1098:')
+ call assert_fails("for x in {'a': 9}", 'E1098:')
+
+ if 0
+ /1/5/2/s/\n
+ endif
+ redraw
+endfunc
+
+func Test_for_over_null_string()
+ let save_enc = &enc
+ set enc=iso8859
+ let cnt = 0
+ for c in test_null_string()
+ let cnt += 1
+ endfor
+ call assert_equal(0, cnt)
+
+ let &enc = save_enc
+endfunc
+
+func Test_for_with_modifier()
+ " this checks has_loop_cmd() works with a modifier
+ let result = []
+ vim9cmd for i in range(3)
+ call extend(result, [i])
+ endfor
+ call assert_equal([0, 1, 2], result)
+endfunc
+
+func Test_for_invalid_line_count()
+ let lines =<< trim END
+ 111111111111111111111111 for line in ['one']
+ endfor
+ END
+ call writefile(lines, 'XinvalidFor', 'D')
+ " only test that this doesn't crash
+ call RunVim([], [], '-u NONE -e -s -S XinvalidFor -c qa')
+endfunc
+
+func Test_readfile_binary()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ setlocal ff=dos
+ silent write XReadfile_bin
+ let lines = 'XReadfile_bin'->readfile()
+ call assert_equal(['one', 'two', 'three'], lines)
+ let lines = readfile('XReadfile_bin', '', 2)
+ call assert_equal(['one', 'two'], lines)
+ let lines = readfile('XReadfile_bin', 'b')
+ call assert_equal(["one\r", "two\r", "three\r", ""], lines)
+ let lines = readfile('XReadfile_bin', 'b', 2)
+ call assert_equal(["one\r", "two\r"], lines)
+
+ bwipe!
+ call delete('XReadfile_bin')
+endfunc
+
+func Test_readfile_binary_empty()
+ call writefile([], 'Xempty-file', 'D')
+ " This used to compare uninitialized memory in Vim <= 8.2.4065
+ call assert_equal([''], readfile('Xempty-file', 'b'))
+endfunc
+
+func Test_readfile_bom()
+ call writefile(["\ufeffFOO", "FOO\ufeffBAR"], 'XReadfile_bom')
+ call assert_equal(['FOO', 'FOOBAR'], readfile('XReadfile_bom'))
+ call delete('XReadfile_bom')
+endfunc
+
+func Test_readfile_max()
+ call writefile(range(1, 4), 'XReadfile_max', 'D')
+ call assert_equal(['1', '2'], readfile('XReadfile_max', '', 2))
+ call assert_equal(['3', '4'], readfile('XReadfile_max', '', -2))
+endfunc
+
+func Test_let_errmsg()
+ call assert_fails('let v:errmsg = []', 'E730:')
+ let v:errmsg = ''
+ call assert_fails('let v:errmsg = []', 'E730:')
+ let v:errmsg = ''
+endfunc
+
+func Test_string_concatenation()
+ call assert_equal('ab', 'a'.'b')
+ call assert_equal('ab', 'a' .'b')
+ call assert_equal('ab', 'a'. 'b')
+ call assert_equal('ab', 'a' . 'b')
+
+ call assert_equal('ab', 'a'..'b')
+ call assert_equal('ab', 'a' ..'b')
+ call assert_equal('ab', 'a'.. 'b')
+ call assert_equal('ab', 'a' .. 'b')
+
+ let a = 'a'
+ let b = 'b'
+ let a .= b
+ call assert_equal('ab', a)
+
+ let a = 'a'
+ let a.=b
+ call assert_equal('ab', a)
+
+ let a = 'a'
+ let a ..= b
+ call assert_equal('ab', a)
+
+ let a = 'a'
+ let a..=b
+ call assert_equal('ab', a)
+
+ let a = 'A'
+ let b = 1.234
+ call assert_equal('A1.234', a .. b)
+endfunc
+
+" Test fix for issue #4507
+func Test_skip_after_throw()
+ try
+ throw 'something'
+ let x = wincol() || &ts
+ catch /something/
+ endtry
+endfunc
+
+scriptversion 2
+func Test_string_concat_scriptversion2()
+ call assert_true(has('vimscript-2'))
+ let a = 'a'
+ let b = 'b'
+
+ call assert_fails('echo a . b', 'E15:')
+ call assert_fails('let a .= b', 'E985:')
+ call assert_fails('let vers = 1.2.3', 'E488:')
+
+ let f = .5
+ call assert_equal(0.5, f)
+endfunc
+
+scriptversion 1
+func Test_string_concat_scriptversion1()
+ call assert_true(has('vimscript-1'))
+ let a = 'a'
+ let b = 'b'
+
+ echo a . b
+ let a .= b
+ let vers = 1.2.3
+ call assert_equal('123', vers)
+
+ call assert_fails('let f = .5', 'E15:')
+endfunc
+
+scriptversion 3
+func Test_vvar_scriptversion3()
+ call assert_true(has('vimscript-3'))
+ call assert_fails('echo version', 'E121:')
+ call assert_false(exists('version'))
+ let version = 1
+ call assert_equal(1, version)
+endfunc
+
+scriptversion 2
+func Test_vvar_scriptversion2()
+ call assert_true(exists('version'))
+ echo version
+ call assert_fails('let version = 1', 'E46:')
+ call assert_equal(v:version, version)
+
+ call assert_equal(v:version, v:versionlong / 10000)
+ call assert_true(v:versionlong > 8011525)
+endfunc
+
+func Test_dict_access_scriptversion2()
+ let l:x = {'foo': 1}
+
+ call assert_false(0 && l:x.foo)
+ call assert_true(1 && l:x.foo)
+endfunc
+
+scriptversion 4
+func Test_vvar_scriptversion4()
+ call assert_true(has('vimscript-4'))
+ call assert_equal(17, 017)
+ call assert_equal(15, 0o17)
+ call assert_equal(15, 0O17)
+ call assert_equal(18, 018)
+ call assert_equal(511, 0o777)
+ call assert_equal(64, 0b1'00'00'00)
+ call assert_equal(1048576, 0x10'00'00)
+ call assert_equal(32768, 0o10'00'00)
+ call assert_equal(1000000, 1'000'000)
+ call assert_equal("1234", execute("echo 1'234")->trim())
+ call assert_equal('1 234', execute("echo 1''234")->trim())
+ call assert_fails("echo 1'''234", 'E115:')
+endfunc
+
+scriptversion 1
+func Test_vvar_scriptversion1()
+ call assert_equal(15, 017)
+ call assert_equal(15, 0o17)
+ call assert_equal(15, 0O17)
+ call assert_equal(18, 018)
+ call assert_equal(511, 0o777)
+endfunc
+
+func Test_scriptversion_fail()
+ call writefile(['scriptversion 9'], 'Xversionscript', 'D')
+ call assert_fails('source Xversionscript', 'E999:')
+endfunc
+
+func Test_execute_cmd_with_null()
+ call assert_fails('execute test_null_list()', 'E730:')
+ call assert_fails('execute test_null_dict()', 'E731:')
+ call assert_fails('execute test_null_blob()', 'E976:')
+ execute test_null_string()
+ call assert_fails('execute test_null_partial()', 'E729:')
+ call assert_fails('execute test_unknown()', 'E908:')
+ if has('job')
+ call assert_fails('execute test_null_job()', 'E908:')
+ call assert_fails('execute test_null_channel()', 'E908:')
+ endif
+endfunc
+
+func Test_number_max_min_size()
+ " This will fail on systems without 64 bit number support or when not
+ " configured correctly.
+ call assert_equal(64, v:numbersize)
+
+ call assert_true(v:numbermin < -9999999)
+ call assert_true(v:numbermax > 9999999)
+endfunc
+
+func Assert_reg(name, type, value, valuestr, expr, exprstr)
+ call assert_equal(a:type, getregtype(a:name))
+ call assert_equal(a:value, getreg(a:name))
+ call assert_equal(a:valuestr, string(getreg(a:name, 0, 1)))
+ call assert_equal(a:expr, getreg(a:name, 1))
+ call assert_equal(a:exprstr, string(getreg(a:name, 1, 1)))
+endfunc
+
+func Test_let_register()
+ let @" = 'abc'
+ call Assert_reg('"', 'v', "abc", "['abc']", "abc", "['abc']")
+ let @" = "abc\n"
+ call Assert_reg('"', 'V', "abc\n", "['abc']", "abc\n", "['abc']")
+ let @" = "abc\<C-m>"
+ call Assert_reg('"', 'V', "abc\r\n", "['abc\r']", "abc\r\n", "['abc\r']")
+ let @= = '"abc"'
+ call Assert_reg('=', 'v', "abc", "['abc']", '"abc"', "['\"abc\"']")
+endfunc
+
+func Assert_regput(name, result)
+ new
+ execute "silent normal! o==\n==\e\"" . a:name . "P"
+ call assert_equal(a:result, getline(2, line('$')))
+ bwipe!
+endfunc
+
+func Test_setreg_basic()
+ call setreg('a', 'abcA', 'c')
+ call Assert_reg('a', 'v', "abcA", "['abcA']", "abcA", "['abcA']")
+ call Assert_regput('a', ['==', '=abcA='])
+
+ call setreg('A', 'abcAc', 'c')
+ call Assert_reg('A', 'v', "abcAabcAc", "['abcAabcAc']", "abcAabcAc", "['abcAabcAc']")
+ call Assert_regput('a', ['==', '=abcAabcAc='])
+
+ call setreg('A', 'abcAl', 'l')
+ call Assert_reg('A', 'V', "abcAabcAcabcAl\n", "['abcAabcAcabcAl']", "abcAabcAcabcAl\n", "['abcAabcAcabcAl']")
+ call Assert_regput('a', ['==', 'abcAabcAcabcAl', '=='])
+
+ call setreg('A', 'abcAc2','c')
+ call Assert_reg('A', 'v', "abcAabcAcabcAl\nabcAc2", "['abcAabcAcabcAl', 'abcAc2']", "abcAabcAcabcAl\nabcAc2", "['abcAabcAcabcAl', 'abcAc2']")
+ call Assert_regput('a', ['==', '=abcAabcAcabcAl', 'abcAc2='])
+
+ call setreg('b', 'abcB', 'v')
+ call Assert_reg('b', 'v', "abcB", "['abcB']", "abcB", "['abcB']")
+ call Assert_regput('b', ['==', '=abcB='])
+
+ call setreg('b', 'abcBc', 'ca')
+ call Assert_reg('b', 'v', "abcBabcBc", "['abcBabcBc']", "abcBabcBc", "['abcBabcBc']")
+ call Assert_regput('b', ['==', '=abcBabcBc='])
+
+ call setreg('b', 'abcBb', 'ba')
+ call Assert_reg('b', "\<C-V>5", "abcBabcBcabcBb", "['abcBabcBcabcBb']", "abcBabcBcabcBb", "['abcBabcBcabcBb']")
+ call Assert_regput('b', ['==', '=abcBabcBcabcBb='])
+
+ call setreg('b', 'abcBc2','ca')
+ call Assert_reg('b', "v", "abcBabcBcabcBb\nabcBc2", "['abcBabcBcabcBb', 'abcBc2']", "abcBabcBcabcBb\nabcBc2", "['abcBabcBcabcBb', 'abcBc2']")
+ call Assert_regput('b', ['==', '=abcBabcBcabcBb', 'abcBc2='])
+
+ call setreg('b', 'abcBb2','b50a')
+ call Assert_reg('b', "\<C-V>50", "abcBabcBcabcBb\nabcBc2abcBb2", "['abcBabcBcabcBb', 'abcBc2abcBb2']", "abcBabcBcabcBb\nabcBc2abcBb2", "['abcBabcBcabcBb', 'abcBc2abcBb2']")
+ call Assert_regput('b', ['==', '=abcBabcBcabcBb =', ' abcBc2abcBb2'])
+
+ call setreg('c', 'abcC', 'l')
+ call Assert_reg('c', 'V', "abcC\n", "['abcC']", "abcC\n", "['abcC']")
+ call Assert_regput('c', ['==', 'abcC', '=='])
+
+ call setreg('C', 'abcCl', 'l')
+ call Assert_reg('C', 'V', "abcC\nabcCl\n", "['abcC', 'abcCl']", "abcC\nabcCl\n", "['abcC', 'abcCl']")
+ call Assert_regput('c', ['==', 'abcC', 'abcCl', '=='])
+
+ call setreg('C', 'abcCc', 'c')
+ call Assert_reg('C', 'v', "abcC\nabcCl\nabcCc", "['abcC', 'abcCl', 'abcCc']", "abcC\nabcCl\nabcCc", "['abcC', 'abcCl', 'abcCc']")
+ call Assert_regput('c', ['==', '=abcC', 'abcCl', 'abcCc='])
+
+ call setreg('d', 'abcD', 'V')
+ call Assert_reg('d', 'V', "abcD\n", "['abcD']", "abcD\n", "['abcD']")
+ call Assert_regput('d', ['==', 'abcD', '=='])
+
+ call setreg('D', 'abcDb', 'b')
+ call Assert_reg('d', "\<C-V>5", "abcD\nabcDb", "['abcD', 'abcDb']", "abcD\nabcDb", "['abcD', 'abcDb']")
+ call Assert_regput('d', ['==', '=abcD =', ' abcDb'])
+
+ call setreg('e', 'abcE', 'b')
+ call Assert_reg('e', "\<C-V>4", "abcE", "['abcE']", "abcE", "['abcE']")
+ call Assert_regput('e', ['==', '=abcE='])
+
+ call setreg('E', 'abcEb', 'b')
+ call Assert_reg('E', "\<C-V>5", "abcE\nabcEb", "['abcE', 'abcEb']", "abcE\nabcEb", "['abcE', 'abcEb']")
+ call Assert_regput('e', ['==', '=abcE =', ' abcEb'])
+
+ call setreg('E', 'abcEl', 'l')
+ call Assert_reg('E', "V", "abcE\nabcEb\nabcEl\n", "['abcE', 'abcEb', 'abcEl']", "abcE\nabcEb\nabcEl\n", "['abcE', 'abcEb', 'abcEl']")
+ call Assert_regput('e', ['==', 'abcE', 'abcEb', 'abcEl', '=='])
+
+ call setreg('f', 'abcF', "\<C-v>")
+ call Assert_reg('f', "\<C-V>4", "abcF", "['abcF']", "abcF", "['abcF']")
+ call Assert_regput('f', ['==', '=abcF='])
+
+ call setreg('F', 'abcFc', 'c')
+ call Assert_reg('F', "v", "abcF\nabcFc", "['abcF', 'abcFc']", "abcF\nabcFc", "['abcF', 'abcFc']")
+ call Assert_regput('f', ['==', '=abcF', 'abcFc='])
+
+ call setreg('g', 'abcG', 'b10')
+ call Assert_reg('g', "\<C-V>10", "abcG", "['abcG']", "abcG", "['abcG']")
+ call Assert_regput('g', ['==', '=abcG ='])
+
+ call setreg('h', 'abcH', "\<C-v>10")
+ call Assert_reg('h', "\<C-V>10", "abcH", "['abcH']", "abcH", "['abcH']")
+ call Assert_regput('h', ['==', '=abcH ='])
+
+ call setreg('I', 'abcI')
+ call Assert_reg('I', "v", "abcI", "['abcI']", "abcI", "['abcI']")
+ call Assert_regput('I', ['==', '=abcI='])
+
+ " Appending NL with setreg()
+ call setreg('a', 'abcA2', 'c')
+ call setreg('b', 'abcB2', 'v')
+ call setreg('c', 'abcC2', 'l')
+ call setreg('d', 'abcD2', 'V')
+ call setreg('e', 'abcE2', 'b')
+ call setreg('f', 'abcF2', "\<C-v>")
+ call setreg('g', 'abcG2', 'b10')
+ call setreg('h', 'abcH2', "\<C-v>10")
+ call setreg('I', 'abcI2')
+
+ call setreg('A', "\n")
+ call Assert_reg('A', 'V', "abcA2\n", "['abcA2']", "abcA2\n", "['abcA2']")
+ call Assert_regput('A', ['==', 'abcA2', '=='])
+
+ call setreg('B', "\n", 'c')
+ call Assert_reg('B', 'v', "abcB2\n", "['abcB2', '']", "abcB2\n", "['abcB2', '']")
+ call Assert_regput('B', ['==', '=abcB2', '='])
+
+ call setreg('C', "\n")
+ call Assert_reg('C', 'V', "abcC2\n\n", "['abcC2', '']", "abcC2\n\n", "['abcC2', '']")
+ call Assert_regput('C', ['==', 'abcC2', '', '=='])
+
+ call setreg('D', "\n", 'l')
+ call Assert_reg('D', 'V', "abcD2\n\n", "['abcD2', '']", "abcD2\n\n", "['abcD2', '']")
+ call Assert_regput('D', ['==', 'abcD2', '', '=='])
+
+ call setreg('E', "\n")
+ call Assert_reg('E', 'V', "abcE2\n\n", "['abcE2', '']", "abcE2\n\n", "['abcE2', '']")
+ call Assert_regput('E', ['==', 'abcE2', '', '=='])
+
+ call setreg('F', "\n", 'b')
+ call Assert_reg('F', "\<C-V>0", "abcF2\n", "['abcF2', '']", "abcF2\n", "['abcF2', '']")
+ call Assert_regput('F', ['==', '=abcF2=', ' '])
+
+ " Setting lists with setreg()
+ call setreg('a', ['abcA3'], 'c')
+ call Assert_reg('a', 'v', "abcA3", "['abcA3']", "abcA3", "['abcA3']")
+ call Assert_regput('a', ['==', '=abcA3='])
+
+ call setreg('b', ['abcB3'], 'l')
+ call Assert_reg('b', 'V', "abcB3\n", "['abcB3']", "abcB3\n", "['abcB3']")
+ call Assert_regput('b', ['==', 'abcB3', '=='])
+
+ call setreg('c', ['abcC3'], 'b')
+ call Assert_reg('c', "\<C-V>5", "abcC3", "['abcC3']", "abcC3", "['abcC3']")
+ call Assert_regput('c', ['==', '=abcC3='])
+
+ call setreg('d', ['abcD3'])
+ call Assert_reg('d', 'V', "abcD3\n", "['abcD3']", "abcD3\n", "['abcD3']")
+ call Assert_regput('d', ['==', 'abcD3', '=='])
+
+ call setreg('e', [1, 2, 'abc', 3])
+ call Assert_reg('e', 'V', "1\n2\nabc\n3\n", "['1', '2', 'abc', '3']", "1\n2\nabc\n3\n", "['1', '2', 'abc', '3']")
+ call Assert_regput('e', ['==', '1', '2', 'abc', '3', '=='])
+
+ call setreg('f', [1, 2, 3])
+ call Assert_reg('f', 'V', "1\n2\n3\n", "['1', '2', '3']", "1\n2\n3\n", "['1', '2', '3']")
+ call Assert_regput('f', ['==', '1', '2', '3', '=='])
+
+ " Appending lists with setreg()
+ call setreg('A', ['abcA3c'], 'c')
+ call Assert_reg('A', 'v', "abcA3\nabcA3c", "['abcA3', 'abcA3c']", "abcA3\nabcA3c", "['abcA3', 'abcA3c']")
+ call Assert_regput('A', ['==', '=abcA3', 'abcA3c='])
+
+ call setreg('b', ['abcB3l'], 'la')
+ call Assert_reg('b', 'V', "abcB3\nabcB3l\n", "['abcB3', 'abcB3l']", "abcB3\nabcB3l\n", "['abcB3', 'abcB3l']")
+ call Assert_regput('b', ['==', 'abcB3', 'abcB3l', '=='])
+
+ call setreg('C', ['abcC3b'], 'lb')
+ call Assert_reg('C', "\<C-V>6", "abcC3\nabcC3b", "['abcC3', 'abcC3b']", "abcC3\nabcC3b", "['abcC3', 'abcC3b']")
+ call Assert_regput('C', ['==', '=abcC3 =', ' abcC3b'])
+
+ call setreg('D', ['abcD32'])
+ call Assert_reg('D', 'V', "abcD3\nabcD32\n", "['abcD3', 'abcD32']", "abcD3\nabcD32\n", "['abcD3', 'abcD32']")
+ call Assert_regput('D', ['==', 'abcD3', 'abcD32', '=='])
+
+ call setreg('A', ['abcA32'])
+ call Assert_reg('A', 'V', "abcA3\nabcA3c\nabcA32\n", "['abcA3', 'abcA3c', 'abcA32']", "abcA3\nabcA3c\nabcA32\n", "['abcA3', 'abcA3c', 'abcA32']")
+ call Assert_regput('A', ['==', 'abcA3', 'abcA3c', 'abcA32', '=='])
+
+ call setreg('B', ['abcB3c'], 'c')
+ call Assert_reg('B', 'v', "abcB3\nabcB3l\nabcB3c", "['abcB3', 'abcB3l', 'abcB3c']", "abcB3\nabcB3l\nabcB3c", "['abcB3', 'abcB3l', 'abcB3c']")
+ call Assert_regput('B', ['==', '=abcB3', 'abcB3l', 'abcB3c='])
+
+ call setreg('C', ['abcC3l'], 'l')
+ call Assert_reg('C', 'V', "abcC3\nabcC3b\nabcC3l\n", "['abcC3', 'abcC3b', 'abcC3l']", "abcC3\nabcC3b\nabcC3l\n", "['abcC3', 'abcC3b', 'abcC3l']")
+ call Assert_regput('C', ['==', 'abcC3', 'abcC3b', 'abcC3l', '=='])
+
+ call setreg('D', ['abcD3b'], 'b')
+ call Assert_reg('D', "\<C-V>6", "abcD3\nabcD32\nabcD3b", "['abcD3', 'abcD32', 'abcD3b']", "abcD3\nabcD32\nabcD3b", "['abcD3', 'abcD32', 'abcD3b']")
+ call Assert_regput('D', ['==', '=abcD3 =', ' abcD32', ' abcD3b'])
+
+ " Appending lists with NL with setreg()
+ call setreg('A', ["\n", 'abcA3l2'], 'l')
+ call Assert_reg('A', "V", "abcA3\nabcA3c\nabcA32\n\n\nabcA3l2\n", "['abcA3', 'abcA3c', 'abcA32', '\n', 'abcA3l2']", "abcA3\nabcA3c\nabcA32\n\n\nabcA3l2\n", "['abcA3', 'abcA3c', 'abcA32', '\n', 'abcA3l2']")
+ call Assert_regput('A', ['==', 'abcA3', 'abcA3c', 'abcA32', "\n", 'abcA3l2', '=='])
+
+ call setreg('B', ["\n", 'abcB3c2'], 'c')
+ call Assert_reg('B', "v", "abcB3\nabcB3l\nabcB3c\n\n\nabcB3c2", "['abcB3', 'abcB3l', 'abcB3c', '\n', 'abcB3c2']", "abcB3\nabcB3l\nabcB3c\n\n\nabcB3c2", "['abcB3', 'abcB3l', 'abcB3c', '\n', 'abcB3c2']")
+ call Assert_regput('B', ['==', '=abcB3', 'abcB3l', 'abcB3c', "\n", 'abcB3c2='])
+
+ call setreg('C', ["\n", 'abcC3b2'], 'b')
+ call Assert_reg('C', "7", "abcC3\nabcC3b\nabcC3l\n\n\nabcC3b2", "['abcC3', 'abcC3b', 'abcC3l', '\n', 'abcC3b2']", "abcC3\nabcC3b\nabcC3l\n\n\nabcC3b2", "['abcC3', 'abcC3b', 'abcC3l', '\n', 'abcC3b2']")
+ call Assert_regput('C', ['==', '=abcC3 =', ' abcC3b', ' abcC3l', " \n", ' abcC3b2'])
+
+ call setreg('D', ["\n", 'abcD3b50'],'b50')
+ call Assert_reg('D', "50", "abcD3\nabcD32\nabcD3b\n\n\nabcD3b50", "['abcD3', 'abcD32', 'abcD3b', '\n', 'abcD3b50']", "abcD3\nabcD32\nabcD3b\n\n\nabcD3b50", "['abcD3', 'abcD32', 'abcD3b', '\n', 'abcD3b50']")
+ call Assert_regput('D', ['==', '=abcD3 =', ' abcD32', ' abcD3b', " \n", ' abcD3b50'])
+
+ " Setting lists with NLs with setreg()
+ call setreg('a', ['abcA4-0', "\n", "abcA4-2\n", "\nabcA4-3", "abcA4-4\nabcA4-4-2"])
+ call Assert_reg('a', "V", "abcA4-0\n\n\nabcA4-2\n\n\nabcA4-3\nabcA4-4\nabcA4-4-2\n", "['abcA4-0', '\n', 'abcA4-2\n', '\nabcA4-3', 'abcA4-4\nabcA4-4-2']", "abcA4-0\n\n\nabcA4-2\n\n\nabcA4-3\nabcA4-4\nabcA4-4-2\n", "['abcA4-0', '\n', 'abcA4-2\n', '\nabcA4-3', 'abcA4-4\nabcA4-4-2']")
+ call Assert_regput('a', ['==', 'abcA4-0', "\n", "abcA4-2\n", "\nabcA4-3", "abcA4-4\nabcA4-4-2", '=='])
+
+ call setreg('b', ['abcB4c-0', "\n", "abcB4c-2\n", "\nabcB4c-3", "abcB4c-4\nabcB4c-4-2"], 'c')
+ call Assert_reg('b', "v", "abcB4c-0\n\n\nabcB4c-2\n\n\nabcB4c-3\nabcB4c-4\nabcB4c-4-2", "['abcB4c-0', '\n', 'abcB4c-2\n', '\nabcB4c-3', 'abcB4c-4\nabcB4c-4-2']", "abcB4c-0\n\n\nabcB4c-2\n\n\nabcB4c-3\nabcB4c-4\nabcB4c-4-2", "['abcB4c-0', '\n', 'abcB4c-2\n', '\nabcB4c-3', 'abcB4c-4\nabcB4c-4-2']")
+ call Assert_regput('b', ['==', '=abcB4c-0', "\n", "abcB4c-2\n", "\nabcB4c-3", "abcB4c-4\nabcB4c-4-2="])
+
+ call setreg('c', ['abcC4l-0', "\n", "abcC4l-2\n", "\nabcC4l-3", "abcC4l-4\nabcC4l-4-2"], 'l')
+ call Assert_reg('c', "V", "abcC4l-0\n\n\nabcC4l-2\n\n\nabcC4l-3\nabcC4l-4\nabcC4l-4-2\n", "['abcC4l-0', '\n', 'abcC4l-2\n', '\nabcC4l-3', 'abcC4l-4\nabcC4l-4-2']", "abcC4l-0\n\n\nabcC4l-2\n\n\nabcC4l-3\nabcC4l-4\nabcC4l-4-2\n", "['abcC4l-0', '\n', 'abcC4l-2\n', '\nabcC4l-3', 'abcC4l-4\nabcC4l-4-2']")
+ call Assert_regput('c', ['==', 'abcC4l-0', "\n", "abcC4l-2\n", "\nabcC4l-3", "abcC4l-4\nabcC4l-4-2", '=='])
+
+ call setreg('d', ['abcD4b-0', "\n", "abcD4b-2\n", "\nabcD4b-3", "abcD4b-4\nabcD4b-4-2"], 'b')
+ call Assert_reg('d', "19", "abcD4b-0\n\n\nabcD4b-2\n\n\nabcD4b-3\nabcD4b-4\nabcD4b-4-2", "['abcD4b-0', '\n', 'abcD4b-2\n', '\nabcD4b-3', 'abcD4b-4\nabcD4b-4-2']", "abcD4b-0\n\n\nabcD4b-2\n\n\nabcD4b-3\nabcD4b-4\nabcD4b-4-2", "['abcD4b-0', '\n', 'abcD4b-2\n', '\nabcD4b-3', 'abcD4b-4\nabcD4b-4-2']")
+ call Assert_regput('d', ['==', '=abcD4b-0 =', " \n", " abcD4b-2\n", " \nabcD4b-3", " abcD4b-4\nabcD4b-4-2"])
+
+ call setreg('e', ['abcE4b10-0', "\n", "abcE4b10-2\n", "\nabcE4b10-3", "abcE4b10-4\nabcE4b10-4-2"], 'b10')
+ call Assert_reg('e', "10", "abcE4b10-0\n\n\nabcE4b10-2\n\n\nabcE4b10-3\nabcE4b10-4\nabcE4b10-4-2", "['abcE4b10-0', '\n', 'abcE4b10-2\n', '\nabcE4b10-3', 'abcE4b10-4\nabcE4b10-4-2']", "abcE4b10-0\n\n\nabcE4b10-2\n\n\nabcE4b10-3\nabcE4b10-4\nabcE4b10-4-2", "['abcE4b10-0', '\n', 'abcE4b10-2\n', '\nabcE4b10-3', 'abcE4b10-4\nabcE4b10-4-2']")
+ call Assert_regput('e', ['==', '=abcE4b10-0=', " \n", " abcE4b10-2\n", " \nabcE4b10-3", " abcE4b10-4\nabcE4b10-4-2"])
+
+ " Search and expressions
+ call setreg('/', ['abc/'])
+ call Assert_reg('/', 'v', "abc/", "['abc/']", "abc/", "['abc/']")
+ call Assert_regput('/', ['==', '=abc/='])
+
+ call setreg('/', ["abc/\n"])
+ call Assert_reg('/', 'v', "abc/\n", "['abc/\n']", "abc/\n", "['abc/\n']")
+ call Assert_regput('/', ['==', "=abc/\n="])
+
+ call setreg('=', ['"abc/"'])
+ call Assert_reg('=', 'v', "abc/", "['abc/']", '"abc/"', "['\"abc/\"']")
+
+ call setreg('=', ["\"abc/\n\""])
+ call Assert_reg('=', 'v', "abc/\n", "['abc/\n']", "\"abc/\n\"", "['\"abc/\n\"']")
+
+ " System clipboard
+ if has('clipboard')
+ new | only!
+ call setline(1, ['clipboard contents', 'something else'])
+ " Save and restore system clipboard.
+ " If no connection to X-Server is possible, test should succeed.
+ let _clipreg = ['*', getreg('*'), getregtype('*')]
+ let _clipopt = &cb
+ let &cb='unnamed'
+ 1y
+ call Assert_reg('*', 'V', "clipboard contents\n", "['clipboard contents']", "clipboard contents\n", "['clipboard contents']")
+ tabdo :windo :echo "hi"
+ 2y
+ call Assert_reg('*', 'V', "something else\n", "['something else']", "something else\n", "['something else']")
+ let &cb=_clipopt
+ call call('setreg', _clipreg)
+ enew!
+ endif
+
+ " Error cases
+ call assert_fails('call setreg()', 'E119:')
+ call assert_fails('call setreg(1)', 'E119:')
+ call assert_fails('call setreg(1, 2, 3, 4)', 'E118:')
+ call assert_fails('call setreg([], 2)', 'E730:')
+ call assert_fails('call setreg(1, 2, [])', 'E730:')
+ call assert_fails('call setreg("/", ["1", "2"])', 'E883:')
+ call assert_fails('call setreg("=", ["1", "2"])', 'E883:')
+ call assert_fails('call setreg(1, ["", "", [], ""])', 'E730:')
+endfunc
+
+func Test_curly_assignment()
+ let s:svar = 'svar'
+ let g:gvar = 'gvar'
+ let lname = 'gvar'
+ let gname = 'gvar'
+ let {'s:'.lname} = {'g:'.gname}
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:'.lname } = { 'g:'.gname }
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:' . lname } = { 'g:' . gname }
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:' .. lname } = { 'g:' .. gname }
+ call assert_equal('gvar', s:gvar)
+
+ unlet s:svar
+ unlet s:gvar
+ unlet g:gvar
+endfunc
+
+func Test_deep_recursion()
+ " this was running out of stack
+ call assert_fails("exe 'if ' .. repeat('(', 1002)", 'E1169: Expression too recursive: ((')
+endfunc
+
+" K_SPECIAL in the modified character used be escaped, which causes
+" double-escaping with feedkeys() or as the return value of an <expr> mapping,
+" and doesn't match what getchar() returns,
+func Test_modified_char_no_escape_special()
+ nnoremap <M-…> <Cmd>let g:got_m_ellipsis += 1<CR>
+ call feedkeys("\<M-…>", 't')
+ call assert_equal("\<M-…>", getchar())
+ let g:got_m_ellipsis = 0
+ call feedkeys("\<M-…>", 'xt')
+ call assert_equal(1, g:got_m_ellipsis)
+ func Func()
+ return "\<M-…>"
+ endfunc
+ nmap <expr> <F2> Func()
+ call feedkeys("\<F2>", 'xt')
+ call assert_equal(2, g:got_m_ellipsis)
+ delfunc Func
+ nunmap <F2>
+ unlet g:got_m_ellipsis
+ nunmap <M-…>
+endfunc
+
+func Test_eval_string_in_special_key()
+ " this was using the '{' inside <> as the start of an interpolated string
+ silent! echo 0{1-$"\<S--{>n|nö%
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ex_equal.vim b/src/testdir/test_ex_equal.vim
new file mode 100644
index 0000000..bb66082
--- /dev/null
+++ b/src/testdir/test_ex_equal.vim
@@ -0,0 +1,34 @@
+" Test Ex := command.
+
+func Test_ex_equal()
+ new
+ call setline(1, ["foo\tbar", "bar\tfoo"])
+
+ let a = execute('=')
+ call assert_equal("\n2", a)
+
+ let a = execute('=#')
+ call assert_equal("\n2\n 1 foo bar", a)
+
+ let a = execute('=l')
+ call assert_equal("\n2\nfoo^Ibar$", a)
+
+ let a = execute('=p')
+ call assert_equal("\n2\nfoo bar", a)
+
+ let a = execute('=l#')
+ call assert_equal("\n2\n 1 foo^Ibar$", a)
+
+ let a = execute('=p#')
+ call assert_equal("\n2\n 1 foo bar", a)
+
+ let a = execute('.=')
+ call assert_equal("\n1", a)
+
+ call assert_fails('3=', 'E16:')
+ call assert_fails('=x', 'E488:')
+
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ex_mode.vim b/src/testdir/test_ex_mode.vim
new file mode 100644
index 0000000..a660222
--- /dev/null
+++ b/src/testdir/test_ex_mode.vim
@@ -0,0 +1,290 @@
+" Test editing line in Ex mode (see :help Q and :help gQ).
+
+source check.vim
+source shared.vim
+
+" Helper function to test editing line in Q Ex mode
+func Ex_Q(cmd)
+ " Is there a simpler way to test editing Ex line?
+ call feedkeys("Q"
+ \ .. "let s:test_ex =<< END\<CR>"
+ \ .. a:cmd .. "\<CR>"
+ \ .. "END\<CR>"
+ \ .. "visual\<CR>", 'tx')
+ return s:test_ex[0]
+endfunc
+
+" Helper function to test editing line in gQ Ex mode
+func Ex_gQ(cmd)
+ call feedkeys("gQ" .. a:cmd .. "\<C-b>\"\<CR>", 'tx')
+ let ret = @:[1:] " Remove leading quote.
+ call feedkeys("visual\<CR>", 'tx')
+ return ret
+endfunc
+
+" Helper function to test editing line with both Q and gQ Ex mode.
+func Ex(cmd)
+ return [Ex_Q(a:cmd), Ex_gQ(a:cmd)]
+endfunc
+
+" Test editing line in Ex mode (both Q and gQ)
+func Test_ex_mode()
+ let encoding_save = &encoding
+ set sw=2
+
+ for e in ['utf8', 'latin1']
+ exe 'set encoding=' . e
+
+ call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
+ call assert_equal(["1\<C-u>2", "1\<C-u>2"], Ex("1\<C-v>\<C-u>2"), e)
+ call assert_equal(["1\<C-b>2\<C-e>3", '213'], Ex("1\<C-b>2\<C-e>3"), e)
+ call assert_equal(['0123', '2013'], Ex("01\<Home>2\<End>3"), e)
+ call assert_equal(['0123', '0213'], Ex("01\<Left>2\<Right>3"), e)
+ call assert_equal(['01234', '0342'], Ex("012\<Left>\<Left>\<Insert>3\<Insert>4"), e)
+ call assert_equal(["foo bar\<C-w>", 'foo '], Ex("foo bar\<C-w>"), e)
+ call assert_equal(['foo', 'foo'], Ex("fooba\<Del>\<Del>"), e)
+ call assert_equal(["foo\tbar", 'foobar'], Ex("foo\<Tab>bar"), e)
+ call assert_equal(["abbrev\t", 'abbreviate'], Ex("abbrev\<Tab>"), e)
+ call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>"), e)
+ call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>\<C-d>"), e)
+ call assert_equal([' foo', ' foo'], Ex(" foo\<C-d>"), e)
+ call assert_equal(['foo', ' foo0'], Ex(" foo0\<C-d>"), e)
+ call assert_equal(['foo', ' foo^'], Ex(" foo^\<C-d>"), e)
+ call assert_equal(['foo', 'foo'],
+ \ Ex("\<BS>\<C-H>\<Del>\<kDel>foo"), e)
+ " default wildchar <Tab> interferes with this test
+ set wildchar=<c-e>
+ call assert_equal(["a\tb", "a\tb"], Ex("a\t\t\<C-H>b"), e)
+ call assert_equal(["\t mn", "\tm\<C-T>n"], Ex("\tm\<C-T>n"), e)
+ set wildchar&
+ endfor
+
+ set sw&
+ let &encoding = encoding_save
+endfunc
+
+" Test substitute confirmation prompt :%s/pat/str/c in Ex mode
+func Test_Ex_substitute()
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('', {'rows': 6})
+
+ call term_sendkeys(buf, ":call setline(1, ['foo foo', 'foo foo', 'foo foo'])\<CR>")
+ call term_sendkeys(buf, ":set number\<CR>")
+ call term_sendkeys(buf, "gQ")
+ call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000)
+
+ call term_sendkeys(buf, "%s/foo/bar/gc\<CR>")
+ call WaitForAssert({-> assert_match(' 1 foo foo', term_getline(buf, 5))},
+ \ 1000)
+ call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, "N\<CR>")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, "n\<CR>")
+ call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))},
+ \ 1000)
+ call term_sendkeys(buf, "y\<CR>")
+
+ call term_sendkeys(buf, "q\<CR>")
+ call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000)
+
+ " Pressing enter in ex mode should print the current line
+ call term_sendkeys(buf, "\<CR>")
+ call WaitForAssert({-> assert_match(' 3 foo foo',
+ \ term_getline(buf, 5))}, 1000)
+
+ call term_sendkeys(buf, ":vi\<CR>")
+ call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for displaying lines from an empty buffer in Ex mode
+func Test_Ex_emptybuf()
+ new
+ call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E749:')
+ call setline(1, "abc")
+ call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E501:')
+ call assert_fails('call feedkeys("Q%d\<CR>", "xt")', 'E749:')
+ close!
+endfunc
+
+" Test for the :open command
+func Test_open_command()
+ new
+ call setline(1, ['foo foo', 'foo bar', 'foo baz'])
+ call feedkeys("Qopen\<CR>j", 'xt')
+ call assert_equal('foo bar', getline('.'))
+ call feedkeys("Qopen /bar/\<CR>", 'xt')
+ call assert_equal(5, col('.'))
+ call assert_fails('call feedkeys("Qopen /baz/\<CR>", "xt")', 'E479:')
+ close!
+endfunc
+
+func Test_open_command_flush_line()
+ " this was accessing freed memory: the regexp match uses a pointer to the
+ " current line which becomes invalid when searching for the ') mark.
+ new
+ call setline(1, ['one', 'two. three'])
+ s/one/ONE
+ try
+ open /\%')/
+ catch /E479/
+ endtry
+ bwipe!
+endfunc
+
+" Test for :g/pat/visual to run vi commands in Ex mode
+" This used to hang Vim before 8.2.0274.
+func Test_Ex_global()
+ new
+ call setline(1, ['', 'foo', 'bar', 'foo', 'bar', 'foo'])
+ call feedkeys("Q\<bs>g/bar/visual\<CR>$rxQ$ryQvisual\<CR>j", "xt")
+ call assert_equal('bax', getline(3))
+ call assert_equal('bay', getline(5))
+ bwipe!
+endfunc
+
+" Test for pressing Ctrl-C in :append inside a loop in Ex mode
+" This used to hang Vim
+func Test_Ex_append_in_loop()
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('', {'rows': 6})
+
+ call term_sendkeys(buf, "gQ")
+ call term_sendkeys(buf, "for i in range(1)\<CR>")
+ call term_sendkeys(buf, "append\<CR>")
+ call WaitForAssert({-> assert_match(': append', term_getline(buf, 5))}, 1000)
+ call term_sendkeys(buf, "\<C-C>")
+ " Wait for input to be flushed
+ call term_wait(buf)
+ call term_sendkeys(buf, "foo\<CR>")
+ call WaitForAssert({-> assert_match('foo', term_getline(buf, 5))}, 1000)
+ call term_sendkeys(buf, ".\<CR>")
+ call WaitForAssert({-> assert_match('.', term_getline(buf, 5))}, 1000)
+ call term_sendkeys(buf, "endfor\<CR>")
+ call term_sendkeys(buf, "vi\<CR>")
+ call WaitForAssert({-> assert_match('foo', term_getline(buf, 1))}, 1000)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" In Ex-mode, a backslash escapes a newline
+func Test_Ex_escape_enter()
+ call feedkeys("gQlet l = \"a\\\<kEnter>b\"\<cr>vi\<cr>", 'xt')
+ call assert_equal("a\rb", l)
+endfunc
+
+" Test for :append! command in Ex mode
+func Test_Ex_append()
+ new
+ call setline(1, "\t abc")
+ call feedkeys("Qappend!\npqr\nxyz\n.\nvisual\n", 'xt')
+ call assert_equal(["\t abc", "\t pqr", "\t xyz"], getline(1, '$'))
+ close!
+endfunc
+
+" In Ex-mode, backslashes at the end of a command should be halved.
+func Test_Ex_echo_backslash()
+ " This test works only when the language is English
+ CheckEnglish
+ let bsl = '\\\\'
+ let bsl2 = '\\\'
+ call assert_fails('call feedkeys("Qecho " .. bsl .. "\nvisual\n", "xt")',
+ \ 'E15: Invalid expression: "\\"')
+ call assert_fails('call feedkeys("Qecho " .. bsl2 .. "\nm\nvisual\n", "xt")',
+ \ "E15: Invalid expression: \"\\\nm\"")
+endfunc
+
+func Test_ex_mode_errors()
+ " Not allowed to enter ex mode when text is locked
+ au InsertCharPre <buffer> normal! gQ<CR>
+ let caught_e565 = 0
+ try
+ call feedkeys("ix\<esc>", 'xt')
+ catch /^Vim\%((\a\+)\)\=:E565/ " catch E565
+ let caught_e565 = 1
+ endtry
+ call assert_equal(1, caught_e565)
+ au! InsertCharPre
+
+ new
+ au CmdLineEnter * call ExEnterFunc()
+ func ExEnterFunc()
+
+ endfunc
+ call feedkeys("gQvi\r", 'xt')
+
+ au! CmdLineEnter
+ delfunc ExEnterFunc
+ quit
+endfunc
+
+func Test_ex_mode_with_global()
+ CheckNotGui
+ CheckFeature timers
+
+ " This will get stuck in Normal mode after the failed "J", use a timer to
+ " get going again.
+ let lines =<< trim END
+ call ch_logfile('logfile', 'w')
+ pedit
+ func FeedQ(id)
+ call feedkeys('Q', 't')
+ endfunc
+ call timer_start(10, 'FeedQ')
+ g/^/vi|HJ
+ call writefile(['done'], 'Xdidexmode')
+ qall!
+ END
+ call writefile(lines, 'Xexmodescript', 'D')
+ call assert_equal(1, RunVim([], [], '-e -s -S Xexmodescript'))
+ call assert_equal(['done'], readfile('Xdidexmode'))
+
+ call delete('logfile')
+ call delete('Xdidexmode')
+endfunc
+
+func Test_ex_mode_count_overflow()
+ " The multiplication causes an integer overflow
+ CheckNotAsan
+
+ " this used to cause a crash
+ let lines =<< trim END
+ call feedkeys("\<Esc>Q\<CR>")
+ v9|9silent! vi|333333233333y32333333%O
+ call writefile(['done'], 'Xdidexmode')
+ qall!
+ END
+ call writefile(lines, 'Xexmodescript', 'D')
+ call assert_equal(1, RunVim([], [], '-e -s -S Xexmodescript -c qa'))
+ call assert_equal(['done'], readfile('Xdidexmode'))
+
+ call delete('Xdidexmode')
+endfunc
+
+func Test_ex_mode_large_indent()
+ new
+ set ts=500 ai
+ call setline(1, "\t")
+ exe "normal gQi\<CR>."
+ set ts=8 noai
+ bwipe!
+endfunc
+
+" This was accessing illegal memory when using "+" for eap->cmd.
+func Test_empty_command_visual_mode()
+ let lines =<< trim END
+ r<sfile>
+ 0norm0V:
+ :qall!
+ END
+ call writefile(lines, 'Xexmodescript', 'D')
+ call assert_equal(1, RunVim([], [], '-u NONE -e -s -S Xexmodescript'))
+
+ " This may cause a dialog to be displayed for an empty command, ignore it.
+ call delete('guidialogfile')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ex_undo.vim b/src/testdir/test_ex_undo.vim
new file mode 100644
index 0000000..48a87df
--- /dev/null
+++ b/src/testdir/test_ex_undo.vim
@@ -0,0 +1,21 @@
+" Tests for :undo
+
+func Test_ex_undo()
+ new ex-undo
+ setlocal ul=10
+ exe "normal ione\n\<Esc>"
+ setlocal ul=10
+ exe "normal itwo\n\<Esc>"
+ setlocal ul=10
+ exe "normal ithree\n\<Esc>"
+ call assert_equal(4, line('$'))
+ undo
+ call assert_equal(3, line('$'))
+ undo 1
+ call assert_equal(2, line('$'))
+ undo 0
+ call assert_equal(1, line('$'))
+ quit!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ex_z.vim b/src/testdir/test_ex_z.vim
new file mode 100644
index 0000000..4607d96
--- /dev/null
+++ b/src/testdir/test_ex_z.vim
@@ -0,0 +1,117 @@
+" Test :z
+
+func Test_z()
+ call setline(1, range(1, 100))
+
+ let a = execute('20z3')
+ call assert_equal("\n20\n21\n22", a)
+ call assert_equal(22, line('.'))
+ " 'window' should be set to the {count} value.
+ call assert_equal(3, &window)
+
+ " If there is only one window, then twice the amount of 'scroll' is used.
+ set scroll=2
+ let a = execute('20z')
+ call assert_equal("\n20\n21\n22\n23", a)
+ call assert_equal(23, line('.'))
+
+ let a = execute('20z+3')
+ " FIXME: I would expect the same result as '20z3' since 'help z'
+ " says: Specifying no mark at all is the same as "+".
+ " However it " gives "\n21\n22\n23" instead. Bug in Vim or in ":help :z"?
+ "call assert_equal("\n20\n21\n22", a)
+ "call assert_equal(22, line('.'))
+
+ let a = execute('20z-3')
+ call assert_equal("\n18\n19\n20", a)
+ call assert_equal(20, line('.'))
+
+ let a = execute('20z=3')
+ call assert_match("^\n18\n19\n-\\+\n20\n-\\+\n21\n22$", a)
+ call assert_equal(20, line('.'))
+
+ let a = execute('20z^3')
+ call assert_equal("\n14\n15\n16\n17", a)
+ call assert_equal(17, line('.'))
+
+ let a = execute('20z.3')
+ call assert_equal("\n19\n20\n21", a)
+ call assert_equal(21, line('.'))
+
+ let a = execute('20z#3')
+ call assert_equal("\n 20 20\n 21 21\n 22 22", a)
+ call assert_equal(22, line('.'))
+
+ let a = execute('20z#-3')
+ call assert_equal("\n 18 18\n 19 19\n 20 20", a)
+ call assert_equal(20, line('.'))
+
+ let a = execute('20z#=3')
+ call assert_match("^\n 18 18\n 19 19\n-\\+\n 20 20\n-\\+\n 21 21\n 22 22$", a)
+ call assert_equal(20, line('.'))
+
+ " Test with {count} bigger than the number of lines in buffer.
+ let a = execute('20z1000')
+ call assert_match("^\n20\n21\n.*\n99\n100$", a)
+ call assert_equal(100, line('.'))
+
+ let a = execute('20z-1000')
+ call assert_equal(20, line('.'))
+
+ let a = execute('20z=1000')
+ call assert_match("^\n1\n.*\n-\\+\n20\n-\\\+\n.*\n100$", a)
+ call assert_equal(20, line('.'))
+
+ " Tests with multiple windows.
+ 5split
+ call setline(1, range(1, 100))
+ " Without a count, the number line is window height - 3.
+ let a = execute('20z')
+ call assert_equal("\n20\n21", a)
+ call assert_equal(21, line('.'))
+ " If window height - 3 is less than 1, it should be clamped to 1.
+ resize 2
+ let a = execute('20z')
+ call assert_equal("\n20", a)
+ call assert_equal(20, line('.'))
+
+ call assert_fails('20z=a', 'E144:')
+
+ set window& scroll&
+ bw!
+endfunc
+
+" :z! is the same as :z but count uses the Vim window height when not specified.
+func Test_z_bang()
+ 4split
+ call setline(1, range(1, 20))
+
+ let a = execute('10z!')
+ call assert_equal("\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20", a)
+
+ let a = execute('10z!#')
+ call assert_equal("\n 10 10\n 11 11\n 12 12\n 13 13\n 14 14\n 15 15\n 16 16\n 17 17\n 18 18\n 19 19\n 20 20", a)
+
+ let a = execute('10z!3')
+ call assert_equal("\n10\n11\n12", a)
+
+ %bwipe!
+endfunc
+
+func Test_z_overflow()
+ " This used to access invalid memory as a result of an integer overflow
+ " and freeze vim.
+ normal ox
+ normal Heat
+ z777777776666666
+ ')
+endfunc
+
+func Test_z_negative_lnum()
+ new
+ z^
+ call assert_equal(1, line('.'))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_excmd.vim b/src/testdir/test_excmd.vim
new file mode 100644
index 0000000..3637351
--- /dev/null
+++ b/src/testdir/test_excmd.vim
@@ -0,0 +1,728 @@
+" Tests for various Ex commands.
+
+source check.vim
+source shared.vim
+source term_util.vim
+
+func Test_ex_delete()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ 2
+ " :dl is :delete with the "l" flag, not :dlist
+ .dl
+ call assert_equal(['a', 'c'], getline(1, 2))
+endfunc
+
+func Test_range_error()
+ call assert_fails(':.echo 1', 'E481:')
+ call assert_fails(':$echo 1', 'E481:')
+ call assert_fails(':1,2echo 1', 'E481:')
+ call assert_fails(':+1echo 1', 'E481:')
+ call assert_fails(':/1/echo 1', 'E481:')
+ call assert_fails(':\/echo 1', 'E481:')
+ normal vv
+ call assert_fails(":'<,'>echo 1", 'E481:')
+ call assert_fails(":\\xcenter", 'E10:')
+endfunc
+
+func Test_buffers_lastused()
+ call test_settime(localtime() - 2000) " middle
+ edit bufa
+ enew
+ call test_settime(localtime() - 10) " newest
+ edit bufb
+ enew
+ call test_settime(1550010000) " oldest
+ edit bufc
+ enew
+ call test_settime(0)
+ enew
+
+ let ls = split(execute('buffers t', 'silent!'), '\n')
+ let bufs = ls->map({i,v->split(v, '"\s*')[1:2]})
+ call assert_equal(['bufb', 'bufa', 'bufc'], bufs[1:]->map({i,v->v[0]}))
+ call assert_match('1[0-3] seconds ago', bufs[1][1])
+ call assert_match('\d\d:\d\d:\d\d', bufs[2][1])
+ call assert_match('2019/02/1\d \d\d:\d\d:00', bufs[3][1])
+
+ bwipeout bufa
+ bwipeout bufb
+ bwipeout bufc
+endfunc
+
+" Test for the :copy command
+func Test_copy()
+ new
+
+ call setline(1, ['L1', 'L2', 'L3', 'L4'])
+ " copy lines in a range to inside the range
+ 1,3copy 2
+ call assert_equal(['L1', 'L2', 'L1', 'L2', 'L3', 'L3', 'L4'], getline(1, 7))
+
+ " Specifying a count before using : to run an ex-command
+ exe "normal! gg4:yank\<CR>"
+ call assert_equal("L1\nL2\nL1\nL2\n", @")
+
+ close!
+endfunc
+
+" Test for the :file command
+func Test_file_cmd()
+ call assert_fails('3file', 'E474:')
+ call assert_fails('0,0file', 'E474:')
+ call assert_fails('0file abc', 'E474:')
+ if !has('win32')
+ " Change the name of the buffer to the same name
+ new Xfile1
+ file Xfile1
+ call assert_equal('Xfile1', @%)
+ call assert_equal('Xfile1', @#)
+ bw!
+ endif
+endfunc
+
+" Test for the :drop command
+func Test_drop_cmd()
+ call writefile(['L1', 'L2'], 'Xdropfile', 'D')
+ enew | only
+ drop Xdropfile
+ call assert_equal('L2', getline(2))
+ " Test for switching to an existing window
+ below new
+ drop Xdropfile
+ call assert_equal(1, winnr())
+ " Test for splitting the current window
+ enew | only
+ set modified
+ drop Xdropfile
+ call assert_equal(2, winnr('$'))
+ " Check for setting the argument list
+ call assert_equal(['Xdropfile'], argv())
+ enew | only!
+endfunc
+
+" Test for the :append command
+func Test_append_cmd()
+ new
+ call setline(1, [' L1'])
+ call feedkeys(":append\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ %delete _
+ " append after a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2append\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L4', ' L5', ' L3'], getline(1, '$'))
+ %delete _
+ " append with toggling 'autoindent'
+ call setline(1, [' L1'])
+ call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " append with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1'])
+ call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
+func Test_append_cmd_empty_buf()
+ CheckRunVimInTerminal
+ let lines =<< trim END
+ func Timer(timer)
+ append
+ aaaaa
+ bbbbb
+ .
+ endfunc
+ call timer_start(10, 'Timer')
+ END
+ call writefile(lines, 'Xtest_append_cmd_empty_buf', 'D')
+ let buf = RunVimInTerminal('-S Xtest_append_cmd_empty_buf', {'rows': 6})
+ call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))})
+ call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for the :insert command
+func Test_insert_cmd()
+ new
+ call setline(1, [' L1'])
+ call feedkeys(":insert\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ %delete _
+ " insert before a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2insert\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L4', ' L5', ' L2', ' L3'], getline(1, '$'))
+ %delete _
+ " insert with toggling 'autoindent'
+ call setline(1, [' L1'])
+ call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " insert with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1'])
+ call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
+func Test_insert_cmd_empty_buf()
+ CheckRunVimInTerminal
+ let lines =<< trim END
+ func Timer(timer)
+ insert
+ aaaaa
+ bbbbb
+ .
+ endfunc
+ call timer_start(10, 'Timer')
+ END
+ call writefile(lines, 'Xtest_insert_cmd_empty_buf', 'D')
+ let buf = RunVimInTerminal('-S Xtest_insert_cmd_empty_buf', {'rows': 6})
+ call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))})
+ call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for the :change command
+func Test_change_cmd()
+ new
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ %delete _
+ " change a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L4', ' L5', ' L3'], getline(1, '$'))
+ %delete _
+ " change with toggling 'autoindent'
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " change with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
+" Test for the :language command
+func Test_language_cmd()
+ CheckFeature multi_lang
+
+ call assert_fails('language ctype non_existing_lang', 'E197:')
+ call assert_fails('language time non_existing_lang', 'E197:')
+endfunc
+
+" Test for the :confirm command dialog
+func Test_confirm_cmd()
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ call writefile(['foo1'], 'Xfoo', 'D')
+ call writefile(['bar1'], 'Xbar', 'D')
+
+ " Test for saving all the modified buffers
+ let lines =<< trim END
+ set nomore
+ new Xfoo
+ call setline(1, 'foo2')
+ new Xbar
+ call setline(1, 'bar2')
+ wincmd b
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
+ call term_sendkeys(buf, ":confirm qall\n")
+ call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, "A")
+ call StopVimInTerminal(buf)
+
+ call assert_equal(['foo2'], readfile('Xfoo'))
+ call assert_equal(['bar2'], readfile('Xbar'))
+
+ " Test for discarding all the changes to modified buffers
+ let lines =<< trim END
+ set nomore
+ new Xfoo
+ call setline(1, 'foo3')
+ new Xbar
+ call setline(1, 'bar3')
+ wincmd b
+ END
+ call writefile(lines, 'Xscript')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
+ call term_sendkeys(buf, ":confirm qall\n")
+ call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, "D")
+ call StopVimInTerminal(buf)
+
+ call assert_equal(['foo2'], readfile('Xfoo'))
+ call assert_equal(['bar2'], readfile('Xbar'))
+
+ " Test for saving and discarding changes to some buffers
+ let lines =<< trim END
+ set nomore
+ new Xfoo
+ call setline(1, 'foo4')
+ new Xbar
+ call setline(1, 'bar4')
+ wincmd b
+ END
+ call writefile(lines, 'Xscript')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
+ call term_sendkeys(buf, ":confirm qall\n")
+ call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, "N")
+ call WaitForAssert({-> assert_match('\[Y\]es, (N)o, (C)ancel: ', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, "Y")
+ call StopVimInTerminal(buf)
+
+ call assert_equal(['foo4'], readfile('Xfoo'))
+ call assert_equal(['bar2'], readfile('Xbar'))
+endfunc
+
+func Test_confirm_cmd_cancel()
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ " Test for closing a window with a modified buffer
+ let lines =<< trim END
+ set nomore
+ new
+ call setline(1, 'abc')
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
+ call term_sendkeys(buf, ":confirm close\n")
+ call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
+ \ term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, "C")
+ call WaitForAssert({-> assert_equal('', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, ":confirm close\n")
+ call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
+ \ term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, "N")
+ call WaitForAssert({-> assert_match('^ *0,0-1 All$',
+ \ term_getline(buf, 20))}, 1000)
+ call StopVimInTerminal(buf)
+endfunc
+
+" The ":confirm" prompt was sometimes used with the terminal in cooked mode.
+" This test verifies that a "\<CR>" character is NOT required to respond to a
+" prompt from the ":conf q" and ":conf wq" commands.
+func Test_confirm_q_wq()
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ call writefile(['foo'], 'Xfoo', 'D')
+
+ let lines =<< trim END
+ set hidden nomore
+ call setline(1, 'abc')
+ edit Xfoo
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
+ call term_sendkeys(buf, ":confirm q\n")
+ call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
+ \ term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, 'C')
+ call WaitForAssert({-> assert_notmatch('^\[Y\]es, (N)o, (C)ancel: C*$',
+ \ term_getline(buf, 20))}, 1000)
+
+ call term_sendkeys(buf, ":edit Xfoo\n")
+ call term_sendkeys(buf, ":confirm wq\n")
+ call WaitForAssert({-> assert_match('^\[Y\]es, (N)o, (C)ancel: *$',
+ \ term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, 'C')
+ call WaitForAssert({-> assert_notmatch('^\[Y\]es, (N)o, (C)ancel: C*$',
+ \ term_getline(buf, 20))}, 1000)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_confirm_write_ro()
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ call writefile(['foo'], 'Xconfirm_write_ro', 'D')
+ let lines =<< trim END
+ set nobackup ff=unix cmdheight=2
+ edit Xconfirm_write_ro
+ norm Abar
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
+
+ " Try to write with 'ro' option.
+ call term_sendkeys(buf, ":set ro | confirm w\n")
+ call WaitForAssert({-> assert_match("^'readonly' option is set for \"Xconfirm_write_ro\"\. *$",
+ \ term_getline(buf, 18))}, 1000)
+ call WaitForAssert({-> assert_match('^Do you wish to write anyway? *$',
+ \ term_getline(buf, 19))}, 1000)
+ call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, 'N')
+ call WaitForAssert({-> assert_match('^ *$', term_getline(buf, 19))}, 1000)
+ call WaitForAssert({-> assert_match('.* All$', term_getline(buf, 20))}, 1000)
+ call assert_equal(['foo'], readfile('Xconfirm_write_ro'))
+
+ call term_sendkeys(buf, ":confirm w\n")
+ call WaitForAssert({-> assert_match("^'readonly' option is set for \"Xconfirm_write_ro\"\. *$",
+ \ term_getline(buf, 18))}, 1000)
+ call WaitForAssert({-> assert_match('^Do you wish to write anyway? *$',
+ \ term_getline(buf, 19))}, 1000)
+ call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, 'Y')
+ call WaitForAssert({-> assert_match('^"Xconfirm_write_ro" 1L, 7B written$',
+ \ term_getline(buf, 19))}, 1000)
+ call assert_equal(['foobar'], readfile('Xconfirm_write_ro'))
+
+ " Try to write with read-only file permissions.
+ call setfperm('Xconfirm_write_ro', 'r--r--r--')
+ call term_sendkeys(buf, ":set noro | undo | confirm w\n")
+ call WaitForAssert({-> assert_match("^File permissions of \"Xconfirm_write_ro\" are read-only\. *$",
+ \ term_getline(buf, 17))}, 1000)
+ call WaitForAssert({-> assert_match('^It may still be possible to write it\. *$',
+ \ term_getline(buf, 18))}, 1000)
+ call WaitForAssert({-> assert_match('^Do you wish to try? *$', term_getline(buf, 19))}, 1000)
+ call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$', term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, 'Y')
+ call WaitForAssert({-> assert_match('^"Xconfirm_write_ro" 1L, 4B written$',
+ \ term_getline(buf, 19))}, 1000)
+ call assert_equal(['foo'], readfile('Xconfirm_write_ro'))
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_confirm_write_partial_file()
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ call writefile(['a', 'b', 'c', 'd'], 'Xwrite_partial', 'D')
+ call writefile(['set nobackup ff=unix cmdheight=2',
+ \ 'edit Xwrite_partial'], 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 20})
+
+ call term_sendkeys(buf, ":confirm 2,3w\n")
+ call WaitForAssert({-> assert_match('^Write partial file? *$',
+ \ term_getline(buf, 19))}, 1000)
+ call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$',
+ \ term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, 'N')
+ call WaitForAssert({-> assert_match('.* All$', term_getline(buf, 20))}, 1000)
+ call assert_equal(['a', 'b', 'c', 'd'], readfile('Xwrite_partial'))
+ call delete('Xwrite_partial')
+
+ call term_sendkeys(buf, ":confirm 2,3w\n")
+ call WaitForAssert({-> assert_match('^Write partial file? *$',
+ \ term_getline(buf, 19))}, 1000)
+ call WaitForAssert({-> assert_match('^(Y)es, \[N\]o: *$',
+ \ term_getline(buf, 20))}, 1000)
+ call term_sendkeys(buf, 'Y')
+ call WaitForAssert({-> assert_match('^"Xwrite_partial" \[New\] 2L, 4B written *$',
+ \ term_getline(buf, 19))}, 1000)
+ call WaitForAssert({-> assert_match('^Press ENTER or type command to continue *$',
+ \ term_getline(buf, 20))}, 1000)
+ call assert_equal(['b', 'c'], readfile('Xwrite_partial'))
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for the :print command
+func Test_print_cmd()
+ call assert_fails('print', 'E749:')
+endfunc
+
+" Test for the :winsize command
+func Test_winsize_cmd()
+ call assert_fails('winsize 1', 'E465:')
+ call assert_fails('winsize 1 x', 'E465:')
+ call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)')
+ " Actually changing the window size would be flaky.
+endfunc
+
+" Test for the :redir command
+" NOTE: if you run tests as root this will fail. Don't run tests as root!
+func Test_redir_cmd()
+ call assert_fails('redir @@', 'E475:')
+ call assert_fails('redir abc', 'E475:')
+ call assert_fails('redir => 1abc', 'E474:')
+ call assert_fails('redir => a b', 'E488:')
+ call assert_fails('redir => abc[1]', 'E121:')
+ let b = 0zFF
+ call assert_fails('redir =>> b', 'E734:')
+ unlet b
+
+ if has('unix')
+ " Redirecting to a directory name
+ call mkdir('Xredir')
+ call assert_fails('redir > Xredir', 'E17:')
+ call delete('Xredir', 'd')
+ endif
+
+ " Test for redirecting to a register
+ redir @q> | echon 'clean ' | redir END
+ redir @q>> | echon 'water' | redir END
+ call assert_equal('clean water', @q)
+
+ " Test for redirecting to a variable
+ redir => color | echon 'blue ' | redir END
+ redir =>> color | echon 'sky' | redir END
+ call assert_equal('blue sky', color)
+endfunc
+
+func Test_redir_cmd_readonly()
+ CheckNotRoot
+
+ " Redirecting to a read-only file
+ call writefile([], 'Xredirfile', 'D')
+ call setfperm('Xredirfile', 'r--r--r--')
+ call assert_fails('redir! > Xredirfile', 'E190:')
+endfunc
+
+" Test for the :filetype command
+func Test_filetype_cmd()
+ call assert_fails('filetype abc', 'E475:')
+endfunc
+
+" Test for the :mode command
+func Test_mode_cmd()
+ call assert_fails('mode abc', 'E359:')
+endfunc
+
+" Test for the :sleep command
+func Test_sleep_cmd()
+ call assert_fails('sleep x', 'E475:')
+endfunc
+
+" Test for the :read command
+func Test_read_cmd()
+ call writefile(['one'], 'Xcmdfile', 'D')
+ new
+ call assert_fails('read', 'E32:')
+ edit Xcmdfile
+ read
+ call assert_equal(['one', 'one'], getline(1, '$'))
+ close!
+ new
+ read Xcmdfile
+ call assert_equal(['', 'one'], getline(1, '$'))
+ call deletebufline('', 1, '$')
+ call feedkeys("Qr Xcmdfile\<CR>visual\<CR>", 'xt')
+ call assert_equal(['one'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for running Ex commands when text is locked.
+" <C-\>e in the command line is used to lock the text
+func Test_run_excmd_with_text_locked()
+ " :quit
+ let cmd = ":\<C-\>eexecute('quit')\<CR>\<C-C>"
+ call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
+
+ " :qall
+ let cmd = ":\<C-\>eexecute('qall')\<CR>\<C-C>"
+ call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
+
+ " :exit
+ let cmd = ":\<C-\>eexecute('exit')\<CR>\<C-C>"
+ call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
+
+ " :close - should be ignored
+ new
+ let cmd = ":\<C-\>eexecute('close')\<CR>\<C-C>"
+ call assert_equal(2, winnr('$'))
+ close
+
+ call assert_fails("call feedkeys(\":\<C-R>=execute('bnext')\<CR>\", 'xt')", 'E565:')
+
+ " :tabfirst
+ tabnew
+ call assert_fails("call feedkeys(\":\<C-R>=execute('tabfirst')\<CR>\", 'xt')", 'E565:')
+ tabclose
+endfunc
+
+" Test for the :verbose command
+func Test_verbose_cmd()
+ set verbose=3
+ call assert_match(' verbose=1\n\s*Last set from ', execute('verbose set vbs'), "\n")
+ call assert_equal([' verbose=0'], split(execute('0verbose set vbs'), "\n"))
+ set verbose=0
+ call assert_match(' verbose=4\n\s*Last set from .*\n verbose=0',
+ \ execute("4verbose set verbose | set verbose"))
+endfunc
+
+" Test for the :delete command and the related abbreviated commands
+func Test_excmd_delete()
+ new
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal(['^Ibar$'], split(execute('dl'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal(['^Ibar$'], split(execute('dell'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal(['^Ibar$'], split(execute('delel'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal(['^Ibar$'], split(execute('deletl'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal(['^Ibar$'], split(execute('deletel'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal([' bar'], split(execute('dp'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal([' bar'], split(execute('dep'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal([' bar'], split(execute('delp'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal([' bar'], split(execute('delep'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal([' bar'], split(execute('deletp'), "\n"))
+ call setline(1, ['foo', "\tbar"])
+ call assert_equal([' bar'], split(execute('deletep'), "\n"))
+ close!
+endfunc
+
+" Test for commands that are blocked in a sandbox
+func Sandbox_tests()
+ call assert_fails("call histadd(':', 'ls')", 'E48:')
+ call assert_fails("call mkdir('Xdir')", 'E48:')
+ call assert_fails("call rename('a', 'b')", 'E48:')
+ call assert_fails("call setbufvar(1, 'myvar', 1)", 'E48:')
+ call assert_fails("call settabvar(1, 'myvar', 1)", 'E48:')
+ call assert_fails("call settabwinvar(1, 1, 'myvar', 1)", 'E48:')
+ call assert_fails("call setwinvar(1, 'myvar', 1)", 'E48:')
+ call assert_fails("call timer_start(100, '')", 'E48:')
+ if has('channel')
+ call assert_fails("call prompt_setcallback(1, '')", 'E48:')
+ call assert_fails("call prompt_setinterrupt(1, '')", 'E48:')
+ call assert_fails("call prompt_setprompt(1, '')", 'E48:')
+ endif
+ call assert_fails("let $TESTVAR=1", 'E48:')
+ call assert_fails("call feedkeys('ivim')", 'E48:')
+ call assert_fails("source! Xsomefile", 'E48:')
+ call assert_fails("call delete('Xthatfile')", 'E48:')
+ call assert_fails("call writefile([], 'Xanotherfile')", 'E48:')
+ call assert_fails('!ls', 'E48:')
+ call assert_fails('shell', 'E48:')
+ call assert_fails('stop', 'E48:')
+ call assert_fails('exe "normal \<C-Z>"', 'E48:')
+ set insertmode
+ call assert_fails('call feedkeys("\<C-Z>", "xt")', 'E48:')
+ set insertmode&
+ call assert_fails('suspend', 'E48:')
+ call assert_fails('call system("ls")', 'E48:')
+ call assert_fails('call systemlist("ls")', 'E48:')
+ if has('clientserver')
+ call assert_fails('let s=remote_expr("gvim", "2+2")', 'E48:')
+ if !has('win32')
+ " remote_foreground() doesn't throw an error message on MS-Windows
+ call assert_fails('call remote_foreground("gvim")', 'E48:')
+ endif
+ call assert_fails('let s=remote_peek("gvim")', 'E48:')
+ call assert_fails('let s=remote_read("gvim")', 'E48:')
+ call assert_fails('let s=remote_send("gvim", "abc")', 'E48:')
+ call assert_fails('let s=server2client("gvim", "abc")', 'E48:')
+ endif
+ if has('terminal')
+ call assert_fails('terminal', 'E48:')
+ call assert_fails('call term_start("vim")', 'E48:')
+ call assert_fails('call term_dumpwrite(1, "Xdumpfile")', 'E48:')
+ endif
+ if has('channel')
+ call assert_fails("call ch_logfile('chlog')", 'E48:')
+ call assert_fails("call ch_open('localhost:8765')", 'E48:')
+ endif
+ if has('job')
+ call assert_fails("call job_start('vim')", 'E48:')
+ endif
+ if has('unix') && has('libcall')
+ call assert_fails("echo libcall('libc.so', 'getenv', 'HOME')", 'E48:')
+ endif
+ if has('unix')
+ call assert_fails('cd `pwd`', 'E48:')
+ endif
+ " some options cannot be changed in a sandbox
+ call assert_fails('set exrc', 'E48:')
+ call assert_fails('set cdpath', 'E48:')
+ if has('xim') && has('gui_gtk')
+ call assert_fails('set imstyle', 'E48:')
+ endif
+endfunc
+
+func Test_sandbox()
+ sandbox call Sandbox_tests()
+endfunc
+
+func Test_command_not_implemented_E319()
+ if !has('mzscheme')
+ call assert_fails('mzscheme', 'E319:')
+ endif
+endfunc
+
+func Test_not_break_expression_register()
+ call setreg('=', '1+1')
+ if 0
+ put =1
+ endif
+ call assert_equal('1+1', getreg('=', 1))
+endfunc
+
+func Test_address_line_overflow()
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+ new
+ call setline(1, range(100))
+ call assert_fails('|.44444444444444444444444', 'E1247:')
+ call assert_fails('|.9223372036854775806', 'E1247:')
+
+ $
+ yank 77777777777777777777
+ call assert_equal("99\n", @")
+
+ bwipe!
+endfunc
+
+" This was leaving the cursor in line zero
+func Test_using_zero_in_range()
+ new
+ norm o00
+ silent! 0;s/\%')
+ bwipe!
+endfunc
+
+" Test :write after changing name with :file and loading it with :edit
+func Test_write_after_rename()
+ call writefile(['text'], 'Xafterfile', 'D')
+
+ enew
+ file Xafterfile
+ call assert_fails('write', 'E13: File exists (add ! to override)')
+
+ " works OK after ":edit"
+ edit
+ write
+
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_exec_while_if.vim b/src/testdir/test_exec_while_if.vim
new file mode 100644
index 0000000..3f13b09
--- /dev/null
+++ b/src/testdir/test_exec_while_if.vim
@@ -0,0 +1,43 @@
+" Test for :execute, :while, :for and :if
+
+func Test_exec_while_if()
+ new
+
+ let i = 0
+ while i < 12
+ let i = i + 1
+ execute "normal o" . i . "\033"
+ if i % 2
+ normal Ax
+ if i == 9
+ break
+ endif
+ if i == 5
+ continue
+ else
+ let j = 9
+ while j > 0
+ execute "normal" j . "a" . j . "\x1b"
+ let j = j - 1
+ endwhile
+ endif
+ endif
+ if i == 9
+ execute "normal Az\033"
+ endif
+ endwhile
+ unlet i j
+
+ call assert_equal(["",
+ \ "1x999999999888888887777777666666555554444333221",
+ \ "2",
+ \ "3x999999999888888887777777666666555554444333221",
+ \ "4",
+ \ "5x",
+ \ "6",
+ \ "7x999999999888888887777777666666555554444333221",
+ \ "8",
+ \ "9x"], getline(1, 10))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_execute_func.vim b/src/testdir/test_execute_func.vim
new file mode 100644
index 0000000..09b976b
--- /dev/null
+++ b/src/testdir/test_execute_func.vim
@@ -0,0 +1,207 @@
+" test execute()
+
+source view_util.vim
+source check.vim
+import './vim9.vim' as v9
+source term_util.vim
+
+func NestedEval()
+ let nested = execute('echo "nested\nlines"')
+ echo 'got: "' . nested . '"'
+endfunc
+
+func NestedRedir()
+ redir => var
+ echo 'broken'
+ redir END
+endfunc
+
+func Test_execute_string()
+ call assert_equal("\nnocompatible", execute('set compatible?'))
+ call assert_equal("\nsomething\nnice", execute('echo "something\nnice"'))
+ call assert_equal("noendofline", execute('echon "noendofline"'))
+ call assert_equal("", execute(123))
+
+ call assert_equal("\ngot: \"\nnested\nlines\"", execute('call NestedEval()'))
+ redir => redired
+ echo 'this'
+ let evaled = execute('echo "that"')
+ echo 'theend'
+ redir END
+ call assert_equal("\nthis\ntheend", redired)
+ call assert_equal("\nthat", evaled)
+
+ call assert_fails('call execute("doesnotexist")', 'E492:')
+ call assert_fails('call execute("call NestedRedir()")', 'E930:')
+
+ call assert_equal("\nsomething", execute('echo "something"', ''))
+ call assert_equal("\nsomething", execute('echo "something"', 'silent'))
+ call assert_equal("\nsomething", execute('echo "something"', 'silent!'))
+ call assert_equal("", execute('burp', 'silent!'))
+ call assert_fails('call execute(3.4)', 'E492:')
+ call assert_equal("\nx", execute("echo \"x\"", 3.4))
+ call v9.CheckDefExecAndScriptFailure(['execute("echo \"x\"", 3.4)'], ['E1013: Argument 2: type mismatch, expected string but got float', 'E1174:'])
+endfunc
+
+func Test_execute_list()
+ call assert_equal("\nsomething\nnice", execute(['echo "something"', 'echo "nice"']))
+ let l = ['for n in range(0, 3)',
+ \ 'echo n',
+ \ 'endfor']
+ call assert_equal("\n0\n1\n2\n3", execute(l))
+
+ call assert_equal("", execute([]))
+endfunc
+
+func Test_execute_does_not_change_col()
+ echo ''
+ echon 'abcd'
+ let x = execute('silent echo 234343')
+ echon 'xyz'
+ let text = ''
+ for col in range(1, 7)
+ let text .= nr2char(screenchar(&lines, col))
+ endfor
+ call assert_equal('abcdxyz', text)
+endfunc
+
+func Test_execute_not_silent()
+ echo ''
+ echon 'abcd'
+ let x = execute('echon 234', '')
+ echo 'xyz'
+ let text1 = ''
+ for col in range(1, 8)
+ let text1 .= nr2char(screenchar(&lines - 1, col))
+ endfor
+ call assert_equal('abcd234 ', text1)
+ let text2 = ''
+ for col in range(1, 4)
+ let text2 .= nr2char(screenchar(&lines, col))
+ endfor
+ call assert_equal('xyz ', text2)
+endfunc
+
+func Test_win_execute()
+ let thiswin = win_getid()
+ new
+ let otherwin = win_getid()
+ call setline(1, 'the new window')
+ call win_gotoid(thiswin)
+ let line = win_execute(otherwin, 'echo getline(1)')
+ call assert_match('the new window', line)
+ let line = win_execute(134343, 'echo getline(1)')
+ call assert_equal('', line)
+
+ if has('popupwin')
+ let popupwin = popup_create('the popup win', {'line': 2, 'col': 3})
+ redraw
+ let line = 'echo getline(1)'->win_execute(popupwin)
+ call assert_match('the popup win', line)
+
+ call popup_close(popupwin)
+ endif
+
+ call win_gotoid(otherwin)
+ bwipe!
+
+ " check :lcd in another window does not change directory
+ let curid = win_getid()
+ let curdir = getcwd()
+ split Xother
+ lcd ..
+ " Use :pwd to get the actual current directory
+ let otherdir = execute('pwd')
+ call win_execute(curid, 'lcd testdir')
+ call assert_equal(otherdir, execute('pwd'))
+ bwipe!
+ execute 'cd ' .. curdir
+endfunc
+
+func Test_win_execute_update_ruler()
+ CheckFeature quickfix
+
+ enew
+ call setline(1, range(500))
+ 20
+ split
+ let winid = win_getid()
+ set ruler
+ wincmd w
+ let height = winheight(winid)
+ redraw
+ call assert_match('20,1', Screenline(height + 1))
+ let line = win_execute(winid, 'call cursor(100, 1)')
+ redraw
+ call assert_match('100,1', Screenline(height + 1))
+
+ bwipe!
+endfunc
+
+func Test_win_execute_other_tab()
+ let thiswin = win_getid()
+ tabnew
+ call win_execute(thiswin, 'let xyz = 1')
+ call assert_equal(1, xyz)
+ tabclose
+ unlet xyz
+endfunc
+
+func Test_win_execute_visual_redraw()
+ call setline(1, ['a', 'b', 'c'])
+ new
+ wincmd p
+ " start Visual in current window, redraw in other window with fewer lines
+ call feedkeys("G\<C-V>", 'txn')
+ call win_execute(winnr('#')->win_getid(), 'redraw')
+ call feedkeys("\<Esc>", 'txn')
+ bwipe!
+ bwipe!
+
+ enew
+ new
+ call setline(1, ['a', 'b', 'c'])
+ let winid = win_getid()
+ wincmd p
+ " start Visual in current window, extend it in other window with more lines
+ call feedkeys("\<C-V>", 'txn')
+ call win_execute(winid, 'call feedkeys("G\<C-V>", ''txn'')')
+ redraw
+
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_win_execute_on_startup()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ [repeat('x', &columns)]->writefile('Xfile1')
+ silent tabedit Xfile2
+ var id = win_getid()
+ silent tabedit Xfile3
+ autocmd VimEnter * win_execute(id, 'close')
+ END
+ call writefile(lines, 'XwinExecute', 'D')
+ let buf = RunVimInTerminal('-p Xfile1 -Nu XwinExecute', {})
+
+ " this was crashing on exit with EXITFREE defined
+ call StopVimInTerminal(buf)
+
+ call delete('Xfile1')
+endfunc
+
+func Test_execute_func_with_null()
+ call assert_equal("", execute(test_null_string()))
+ call assert_equal("", execute(test_null_list()))
+ call assert_fails('call execute(test_null_dict())', 'E731:')
+ call assert_fails('call execute(test_null_blob())', 'E976:')
+ call assert_fails('call execute(test_null_partial())','E729:')
+ if has('job')
+ call assert_fails('call execute(test_null_job())', 'E908:')
+ call assert_fails('call execute(test_null_channel())', 'E908:')
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_exists.vim b/src/testdir/test_exists.vim
new file mode 100644
index 0000000..a735a7d
--- /dev/null
+++ b/src/testdir/test_exists.vim
@@ -0,0 +1,337 @@
+" Tests for the exists() function
+
+func Test_exists()
+ augroup myagroup
+ autocmd! BufEnter *.my echo "myfile edited"
+ autocmd! FuncUndefined UndefFun exec "fu UndefFun()\nendfu"
+ augroup END
+ set rtp+=./sautest
+
+ " valid autocmd group
+ call assert_equal(1, exists('#myagroup'))
+ " valid autocmd group with garbage
+ call assert_equal(0, exists('#myagroup+b'))
+ " Valid autocmd group and event
+ call assert_equal(1, exists('#myagroup#BufEnter'))
+ " Valid autocmd group, event and pattern
+ call assert_equal(1, exists('#myagroup#BufEnter#*.my'))
+ " Valid autocmd event
+ call assert_equal(1, exists('#BufEnter'))
+ " Valid autocmd event and pattern
+ call assert_equal(1, exists('#BufEnter#*.my'))
+ " Non-existing autocmd group or event
+ call assert_equal(0, exists('#xyzagroup'))
+ " Non-existing autocmd group and valid autocmd event
+ call assert_equal(0, exists('#xyzagroup#BufEnter'))
+ " Valid autocmd group and event with no matching pattern
+ call assert_equal(0, exists('#myagroup#CmdwinEnter'))
+ " Valid autocmd group and non-existing autocmd event
+ call assert_equal(0, exists('#myagroup#xyzacmd'))
+ " Valid autocmd group and event and non-matching pattern
+ call assert_equal(0, exists('#myagroup#BufEnter#xyzpat'))
+ " Valid autocmd event and non-matching pattern
+ call assert_equal(0, exists('#BufEnter#xyzpat'))
+ " Empty autocmd group, event and pattern
+ call assert_equal(0, exists('###'))
+ " Empty autocmd group and event or empty event and pattern
+ call assert_equal(0, exists('##'))
+ " Valid autocmd event
+ call assert_equal(1, exists('##FileReadCmd'))
+ " Non-existing autocmd event
+ call assert_equal(0, exists('##MySpecialCmd'))
+
+ " Existing and working option (long form)
+ call assert_equal(1, exists('&textwidth'))
+ " Existing and working option (short form)
+ call assert_equal(1, exists('&tw'))
+ " Existing and working option with garbage
+ call assert_equal(0, exists('&tw-'))
+ " Global option
+ call assert_equal(1, exists('&g:errorformat'))
+ " Local option
+ call assert_equal(1, exists('&l:errorformat'))
+ " Negative form of existing and working option (long form)
+ call assert_equal(0, exists('&nojoinspaces'))
+ " Negative form of existing and working option (short form)
+ call assert_equal(0, exists('&nojs'))
+ " Non-existing option
+ call assert_equal(0, exists('&myxyzoption'))
+
+ " Existing and working option (long form)
+ call assert_equal(1, exists('+incsearch'))
+ " Existing and working option with garbage
+ call assert_equal(0, exists('+incsearch!1'))
+ " Existing and working option (short form)
+ call assert_equal(1, exists('+is'))
+ " Existing option that is hidden.
+ call assert_equal(0, exists('+autoprint'))
+
+ " Existing environment variable
+ let $EDITOR_NAME = 'Vim Editor'
+ call assert_equal(1, exists('$EDITOR_NAME'))
+ if has('unix')
+ " ${name} environment variables are supported only on Unix-like systems
+ call assert_equal(1, exists('${VIM}'))
+ endif
+ " Non-existing environment variable
+ call assert_equal(0, exists('$NON_ENV_VAR'))
+
+ " Valid internal function
+ call assert_equal(1, exists('*bufnr'))
+ " Valid internal function with ()
+ call assert_equal(1, exists('*bufnr()'))
+ " Non-existing internal function
+ call assert_equal(0, exists('*myxyzfunc'))
+ " Valid internal function with garbage
+ call assert_equal(0, exists('*bufnr&6'))
+ " Valid user defined function
+ call assert_equal(1, exists('*Test_exists'))
+ " Non-existing user defined function
+ call assert_equal(0, exists('*MyxyzFunc'))
+ " Function that may be created by FuncUndefined event
+ call assert_equal(0, exists('*UndefFun'))
+ " Function that may be created by script autoloading
+ call assert_equal(0, exists('*footest#F'))
+
+ call assert_equal(has('float'), exists('*acos'))
+ call assert_equal(1, exists('?acos'))
+ call assert_equal(has('win32'), exists('*debugbreak'))
+ call assert_equal(1, exists('?debugbreak'))
+
+ " Valid internal command (full match)
+ call assert_equal(2, exists(':edit'))
+ " Valid internal command (full match) with garbage
+ call assert_equal(0, exists(':edit/a'))
+ " Valid internal command (partial match)
+ call assert_equal(1, exists(':q'))
+ " Valid internal command with a digit
+ call assert_equal(2, exists(':2match'))
+ " Non-existing internal command
+ call assert_equal(0, exists(':invalidcmd'))
+ " Internal command with a count
+ call assert_equal(0, exists(':3buffer'))
+
+ " User defined command (full match)
+ command! MyCmd :echo 'My command'
+ call assert_equal(2, exists(':MyCmd'))
+ " User defined command (partial match)
+ command! MyOtherCmd :echo 'Another command'
+ call assert_equal(3, exists(':My'))
+
+ " Command modifier
+ call assert_equal(2, exists(':rightbelow'))
+
+ " Non-existing user defined command (full match)
+ delcommand MyCmd
+ call assert_equal(0, exists(':MyCmd'))
+
+ " Non-existing user defined command (partial match)
+ delcommand MyOtherCmd
+ call assert_equal(0, exists(':My'))
+
+ " Valid local variable
+ let local_var = 1
+ call assert_equal(1, exists('local_var'))
+ " Valid local variable with garbage
+ call assert_equal(0, exists('local_var%n'))
+ " Non-existing local variable
+ unlet local_var
+ call assert_equal(0, exists('local_var'))
+
+ " Non-existing autoload variable that may be autoloaded
+ call assert_equal(0, exists('footest#x'))
+
+ " Valid local list
+ let local_list = ["blue", "orange"]
+ call assert_equal(1, exists('local_list'))
+ " Valid local list item
+ call assert_equal(1, exists('local_list[1]'))
+ " Valid local list item with garbage
+ call assert_equal(0, exists('local_list[1]+5'))
+ " Invalid local list item
+ call assert_equal(0, exists('local_list[2]'))
+ " Non-existing local list
+ unlet local_list
+ call assert_equal(0, exists('local_list'))
+ " Valid local dictionary
+ let local_dict = {"xcord":100, "ycord":2}
+ call assert_equal(1, exists('local_dict'))
+ " Non-existing local dictionary
+ unlet local_dict
+ call assert_equal(0, exists('local_dict'))
+ " Existing local curly-brace variable
+ let str = "local"
+ let curly_{str}_var = 1
+ call assert_equal(1, exists('curly_{str}_var'))
+ " Non-existing local curly-brace variable
+ unlet curly_{str}_var
+ call assert_equal(0, exists('curly_{str}_var'))
+
+ " Existing global variable
+ let g:global_var = 1
+ call assert_equal(1, exists('g:global_var'))
+ " Existing global variable with garbage
+ call assert_equal(0, exists('g:global_var-n'))
+ " Non-existing global variable
+ unlet g:global_var
+ call assert_equal(0, exists('g:global_var'))
+ " Existing global list
+ let g:global_list = ["blue", "orange"]
+ call assert_equal(1, exists('g:global_list'))
+ " Non-existing global list
+ unlet g:global_list
+ call assert_equal(0, exists('g:global_list'))
+ " Existing global dictionary
+ let g:global_dict = {"xcord":100, "ycord":2}
+ call assert_equal(1, exists('g:global_dict'))
+ " Non-existing global dictionary
+ unlet g:global_dict
+ call assert_equal(0, exists('g:global_dict'))
+ " Existing global curly-brace variable
+ let str = "global"
+ let g:curly_{str}_var = 1
+ call assert_equal(1, exists('g:curly_{str}_var'))
+ " Non-existing global curly-brace variable
+ unlet g:curly_{str}_var
+ call assert_equal(0, exists('g:curly_{str}_var'))
+
+ " Existing window variable
+ let w:window_var = 1
+ call assert_equal(1, exists('w:window_var'))
+ " Non-existing window variable
+ unlet w:window_var
+ call assert_equal(0, exists('w:window_var'))
+ " Existing window list
+ let w:window_list = ["blue", "orange"]
+ call assert_equal(1, exists('w:window_list'))
+ " Non-existing window list
+ unlet w:window_list
+ call assert_equal(0, exists('w:window_list'))
+ " Existing window dictionary
+ let w:window_dict = {"xcord":100, "ycord":2}
+ call assert_equal(1, exists('w:window_dict'))
+ " Non-existing window dictionary
+ unlet w:window_dict
+ call assert_equal(0, exists('w:window_dict'))
+ " Existing window curly-brace variable
+ let str = "window"
+ let w:curly_{str}_var = 1
+ call assert_equal(1, exists('w:curly_{str}_var'))
+ " Non-existing window curly-brace variable
+ unlet w:curly_{str}_var
+ call assert_equal(0, exists('w:curly_{str}_var'))
+
+ " Existing tab variable
+ let t:tab_var = 1
+ call assert_equal(1, exists('t:tab_var'))
+ " Non-existing tab variable
+ unlet t:tab_var
+ call assert_equal(0, exists('t:tab_var'))
+ " Existing tab list
+ let t:tab_list = ["blue", "orange"]
+ call assert_equal(1, exists('t:tab_list'))
+ " Non-existing tab list
+ unlet t:tab_list
+ call assert_equal(0, exists('t:tab_list'))
+ " Existing tab dictionary
+ let t:tab_dict = {"xcord":100, "ycord":2}
+ call assert_equal(1, exists('t:tab_dict'))
+ " Non-existing tab dictionary
+ unlet t:tab_dict
+ call assert_equal(0, exists('t:tab_dict'))
+ " Existing tab curly-brace variable
+ let str = "tab"
+ let t:curly_{str}_var = 1
+ call assert_equal(1, exists('t:curly_{str}_var'))
+ " Non-existing tab curly-brace variable
+ unlet t:curly_{str}_var
+ call assert_equal(0, exists('t:curly_{str}_var'))
+
+ " Existing buffer variable
+ let b:buffer_var = 1
+ call assert_equal(1, exists('b:buffer_var'))
+ " Non-existing buffer variable
+ unlet b:buffer_var
+ call assert_equal(0, exists('b:buffer_var'))
+ " Existing buffer list
+ let b:buffer_list = ["blue", "orange"]
+ call assert_equal(1, exists('b:buffer_list'))
+ " Non-existing buffer list
+ unlet b:buffer_list
+ call assert_equal(0, exists('b:buffer_list'))
+ " Existing buffer dictionary
+ let b:buffer_dict = {"xcord":100, "ycord":2}
+ call assert_equal(1, exists('b:buffer_dict'))
+ " Non-existing buffer dictionary
+ unlet b:buffer_dict
+ call assert_equal(0, exists('b:buffer_dict'))
+ " Existing buffer curly-brace variable
+ let str = "buffer"
+ let b:curly_{str}_var = 1
+ call assert_equal(1, exists('b:curly_{str}_var'))
+ " Non-existing buffer curly-brace variable
+ unlet b:curly_{str}_var
+ call assert_equal(0, exists('b:curly_{str}_var'))
+
+ " Existing Vim internal variable
+ call assert_equal(1, exists('v:version'))
+ " Non-existing Vim internal variable
+ call assert_equal(0, exists('v:non_exists_var'))
+
+ " Existing script-local variable
+ let s:script_var = 1
+ call assert_equal(1, exists('s:script_var'))
+ " Non-existing script-local variable
+ unlet s:script_var
+ call assert_equal(0, exists('s:script_var'))
+ " Existing script-local list
+ let s:script_list = ["blue", "orange"]
+ call assert_equal(1, exists('s:script_list'))
+ " Non-existing script-local list
+ unlet s:script_list
+ call assert_equal(0, exists('s:script_list'))
+ " Existing script-local dictionary
+ let s:script_dict = {"xcord":100, "ycord":2}
+ call assert_equal(1, exists('s:script_dict'))
+ " Non-existing script-local dictionary
+ unlet s:script_dict
+ call assert_equal(0, exists('s:script_dict'))
+ " Existing script curly-brace variable
+ let str = "script"
+ let s:curly_{str}_var = 1
+ call assert_equal(1, exists('s:curly_{str}_var'))
+ " Non-existing script-local curly-brace variable
+ unlet s:curly_{str}_var
+ call assert_equal(0, exists('s:curly_{str}_var'))
+
+ " Existing script-local function
+ function! s:my_script_func()
+ endfunction
+
+ echo '*s:my_script_func: 1'
+ call assert_equal(1, exists('*s:my_script_func'))
+
+ " Non-existing script-local function
+ delfunction s:my_script_func
+
+ call assert_equal(0, exists('*s:my_script_func'))
+ unlet str
+
+ call assert_equal(1, g:footest#x)
+ call assert_equal(0, footest#F())
+ call assert_equal(0, UndefFun())
+endfunc
+
+" exists() test for Function arguments
+func FuncArg_Tests(func_arg, ...)
+ call assert_equal(1, exists('a:func_arg'))
+ call assert_equal(0, exists('a:non_exists_arg'))
+ call assert_equal(1, exists('a:1'))
+ call assert_equal(0, exists('a:2'))
+endfunc
+
+func Test_exists_funcarg()
+ call FuncArg_Tests("arg1", "arg2")
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_exists_autocmd.vim b/src/testdir/test_exists_autocmd.vim
new file mode 100644
index 0000000..58ad9b5
--- /dev/null
+++ b/src/testdir/test_exists_autocmd.vim
@@ -0,0 +1,28 @@
+" Test that groups and patterns are tested correctly when calling exists() for
+" autocommands.
+
+function Test_AutoCommands()
+ let results=[]
+ augroup auexists
+ augroup END
+ call assert_true(exists("##BufEnter"))
+ call assert_false(exists("#BufEnter"))
+ au BufEnter * let g:entered=1
+ call assert_true(exists("#BufEnter"))
+ call assert_false(exists("#auexists#BufEnter"))
+ augroup auexists
+ au BufEnter * let g:entered=1
+ augroup END
+ call assert_true(exists("#auexists#BufEnter"))
+ call assert_false(exists("#BufEnter#*.test"))
+ au BufEnter *.test let g:entered=1
+ call assert_true(exists("#BufEnter#*.test"))
+ edit testfile.test
+ call assert_false(exists("#BufEnter#<buffer>"))
+ au BufEnter <buffer> let g:entered=1
+ call assert_true(exists("#BufEnter#<buffer>"))
+ edit testfile2.test
+ call assert_false(exists("#BufEnter#<buffer>"))
+endfunction
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_exit.vim b/src/testdir/test_exit.vim
new file mode 100644
index 0000000..9e9370e
--- /dev/null
+++ b/src/testdir/test_exit.vim
@@ -0,0 +1,130 @@
+" Tests for exiting Vim.
+
+source shared.vim
+source check.vim
+
+func Test_exiting()
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ quit
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ help
+ wincmd w
+ quit
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ split
+ new
+ qall
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ " ExitPre autocommand splits the window, so that it's no longer the last one.
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ augroup nasty
+ au ExitPre * split
+ augroup END
+ quit
+ augroup nasty
+ au! ExitPre
+ augroup END
+ quit
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
+ \ readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ " ExitPre autocommand splits and closes the window, so that there is still
+ " one window but it's a different one.
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ augroup nasty
+ au ExitPre * split | only
+ augroup END
+ quit
+ augroup nasty
+ au! ExitPre
+ augroup END
+ quit
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
+ \ readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfunc
+
+" Test for getting the Vim exit code from v:exiting
+func Test_exit_code()
+ call assert_equal(v:null, v:exiting)
+
+ let before =<< trim [CODE]
+ au QuitPre * call writefile(['qp = ' .. v:exiting], 'Xtestout', 'a')
+ au ExitPre * call writefile(['ep = ' .. v:exiting], 'Xtestout', 'a')
+ au VimLeavePre * call writefile(['lp = ' .. v:exiting], 'Xtestout', 'a')
+ au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout', 'a')
+ [CODE]
+
+ if RunVim(before, ['quit'], '')
+ call assert_equal(['qp = v:null', 'ep = v:null', 'lp = 0', 'l = 0'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ if RunVim(before, ['cquit'], '')
+ call assert_equal(['lp = 1', 'l = 1'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ if RunVim(before, ['cquit 4'], '')
+ call assert_equal(['lp = 4', 'l = 4'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfunc
+
+func Test_exit_error_reading_input()
+ CheckNotGui
+ CheckNotMSWindows
+ " The early exit causes memory not to be freed somehow
+ CheckNotAsan
+ CheckNotValgrind
+
+ call writefile([":au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout')", ":tabnew", "q:"], 'Xscript', 'bD')
+
+ if RunVim([], [], '<Xscript')
+ call assert_equal(1, v:shell_error)
+ call assert_equal(['l = 1'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfun
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_expand.vim b/src/testdir/test_expand.vim
new file mode 100644
index 0000000..d7c393a
--- /dev/null
+++ b/src/testdir/test_expand.vim
@@ -0,0 +1,224 @@
+" Test for expanding file names
+
+source shared.vim
+source check.vim
+
+func Test_with_directories()
+ call mkdir('Xdir1')
+ call mkdir('Xdir2')
+ call mkdir('Xdir3')
+ cd Xdir3
+ call mkdir('Xdir4')
+ cd ..
+
+ split Xdir1/file
+ call setline(1, ['a', 'b'])
+ w
+ w Xdir3/Xdir4/file
+ close
+
+ next Xdir?/*/file
+ call assert_equal('Xdir3/Xdir4/file', expand('%'))
+ if has('unix')
+ next! Xdir?/*/nofile
+ call assert_equal('Xdir?/*/nofile', expand('%'))
+ endif
+ " Edit another file, on MS-Windows the swap file would be in use and can't
+ " be deleted.
+ edit foo
+
+ call assert_equal(0, delete('Xdir1', 'rf'))
+ call assert_equal(0, delete('Xdir2', 'rf'))
+ call assert_equal(0, delete('Xdir3', 'rf'))
+endfunc
+
+func Test_with_tilde()
+ let dir = getcwd()
+ call mkdir('Xdir ~ dir')
+ call assert_true(isdirectory('Xdir ~ dir'))
+ cd Xdir\ ~\ dir
+ call assert_true(getcwd() =~ 'Xdir \~ dir')
+ call chdir(dir)
+ call delete('Xdir ~ dir', 'd')
+ call assert_false(isdirectory('Xdir ~ dir'))
+endfunc
+
+func Test_expand_tilde_filename()
+ split ~
+ call assert_equal('~', expand('%'))
+ call assert_notequal(expand('%:p'), expand('~/'))
+ call assert_match('\~', expand('%:p'))
+ bwipe!
+endfunc
+
+func Test_expandcmd()
+ let $FOO = 'Test'
+ call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y'))
+ unlet $FOO
+
+ new
+ edit Xpandfile1
+ call assert_equal('e Xpandfile1', expandcmd('e %'))
+ edit Xpandfile2
+ edit Xpandfile1
+ call assert_equal('e Xpandfile2', 'e #'->expandcmd())
+ edit Xpandfile2
+ edit Xpandfile3
+ edit Xpandfile4
+ let bnum = bufnr('Xpandfile2')
+ call assert_equal('e Xpandfile2', expandcmd('e #' . bnum))
+ call setline('.', 'Vim!@#')
+ call assert_equal('e Vim', expandcmd('e <cword>'))
+ call assert_equal('e Vim!@#', expandcmd('e <cWORD>'))
+ enew!
+ edit Xpandfile.java
+ call assert_equal('e Xpandfile.py', expandcmd('e %:r.py'))
+ call assert_equal('make abc.java', expandcmd('make abc.%:e'))
+ call assert_equal('make Xabc.java', expandcmd('make %:s?pandfile?abc?'))
+ edit a1a2a3.rb
+ call assert_equal('make b1b2b3.rb a1a2a3 Xpandfile.o', expandcmd('make %:gs?a?b? %< #<.o'))
+
+ call assert_equal('make <afile>', expandcmd("make <afile>"))
+ call assert_equal('make <amatch>', expandcmd("make <amatch>"))
+ call assert_equal('make <abuf>', expandcmd("make <abuf>"))
+ enew
+ call assert_equal('make %', expandcmd("make %"))
+ let $FOO="blue\tsky"
+ call setline(1, "$FOO")
+ call assert_equal("grep pat blue\tsky", expandcmd('grep pat <cfile>'))
+
+ " Test for expression expansion `=
+ let $FOO= "blue"
+ call assert_equal("blue sky", expandcmd("`=$FOO .. ' sky'`"))
+ let x = expandcmd("`=axbycz`")
+ call assert_equal('`=axbycz`', x)
+ call assert_fails('let x = expandcmd("`=axbycz`", #{errmsg: 1})', 'E121:')
+ let x = expandcmd("`=axbycz`", #{abc: []})
+ call assert_equal('`=axbycz`', x)
+
+ " Test for env variable with spaces
+ let $FOO= "foo bar baz"
+ call assert_equal("e foo bar baz", expandcmd("e $FOO"))
+
+ if has('unix') && executable('bash')
+ " test for using the shell to expand a command argument.
+ " only bash supports the {..} syntax
+ set shell=bash
+ let x = expandcmd('{1..4}')
+ call assert_equal('{1..4}', x)
+ call assert_fails("let x = expandcmd('{1..4}', #{errmsg: v:true})", 'E77:')
+ let x = expandcmd('{1..4}', #{error: v:true})
+ call assert_equal('{1..4}', x)
+ set shell&
+ endif
+
+ unlet $FOO
+ close!
+endfunc
+
+" Test for expanding <sfile>, <slnum> and <sflnum> outside of sourcing a script
+func Test_source_sfile()
+ let lines =<< trim [SCRIPT]
+ :call assert_equal('<sfile>', expandcmd("<sfile>"))
+ :call assert_equal('<slnum>', expandcmd("<slnum>"))
+ :call assert_equal('<sflnum>', expandcmd("<sflnum>"))
+ :call assert_equal('edit <cfile>', expandcmd("edit <cfile>"))
+ :call assert_equal('edit #', expandcmd("edit #"))
+ :call assert_equal('edit #<2', expandcmd("edit #<2"))
+ :call assert_equal('edit <cword>', expandcmd("edit <cword>"))
+ :call assert_equal('edit <cexpr>', expandcmd("edit <cexpr>"))
+ :call assert_fails('autocmd User MyCmd echo "<sfile>"', 'E498:')
+ :
+ :call assert_equal('', expand('<script>'))
+ :verbose echo expand('<script>')
+ :call add(v:errors, v:errmsg)
+ :verbose echo expand('<sfile>')
+ :call add(v:errors, v:errmsg)
+ :call writefile(v:errors, 'Xresult')
+ :qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -s Xscript')
+ call assert_equal([
+ \ 'E1274: No script file name to substitute for "<script>"',
+ \ 'E498: No :source file name to substitute for "<sfile>"'],
+ \ readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test for expanding filenames multiple times in a command line
+func Test_expand_filename_multicmd()
+ edit foo
+ call setline(1, 'foo!')
+ new
+ call setline(1, 'foo!')
+ new <cword> | new <cWORD>
+ call assert_equal(4, winnr('$'))
+ call assert_equal('foo!', bufname(winbufnr(1)))
+ call assert_equal('foo', bufname(winbufnr(2)))
+ call assert_fails('e %:s/.*//', 'E500:')
+ %bwipe!
+endfunc
+
+func Test_expandcmd_shell_nonomatch()
+ CheckNotMSWindows
+ call assert_equal('$*', expandcmd('$*'))
+endfunc
+
+func Test_expand_script_source()
+ let lines0 =<< trim [SCRIPT]
+ call extend(g:script_level, [expand('<script>:t')])
+ so Xscript1
+ func F0()
+ call extend(g:func_level, [expand('<script>:t')])
+ endfunc
+
+ au User * call extend(g:au_level, [expand('<script>:t')])
+ [SCRIPT]
+
+ let lines1 =<< trim [SCRIPT]
+ call extend(g:script_level, [expand('<script>:t')])
+ so Xscript2
+ func F1()
+ call extend(g:func_level, [expand('<script>:t')])
+ endfunc
+
+ au User * call extend(g:au_level, [expand('<script>:t')])
+ [SCRIPT]
+
+ let lines2 =<< trim [SCRIPT]
+ call extend(g:script_level, [expand('<script>:t')])
+ func F2()
+ call extend(g:func_level, [expand('<script>:t')])
+ endfunc
+
+ au User * call extend(g:au_level, [expand('<script>:t')])
+ [SCRIPT]
+
+ call writefile(lines0, 'Xscript0', 'D')
+ call writefile(lines1, 'Xscript1', 'D')
+ call writefile(lines2, 'Xscript2', 'D')
+
+ " Check the expansion of <script> at different levels.
+ let g:script_level = []
+ let g:func_level = []
+ let g:au_level = []
+
+ so Xscript0
+ call F0()
+ call F1()
+ call F2()
+ doautocmd User
+
+ call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:script_level)
+ call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:func_level)
+ call assert_equal(['Xscript2', 'Xscript1', 'Xscript0'], g:au_level)
+
+ unlet g:script_level g:func_level
+ delfunc F0
+ delfunc F1
+ delfunc F2
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_expand_dllpath.vim b/src/testdir/test_expand_dllpath.vim
new file mode 100644
index 0000000..8e48972
--- /dev/null
+++ b/src/testdir/test_expand_dllpath.vim
@@ -0,0 +1,36 @@
+" Test for expanding dllpath options
+
+func s:test_expand_dllpath(optname)
+ let $TEST_EXPAND_DLLPATH = '/dllpath/lib' . substitute(a:optname, '\zedll$', '.', '')
+ execute 'let dllpath_save = &' . a:optname
+ try
+ execute 'set ' . a:optname . '=$TEST_EXPAND_DLLPATH'
+ execute 'call assert_equal("' . $TEST_EXPAND_DLLPATH . '", &' . a:optname . ')'
+
+ execute 'set ' . a:optname . '=~' . $TEST_EXPAND_DLLPATH
+ let home = substitute($HOME, '\\', '/', 'g')
+ execute 'call assert_equal("' . home . $TEST_EXPAND_DLLPATH . '", &' . a:optname . ')'
+ finally
+ execute 'let &' . a:optname . ' = dllpath_save'
+ let $TEST_EXPAND_DLLPATH = ''
+ endtry
+endfunc
+
+func s:generate_test_if_exists(optname)
+ if exists('+' . a:optname)
+ execute join([
+ \ 'func Test_expand_' . a:optname . '()',
+ \ ' call s:test_expand_dllpath("' . a:optname . '")',
+ \ 'endfunc'
+ \ ], "\n")
+ endif
+endfunc
+
+call s:generate_test_if_exists('luadll')
+call s:generate_test_if_exists('perldll')
+call s:generate_test_if_exists('pythondll')
+call s:generate_test_if_exists('pythonthreedll')
+call s:generate_test_if_exists('rubydll')
+call s:generate_test_if_exists('tcldll')
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_expand_func.vim b/src/testdir/test_expand_func.vim
new file mode 100644
index 0000000..112809a
--- /dev/null
+++ b/src/testdir/test_expand_func.vim
@@ -0,0 +1,144 @@
+" Tests for expand()
+
+source shared.vim
+
+let s:sfile = expand('<sfile>')
+let s:slnum = str2nr(expand('<slnum>'))
+let s:sflnum = str2nr(expand('<sflnum>'))
+
+func s:expand_sfile()
+ return expand('<sfile>')
+endfunc
+
+func s:expand_slnum()
+ return str2nr(expand('<slnum>'))
+endfunc
+
+func s:expand_sflnum()
+ return str2nr(expand('<sflnum>'))
+endfunc
+
+" This test depends on the location in the test file, put it first.
+func Test_expand_sflnum()
+ call assert_equal(7, s:sflnum)
+ call assert_equal(24, str2nr(expand('<sflnum>')))
+
+ " Line-continuation
+ call assert_equal(
+ \ 27,
+ \ str2nr(expand('<sflnum>')))
+
+ " Call in script-local function
+ call assert_equal(18, s:expand_sflnum())
+
+ " Call in command
+ command Flnum echo expand('<sflnum>')
+ call assert_equal(36, str2nr(trim(execute('Flnum'))))
+ delcommand Flnum
+endfunc
+
+func Test_expand_sfile_and_stack()
+ call assert_match('test_expand_func\.vim$', s:sfile)
+ let expected = 'script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack'
+ call assert_match(expected .. '$', expand('<sfile>'))
+ call assert_match(expected .. '\[4\]$' , expand('<stack>'))
+
+ " Call in script-local function
+ call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack\[7\]\.\.<SNR>\d\+_expand_sfile$', s:expand_sfile())
+
+ " Call in command
+ command Sfile echo expand('<sfile>')
+ call assert_match('script .*testdir/runtest.vim\[\d\+\]\.\.function RunTheTest\[\d\+\]\.\.Test_expand_sfile_and_stack$', trim(execute('Sfile')))
+ delcommand Sfile
+
+ " Use <stack> from sourced script.
+ let lines =<< trim END
+ " comment here
+ let g:stack_value = expand('<stack>')
+ END
+ call writefile(lines, 'Xstack', 'D')
+ source Xstack
+ call assert_match('\<Xstack\[2\]$', g:stack_value)
+ unlet g:stack_value
+
+ if exists('+shellslash')
+ call mkdir('Xshellslash', 'R')
+ let lines =<< trim END
+ let g:stack1 = expand('<stack>')
+ set noshellslash
+ let g:stack2 = expand('<stack>')
+ set shellslash
+ let g:stack3 = expand('<stack>')
+ END
+ call writefile(lines, 'Xshellslash/Xstack')
+ " Test that changing 'shellslash' always affects the result of expand()
+ " when sourcing a script multiple times.
+ for i in range(2)
+ source Xshellslash/Xstack
+ call assert_match('\<Xshellslash/Xstack\[1\]$', g:stack1)
+ call assert_match('\<Xshellslash\\Xstack\[3\]$', g:stack2)
+ call assert_match('\<Xshellslash/Xstack\[5\]$', g:stack3)
+ unlet g:stack1
+ unlet g:stack2
+ unlet g:stack3
+ endfor
+ endif
+endfunc
+
+func Test_expand_slnum()
+ call assert_equal(6, s:slnum)
+ call assert_equal(2, str2nr(expand('<slnum>')))
+
+ " Line-continuation
+ call assert_equal(
+ \ 5,
+ \ str2nr(expand('<slnum>')))
+
+ " Call in script-local function
+ call assert_equal(1, s:expand_slnum())
+
+ " Call in command
+ command Slnum echo expand('<slnum>')
+ call assert_equal(14, str2nr(trim(execute('Slnum'))))
+ delcommand Slnum
+endfunc
+
+func Test_expand()
+ new
+ call assert_equal("", expand('%:S'))
+ call assert_equal('3', '<slnum>'->expand())
+ call assert_equal(['4'], expand('<slnum>', v:false, v:true))
+ " Don't add any line above this, otherwise <slnum> will change.
+ call assert_equal("", expand('%'))
+ set verbose=1
+ call assert_equal("", expand('%'))
+ set verbose=0
+ call assert_equal("", expand('%:p'))
+ quit
+endfunc
+
+func s:sid_test()
+ return 'works'
+endfunc
+
+func Test_expand_SID()
+ let sid = expand('<SID>')
+ execute 'let g:sid_result = ' .. sid .. 'sid_test()'
+ call assert_equal('works', g:sid_result)
+endfunc
+
+
+" Test for 'wildignore' with expand()
+func Test_expand_wildignore()
+ set wildignore=*.vim
+ call assert_equal('', expand('test_expand_func.vim'))
+ call assert_equal('', expand('test_expand_func.vim', 0))
+ call assert_equal([], expand('test_expand_func.vim', 0, 1))
+ call assert_equal('test_expand_func.vim', expand('test_expand_func.vim', 1))
+ call assert_equal(['test_expand_func.vim'],
+ \ expand('test_expand_func.vim', 1, 1))
+ call assert_fails("call expand('*', [])", 'E745:')
+ set wildignore&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim
new file mode 100644
index 0000000..6d6efe7
--- /dev/null
+++ b/src/testdir/test_expr.vim
@@ -0,0 +1,1023 @@
+" Tests for expressions.
+
+source check.vim
+import './vim9.vim' as v9
+
+func Test_equal()
+ let base = {}
+ func base.method()
+ return 1
+ endfunc
+ func base.other() dict
+ return 1
+ endfunc
+ let instance = copy(base)
+ call assert_true(base.method == instance.method)
+ call assert_true([base.method] == [instance.method])
+ call assert_true(base.other == instance.other)
+ call assert_true([base.other] == [instance.other])
+
+ call assert_false(base.method == base.other)
+ call assert_false([base.method] == [base.other])
+ call assert_false(base.method == instance.other)
+ call assert_false([base.method] == [instance.other])
+
+ call assert_fails('echo base.method > instance.method')
+ call assert_equal(0, test_null_function() == function('min'))
+ call assert_equal(1, test_null_function() == test_null_function())
+ call assert_fails('eval 10 == test_unknown()', 'E685:')
+endfunc
+
+func Test_version()
+ call assert_true(has('patch-7.4.001'))
+ call assert_true(has('patch-7.4.01'))
+ call assert_true(has('patch-7.4.1'))
+ call assert_true(has('patch-6.9.999'))
+ call assert_true(has('patch-7.1.999'))
+ call assert_true(has('patch-7.4.123'))
+ call assert_true(has('patch-7.4.123 ')) " Trailing space can be allowed.
+
+ call assert_false(has('patch-7'))
+ call assert_false(has('patch-7.4'))
+ call assert_false(has('patch-7.4.'))
+ call assert_false(has('patch-9.1.0'))
+ call assert_false(has('patch-9.9.1'))
+
+ call assert_false(has('patch-abc'))
+ call assert_false(has('patchabc'))
+
+ call assert_false(has('patch-8x001'))
+ call assert_false(has('patch-9X0X0'))
+ call assert_false(has('patch-9-0-0'))
+ call assert_false(has('patch-09.0.0'))
+ call assert_false(has('patch-9.00.0'))
+endfunc
+
+func Test_op_ternary()
+ let lines =<< trim END
+ call assert_equal('yes', 1 ? 'yes' : 'no')
+ call assert_equal('no', 0 ? 'yes' : 'no')
+
+ call assert_fails('echo [1] ? "yes" : "no"', 'E745:')
+ call assert_fails('echo {} ? "yes" : "no"', 'E728:')
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call assert_equal('no', 'x' ? 'yes' : 'no')
+ call v9.CheckDefAndScriptFailure(["'x' ? 'yes' : 'no'"], 'E1135:')
+ call assert_equal('yes', '1x' ? 'yes' : 'no')
+ call v9.CheckDefAndScriptFailure(["'1x' ? 'yes' : 'no'"], 'E1135:')
+endfunc
+
+func Test_op_falsy()
+ let lines =<< trim END
+ call assert_equal(v:true, v:true ?? 456)
+ call assert_equal(123, 123 ?? 456)
+ call assert_equal('yes', 'yes' ?? 456)
+ call assert_equal(0z00, 0z00 ?? 456)
+ call assert_equal([1], [1] ?? 456)
+ call assert_equal({'one': 1}, {'one': 1} ?? 456)
+ call assert_equal(0.1, 0.1 ?? 456)
+
+ call assert_equal(456, v:false ?? 456)
+ call assert_equal(456, 0 ?? 456)
+ call assert_equal(456, '' ?? 456)
+ call assert_equal(456, 0z ?? 456)
+ call assert_equal(456, [] ?? 456)
+ call assert_equal(456, {} ?? 456)
+ call assert_equal(456, 0.0 ?? 456)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_dict()
+ let lines =<< trim END
+ VAR d = {'': 'empty', 'a': 'a', 0: 'zero'}
+ call assert_equal('empty', d[''])
+ call assert_equal('a', d['a'])
+ call assert_equal('zero', d[0])
+ call assert_true(has_key(d, ''))
+ call assert_true(has_key(d, 'a'))
+
+ LET d[''] = 'none'
+ LET d['a'] = 'aaa'
+ call assert_equal('none', d[''])
+ call assert_equal('aaa', d['a'])
+
+ LET d[ 'b' ] = 'bbb'
+ call assert_equal('bbb', d[ 'b' ])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call v9.CheckLegacyAndVim9Failure(["VAR i = has_key([], 'a')"], ['E1206:', 'E1013:', 'E1206:'])
+endfunc
+
+func Test_strgetchar()
+ let lines =<< trim END
+ call assert_equal(char2nr('a'), strgetchar('axb', 0))
+ call assert_equal(char2nr('x'), 'axb'->strgetchar(1))
+ call assert_equal(char2nr('b'), strgetchar('axb', 2))
+
+ call assert_equal(-1, strgetchar('axb', -1))
+ call assert_equal(-1, strgetchar('axb', 3))
+ call assert_equal(-1, strgetchar('', 0))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call v9.CheckLegacyAndVim9Failure(["VAR c = strgetchar([], 1)"], ['E730:', 'E1013:', 'E1174:'])
+ call v9.CheckLegacyAndVim9Failure(["VAR c = strgetchar('axb', [])"], ['E745:', 'E1013:', 'E1210:'])
+endfunc
+
+func Test_strcharpart()
+ let lines =<< trim END
+ call assert_equal('a', strcharpart('axb', 0, 1))
+ call assert_equal('x', 'axb'->strcharpart(1, 1))
+ call assert_equal('b', strcharpart('axb', 2, 1))
+ call assert_equal('xb', strcharpart('axb', 1))
+
+ call assert_equal('', strcharpart('axb', 1, 0))
+ call assert_equal('', strcharpart('axb', 1, -1))
+ call assert_equal('', strcharpart('axb', -1, 1))
+ call assert_equal('', strcharpart('axb', -2, 2))
+
+ call assert_equal('a', strcharpart('axb', -1, 2))
+
+ call assert_equal('edit', "editor"[-10 : 3])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_getreg_empty_list()
+ let lines =<< trim END
+ call assert_equal('', getreg('x'))
+ call assert_equal([], getreg('x', 1, 1))
+ VAR x = getreg('x', 1, 1)
+ VAR y = x
+ call add(x, 'foo')
+ call assert_equal(['foo'], y)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call v9.CheckLegacyAndVim9Failure(['call getreg([])'], ['E730:', 'E1013:', 'E1174:'])
+endfunc
+
+func Test_loop_over_null_list()
+ let lines =<< trim END
+ VAR nulllist = test_null_list()
+ for i in nulllist
+ call assert_report('should not get here')
+ endfor
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ var nulllist = null_list
+ for i in nulllist
+ call assert_report('should not get here')
+ endfor
+ END
+ call v9.CheckDefAndScriptSuccess(lines)
+
+ let lines =<< trim END
+ let nulllist = null_list
+ for i in nulllist
+ call assert_report('should not get here')
+ endfor
+ END
+ call v9.CheckScriptFailure(lines, 'E121: Undefined variable: null_list')
+endfunc
+
+func Test_compare_with_null()
+ let s:value = v:null
+ call assert_true(s:value == v:null)
+ let s:value = v:true
+ call assert_false(s:value == v:null)
+ let s:value = v:none
+ call assert_false(s:value == v:null)
+ let s:value = 0
+ call assert_true(s:value == v:null)
+ let s:value = 0.0
+ call assert_true(s:value == v:null)
+ let s:value = ''
+ call assert_false(s:value == v:null)
+ let s:value = 0z
+ call assert_false(s:value == v:null)
+ let s:value = []
+ call assert_false(s:value == v:null)
+ let s:value = {}
+ call assert_false(s:value == v:null)
+ let s:value = function('len')
+ call assert_false(s:value == v:null)
+ if has('job')
+ let s:value = test_null_job()
+ call assert_true(s:value == v:null)
+ let s:value = test_null_channel()
+ call assert_true(s:value == v:null)
+ endif
+ unlet s:value
+endfunc
+
+func Test_setreg_null_list()
+ let lines =<< trim END
+ call setreg('x', test_null_list())
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_special_char()
+ " The failure is only visible using valgrind.
+ call v9.CheckLegacyAndVim9Failure(['echo "\<C-">'], ['E15:', 'E1004:', 'E1004:'])
+endfunc
+
+func Test_method_with_prefix()
+ let lines =<< trim END
+ call assert_equal(TRUE, !range(5)->empty())
+ call assert_equal(FALSE, !-3)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call assert_equal([0, 1, 2], --3->range())
+ call v9.CheckDefAndScriptFailure(['eval --3->range()'], 'E15')
+
+ call assert_equal(1, !+-+0)
+ call v9.CheckDefAndScriptFailure(['eval !+-+0'], 'E15')
+endfunc
+
+func Test_option_value()
+ let lines =<< trim END
+ #" boolean
+ set bri
+ call assert_equal(TRUE, &bri)
+ set nobri
+ call assert_equal(FALSE, &bri)
+
+ #" number
+ set ts=1
+ call assert_equal(1, &ts)
+ set ts=8
+ call assert_equal(8, &ts)
+
+ #" string
+ exe "set cedit=\<Esc>"
+ call assert_equal("\<Esc>", &cedit)
+ set cpo=
+ call assert_equal("", &cpo)
+ set cpo=abcdefgi
+ call assert_equal("abcdefgi", &cpo)
+ set cpo&vim
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_misc()
+ let lines =<< trim END
+ call assert_equal('123', printf('123'))
+
+ call assert_equal('123', printf('%d', 123))
+ call assert_equal('123', printf('%i', 123))
+ call assert_equal('123', printf('%D', 123))
+ call assert_equal('123', printf('%U', 123))
+ call assert_equal('173', printf('%o', 123))
+ call assert_equal('173', printf('%O', 123))
+ call assert_equal('7b', printf('%x', 123))
+ call assert_equal('7B', printf('%X', 123))
+
+ call assert_equal('123', printf('%hd', 123))
+ call assert_equal('-123', printf('%hd', -123))
+ call assert_equal('-1', printf('%hd', 0xFFFF))
+ call assert_equal('-1', printf('%hd', 0x1FFFFF))
+
+ call assert_equal('123', printf('%hu', 123))
+ call assert_equal('65413', printf('%hu', -123))
+ call assert_equal('65535', printf('%hu', 0xFFFF))
+ call assert_equal('65535', printf('%hu', 0x1FFFFF))
+
+ call assert_equal('123', printf('%ld', 123))
+ call assert_equal('-123', printf('%ld', -123))
+ call assert_equal('65535', printf('%ld', 0xFFFF))
+ call assert_equal('131071', printf('%ld', 0x1FFFF))
+
+ call assert_equal('{', printf('%c', 123))
+ call assert_equal('abc', printf('%s', 'abc'))
+ call assert_equal('abc', printf('%S', 'abc'))
+
+ call assert_equal('+123', printf('%+d', 123))
+ call assert_equal('-123', printf('%+d', -123))
+ call assert_equal('+123', printf('%+ d', 123))
+ call assert_equal(' 123', printf('% d', 123))
+ call assert_equal(' 123', printf('% d', 123))
+ call assert_equal('-123', printf('% d', -123))
+
+ call assert_equal('123', printf('%2d', 123))
+ call assert_equal(' 123', printf('%6d', 123))
+ call assert_equal('000123', printf('%06d', 123))
+ call assert_equal('+00123', printf('%+06d', 123))
+ call assert_equal(' 00123', printf('% 06d', 123))
+ call assert_equal(' +123', printf('%+6d', 123))
+ call assert_equal(' 123', printf('% 6d', 123))
+ call assert_equal(' -123', printf('% 6d', -123))
+
+ #" Test left adjusted.
+ call assert_equal('123 ', printf('%-6d', 123))
+ call assert_equal('+123 ', printf('%-+6d', 123))
+ call assert_equal(' 123 ', printf('%- 6d', 123))
+ call assert_equal('-123 ', printf('%- 6d', -123))
+
+ call assert_equal(' 00123', printf('%7.5d', 123))
+ call assert_equal(' -00123', printf('%7.5d', -123))
+ call assert_equal(' +00123', printf('%+7.5d', 123))
+
+ #" Precision field should not be used when combined with %0
+ call assert_equal(' 00123', printf('%07.5d', 123))
+ call assert_equal(' -00123', printf('%07.5d', -123))
+
+ call assert_equal(' 123', printf('%*d', 5, 123))
+ call assert_equal('123 ', printf('%*d', -5, 123))
+ call assert_equal('00123', printf('%.*d', 5, 123))
+ call assert_equal(' 123', printf('% *d', 5, 123))
+ call assert_equal(' +123', printf('%+ *d', 5, 123))
+
+ call assert_equal('foobar', printf('%.*s', 9, 'foobar'))
+ call assert_equal('foo', printf('%.*s', 3, 'foobar'))
+ call assert_equal('', printf('%.*s', 0, 'foobar'))
+ call assert_equal('foobar', printf('%.*s', -1, 'foobar'))
+
+ #" Simple quote (thousand grouping char) is ignored.
+ call assert_equal('+00123456', printf("%+'09d", 123456))
+
+ #" Unrecognized format specifier kept as-is.
+ call assert_equal('_123', printf("%_%d", 123))
+
+ #" Test alternate forms.
+ call assert_equal('0x7b', printf('%#x', 123))
+ call assert_equal('0X7B', printf('%#X', 123))
+ call assert_equal('0173', printf('%#o', 123))
+ call assert_equal('0173', printf('%#O', 123))
+ call assert_equal('abc', printf('%#s', 'abc'))
+ call assert_equal('abc', printf('%#S', 'abc'))
+ call assert_equal(' 0173', printf('%#6o', 123))
+ call assert_equal(' 00173', printf('%#6.5o', 123))
+ call assert_equal(' 0173', printf('%#6.2o', 123))
+ call assert_equal(' 0173', printf('%#6.2o', 123))
+ call assert_equal('0173', printf('%#2.2o', 123))
+
+ call assert_equal(' 00123', printf('%6.5d', 123))
+ call assert_equal(' 0007b', printf('%6.5x', 123))
+
+ call assert_equal('123', printf('%.2d', 123))
+ call assert_equal('0123', printf('%.4d', 123))
+ call assert_equal('0000000123', printf('%.10d', 123))
+ call assert_equal('123', printf('%.0d', 123))
+
+ call assert_equal('abc', printf('%2s', 'abc'))
+ call assert_equal('abc', printf('%2S', 'abc'))
+ call assert_equal('abc', printf('%.4s', 'abc'))
+ call assert_equal('abc', printf('%.4S', 'abc'))
+ call assert_equal('ab', printf('%.2s', 'abc'))
+ call assert_equal('ab', printf('%.2S', 'abc'))
+ call assert_equal('', printf('%.0s', 'abc'))
+ call assert_equal('', printf('%.s', 'abc'))
+ call assert_equal(' abc', printf('%4s', 'abc'))
+ call assert_equal(' abc', printf('%4S', 'abc'))
+ call assert_equal('0abc', printf('%04s', 'abc'))
+ call assert_equal('0abc', printf('%04S', 'abc'))
+ call assert_equal('abc ', printf('%-4s', 'abc'))
+ call assert_equal('abc ', printf('%-4S', 'abc'))
+
+ call assert_equal('ðŸ', printf('%.2S', 'ðŸðŸ'))
+ call assert_equal('', printf('%.1S', 'ðŸðŸ'))
+
+ call assert_equal('[ ã‚ã„ã†]', printf('[%10.6S]', 'ã‚ã„ã†ãˆãŠ'))
+ call assert_equal('[ ã‚ã„ã†ãˆ]', printf('[%10.8S]', 'ã‚ã„ã†ãˆãŠ'))
+ call assert_equal('[ã‚ã„ã†ãˆãŠ]', printf('[%10.10S]', 'ã‚ã„ã†ãˆãŠ'))
+ call assert_equal('[ã‚ã„ã†ãˆãŠ]', printf('[%10.12S]', 'ã‚ã„ã†ãˆãŠ'))
+
+ call assert_equal('ã‚ã„ã†', printf('%S', 'ã‚ã„ã†'))
+ call assert_equal('ã‚ã„ã†', printf('%#S', 'ã‚ã„ã†'))
+
+ call assert_equal('ã‚b', printf('%2S', 'ã‚b'))
+ call assert_equal('ã‚b', printf('%.4S', 'ã‚b'))
+ call assert_equal('ã‚', printf('%.2S', 'ã‚b'))
+ call assert_equal(' ã‚b', printf('%4S', 'ã‚b'))
+ call assert_equal('0ã‚b', printf('%04S', 'ã‚b'))
+ call assert_equal('ã‚b ', printf('%-4S', 'ã‚b'))
+ call assert_equal('ã‚ ', printf('%-4.2S', 'ã‚b'))
+
+ call assert_equal('aã„', printf('%2S', 'aã„'))
+ call assert_equal('aã„', printf('%.4S', 'aã„'))
+ call assert_equal('a', printf('%.2S', 'aã„'))
+ call assert_equal(' aã„', printf('%4S', 'aã„'))
+ call assert_equal('0aã„', printf('%04S', 'aã„'))
+ call assert_equal('aã„ ', printf('%-4S', 'aã„'))
+ call assert_equal('a ', printf('%-4.2S', 'aã„'))
+
+ call assert_equal('[ã‚ã„ã†]', printf('[%05S]', 'ã‚ã„ã†'))
+ call assert_equal('[ã‚ã„ã†]', printf('[%06S]', 'ã‚ã„ã†'))
+ call assert_equal('[0ã‚ã„ã†]', printf('[%07S]', 'ã‚ã„ã†'))
+
+ call assert_equal('[ã‚iã†]', printf('[%05S]', 'ã‚iã†'))
+ call assert_equal('[0ã‚iã†]', printf('[%06S]', 'ã‚iã†'))
+ call assert_equal('[00ã‚iã†]', printf('[%07S]', 'ã‚iã†'))
+
+ call assert_equal('[0ã‚ã„]', printf('[%05.4S]', 'ã‚ã„ã†'))
+ call assert_equal('[00ã‚ã„]', printf('[%06.4S]', 'ã‚ã„ã†'))
+ call assert_equal('[000ã‚ã„]', printf('[%07.4S]', 'ã‚ã„ã†'))
+
+ call assert_equal('[00ã‚i]', printf('[%05.4S]', 'ã‚iã†'))
+ call assert_equal('[000ã‚i]', printf('[%06.4S]', 'ã‚iã†'))
+ call assert_equal('[0000ã‚i]', printf('[%07.4S]', 'ã‚iã†'))
+
+ call assert_equal('[0ã‚ã„]', printf('[%05.5S]', 'ã‚ã„ã†'))
+ call assert_equal('[00ã‚ã„]', printf('[%06.5S]', 'ã‚ã„ã†'))
+ call assert_equal('[000ã‚ã„]', printf('[%07.5S]', 'ã‚ã„ã†'))
+
+ call assert_equal('[ã‚iã†]', printf('[%05.5S]', 'ã‚iã†'))
+ call assert_equal('[0ã‚iã†]', printf('[%06.5S]', 'ã‚iã†'))
+ call assert_equal('[00ã‚iã†]', printf('[%07.5S]', 'ã‚iã†'))
+
+ call assert_equal('[0000000000]', printf('[%010.0S]', 'ã‚ã„ã†'))
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'ã‚ã„ã†'))
+ call assert_equal('[00000000ã‚]', printf('[%010.2S]', 'ã‚ã„ã†'))
+ call assert_equal('[00000000ã‚]', printf('[%010.3S]', 'ã‚ã„ã†'))
+ call assert_equal('[000000ã‚ã„]', printf('[%010.4S]', 'ã‚ã„ã†'))
+ call assert_equal('[000000ã‚ã„]', printf('[%010.5S]', 'ã‚ã„ã†'))
+ call assert_equal('[0000ã‚ã„ã†]', printf('[%010.6S]', 'ã‚ã„ã†'))
+ call assert_equal('[0000ã‚ã„ã†]', printf('[%010.7S]', 'ã‚ã„ã†'))
+
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'ã‚iã†'))
+ call assert_equal('[00000000ã‚]', printf('[%010.2S]', 'ã‚iã†'))
+ call assert_equal('[0000000ã‚i]', printf('[%010.3S]', 'ã‚iã†'))
+ call assert_equal('[0000000ã‚i]', printf('[%010.4S]', 'ã‚iã†'))
+ call assert_equal('[00000ã‚iã†]', printf('[%010.5S]', 'ã‚iã†'))
+ call assert_equal('[00000ã‚iã†]', printf('[%010.6S]', 'ã‚iã†'))
+ call assert_equal('[00000ã‚iã†]', printf('[%010.7S]', 'ã‚iã†'))
+
+ call assert_equal('1%', printf('%d%%', 1))
+ call assert_notequal('', printf('%p', "abc"))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call v9.CheckLegacyAndVim9Failure(["call printf('123', 3)"], "E767:")
+endfunc
+
+func Test_printf_float()
+ let lines =<< trim END
+ call assert_equal('1.000000', printf('%f', 1))
+ call assert_equal('1.230000', printf('%f', 1.23))
+ call assert_equal('1.230000', printf('%F', 1.23))
+ call assert_equal('9999999.9', printf('%g', 9999999.9))
+ call assert_equal('9999999.9', printf('%G', 9999999.9))
+ call assert_equal('1.00000001e7', printf('%.8g', 10000000.1))
+ call assert_equal('1.00000001E7', printf('%.8G', 10000000.1))
+ call assert_equal('1.230000e+00', printf('%e', 1.23))
+ call assert_equal('1.230000E+00', printf('%E', 1.23))
+ call assert_equal('1.200000e-02', printf('%e', 0.012))
+ call assert_equal('-1.200000e-02', printf('%e', -0.012))
+ call assert_equal('0.33', printf('%.2f', 1.0 / 3.0))
+ call assert_equal(' 0.33', printf('%6.2f', 1.0 / 3.0))
+ call assert_equal(' -0.33', printf('%6.2f', -1.0 / 3.0))
+ call assert_equal('000.33', printf('%06.2f', 1.0 / 3.0))
+ call assert_equal('-00.33', printf('%06.2f', -1.0 / 3.0))
+ call assert_equal('-00.33', printf('%+06.2f', -1.0 / 3.0))
+ call assert_equal('+00.33', printf('%+06.2f', 1.0 / 3.0))
+ call assert_equal(' 00.33', printf('% 06.2f', 1.0 / 3.0))
+ call assert_equal('000.33', printf('%06.2g', 1.0 / 3.0))
+ call assert_equal('-00.33', printf('%06.2g', -1.0 / 3.0))
+ call assert_equal('0.33', printf('%3.2f', 1.0 / 3.0))
+ call assert_equal('003.33e-01', printf('%010.2e', 1.0 / 3.0))
+ call assert_equal(' 03.33e-01', printf('% 010.2e', 1.0 / 3.0))
+ call assert_equal('+03.33e-01', printf('%+010.2e', 1.0 / 3.0))
+ call assert_equal('-03.33e-01', printf('%010.2e', -1.0 / 3.0))
+
+ #" When precision is 0, the dot should be omitted.
+ call assert_equal(' 2', printf('%3.f', 7.0 / 3.0))
+ call assert_equal(' 2', printf('%3.g', 7.0 / 3.0))
+ call assert_equal(' 2e+00', printf('%7.e', 7.0 / 3.0))
+
+ #" Float zero can be signed.
+ call assert_equal('+0.000000', printf('%+f', 0.0))
+ call assert_equal('0.000000', printf('%f', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.000000', printf('%f', 1.0 / (-1.0 / 0.0)))
+ call assert_equal('0.0', printf('%s', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.0', printf('%s', 1.0 / (-1.0 / 0.0)))
+ call assert_equal('0.0', printf('%S', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.0', printf('%S', 1.0 / (-1.0 / 0.0)))
+
+ #" Float infinity can be signed.
+ call assert_equal('inf', printf('%f', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%f', -1.0 / 0.0))
+ call assert_equal('inf', printf('%g', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%g', -1.0 / 0.0))
+ call assert_equal('inf', printf('%e', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%e', -1.0 / 0.0))
+ call assert_equal('INF', printf('%F', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%F', -1.0 / 0.0))
+ call assert_equal('INF', printf('%E', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%E', -1.0 / 0.0))
+ call assert_equal('INF', printf('%E', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%G', -1.0 / 0.0))
+ call assert_equal('+inf', printf('%+f', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%+f', -1.0 / 0.0))
+ call assert_equal(' inf', printf('% f', 1.0 / 0.0))
+ call assert_equal(' inf', printf('%6f', 1.0 / 0.0))
+ call assert_equal(' -inf', printf('%6f', -1.0 / 0.0))
+ call assert_equal(' inf', printf('%6g', 1.0 / 0.0))
+ call assert_equal(' -inf', printf('%6g', -1.0 / 0.0))
+ call assert_equal(' +inf', printf('%+6f', 1.0 / 0.0))
+ call assert_equal(' inf', printf('% 6f', 1.0 / 0.0))
+ call assert_equal(' +inf', printf('%+06f', 1.0 / 0.0))
+ call assert_equal('inf ', printf('%-6f', 1.0 / 0.0))
+ call assert_equal('-inf ', printf('%-6f', -1.0 / 0.0))
+ call assert_equal('+inf ', printf('%-+6f', 1.0 / 0.0))
+ call assert_equal(' inf ', printf('%- 6f', 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%-6F', -1.0 / 0.0))
+ call assert_equal('+INF ', printf('%-+6F', 1.0 / 0.0))
+ call assert_equal(' INF ', printf('%- 6F', 1.0 / 0.0))
+ call assert_equal('INF ', printf('%-6G', 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%-6G', -1.0 / 0.0))
+ call assert_equal('INF ', printf('%-6E', 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%-6E', -1.0 / 0.0))
+ call assert_equal('inf', printf('%s', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%s', -1.0 / 0.0))
+
+ #" Test special case where max precision is truncated at 340.
+ call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.330f', 1.0))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.340f', 1.0))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.350f', 1.0))
+
+ #" Float nan (not a number) has no sign.
+ call assert_equal('nan', printf('%f', sqrt(-1.0)))
+ call assert_equal('nan', printf('%f', 0.0 / 0.0))
+ call assert_equal('nan', printf('%f', -0.0 / 0.0))
+ call assert_equal('nan', printf('%g', 0.0 / 0.0))
+ call assert_equal('nan', printf('%e', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%F', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%G', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%E', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%F', -0.0 / 0.0))
+ call assert_equal('NAN', printf('%G', -0.0 / 0.0))
+ call assert_equal('NAN', printf('%E', -0.0 / 0.0))
+ call assert_equal(' nan', printf('%6f', 0.0 / 0.0))
+ call assert_equal(' nan', printf('%06f', 0.0 / 0.0))
+ call assert_equal('nan ', printf('%-6f', 0.0 / 0.0))
+ call assert_equal('nan ', printf('%- 6f', 0.0 / 0.0))
+ call assert_equal('nan', printf('%s', 0.0 / 0.0))
+ call assert_equal('nan', printf('%s', -0.0 / 0.0))
+ call assert_equal('nan', printf('%S', 0.0 / 0.0))
+ call assert_equal('nan', printf('%S', -0.0 / 0.0))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%f", "a")'], 'E807:')
+endfunc
+
+func Test_printf_errors()
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%d", {})'], 'E728:')
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%d", [])'], 'E745:')
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%d", 1, 2)'], 'E767:')
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%*d", 1)'], 'E766:')
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%s")'], 'E766:')
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%d", 1.2)'], 'E805:')
+ call v9.CheckLegacyAndVim9Failure(['echo printf("%f")'], 'E766:')
+endfunc
+
+func Test_printf_64bit()
+ let lines =<< trim END
+ call assert_equal("123456789012345", printf('%d', 123456789012345))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_spec_s()
+ let lines =<< trim END
+ #" number
+ call assert_equal("1234567890", printf('%s', 1234567890))
+
+ #" string
+ call assert_equal("abcdefgi", printf('%s', "abcdefgi"))
+
+ #" float
+ call assert_equal("1.23", printf('%s', 1.23))
+
+ #" list
+ VAR lvalue = [1, 'two', ['three', 4]]
+ call assert_equal(string(lvalue), printf('%s', lvalue))
+
+ #" dict
+ VAR dvalue = {'key1': 'value1', 'key2': ['list', 'lvalue'], 'key3': {'dict': 'lvalue'}}
+ call assert_equal(string(dvalue), printf('%s', dvalue))
+
+ #" funcref
+ call assert_equal('printf', printf('%s', 'printf'->function()))
+
+ #" partial
+ call assert_equal(string(function('printf', ['%s'])), printf('%s', function('printf', ['%s'])))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_spec_b()
+ let lines =<< trim END
+ call assert_equal("0", printf('%b', 0))
+ call assert_equal("00001100", printf('%08b', 12))
+ call assert_equal("11111111", printf('%08b', 0xff))
+ call assert_equal(" 1111011", printf('%10b', 123))
+ call assert_equal("0001111011", printf('%010b', 123))
+ call assert_equal(" 0b1111011", printf('%#10b', 123))
+ call assert_equal("0B01111011", printf('%#010B', 123))
+ call assert_equal("1001001100101100000001011010010", printf('%b', 1234567890))
+ call assert_equal("11100000100100010000110000011011101111101111001", printf('%b', 123456789012345))
+ call assert_equal("1111111111111111111111111111111111111111111111111111111111111111", printf('%b', -1))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_max_min_errors()
+ call v9.CheckLegacyAndVim9Failure(['call max(v:true)'], ['E712:', 'E1013:', 'E1227:'])
+ call v9.CheckLegacyAndVim9Failure(['call max(v:true)'], ['max()', 'E1013:', 'E1227:'])
+ call v9.CheckLegacyAndVim9Failure(['call min(v:true)'], ['E712:', 'E1013:', 'E1227:'])
+ call v9.CheckLegacyAndVim9Failure(['call min(v:true)'], ['min()', 'E1013:', 'E1227:'])
+endfunc
+
+func Test_function_with_funcref()
+ let lines =<< trim END
+ let s:F = function('type')
+ let s:Fref = function(s:F)
+ call assert_equal(v:t_string, s:Fref('x'))
+ call assert_fails("call function('s:F')", 'E700:')
+
+ call assert_fails("call function('foo()')", 'E475:')
+ call assert_fails("call function('foo()')", 'foo()')
+ call assert_fails("function('')", 'E129:')
+
+ let s:Len = {s -> strlen(s)}
+ call assert_equal(6, s:Len('foobar'))
+ let name = string(s:Len)
+ " can evaluate "function('<lambda>99')"
+ call execute('let Ref = ' .. name)
+ call assert_equal(4, Ref('text'))
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ var F = function('type')
+ var Fref = function(F)
+ call assert_equal(v:t_string, Fref('x'))
+ call assert_fails("call function('F')", 'E700:')
+
+ call assert_fails("call function('foo()')", 'E475:')
+ call assert_fails("call function('foo()')", 'foo()')
+ call assert_fails("function('')", 'E129:')
+
+ var Len = (s) => strlen(s)
+ call assert_equal(6, Len('foobar'))
+ var name = string(Len)
+ # can evaluate "function('<lambda>99')"
+ call execute('var Ref = ' .. name)
+ call assert_equal(4, Ref('text'))
+ END
+ call v9.CheckScriptSuccess(lines)
+endfunc
+
+func Test_funcref()
+ func! One()
+ return 1
+ endfunc
+ let OneByName = function('One')
+ let OneByRef = funcref('One')
+ func! One()
+ return 2
+ endfunc
+ call assert_equal(2, OneByName())
+ call assert_equal(1, OneByRef())
+ let OneByRef = 'One'->funcref()
+ call assert_equal(2, OneByRef())
+ call assert_fails('echo funcref("{")', 'E475:')
+ let OneByRef = funcref("One", repeat(["foo"], 20))
+ call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:')
+ call assert_fails('echo function("min") =~ function("min")', 'E694:')
+ call assert_fails('echo test_null_function()->funcref()', 'E475: Invalid argument: NULL')
+endfunc
+
+" Test for calling function() and funcref() outside of a Vim script context.
+func Test_function_outside_script()
+ let cleanup =<< trim END
+ call writefile([execute('messages')], 'Xtest.out')
+ qall
+ END
+ call writefile(cleanup, 'Xverify.vim', 'D')
+ call RunVim([], [], "-c \"echo function('s:abc')\" -S Xverify.vim")
+ call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+ call RunVim([], [], "-c \"echo funcref('s:abc')\" -S Xverify.vim")
+ call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+ call delete('Xtest.out')
+endfunc
+
+func Test_setmatches()
+ let lines =<< trim END
+ hi def link 1 Comment
+ hi def link 2 PreProc
+ VAR set = [{"group": 1, "pattern": 2, "id": 3, "priority": 4}]
+ VAR exp = [{"group": '1', "pattern": '2', "id": 3, "priority": 4}]
+ if has('conceal')
+ LET set[0]['conceal'] = 5
+ LET exp[0]['conceal'] = '5'
+ endif
+ eval set->setmatches()
+ call assert_equal(exp, getmatches())
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call v9.CheckLegacyAndVim9Failure(['VAR m = setmatches([], [])'], ['E745:', 'E1013:', 'E1210:'])
+endfunc
+
+func Test_empty_concatenate()
+ let lines =<< trim END
+ call assert_equal('b', 'a'[4 : 0] .. 'b')
+ call assert_equal('b', 'b' .. 'a'[4 : 0])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_broken_number()
+ call v9.CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 1X'], 'E15:')
+ call v9.CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 0b1X'], 'E15:')
+ call v9.CheckLegacyAndVim9Failure(['echo 0b12'], 'E15:')
+ call v9.CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 0x1X'], 'E15:')
+ call v9.CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 011X'], 'E15:')
+
+ call v9.CheckLegacyAndVim9Success(['call assert_equal(2, str2nr("2a"))'])
+
+ call v9.CheckLegacyAndVim9Failure(['inoremap <Char-0b1z> b'], 'E474:')
+endfunc
+
+func Test_eval_after_if()
+ let s:val = ''
+ func SetVal(x)
+ let s:val ..= a:x
+ endfunc
+ if 0 | eval SetVal('a') | endif | call SetVal('b')
+ call assert_equal('b', s:val)
+endfunc
+
+func Test_divide_by_zero()
+ " only tests that this doesn't crash, the result is not important
+ echo 0 / 0
+ echo 0 / 0 / -1
+endfunc
+
+" Test for command-line completion of expressions
+func Test_expr_completion()
+ CheckFeature cmdline_compl
+ for cmd in [
+ \ 'let a = ',
+ \ 'const a = ',
+ \ 'if',
+ \ 'elseif',
+ \ 'while',
+ \ 'for',
+ \ 'echo',
+ \ 'echon',
+ \ 'execute',
+ \ 'echomsg',
+ \ 'echoerr',
+ \ 'call',
+ \ 'return',
+ \ 'cexpr',
+ \ 'caddexpr',
+ \ 'cgetexpr',
+ \ 'lexpr',
+ \ 'laddexpr',
+ \ 'lgetexpr']
+ call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"' . cmd . ' getline(', getreg(':'))
+ endfor
+
+ " completion for the expression register
+ call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"float2nr("', @=)
+
+ " completion for window local variables
+ let w:wvar1 = 10
+ let w:wvar2 = 10
+ call feedkeys(":echo w:wvar\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo w:wvar1 w:wvar2', @:)
+ unlet w:wvar1 w:wvar2
+
+ " completion for tab local variables
+ let t:tvar1 = 10
+ let t:tvar2 = 10
+ call feedkeys(":echo t:tvar\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo t:tvar1 t:tvar2', @:)
+ unlet t:tvar1 t:tvar2
+
+ " completion for variables
+ let g:tvar1 = 1
+ let g:tvar2 = 2
+ call feedkeys(":let g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"let g:tvar1 g:tvar2', @:)
+ " completion for variables after a ||
+ call feedkeys(":echo 1 || g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo 1 || g:tvar1 g:tvar2', @:)
+
+ " completion for options
+ call feedkeys(":echo &compat\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo &compatible', @:)
+ call feedkeys(":echo 1 && &compat\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo 1 && &compatible', @:)
+ call feedkeys(":echo &g:equala\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo &g:equalalways', @:)
+
+ " completion for string
+ call feedkeys(":echo \"Hello\\ World\"\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"echo \"Hello\\ World\"\<C-A>", @:)
+ call feedkeys(":echo 'Hello World'\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"echo 'Hello World'\<C-A>", @:)
+
+ " completion for command after a |
+ call feedkeys(":echo 'Hello' | cwin\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"echo 'Hello' | cwindow", @:)
+
+ " completion for environment variable
+ let $X_VIM_TEST_COMPLETE_ENV = 'foo'
+ call feedkeys(":let $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"let $X_VIM_TEST_COMPLETE_ENV', @:)
+ unlet $X_VIM_TEST_COMPLETE_ENV
+endfunc
+
+" Test for errors in expression evaluation
+func Test_expr_eval_error()
+ call v9.CheckLegacyAndVim9Failure(["VAR i = 'abc' .. []"], ['E730:', 'E1105:', 'E730:'])
+ call v9.CheckLegacyAndVim9Failure(["VAR l = [] + 10"], ['E745:', 'E1051:', 'E745'])
+ call v9.CheckLegacyAndVim9Failure(["VAR v = 10 + []"], ['E745:', 'E1051:', 'E745:'])
+ call v9.CheckLegacyAndVim9Failure(["VAR v = 10 / []"], ['E745:', 'E1036:', 'E745:'])
+ call v9.CheckLegacyAndVim9Failure(["VAR v = -{}"], ['E728:', 'E1012:', 'E728:'])
+endfunc
+
+func Test_white_in_function_call()
+ let lines =<< trim END
+ VAR text = substitute ( 'some text' , 't' , 'T' , 'g' )
+ call assert_equal('some TexT', text)
+ END
+ call v9.CheckTransLegacySuccess(lines)
+
+ let lines =<< trim END
+ var text = substitute ( 'some text' , 't' , 'T' , 'g' )
+ call assert_equal('some TexT', text)
+ END
+ call v9.CheckDefAndScriptFailure(lines, ['E1001:', 'E121:'])
+endfunc
+
+" Test for float value comparison
+func Test_float_compare()
+ let lines =<< trim END
+ call assert_true(1.2 == 1.2)
+ call assert_true(1.0 != 1.2)
+ call assert_true(1.2 > 1.0)
+ call assert_true(1.2 >= 1.2)
+ call assert_true(1.0 < 1.2)
+ call assert_true(1.2 <= 1.2)
+ call assert_true(+0.0 == -0.0)
+ #" two NaNs (not a number) are not equal
+ call assert_true(sqrt(-4.01) != (0.0 / 0.0))
+ #" two inf (infinity) are equal
+ call assert_true((1.0 / 0) == (2.0 / 0))
+ #" two -inf (infinity) are equal
+ call assert_true(-(1.0 / 0) == -(2.0 / 0))
+ #" +infinity != -infinity
+ call assert_true((1.0 / 0) != -(2.0 / 0))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_string_interp()
+ let lines =<< trim END
+ call assert_equal('', $"")
+ call assert_equal('foobar', $"foobar")
+ #" Escaping rules.
+ call assert_equal('"foo"{bar}', $"\"foo\"{{bar}}")
+ call assert_equal('"foo"{bar}', $'"foo"{{bar}}')
+ call assert_equal('foobar', $"{"foo"}" .. $'{'bar'}')
+ #" Whitespace before/after the expression.
+ call assert_equal('3', $"{ 1 + 2 }")
+ #" String conversion.
+ call assert_equal('hello from ' .. v:version, $"hello from {v:version}")
+ call assert_equal('hello from ' .. v:version, $'hello from {v:version}')
+ #" Paper over a small difference between VimScript behaviour.
+ call assert_equal(string(v:true), $"{v:true}")
+ call assert_equal('(1+1=2)', $"(1+1={1 + 1})")
+ #" Hex-escaped opening brace: char2nr('{') == 0x7b
+ call assert_equal('esc123ape', $"esc{123}ape")
+ call assert_equal('me{}me', $"me{"\x7b"}\x7dme")
+ VAR var1 = "sun"
+ VAR var2 = "shine"
+ call assert_equal('sunshine', $"{var1}{var2}")
+ call assert_equal('sunsunsun', $"{var1->repeat(3)}")
+ #" Multibyte strings.
+ call assert_equal('say ãƒãƒ­ãƒ¼ãƒ»ãƒ¯ãƒ¼ãƒ«ãƒ‰', $"say {'ãƒãƒ­ãƒ¼ãƒ»ãƒ¯ãƒ¼ãƒ«ãƒ‰'}")
+ #" Nested.
+ call assert_equal('foobarbaz', $"foo{$"{'bar'}"}baz")
+ #" Do not evaluate blocks when the expr is skipped.
+ VAR tmp = 0
+ if v:false
+ echo "${ LET tmp += 1 }"
+ endif
+ call assert_equal(0, tmp)
+
+ #" Stray closing brace.
+ call assert_fails('echo $"moo}"', 'E1278:')
+ #" Undefined variable in expansion.
+ call assert_fails('echo $"{moo}"', 'E121:')
+ #" Empty blocks are rejected.
+ call assert_fails('echo $"{}"', 'E15:')
+ call assert_fails('echo $"{ }"', 'E15:')
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ call assert_equal('5', $"{({x -> x + 1})(4)}")
+ END
+ call v9.CheckLegacySuccess(lines)
+
+ let lines =<< trim END
+ call assert_equal('5', $"{((x) => x + 1)(4)}")
+ call assert_fails('echo $"{ # foo }"', 'E1279:')
+ END
+ call v9.CheckDefAndScriptSuccess(lines)
+endfunc
+
+" Test for bitwise left and right shift (<< and >>)
+func Test_bitwise_shift()
+ let lines =<< trim END
+ call assert_equal(16, 1 << 4)
+ call assert_equal(2, 16 >> 3)
+ call assert_equal(0, 0 << 2)
+ call assert_equal(0, 0 >> 4)
+ call assert_equal(3, 3 << 0)
+ call assert_equal(3, 3 >> 0)
+ call assert_equal(0, 0 >> 4)
+ call assert_equal(0, 999999 >> 100)
+ call assert_equal(0, 999999 << 100)
+ call assert_equal(-1, -1 >> 0)
+ call assert_equal(-1, -1 << 0)
+ VAR a = 8
+ VAR b = 2
+ call assert_equal(2, a >> b)
+ call assert_equal(32, a << b)
+ #" operator precedence
+ call assert_equal(48, 1 + 2 << 5 - 1)
+ call assert_equal(3, 8 + 4 >> 4 - 2)
+ call assert_true(1 << 2 < 1 << 3)
+ call assert_true(1 << 4 > 1 << 3)
+ VAR val = 0
+ for i in range(0, v:numbersize - 2)
+ LET val = or(val, 1 << i)
+ endfor
+ call assert_equal(v:numbermax, val)
+ LET val = v:numbermax
+ for i in range(0, v:numbersize - 2)
+ LET val = and(val, invert(1 << i))
+ endfor
+ #" -1 has all the bits set
+ call assert_equal(-2, -1 << 1)
+ call assert_equal(-4, -1 << 2)
+ call assert_equal(-8, -1 << 3)
+ if v:numbersize == 64
+ call assert_equal(0x7fffffffffffffff, -1 >> 1)
+ call assert_equal(0x3fffffffffffffff, -1 >> 2)
+ call assert_equal(0x1fffffffffffffff, -1 >> 3)
+ endif
+ call assert_equal(0, val)
+ #" multiple operators
+ call assert_equal(16, 1 << 2 << 2)
+ call assert_equal(4, 64 >> 2 >> 2)
+ call assert_true(1 << 2 << 2 == 256 >> 2 >> 2)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call v9.CheckLegacyAndVim9Failure(['VAR v = 2 << -1'], ['E1283:', 'E1283:', 'E1283:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR a = 2', 'VAR b = -1', 'VAR v = a << b'], ['E1283:', 'E1283:', 'E1283:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR v = "8" >> 2'], ['E1282:', 'E1282:', 'E1282:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR v = 1 << "2"'], ['E1282:', 'E1282:', 'E1282:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR a = "8"', 'VAR b = 2', 'VAR v = a << b'], ['E1282:', 'E1012:', 'E1282:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR a = 8', 'VAR b = "2"', 'VAR v = a >> b'], ['E1282:', 'E1012:', 'E1282:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR v = ![] << 1'], ['E745:', 'E1012:', 'E1282:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR v = 1 << ![]'], ['E745:', 'E1012:', 'E1282:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR v = ![] >> 1'], ['E745:', 'E1012:', 'E1282:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR v = 1 >> ![]'], ['E745:', 'E1012:', 'E1282:'])
+ call v9.CheckDefAndScriptFailure(['echo 1<< 2'], ['E1004:', 'E1004:'])
+ call v9.CheckDefAndScriptFailure(['echo 1 <<2'], ['E1004:', 'E1004:'])
+ call v9.CheckDefAndScriptFailure(['echo 1>> 2'], ['E1004:', 'E1004:'])
+ call v9.CheckDefAndScriptFailure(['echo 1 >>2'], ['E1004:', 'E1004:'])
+
+ let lines =<< trim END
+ var a = 1
+ <<
+ 4
+ assert_equal(16, a)
+ END
+ call v9.CheckDefAndScriptSuccess(lines)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_expr_utf8.vim b/src/testdir/test_expr_utf8.vim
new file mode 100644
index 0000000..c6d2e4e
--- /dev/null
+++ b/src/testdir/test_expr_utf8.vim
@@ -0,0 +1,44 @@
+" Tests for expressions using utf-8.
+
+func Test_strgetchar()
+ call assert_equal(char2nr('á'), strgetchar('áxb', 0))
+ call assert_equal(char2nr('x'), strgetchar('áxb', 1))
+
+ call assert_equal(char2nr('a'), strgetchar('àxb', 0))
+ call assert_equal(char2nr('̀'), strgetchar('àxb', 1))
+ call assert_equal(char2nr('x'), strgetchar('àxb', 2))
+
+ call assert_equal(char2nr('ã‚'), strgetchar('ã‚aã„', 0))
+ call assert_equal(char2nr('a'), strgetchar('ã‚aã„', 1))
+ call assert_equal(char2nr('ã„'), strgetchar('ã‚aã„', 2))
+endfunc
+
+func Test_strcharpart()
+ call assert_equal('áxb', strcharpart('áxb', 0))
+ call assert_equal('á', strcharpart('áxb', 0, 1))
+ call assert_equal('x', strcharpart('áxb', 1, 1))
+
+ call assert_equal('ã„ã†eãŠ', strcharpart('ã‚ã„ã†eãŠ', 1))
+ call assert_equal('ã„', strcharpart('ã‚ã„ã†eãŠ', 1, 1))
+ call assert_equal('ã„ã†', strcharpart('ã‚ã„ã†eãŠ', 1, 2))
+ call assert_equal('ã„ã†e', strcharpart('ã‚ã„ã†eãŠ', 1, 3))
+ call assert_equal('ã„ã†eãŠ', strcharpart('ã‚ã„ã†eãŠ', 1, 4))
+ call assert_equal('eãŠ', strcharpart('ã‚ã„ã†eãŠ', 3))
+ call assert_equal('e', strcharpart('ã‚ã„ã†eãŠ', 3, 1))
+
+ call assert_equal('ã‚', strcharpart('ã‚ã„ã†eãŠ', -3, 4))
+
+ call assert_equal('a', strcharpart('àxb', 0, 1))
+ call assert_equal('̀', strcharpart('àxb', 1, 1))
+ call assert_equal('x', strcharpart('àxb', 2, 1))
+
+
+ call assert_equal('a', strcharpart('àxb', 0, 1, 0))
+ call assert_equal('à', strcharpart('àxb', 0, 1, 1))
+ call assert_equal('x', strcharpart('àxb', 1, 1, 1))
+
+ call assert_fails("let v = strcharpart('abc', 0, 0, [])", 'E745:')
+ call assert_fails("let v = strcharpart('abc', 0, 0, 2)", 'E1023:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_file_perm.vim b/src/testdir/test_file_perm.vim
new file mode 100644
index 0000000..8af9532
--- /dev/null
+++ b/src/testdir/test_file_perm.vim
@@ -0,0 +1,29 @@
+" Test getting and setting file permissions.
+
+func Test_file_perm()
+ call assert_equal('', getfperm('XtestPerm'))
+ call assert_equal(0, 'XtestPerm'->setfperm('r--------'))
+
+ call writefile(['one'], 'XtestPerm', 'D')
+ call assert_true(len('XtestPerm'->getfperm()) == 9)
+
+ call assert_equal(1, setfperm('XtestPerm', 'rwx------'))
+ if has('win32')
+ call assert_equal('rw-rw-rw-', getfperm('XtestPerm'))
+ else
+ call assert_equal('rwx------', getfperm('XtestPerm'))
+ endif
+
+ call assert_equal(1, setfperm('XtestPerm', 'r--r--r--'))
+ call assert_equal('r--r--r--', getfperm('XtestPerm'))
+
+ call assert_fails("setfperm('XtestPerm', '---')")
+
+ call assert_equal(1, setfperm('XtestPerm', 'rwx------'))
+
+ call assert_fails("call setfperm(['Xpermfile'], 'rw-rw-rw-')", 'E730:')
+ call assert_fails("call setfperm('Xpermfile', [])", 'E730:')
+ call assert_fails("call setfperm('Xpermfile', 'rwxrwxrwxrw')", 'E475:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_file_size.vim b/src/testdir/test_file_size.vim
new file mode 100644
index 0000000..d349bc2
--- /dev/null
+++ b/src/testdir/test_file_size.vim
@@ -0,0 +1,61 @@
+" Inserts 2 million lines with consecutive integers starting from 1
+" (essentially, the output of GNU's seq 1 2000000), writes them to Xtest
+" and writes its cksum to test.out.
+"
+" We need 2 million lines to trigger a call to mf_hash_grow(). If it would mess
+" up the lines the checksum would differ.
+"
+" cksum is part of POSIX and so should be available on most Unixes.
+" If it isn't available then the test will be skipped.
+
+source check.vim
+
+func Test_File_Size()
+ CheckExecutable cksum
+
+ new
+ set fileformat=unix undolevels=-1
+ for i in range(1, 2000000, 100)
+ call append(i, range(i, i + 99))
+ endfor
+
+ 1delete
+ w! Xtest
+ let res = systemlist('cksum Xtest')[0]
+ let res = substitute(res, "\r", "", "")
+ call assert_equal('3678979763 14888896 Xtest', res)
+
+ enew!
+ call delete('Xtest')
+ set fileformat& undolevels&
+endfunc
+
+" Test for writing and reading a file of over 100 Kbyte
+func Test_File_Read_Write()
+ enew!
+
+ " Create a file with the following contents
+ " 1 line: "This is the start"
+ " 3001 lines: "This is the leader"
+ " 1 line: "This is the middle"
+ " 3001 lines: "This is the trailer"
+ " 1 line: "This is the end"
+ call append(0, "This is the start")
+ call append(1, repeat(["This is the leader"], 3001))
+ call append(3002, "This is the middle")
+ call append(3003, repeat(["This is the trailer"], 3001))
+ call append(6004, "This is the end")
+
+ write! Xtest
+ enew!
+ edit! Xtest
+
+ call assert_equal("This is the start", getline(1))
+ call assert_equal("This is the middle", getline(3003))
+ call assert_equal("This is the end", getline(6005))
+
+ enew!
+ call delete("Xtest")
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_filechanged.vim b/src/testdir/test_filechanged.vim
new file mode 100644
index 0000000..c3a5664
--- /dev/null
+++ b/src/testdir/test_filechanged.vim
@@ -0,0 +1,264 @@
+" Tests for when a file was changed outside of Vim.
+
+source check.vim
+
+func Test_FileChangedShell_reload()
+ CheckUnix
+
+ augroup testreload
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
+ augroup END
+ new Xchanged_r
+ call setline(1, 'reload this')
+ write
+ " Need to wait until the timestamp would change by at least a second.
+ sleep 2
+ silent !echo 'extra line' >>Xchanged_r
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(2, line('$'))
+ call assert_equal('extra line', getline(2))
+
+ " Only triggers once
+ let g:reason = ''
+ checktime
+ call assert_equal('', g:reason)
+
+ " When deleted buffer is not reloaded
+ silent !rm Xchanged_r
+ let g:reason = ''
+ checktime
+ call assert_equal('deleted', g:reason)
+ call assert_equal(2, line('$'))
+ call assert_equal('extra line', getline(2))
+
+ " When recreated buffer is reloaded
+ call setline(1, 'buffer is changed')
+ silent !echo 'new line' >>Xchanged_r
+ let g:reason = ''
+ checktime
+ call assert_equal('conflict', g:reason)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only mode changed
+ silent !chmod +x Xchanged_r
+ let g:reason = ''
+ checktime
+ call assert_equal('mode', g:reason)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only time changed
+ sleep 2
+ silent !touch Xchanged_r
+ let g:reason = ''
+ checktime
+ call assert_equal('time', g:reason)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ if has('persistent_undo')
+ " With an undo file the reload can be undone and a change before the
+ " reload.
+ set undofile
+ call setline(2, 'before write')
+ write
+ call setline(2, 'after write')
+ sleep 2
+ silent !echo 'different line' >>Xchanged_r
+ let g:reason = ''
+ checktime
+ call assert_equal('conflict', g:reason)
+ call assert_equal(3, line('$'))
+ call assert_equal('before write', getline(2))
+ call assert_equal('different line', getline(3))
+ " undo the reload
+ undo
+ call assert_equal(2, line('$'))
+ call assert_equal('after write', getline(2))
+ " undo the change before reload
+ undo
+ call assert_equal(2, line('$'))
+ call assert_equal('before write', getline(2))
+
+ set noundofile
+ endif
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+ call delete('Xchanged_r')
+endfunc
+
+func Test_FileChangedShell_edit()
+ CheckUnix
+
+ new Xchanged_r
+ call setline(1, 'reload this')
+ set fileformat=unix
+ write
+
+ " File format changed, reload (content only, no 'ff' etc)
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r', 'D')
+ let g:reason = ''
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'unix')
+ call assert_equal("line1\r", getline(1))
+ call assert_equal("line2\r", getline(2))
+ %s/\r
+ write
+
+ " File format changed, reload with 'ff', etc
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'edit'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'dos')
+ call assert_equal('line1', getline(1))
+ call assert_equal('line2', getline(2))
+ set fileformat=unix
+ write
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+endfunc
+
+func Test_FileChangedShell_edit_dialog()
+ CheckNotGui
+ CheckUnix " Using low level feedkeys() does not work on MS-Windows.
+
+ new Xchanged_r
+ call setline(1, 'reload this')
+ set fileformat=unix
+ write
+
+ " File format changed, reload (content only) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r', 'D')
+ let g:reason = ''
+ call feedkeys('L', 'L') " load file content only
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'unix')
+ call assert_equal("line1\r", getline(1))
+ call assert_equal("line2\r", getline(2))
+ %s/\r
+ write
+
+ " File format changed, reload (file and options) via prompt
+ augroup testreload
+ au!
+ au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask'
+ augroup END
+ call assert_equal(&fileformat, 'unix')
+ call writefile(["line1\r", "line2\r"], 'Xchanged_r')
+ let g:reason = ''
+ call feedkeys('a', 'L') " load file content and options
+ checktime
+ call assert_equal('changed', g:reason)
+ call assert_equal(&fileformat, 'dos')
+ call assert_equal("line1", getline(1))
+ call assert_equal("line2", getline(2))
+ set fileformat=unix
+ write
+
+ au! testreload
+ bwipe!
+ call delete(undofile('Xchanged_r'))
+endfunc
+
+func Test_file_changed_dialog()
+ CheckUnix
+ CheckNotGui
+ au! FileChangedShell
+
+ new Xchanged_d
+ call setline(1, 'reload this')
+ write
+ " Need to wait until the timestamp would change by at least a second.
+ sleep 2
+ silent !echo 'extra line' >>Xchanged_d
+ call feedkeys('L', 'L')
+ checktime
+ call assert_match('W11:', v:warningmsg)
+ call assert_equal(2, line('$'))
+ call assert_equal('reload this', getline(1))
+ call assert_equal('extra line', getline(2))
+
+ " delete buffer, only shows an error, no prompt
+ silent !rm Xchanged_d
+ checktime
+ call assert_match('E211:', v:warningmsg)
+ call assert_equal(2, line('$'))
+ call assert_equal('extra line', getline(2))
+ let v:warningmsg = 'empty'
+
+ " change buffer, recreate the file and reload
+ call setline(1, 'buffer is changed')
+ silent !echo 'new line' >Xchanged_d
+ call feedkeys('L', 'L')
+ checktime
+ call assert_match('W12:', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only mode changed, reload
+ silent !chmod +x Xchanged_d
+ call feedkeys('L', 'L')
+ checktime
+ call assert_match('W16:', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only time changed, no prompt
+ sleep 2
+ silent !touch Xchanged_d
+ let v:warningmsg = ''
+ checktime Xchanged_d
+ call assert_equal('', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " File created after starting to edit it
+ call delete('Xchanged_d')
+ new Xchanged_d
+ call writefile(['one'], 'Xchanged_d', 'D')
+ call feedkeys('L', 'L')
+ checktime Xchanged_d
+ call assert_equal(['one'], getline(1, '$'))
+ close!
+
+ bwipe!
+endfunc
+
+" Test for editing a new buffer from a FileChangedShell autocmd
+func Test_FileChangedShell_newbuf()
+ call writefile(['one', 'two'], 'Xchfile', 'D')
+ new Xchfile
+ augroup testnewbuf
+ autocmd FileChangedShell * enew
+ augroup END
+ call writefile(['red'], 'Xchfile')
+ call assert_fails('checktime', 'E811:')
+
+ au! testnewbuf
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_fileformat.vim b/src/testdir/test_fileformat.vim
new file mode 100644
index 0000000..7d919c2
--- /dev/null
+++ b/src/testdir/test_fileformat.vim
@@ -0,0 +1,327 @@
+" Test for 'fileformat'
+
+source shared.vim
+
+" Test behavior of fileformat after bwipeout of last buffer
+func Test_fileformat_after_bw()
+ bwipeout
+ set fileformat&
+ if &fileformat == 'dos'
+ let test_fileformats = 'unix'
+ elseif &fileformat == 'unix'
+ let test_fileformats = 'mac'
+ else " must be mac
+ let test_fileformats = 'dos'
+ endif
+ exec 'set fileformats='.test_fileformats
+ bwipeout!
+ call assert_equal(test_fileformats, &fileformat)
+ set fileformats&
+endfunc
+
+func Test_fileformat_autocommand()
+ let filecnt = ["", "foobar\<CR>", "eins\<CR>", "\<CR>", "zwei\<CR>", "drei", "vier", "fünf", ""]
+ let ffs = &ffs
+ call writefile(filecnt, 'Xffafile', 'bD')
+ au BufReadPre Xffafile set ffs=dos ff=dos
+ new Xffafile
+ call assert_equal('dos', &l:ff)
+ call assert_equal('dos', &ffs)
+
+ " cleanup
+ let &ffs = ffs
+ au! BufReadPre Xffafile
+ bw!
+endfunc
+
+func Test_fileformat_nomodifiable()
+ new
+ setlocal nomodifiable
+
+ call assert_fails('set fileformat=latin1', 'E21:')
+
+ bw
+endfunc
+
+" Convert the contents of a file into a literal string
+func s:file2str(fname)
+ let b = readfile(a:fname, 'B')
+ let s = ''
+ for c in b
+ let s .= nr2char(c)
+ endfor
+ return s
+endfunc
+
+" Concatenate the contents of files 'f1' and 'f2' and create 'destfile'
+func s:concat_files(f1, f2, destfile)
+ let b1 = readfile(a:f1, 'B')
+ let b2 = readfile(a:f2, 'B')
+ let b3 = b1 + b2
+ call writefile(b3, a:destfile, 'B')
+endfun
+
+" Test for a lot of variations of the 'fileformats' option
+func Test_fileformats()
+ " create three test files, one in each format
+ call writefile(['unix', 'unix'], 'XXUnix', 'D')
+ call writefile(["dos\r", "dos\r"], 'XXDos', 'D')
+ call writefile(["mac\rmac\r"], 'XXMac', 'bD')
+ " create a file with no End Of Line
+ call writefile(["noeol"], 'XXEol', 'bD')
+ " create mixed format files
+ call s:concat_files('XXUnix', 'XXDos', 'XXUxDs')
+ call s:concat_files('XXUnix', 'XXMac', 'XXUxMac')
+ call s:concat_files('XXDos', 'XXMac', 'XXDosMac')
+ call s:concat_files('XXMac', 'XXEol', 'XXMacEol')
+ call s:concat_files('XXUxDs', 'XXMac', 'XXUxDsMc')
+
+ " The :bwipe commands below cause us to get back to the current buffer.
+ " Avoid stray errors for various 'fileformat' values which may cause a
+ " modeline to be misinterpreted by wiping the buffer and editing a new one.
+ only!
+ bwipe!
+ enew
+
+ " Test 1: try reading and writing with 'fileformats' empty
+ set fileformats=
+
+ " try with 'fileformat' set to 'unix'
+ set fileformat=unix
+ e! XXUnix
+ w! Xtest
+ call assert_equal("unix\nunix\n", s:file2str('Xtest'))
+ e! XXDos
+ w! Xtest
+ call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest'))
+ e! XXMac
+ w! Xtest
+ call assert_equal("mac\rmac\r\n", s:file2str('Xtest'))
+ bwipe XXUnix XXDos XXMac
+
+ " try with 'fileformat' set to 'dos'
+ set fileformat=dos
+ e! XXUnix
+ w! Xtest
+ call assert_equal("unix\r\nunix\r\n", s:file2str('Xtest'))
+ e! XXDos
+ w! Xtest
+ call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest'))
+ e! XXMac
+ w! Xtest
+ call assert_equal("mac\rmac\r\r\n", s:file2str('Xtest'))
+ bwipe XXUnix XXDos XXMac
+
+ " try with 'fileformat' set to 'mac'
+ set fileformat=mac
+ e! XXUnix
+ w! Xtest
+ call assert_equal("unix\nunix\n\r", s:file2str('Xtest'))
+ e! XXDos
+ w! Xtest
+ call assert_equal("dos\r\ndos\r\n\r", s:file2str('Xtest'))
+ e! XXMac
+ w! Xtest
+ call assert_equal("mac\rmac\r", s:file2str('Xtest'))
+ bwipe XXUnix XXDos XXMac
+
+ " Test 2: try reading and writing with 'fileformats' set to one format
+
+ " try with 'fileformats' set to 'unix'
+ set fileformats=unix
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ " try with 'fileformats' set to 'dos'
+ set fileformats=dos
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ " try with 'fileformats' set to 'mac'
+ set fileformats=mac
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ " Test 3: try reading and writing with 'fileformats' set to two formats
+
+ " try with 'fileformats' set to 'unix,dos'
+ set fileformats=unix,dos
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ e! XXUxMac
+ w! Xtest
+ call assert_equal("unix\nunix\nmac\rmac\r\n", s:file2str('Xtest'))
+ bwipe XXUxMac
+
+ e! XXDosMac
+ w! Xtest
+ call assert_equal("dos\r\ndos\r\nmac\rmac\r\r\n", s:file2str('Xtest'))
+ bwipe XXDosMac
+
+ " try with 'fileformats' set to 'unix,mac'
+ set fileformats=unix,mac
+ e! XXUxDs
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\n", s:file2str('Xtest'))
+ bwipe XXUxDs
+
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ e! XXDosMac
+ w! Xtest
+ call assert_equal("dos\r\ndos\r\nmac\rmac\r", s:file2str('Xtest'))
+ bwipe XXDosMac
+
+ e! XXEol
+ exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
+ w! Xtest
+ call assert_equal("unix,mac:unix\nnoeol\n", s:file2str('Xtest'))
+ bwipe! XXEol
+
+ " try with 'fileformats' set to 'dos,mac'
+ set fileformats=dos,mac
+ e! XXUxDs
+ w! Xtest
+ call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\n", s:file2str('Xtest'))
+ bwipe XXUxDs
+
+ e! XXUxMac
+ exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
+ w! Xtest
+ call assert_equal("dos,mac:dos\r\nunix\r\nunix\r\nmac\rmac\r\r\n",
+ \ s:file2str('Xtest'))
+ bwipe! XXUxMac
+
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ e! XXMacEol
+ exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
+ w! Xtest
+ call assert_equal("dos,mac:mac\rmac\rmac\rnoeol\r", s:file2str('Xtest'))
+ bwipe! XXMacEol
+
+ " Test 4: try reading and writing with 'fileformats' set to three formats
+ set fileformats=unix,dos,mac
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ e! XXEol
+ exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
+ w! Xtest
+ call assert_equal("unix,dos,mac:unix\nnoeol\n", s:file2str('Xtest'))
+ bwipe! XXEol
+
+ set fileformats=mac,dos,unix
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ e! XXEol
+ exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>"
+ w! Xtest
+ call assert_equal("mac,dos,unix:mac\rnoeol\r", s:file2str('Xtest'))
+ bwipe! XXEol
+
+ " Test 5: try with 'binary' set
+ set fileformats=mac,unix,dos
+ set binary
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ set fileformats=mac
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ set fileformats=dos
+ e! XXUxDsMc
+ w! Xtest
+ call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r",
+ \ s:file2str('Xtest'))
+ bwipe XXUxDsMc
+
+ e! XXUnix
+ w! Xtest
+ call assert_equal("unix\nunix\n", s:file2str('Xtest'))
+ bwipe! XXUnix
+
+ set nobinary ff& ffs&
+
+ " cleanup
+ only
+ %bwipe!
+ call delete('XXUxDs')
+ call delete('XXUxMac')
+ call delete('XXDosMac')
+ call delete('XXMacEol')
+ call delete('XXUxDsMc')
+ call delete('Xtest')
+endfunc
+
+" Test for changing the fileformat using ++read
+func Test_fileformat_plusplus_read()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ w ++ff=dos Xfile1
+ enew!
+ set ff=unix
+ " A :read doesn't change the fileformat, but does apply to the read lines.
+ r ++fileformat=unix Xfile1
+ call assert_equal('unix', &fileformat)
+ call assert_equal("three\r", getline('$'))
+ 3r ++edit Xfile1
+ call assert_equal('dos', &fileformat)
+ close!
+ call delete('Xfile1')
+ set fileformat&
+ call assert_fails('e ++fileformat Xfile1', 'E474:')
+ call assert_fails('e ++ff=abc Xfile1', 'E474:')
+ call assert_fails('e ++abc1 Xfile1', 'E474:')
+endfunc
+
+" When Vim starts up with an empty buffer the first item in 'fileformats' is
+" used as the 'fileformat'.
+func Test_fileformat_on_startup()
+ let after =<< trim END
+ call writefile([&fileformat], 'Xonsfile', 'a')
+ quit
+ END
+ call RunVim(["set ffs=dos,unix,mac"], after, '')
+ call RunVim(["set ffs=mac,dos,unix"], after, '')
+ call RunVim(["set ffs=unix,mac,dos"], after, '')
+ call assert_equal(['dos', 'mac', 'unix'], readfile('Xonsfile'))
+ call delete('Xonsfile')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_filetype.vim b/src/testdir/test_filetype.vim
new file mode 100644
index 0000000..ff438bb
--- /dev/null
+++ b/src/testdir/test_filetype.vim
@@ -0,0 +1,2023 @@
+" Test :setfiletype
+
+func Test_detection()
+ filetype on
+ augroup filetypedetect
+ au BufNewFile,BufRead * call assert_equal(1, did_filetype())
+ augroup END
+ new something.vim
+ call assert_equal('vim', &filetype)
+
+ bwipe!
+ filetype off
+endfunc
+
+func Test_conf_type()
+ filetype on
+ call writefile(['# some comment', 'must be conf'], 'Xconffile', 'D')
+ augroup filetypedetect
+ au BufNewFile,BufRead * call assert_equal(0, did_filetype())
+ augroup END
+ split Xconffile
+ call assert_equal('conf', &filetype)
+
+ bwipe!
+ filetype off
+endfunc
+
+func Test_other_type()
+ filetype on
+ augroup filetypedetect
+ au BufNewFile,BufRead * call assert_equal(0, did_filetype())
+ au BufNewFile,BufRead Xotherfile setf testfile
+ au BufNewFile,BufRead * call assert_equal(1, did_filetype())
+ augroup END
+ call writefile(['# some comment', 'must be conf'], 'Xotherfile', 'D')
+ split Xotherfile
+ call assert_equal('testfile', &filetype)
+
+ bwipe!
+ filetype off
+endfunc
+
+" Filetypes detected just from matching the file name.
+" First one is checking that these files have no filetype.
+let s:filename_checks = {
+ \ 'none': ['bsd', 'some-bsd'],
+ \ '8th': ['file.8th'],
+ \ 'a2ps': ['/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc', 'any/etc/a2ps.cfg', 'any/etc/a2ps/file.cfg'],
+ \ 'a65': ['file.a65'],
+ \ 'aap': ['file.aap'],
+ \ 'abap': ['file.abap'],
+ \ 'abc': ['file.abc'],
+ \ 'abel': ['file.abl'],
+ \ 'acedb': ['file.wrm'],
+ \ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'],
+ \ 'ahdl': ['file.tdf'],
+ \ 'aidl': ['file.aidl'],
+ \ 'alsaconf': ['.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf', 'any/etc/asound.conf', 'any/usr/share/alsa/alsa.conf'],
+ \ 'aml': ['file.aml'],
+ \ 'ampl': ['file.run'],
+ \ 'ant': ['build.xml'],
+ \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file', '/etc/httpd/mods-some/file', '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'],
+ \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file', 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'],
+ \ 'applescript': ['file.scpt'],
+ \ 'aptconf': ['apt.conf', '/.aptitude/config', 'any/.aptitude/config'],
+ \ 'arch': ['.arch-inventory', '=tagging-method'],
+ \ 'arduino': ['file.ino', 'file.pde'],
+ \ 'art': ['file.art'],
+ \ 'asciidoc': ['file.asciidoc', 'file.adoc'],
+ \ 'asn': ['file.asn', 'file.asn1'],
+ \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'],
+ \ 'astro': ['file.astro'],
+ \ 'atlas': ['file.atl', 'file.as'],
+ \ 'autohotkey': ['file.ahk'],
+ \ 'autoit': ['file.au3'],
+ \ 'automake': ['GNUmakefile.am', 'makefile.am', 'Makefile.am'],
+ \ 'ave': ['file.ave'],
+ \ 'awk': ['file.awk', 'file.gawk'],
+ \ 'b': ['file.mch', 'file.ref', 'file.imp'],
+ \ 'basic': ['file.bas', 'file.bi', 'file.bm'],
+ \ 'bass': ['file.bass'],
+ \ 'bc': ['file.bc'],
+ \ 'bdf': ['file.bdf'],
+ \ 'beancount': ['file.beancount'],
+ \ 'bib': ['file.bib'],
+ \ 'bicep': ['file.bicep'],
+ \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'],
+ \ 'bitbake': ['file.bb', 'file.bbappend', 'file.bbclass', 'build/conf/local.conf', 'meta/conf/layer.conf', 'build/conf/bbappend.conf', 'meta-layer/conf/distro/foo.conf'],
+ \ 'blank': ['file.bl'],
+ \ 'blueprint': ['file.blp'],
+ \ 'bsdl': ['file.bsd', 'file.bsdl'],
+ \ 'bst': ['file.bst'],
+ \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE', 'WORKSPACE.bzlmod'],
+ \ 'bzr': ['bzr_log.any', 'bzr_log.file'],
+ \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg'],
+ \ 'cabal': ['file.cabal'],
+ \ 'cabalconfig': ['cabal.config'],
+ \ 'cabalproject': ['cabal.project', 'cabal.project.local'],
+ \ 'calendar': ['calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'],
+ \ 'capnp': ['file.capnp'],
+ \ 'catalog': ['catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'],
+ \ 'cdl': ['file.cdl'],
+ \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'],
+ \ 'cdrtoc': ['file.toc'],
+ \ 'cf': ['file.cfm', 'file.cfi', 'file.cfc'],
+ \ 'cfengine': ['cfengine.conf'],
+ \ 'cfg': ['file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'],
+ \ 'ch': ['file.chf'],
+ \ 'chaiscript': ['file.chai'],
+ \ 'chaskell': ['file.chs'],
+ \ 'chatito': ['file.chatito'],
+ \ 'chill': ['file..ch'],
+ \ 'chordpro': ['file.chopro', 'file.crd', 'file.cho', 'file.crdpro', 'file.chordpro'],
+ \ 'cl': ['file.eni'],
+ \ 'clean': ['file.dcl', 'file.icl'],
+ \ 'clojure': ['file.clj', 'file.cljs', 'file.cljx', 'file.cljc'],
+ \ 'cmake': ['CMakeLists.txt', 'file.cmake', 'file.cmake.in'],
+ \ 'cmod': ['file.cmod'],
+ \ 'cmusrc': ['any/.cmus/autosave', 'any/.cmus/rc', 'any/.cmus/command-history', 'any/.cmus/file.theme', 'any/cmus/rc', 'any/cmus/file.theme', '/.cmus/autosave', '/.cmus/command-history', '/.cmus/file.theme', '/.cmus/rc', '/cmus/file.theme', '/cmus/rc'],
+ \ 'cobol': ['file.cbl', 'file.cob', 'file.lib'],
+ \ 'coco': ['file.atg'],
+ \ 'conaryrecipe': ['file.recipe'],
+ \ 'conf': ['auto.master'],
+ \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'],
+ \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf', 'any/.aws/config', 'any/.aws/credentials', 'file.nmconnection'],
+ \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'],
+ \ 'cook': ['file.cook'],
+ \ 'cpon': ['file.cpon'],
+ \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'],
+ \ 'cqlang': ['file.cql'],
+ \ 'crm': ['file.crm'],
+ \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'],
+ \ 'cs': ['file.cs', 'file.csx'],
+ \ 'csc': ['file.csc'],
+ \ 'csdl': ['file.csdl'],
+ \ 'csp': ['file.csp', 'file.fdr'],
+ \ 'css': ['file.css'],
+ \ 'cterm': ['file.con'],
+ \ 'csv': ['file.csv'],
+ \ 'cucumber': ['file.feature'],
+ \ 'cuda': ['file.cu', 'file.cuh'],
+ \ 'cue': ['file.cue'],
+ \ 'cupl': ['file.pld'],
+ \ 'cuplsim': ['file.si'],
+ \ 'cvs': ['cvs123'],
+ \ 'cvsrc': ['.cvsrc'],
+ \ 'cynpp': ['file.cyn'],
+ \ 'd': ['file.d'],
+ \ 'dart': ['file.dart', 'file.drt'],
+ \ 'datascript': ['file.ds'],
+ \ 'dcd': ['file.dcd'],
+ \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'],
+ \ 'debcontrol': ['/debian/control', 'any/debian/control'],
+ \ 'debcopyright': ['/debian/copyright', 'any/debian/copyright'],
+ \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list', 'any/etc/apt/sources.list', 'any/etc/apt/sources.list.d/file.list'],
+ \ 'def': ['file.def'],
+ \ 'denyhosts': ['denyhosts.conf'],
+ \ 'desc': ['file.desc'],
+ \ 'desktop': ['file.desktop', '.directory', 'file.directory'],
+ \ 'dhall': ['file.dhall'],
+ \ 'dictconf': ['dict.conf', '.dictrc'],
+ \ 'dictdconf': ['dictd.conf', 'dictdfile.conf', 'dictd-file.conf'],
+ \ 'diff': ['file.diff', 'file.rej'],
+ \ 'dircolors': ['.dir_colors', '.dircolors', '/etc/DIR_COLORS', 'any/etc/DIR_COLORS'],
+ \ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'],
+ \ 'dockerfile': ['Containerfile', 'Dockerfile', 'dockerfile', 'file.Dockerfile', 'file.dockerfile', 'Dockerfile.debian', 'Containerfile.something'],
+ \ 'dosbatch': ['file.bat'],
+ \ 'dosini': ['/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'],
+ \ 'dot': ['file.dot', 'file.gv'],
+ \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'],
+ \ 'dtd': ['file.dtd'],
+ \ 'dtrace': ['/usr/lib/dtrace/io.d'],
+ \ 'dts': ['file.dts', 'file.dtsi'],
+ \ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace'],
+ \ 'dylan': ['file.dylan'],
+ \ 'dylanintr': ['file.intr'],
+ \ 'dylanlid': ['file.lid'],
+ \ 'ecd': ['file.ecd'],
+ \ 'edif': ['file.edf', 'file.edif', 'file.edo'],
+ \ 'editorconfig': ['.editorconfig'],
+ \ 'eelixir': ['file.eex', 'file.leex'],
+ \ 'elinks': ['elinks.conf'],
+ \ 'elixir': ['file.ex', 'file.exs', 'mix.lock'],
+ \ 'elm': ['file.elm'],
+ \ 'elmfilt': ['filter-rules'],
+ \ 'elsa': ['file.lc'],
+ \ 'elvish': ['file.elv'],
+ \ 'epuppet': ['file.epp'],
+ \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'],
+ \ 'eruby': ['file.erb', 'file.rhtml'],
+ \ 'esmtprc': ['anyesmtprc', 'esmtprc', 'some-esmtprc'],
+ \ 'esqlc': ['file.ec', 'file.EC'],
+ \ 'esterel': ['file.strl'],
+ \ 'eterm': ['anyEterm/file.cfg', 'Eterm/file.cfg', 'some-Eterm/file.cfg'],
+ \ 'exim': ['exim.conf'],
+ \ 'expect': ['file.exp'],
+ \ 'exports': ['exports'],
+ \ 'factor': ['file.factor'],
+ \ 'falcon': ['file.fal'],
+ \ 'fan': ['file.fan', 'file.fwt'],
+ \ 'fennel': ['file.fnl'],
+ \ 'fetchmail': ['.fetchmailrc'],
+ \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'],
+ \ 'firrtl': ['file.fir'],
+ \ 'fish': ['file.fish'],
+ \ 'focexec': ['file.fex', 'file.focexec'],
+ \ 'form': ['file.frm'],
+ \ 'forth': ['file.ft', 'file.fth'],
+ \ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'],
+ \ 'fpcmake': ['file.fpc'],
+ \ 'framescript': ['file.fsl'],
+ \ 'freebasic': ['file.fb'],
+ \ 'fsh': ['file.fsh'],
+ \ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'],
+ \ 'fstab': ['fstab', 'mtab'],
+ \ 'func': ['file.fc'],
+ \ 'fusion': ['file.fusion'],
+ \ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'],
+ \ 'gdb': ['.gdbinit', 'gdbinit', 'file.gdb', '.config/gdbearlyinit', '.gdbearlyinit'],
+ \ 'gdmo': ['file.mo', 'file.gdmo'],
+ \ 'gdresource': ['file.tscn', 'file.tres'],
+ \ 'gdscript': ['file.gd'],
+ \ 'gdshader': ['file.gdshader', 'file.shader'],
+ \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'],
+ \ 'gemtext': ['file.gmi', 'file.gemini'],
+ \ 'gift': ['file.gift'],
+ \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'],
+ \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'],
+ \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'],
+ \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'],
+ \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'],
+ \ 'gitrebase': ['git-rebase-todo'],
+ \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'],
+ \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'],
+ \ 'gleam': ['file.gleam'],
+ \ 'glsl': ['file.glsl'],
+ \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'],
+ \ 'gnuplot': ['file.gpi', '.gnuplot'],
+ \ 'go': ['file.go'],
+ \ 'gomod': ['go.mod'],
+ \ 'gosum': ['go.sum', 'go.work.sum'],
+ \ 'gowork': ['go.work'],
+ \ 'gp': ['file.gp', '.gprc'],
+ \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'],
+ \ 'grads': ['file.gs'],
+ \ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'],
+ \ 'gretl': ['file.gretl'],
+ \ 'groovy': ['file.gradle', 'file.groovy'],
+ \ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'],
+ \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'],
+ \ 'gsp': ['file.gsp'],
+ \ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'],
+ \ 'gyp': ['file.gyp', 'file.gypi'],
+ \ 'hack': ['file.hack', 'file.hackpartial'],
+ \ 'haml': ['file.haml'],
+ \ 'hamster': ['file.hsm'],
+ \ 'handlebars': ['file.hbs'],
+ \ 'hare': ['file.ha'],
+ \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'],
+ \ 'haste': ['file.ht'],
+ \ 'hastepreproc': ['file.htpp'],
+ \ 'hb': ['file.hb'],
+ \ 'hcl': ['file.hcl'],
+ \ 'heex': ['file.heex'],
+ \ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'],
+ \ 'hex': ['file.hex', 'file.h32'],
+ \ 'hgcommit': ['hg-editor-file.txt'],
+ \ 'hjson': ['file.hjson'],
+ \ 'hlsplaylist': ['file.m3u', 'file.m3u8'],
+ \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
+ \ 'hollywood': ['file.hws'],
+ \ 'hoon': ['file.hoon'],
+ \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'],
+ \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'],
+ \ 'html': ['file.html', 'file.htm', 'file.cshtml'],
+ \ 'htmlm4': ['file.html.m4'],
+ \ 'httest': ['file.htt', 'file.htb'],
+ \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'],
+ \ 'ibasic': ['file.iba', 'file.ibi'],
+ \ 'icemenu': ['/.icewm/menu', 'any/.icewm/menu'],
+ \ 'icon': ['file.icn'],
+ \ 'indent': ['.indent.pro', 'indentrc'],
+ \ 'inform': ['file.inf', 'file.INF'],
+ \ 'initng': ['/etc/initng/any/file.i', 'file.ii', 'any/etc/initng/any/file.i'],
+ \ 'inittab': ['inittab'],
+ \ 'ipfilter': ['ipf.conf', 'ipf6.conf', 'ipf.rules'],
+ \ 'iss': ['file.iss'],
+ \ 'ist': ['file.ist', 'file.mst'],
+ \ 'j': ['file.ijs'],
+ \ 'jal': ['file.jal', 'file.JAL'],
+ \ 'jam': ['file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'],
+ \ 'java': ['file.java', 'file.jav'],
+ \ 'javacc': ['file.jj', 'file.jjt'],
+ \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
+ \ 'javascript.glimmer': ['file.gjs'],
+ \ 'javascriptreact': ['file.jsx'],
+ \ 'jess': ['file.clp'],
+ \ 'jgraph': ['file.jgr'],
+ \ 'jq': ['file.jq'],
+ \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
+ \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'],
+ \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.prettierrc', '.firebaserc', '.stylelintrc', 'file.slnf'],
+ \ 'json5': ['file.json5'],
+ \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json'],
+ \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'],
+ \ 'jsp': ['file.jsp'],
+ \ 'julia': ['file.jl'],
+ \ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'],
+ \ 'kdl': ['file.kdl'],
+ \ 'kivy': ['file.kv'],
+ \ 'kix': ['file.kix'],
+ \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'],
+ \ 'krl': ['file.sub', 'file.Sub', 'file.SUB'],
+ \ 'kscript': ['file.ks'],
+ \ 'kwt': ['file.k'],
+ \ 'lace': ['file.ace', 'file.ACE'],
+ \ 'latte': ['file.latte', 'file.lte'],
+ \ 'ld': ['file.ld'],
+ \ 'ldif': ['file.ldif'],
+ \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'],
+ \ 'less': ['file.less'],
+ \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'],
+ \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'],
+ \ 'lhaskell': ['file.lhs'],
+ \ 'libao': ['/etc/libao.conf', '/.libao', 'any/.libao', 'any/etc/libao.conf'],
+ \ 'lifelines': ['file.ll'],
+ \ 'lilo': ['lilo.conf', 'lilo.conf-file'],
+ \ 'lilypond': ['file.ly', 'file.ily'],
+ \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'],
+ \ 'liquid': ['file.liquid'],
+ \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
+ \ 'lite': ['file.lite', 'file.lt'],
+ \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'],
+ \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'],
+ \ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'],
+ \ 'logindefs': ['/etc/login.defs', 'any/etc/login.defs'],
+ \ 'logtalk': ['file.lgt'],
+ \ 'lotos': ['file.lot', 'file.lotos'],
+ \ 'lout': ['file.lou', 'file.lout'],
+ \ 'lpc': ['file.lpc', 'file.ulpc'],
+ \ 'lsl': ['file.lsl'],
+ \ 'lss': ['file.lss'],
+ \ 'lua': ['file.lua', 'file.rockspec', 'file.nse', '.luacheckrc'],
+ \ 'lynx': ['lynx.cfg'],
+ \ 'lyrics': ['file.lrc'],
+ \ 'm3build': ['m3makefile', 'm3overrides'],
+ \ 'm3quake': ['file.quake', 'cm3.cfg'],
+ \ 'm4': ['file.at'],
+ \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml', 'reportbug-file'],
+ \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases', 'any/etc/aliases', 'any/etc/mail/aliases'],
+ \ 'mailcap': ['.mailcap', 'mailcap'],
+ \ 'make': ['file.mk', 'file.mak', 'file.dsp', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile'],
+ \ 'mallard': ['file.page'],
+ \ 'man': ['file.man'],
+ \ 'manconf': ['/etc/man.conf', 'man.config', 'any/etc/man.conf'],
+ \ 'map': ['file.map'],
+ \ 'maple': ['file.mv', 'file.mpl', 'file.mws'],
+ \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'],
+ \ 'mason': ['file.mason', 'file.mhtml', 'file.comp'],
+ \ 'master': ['file.mas', 'file.master'],
+ \ 'matlab': ['file.m'],
+ \ 'maxima': ['file.demo', 'file.dmt', 'file.dm1', 'file.dm2', 'file.dm3',
+ \ 'file.wxm', 'maxima-init.mac'],
+ \ 'mel': ['file.mel'],
+ \ 'mermaid': ['file.mmd', 'file.mmdc', 'file.mermaid'],
+ \ 'meson': ['meson.build', 'meson_options.txt'],
+ \ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user',
+ \ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log',
+ \ '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err',
+ \ '/log/auth.info', '/log/cron.info', '/log/daemon.info', '/log/debug.info', '/log/kern.info', '/log/lpr.info', '/log/mail.info', '/log/messages.info', '/log/news/news.info', '/log/syslog.info', '/log/user.info',
+ \ '/log/auth.warn', '/log/cron.warn', '/log/daemon.warn', '/log/debug.warn', '/log/kern.warn', '/log/lpr.warn', '/log/mail.warn', '/log/messages.warn', '/log/news/news.warn', '/log/syslog.warn', '/log/user.warn',
+ \ '/log/auth.crit', '/log/cron.crit', '/log/daemon.crit', '/log/debug.crit', '/log/kern.crit', '/log/lpr.crit', '/log/mail.crit', '/log/messages.crit', '/log/news/news.crit', '/log/syslog.crit', '/log/user.crit',
+ \ '/log/auth.notice', '/log/cron.notice', '/log/daemon.notice', '/log/debug.notice', '/log/kern.notice', '/log/lpr.notice', '/log/mail.notice', '/log/messages.notice', '/log/news/news.notice', '/log/syslog.notice', '/log/user.notice'],
+ \ 'mf': ['file.mf'],
+ \ 'mgl': ['file.mgl'],
+ \ 'mgp': ['file.mgp'],
+ \ 'mib': ['file.mib', 'file.my'],
+ \ 'mix': ['file.mix', 'file.mixal'],
+ \ 'mma': ['file.nb'],
+ \ 'mmp': ['file.mmp'],
+ \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'],
+ \ 'modula2': ['file.m2', 'file.mi'],
+ \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig', 'file.lm3'],
+ \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'],
+ \ 'moo': ['file.moo'],
+ \ 'moonscript': ['file.moon'],
+ \ 'move': ['file.move'],
+ \ 'mp': ['file.mp', 'file.mpxl', 'file.mpiv', 'file.mpvi'],
+ \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'],
+ \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'],
+ \ 'msidl': ['file.odl', 'file.mof'],
+ \ 'msql': ['file.msql'],
+ \ 'mupad': ['file.mu'],
+ \ 'mush': ['file.mush'],
+ \ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', '/etc/Muttrc.d/file.rc', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'],
+ \ 'mysql': ['file.mysql'],
+ \ 'n1ql': ['file.n1ql', 'file.nql'],
+ \ 'named': ['namedfile.conf', 'rndcfile.conf', 'named-file.conf', 'named.conf', 'rndc-file.conf', 'rndc-file.key', 'rndc.conf', 'rndc.key'],
+ \ 'nanorc': ['/etc/nanorc', 'file.nanorc', 'any/etc/nanorc'],
+ \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'],
+ \ 'ncf': ['file.ncf'],
+ \ 'neomuttrc': ['Neomuttrc', '.neomuttrc', '.neomuttrc-file', '/.neomutt/neomuttrc', '/.neomutt/neomuttrc-file', 'Neomuttrc', 'Neomuttrc-file', 'any/.neomutt/neomuttrc', 'any/.neomutt/neomuttrc-file', 'neomuttrc', 'neomuttrc-file'],
+ \ 'netrc': ['.netrc'],
+ \ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'],
+ \ 'nim': ['file.nim', 'file.nims', 'file.nimble'],
+ \ 'ninja': ['file.ninja'],
+ \ 'nix': ['file.nix'],
+ \ 'nqc': ['file.nqc'],
+ \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'],
+ \ 'nsis': ['file.nsi', 'file.nsh'],
+ \ 'obj': ['file.obj'],
+ \ 'obse': ['file.obl', 'file.obse', 'file.oblivion', 'file.obscript'],
+ \ 'ocaml': ['file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit', 'file.mlt', 'file.mlp', 'file.mlip', 'file.mli.cppo', 'file.ml.cppo'],
+ \ 'occam': ['file.occ'],
+ \ 'octave': ['octaverc', '.octaverc', 'octave.conf'],
+ \ 'omnimark': ['file.xom', 'file.xin'],
+ \ 'opam': ['opam', 'file.opam', 'file.opam.template'],
+ \ 'openroad': ['file.or'],
+ \ 'openscad': ['file.scad'],
+ \ 'openvpn': ['file.ovpn', '/etc/openvpn/client/client.conf', '/usr/share/openvpn/examples/server.conf'],
+ \ 'opl': ['file.OPL', 'file.OPl', 'file.OpL', 'file.Opl', 'file.oPL', 'file.oPl', 'file.opL', 'file.opl'],
+ \ 'ora': ['file.ora'],
+ \ 'org': ['file.org', 'file.org_archive'],
+ \ 'pamconf': ['/etc/pam.conf', '/etc/pam.d/file', 'any/etc/pam.conf', 'any/etc/pam.d/file'],
+ \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment', '.pam_environment', 'pam_env.conf'],
+ \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'],
+ \ 'pascal': ['file.pas', 'file.dpr', 'file.lpr'],
+ \ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak', '/etc/passwd', '/etc/passwd-', '/etc/passwd.edit', '/etc/shadow', '/etc/shadow-', '/etc/shadow.edit', '/var/backups/passwd.bak', '/var/backups/shadow.bak'],
+ \ 'pbtxt': ['file.pbtxt'],
+ \ 'pccts': ['file.g'],
+ \ 'pcmk': ['file.pcmk'],
+ \ 'pdf': ['file.pdf'],
+ \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc', '.latexmkrc', 'latexmkrc'],
+ \ 'pf': ['pf.conf'],
+ \ 'pfmain': ['main.cf', 'main.cf.proto'],
+ \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt', 'file.theme'],
+ \ 'pike': ['file.pike', 'file.pmod'],
+ \ 'pilrc': ['file.rcp'],
+ \ 'pine': ['.pinerc', 'pinerc', '.pinercex', 'pinercex'],
+ \ 'pinfo': ['/etc/pinforc', '/.pinforc', 'any/.pinforc', 'any/etc/pinforc'],
+ \ 'pli': ['file.pli', 'file.pl1'],
+ \ 'plm': ['file.plm', 'file.p36', 'file.pac'],
+ \ 'plp': ['file.plp'],
+ \ 'plsql': ['file.pls', 'file.plsql'],
+ \ 'po': ['file.po', 'file.pot'],
+ \ 'pod': ['file.pod'],
+ \ 'poefilter': ['file.filter'],
+ \ 'poke': ['file.pk'],
+ \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'],
+ \ 'pov': ['file.pov'],
+ \ 'povini': ['.povrayrc'],
+ \ 'ppd': ['file.ppd'],
+ \ 'ppwiz': ['file.it', 'file.ih'],
+ \ 'prisma': ['file.prisma'],
+ \ 'privoxy': ['file.action'],
+ \ 'proc': ['file.pc'],
+ \ 'procmail': ['.procmail', '.procmailrc'],
+ \ 'prolog': ['file.pdb'],
+ \ 'promela': ['file.pml'],
+ \ 'proto': ['file.proto'],
+ \ 'protocols': ['/etc/protocols', 'any/etc/protocols'],
+ \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'],
+ \ 'ps1xml': ['file.ps1xml'],
+ \ 'psf': ['file.psf'],
+ \ 'psl': ['file.psl'],
+ \ 'pug': ['file.pug'],
+ \ 'puppet': ['file.pp'],
+ \ 'pyret': ['file.arr'],
+ \ 'pyrex': ['file.pyx', 'file.pxd'],
+ \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
+ \ 'ql': ['file.ql', 'file.qll'],
+ \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'],
+ \ 'quarto': ['file.qmd'],
+ \ 'r': ['file.r', '.Rprofile', 'Rprofile', 'Rprofile.site'],
+ \ 'radiance': ['file.rad', 'file.mat'],
+ \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'],
+ \ 'raml': ['file.raml'],
+ \ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'],
+ \ 'rbs': ['file.rbs'],
+ \ 'rc': ['file.rc', 'file.rch'],
+ \ 'rcs': ['file,v'],
+ \ 'readline': ['.inputrc', 'inputrc'],
+ \ 'rego': ['file.rego'],
+ \ 'remind': ['.reminders', 'file.remind', 'file.rem', '.reminders-file'],
+ \ 'rescript': ['file.res', 'file.resi'],
+ \ 'resolv': ['resolv.conf'],
+ \ 'reva': ['file.frt'],
+ \ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'],
+ \ 'rhelp': ['file.rd'],
+ \ 'rib': ['file.rib'],
+ \ 'rmd': ['file.rmd', 'file.smd'],
+ \ 'rnc': ['file.rnc'],
+ \ 'rng': ['file.rng'],
+ \ 'rnoweb': ['file.rnw', 'file.snw'],
+ \ 'robot': ['file.robot', 'file.resource'],
+ \ 'robots': ['robots.txt'],
+ \ 'ron': ['file.ron'],
+ \ 'routeros': ['file.rsc'],
+ \ 'rpcgen': ['file.x'],
+ \ 'rpl': ['file.rpl'],
+ \ 'rrst': ['file.rrst', 'file.srst'],
+ \ 'rst': ['file.rst'],
+ \ 'rtf': ['file.rtf'],
+ \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile', 'Vagrantfile'],
+ \ 'rust': ['file.rs'],
+ \ 'samba': ['smb.conf'],
+ \ 'sas': ['file.sas'],
+ \ 'sass': ['file.sass'],
+ \ 'sather': ['file.sa'],
+ \ 'sbt': ['file.sbt'],
+ \ 'scala': ['file.scala'],
+ \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'],
+ \ 'scilab': ['file.sci', 'file.sce'],
+ \ 'screen': ['.screenrc', 'screenrc'],
+ \ 'scss': ['file.scss'],
+ \ 'sd': ['file.sd'],
+ \ 'sdc': ['file.sdc'],
+ \ 'sdl': ['file.sdl', 'file.pr'],
+ \ 'sed': ['file.sed'],
+ \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', '/etc/sensors.d/file', 'any/etc/sensors.conf', 'any/etc/sensors3.conf', 'any/etc/sensors.d/file'],
+ \ 'services': ['/etc/services', 'any/etc/services'],
+ \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'],
+ \ 'sexplib': ['file.sexp'],
+ \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'],
+ \ 'sieve': ['file.siv', 'file.sieve'],
+ \ 'sil': ['file.sil'],
+ \ 'simula': ['file.sim'],
+ \ 'sinda': ['file.sin', 'file.s85'],
+ \ 'sisu': ['file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'],
+ \ 'skill': ['file.il', 'file.ils', 'file.cdf'],
+ \ 'cdc': ['file.cdc'],
+ \ 'slang': ['file.sl'],
+ \ 'slice': ['file.ice'],
+ \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'],
+ \ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'],
+ \ 'slpspi': ['/etc/slp.spi', 'any/etc/slp.spi'],
+ \ 'slrnrc': ['.slrnrc'],
+ \ 'slrnsc': ['file.score'],
+ \ 'sm': ['sendmail.cf'],
+ \ 'smali': ['file.smali'],
+ \ 'smarty': ['file.tpl'],
+ \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'],
+ \ 'smith': ['file.smt', 'file.smith'],
+ \ 'smithy': ['file.smithy'],
+ \ 'sml': ['file.sml'],
+ \ 'snobol4': ['file.sno', 'file.spt'],
+ \ 'solidity': ['file.sol'],
+ \ 'solution': ['file.sln'],
+ \ 'sparql': ['file.rq', 'file.sparql'],
+ \ 'spec': ['file.spec'],
+ \ 'spice': ['file.sp', 'file.spice'],
+ \ 'spup': ['file.speedup', 'file.spdata', 'file.spd'],
+ \ 'spyce': ['file.spy', 'file.spi'],
+ \ 'sql': ['file.tyb', 'file.typ', 'file.tyc', 'file.pkb', 'file.pks'],
+ \ 'sqlj': ['file.sqlj'],
+ \ 'prql': ['file.prql'],
+ \ 'sqr': ['file.sqr', 'file.sqi'],
+ \ 'squid': ['squid.conf'],
+ \ 'squirrel': ['file.nut'],
+ \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'],
+ \ 'srt': ['file.srt'],
+ \ 'ssa': ['file.ass', 'file.ssa'],
+ \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'],
+ \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'],
+ \ 'st': ['file.st'],
+ \ 'starlark': ['file.ipd', 'file.star', 'file.starlark'],
+ \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'],
+ \ 'stp': ['file.stp'],
+ \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'],
+ \ 'supercollider': ['file.quark'],
+ \ 'surface': ['file.sface'],
+ \ 'svelte': ['file.svelte'],
+ \ 'svg': ['file.svg'],
+ \ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'],
+ \ 'swayconfig': ['/home/user/.sway/config', '/home/user/.config/sway/config', '/etc/sway/config', '/etc/xdg/sway/config'],
+ \ 'swift': ['file.swift'],
+ \ 'swiftgyb': ['file.swift.gyb'],
+ \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf', 'any/etc/sysctl.conf', 'any/etc/sysctl.d/file.conf'],
+ \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.dnssd', 'any/systemd/file.link', 'any/systemd/file.mount', 'any/systemd/file.netdev', 'any/systemd/file.network', 'any/systemd/file.nspawn', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.slice', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/some.conf.d/file.conf', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile', '/.config/systemd/user/.#', '/.config/systemd/user/.#-file', '/.config/systemd/user/file.d/.#', '/.config/systemd/user/file.d/.#-file', '/.config/systemd/user/file.d/file.conf', '/etc/systemd/file.conf.d/file.conf', '/etc/systemd/system/.#', '/etc/systemd/system/.#-file', '/etc/systemd/system/file.d/.#', '/etc/systemd/system/file.d/.#-file', '/etc/systemd/system/file.d/file.conf', '/systemd/file.automount', '/systemd/file.dnssd', '/systemd/file.link', '/systemd/file.mount', '/systemd/file.netdev', '/systemd/file.network', '/systemd/file.nspawn', '/systemd/file.path', '/systemd/file.service', '/systemd/file.slice', '/systemd/file.socket', '/systemd/file.swap', '/systemd/file.target', '/systemd/file.timer', 'any/.config/systemd/user/.#', 'any/.config/systemd/user/.#-file', 'any/.config/systemd/user/file.d/.#', 'any/.config/systemd/user/file.d/.#-file', 'any/.config/systemd/user/file.d/file.conf', 'any/etc/systemd/file.conf.d/file.conf', 'any/etc/systemd/system/.#', 'any/etc/systemd/system/.#-file', 'any/etc/systemd/system/file.d/.#', 'any/etc/systemd/system/file.d/.#-file', 'any/etc/systemd/system/file.d/file.conf'],
+ \ 'systemverilog': ['file.sv', 'file.svh'],
+ \ 'tags': ['tags'],
+ \ 'tak': ['file.tak'],
+ \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'],
+ \ 'taskedit': ['file.task'],
+ \ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'],
+ \ 'teal': ['file.tl'],
+ \ 'template': ['file.tmpl'],
+ \ 'teraterm': ['file.ttl'],
+ \ 'terminfo': ['file.ti'],
+ \ 'terraform-vars': ['file.tfvars'],
+ \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'],
+ \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'],
+ \ 'texmf': ['texmf.cnf'],
+ \ 'text': ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'],
+ \ 'tf': ['file.tf', '.tfrc', 'tfrc'],
+ \ 'thrift': ['file.thrift'],
+ \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'],
+ \ 'tilde': ['file.t.html'],
+ \ 'tla': ['file.tla'],
+ \ 'tli': ['file.tli'],
+ \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'],
+ \ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'],
+ \ 'tpp': ['file.tpp'],
+ \ 'treetop': ['file.treetop'],
+ \ 'trustees': ['trustees.conf'],
+ \ 'tsalt': ['file.slt'],
+ \ 'tsscl': ['file.tsscl'],
+ \ 'tssgm': ['file.tssgm'],
+ \ 'tssop': ['file.tssop'],
+ \ 'tsv': ['file.tsv'],
+ \ 'twig': ['file.twig'],
+ \ 'typescript': ['file.mts', 'file.cts'],
+ \ 'typescript.glimmer': ['file.gts'],
+ \ 'typescriptreact': ['file.tsx'],
+ \ 'ungrammar': ['file.ungram'],
+ \ 'uc': ['file.uc'],
+ \ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'],
+ \ 'udevperm': ['/etc/udev/permissions.d/file.permissions', 'any/etc/udev/permissions.d/file.permissions'],
+ \ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'],
+ \ 'uil': ['file.uit', 'file.uil'],
+ \ 'updatedb': ['/etc/updatedb.conf', 'any/etc/updatedb.conf'],
+ \ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override', 'any/.config/upstart/file.conf', 'any/.config/upstart/file.override', 'any/.init/file.conf', 'any/.init/file.override', 'any/etc/init/file.conf', 'any/etc/init/file.override', 'any/usr/share/upstart/file.conf', 'any/usr/share/upstart/file.override'],
+ \ 'upstreamdat': ['upstream.dat', 'UPSTREAM.DAT', 'upstream.file.dat', 'UPSTREAM.FILE.DAT', 'file.upstream.dat', 'FILE.UPSTREAM.DAT'],
+ \ 'upstreaminstalllog': ['upstreaminstall.log', 'UPSTREAMINSTALL.LOG', 'upstreaminstall.file.log', 'UPSTREAMINSTALL.FILE.LOG', 'file.upstreaminstall.log', 'FILE.UPSTREAMINSTALL.LOG'],
+ \ 'upstreamlog': ['fdrupstream.log', 'upstream.log', 'UPSTREAM.LOG', 'upstream.file.log', 'UPSTREAM.FILE.LOG', 'file.upstream.log', 'FILE.UPSTREAM.LOG', 'UPSTREAM-file.log', 'UPSTREAM-FILE.LOG'],
+ \ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'],
+ \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'],
+ \ 'vala': ['file.vala'],
+ \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'],
+ \ 'vdf': ['file.vdf'],
+ \ 'vdmpp': ['file.vpp', 'file.vdmpp'],
+ \ 'vdmrt': ['file.vdmrt'],
+ \ 'vdmsl': ['file.vdm', 'file.vdmsl'],
+ \ 'vera': ['file.vr', 'file.vri', 'file.vrh'],
+ \ 'verilog': ['file.v'],
+ \ 'verilogams': ['file.va', 'file.vams'],
+ \ 'vgrindefs': ['vgrindefs'],
+ \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho', 'some.vhdl_1', 'some.vhdl_1-file'],
+ \ 'vhs': ['file.tape'],
+ \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'],
+ \ 'viminfo': ['.viminfo', '_viminfo'],
+ \ 'vmasm': ['file.mar'],
+ \ 'voscm': ['file.cm'],
+ \ 'vrml': ['file.wrl'],
+ \ 'vroom': ['file.vroom'],
+ \ 'vue': ['file.vue'],
+ \ 'wast': ['file.wast', 'file.wat'],
+ \ 'wdl': ['file.wdl'],
+ \ 'webmacro': ['file.wm'],
+ \ 'wget': ['.wgetrc', 'wgetrc'],
+ \ 'wget2': ['.wget2rc', 'wget2rc'],
+ \ 'winbatch': ['file.wbt'],
+ \ 'wml': ['file.wml'],
+ \ 'wsh': ['file.wsf', 'file.wsc'],
+ \ 'wsml': ['file.wsml'],
+ \ 'wvdial': ['wvdial.conf', '.wvdialrc'],
+ \ 'xdefaults': ['.Xdefaults', '.Xpdefaults', '.Xresources', 'xdm-config', 'file.ad', '/Xresources/file', '/app-defaults/file', 'Xresources', 'Xresources-file', 'any/Xresources/file', 'any/app-defaults/file'],
+ \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'],
+ \ 'xhtml': ['file.xhtml', 'file.xht'],
+ \ 'xinetd': ['/etc/xinetd.conf', '/etc/xinetd.d/file', 'any/etc/xinetd.conf', 'any/etc/xinetd.d/file'],
+ \ 'xmath': ['file.msc', 'file.msf'],
+ \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd'],
+ \ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'],
+ \ 'xpm': ['file.xpm'],
+ \ 'xpm2': ['file.xpm2'],
+ \ 'xquery': ['file.xq', 'file.xql', 'file.xqm', 'file.xquery', 'file.xqy'],
+ \ 'xs': ['file.xs'],
+ \ 'xsd': ['file.xsd'],
+ \ 'xslt': ['file.xsl', 'file.xslt'],
+ \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'],
+ \ 'yaml': ['file.yaml', 'file.yml', '.clangd', '.clang-format', '.clang-tidy'],
+ \ 'yang': ['file.yang'],
+ \ 'yuck': ['file.yuck'],
+ \ 'z8a': ['file.z8a'],
+ \ 'zig': ['file.zig'],
+ \ 'zimbu': ['file.zu'],
+ \ 'zimbutempl': ['file.zut'],
+ \ 'zir': ['file.zir'],
+ \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'],
+ \
+ \ 'help': [$VIMRUNTIME . '/doc/help.txt'],
+ \ }
+
+let s:filename_case_checks = {
+ \ 'modula2': ['file.DEF'],
+ \ 'bzl': ['file.BUILD', 'BUILD'],
+ \ }
+
+func CheckItems(checks)
+ set noswapfile
+ for [ft, names] in items(a:checks)
+ for i in range(0, len(names) - 1)
+ new
+ try
+ exe 'edit ' . fnameescape(names[i])
+ catch
+ call assert_report('cannot edit "' . names[i] . '": ' . v:exception)
+ endtry
+ if &filetype == '' && &readonly
+ " File exists but not able to edit it (permission denied)
+ else
+ let expected = ft == 'none' ? '' : ft
+ call assert_equal(expected, &filetype, 'with file name: ' . names[i])
+ endif
+ bwipe!
+ endfor
+ endfor
+ set swapfile&
+endfunc
+
+func Test_filetype_detection()
+ filetype on
+ call CheckItems(s:filename_checks)
+ if has('fname_case')
+ call CheckItems(s:filename_case_checks)
+ endif
+ filetype off
+endfunc
+
+" Filetypes detected from the file contents by scripts.vim
+let s:script_checks = {
+ \ 'virata': [['% Virata'],
+ \ ['', '% Virata'],
+ \ ['', '', '% Virata'],
+ \ ['', '', '', '% Virata'],
+ \ ['', '', '', '', '% Virata']],
+ \ 'strace': [['execve("/usr/bin/pstree", ["pstree"], 0x7ff0 /* 63 vars */) = 0'],
+ \ ['15:17:47 execve("/usr/bin/pstree", ["pstree"], ... "_=/usr/bin/strace"]) = 0'],
+ \ ['__libc_start_main and something']],
+ \ 'clojure': [['#!/path/clojure']],
+ \ 'scala': [['#!/path/scala']],
+ \ 'sh': [['#!/path/sh'],
+ \ ['#!/path/bash'],
+ \ ['#!/path/bash2'],
+ \ ['#!/path/dash'],
+ \ ['#!/path/ksh'],
+ \ ['#!/path/ksh93']],
+ \ 'csh': [['#!/path/csh']],
+ \ 'tcsh': [['#!/path/tcsh']],
+ \ 'zsh': [['#!/path/zsh']],
+ \ 'tcl': [['#!/path/tclsh'],
+ \ ['#!/path/wish'],
+ \ ['#!/path/expectk'],
+ \ ['#!/path/itclsh'],
+ \ ['#!/path/itkwish']],
+ \ 'expect': [['#!/path/expect']],
+ \ 'gnuplot': [['#!/path/gnuplot']],
+ \ 'make': [['#!/path/make']],
+ \ 'pike': [['#!/path/pike'],
+ \ ['#!/path/pike0'],
+ \ ['#!/path/pike9']],
+ \ 'lua': [['#!/path/lua']],
+ \ 'raku': [['#!/path/raku']],
+ \ 'perl': [['#!/path/perl']],
+ \ 'php': [['#!/path/php']],
+ \ 'python': [['#!/path/python'],
+ \ ['#!/path/python2'],
+ \ ['#!/path/python3']],
+ \ 'groovy': [['#!/path/groovy']],
+ \ 'ruby': [['#!/path/ruby']],
+ \ 'javascript': [['#!/path/node'],
+ \ ['#!/path/js'],
+ \ ['#!/path/nodejs'],
+ \ ['#!/path/rhino']],
+ \ 'bc': [['#!/path/bc']],
+ \ 'sed': [['#!/path/sed'], ['#n'], ['#n comment']],
+ \ 'ocaml': [['#!/path/ocaml']],
+ \ 'awk': [['#!/path/awk'],
+ \ ['#!/path/gawk']],
+ \ 'wml': [['#!/path/wml']],
+ \ 'scheme': [['#!/path/scheme'],
+ \ ['#!/path/guile']],
+ \ 'cfengine': [['#!/path/cfengine']],
+ \ 'erlang': [['#!/path/escript']],
+ \ 'haskell': [['#!/path/haskell']],
+ \ 'cpp': [['// Standard iostream objects -*- C++ -*-'],
+ \ ['// -*- C++ -*-']],
+ \ 'yaml': [['%YAML 1.2']],
+ \ 'pascal': [['#!/path/instantfpc']],
+ \ 'fennel': [['#!/path/fennel']],
+ \ 'routeros': [['#!/path/rsc']],
+ \ 'fish': [['#!/path/fish']],
+ \ 'forth': [['#!/path/gforth']],
+ \ 'icon': [['#!/path/icon']],
+ \ }
+
+" Various forms of "env" optional arguments.
+let s:script_env_checks = {
+ \ 'perl': [['#!/usr/bin/env VAR=val perl']],
+ \ 'scala': [['#!/usr/bin/env VAR=val VVAR=vval scala']],
+ \ 'awk': [['#!/usr/bin/env VAR=val -i awk']],
+ \ 'scheme': [['#!/usr/bin/env VAR=val --ignore-environment scheme']],
+ \ 'python': [['#!/usr/bin/env VAR=val -S python -w -T']],
+ \ 'wml': [['#!/usr/bin/env VAR=val --split-string wml']],
+ \ }
+
+func Run_script_detection(test_dict)
+ filetype on
+ for [ft, files] in items(a:test_dict)
+ for file in files
+ call writefile(file, 'Xtest', 'D')
+ split Xtest
+ call assert_equal(ft, &filetype, 'for text: ' . string(file))
+ bwipe!
+ endfor
+ endfor
+ filetype off
+endfunc
+
+func Test_script_detection()
+ call Run_script_detection(s:script_checks)
+ call Run_script_detection(s:script_env_checks)
+endfunc
+
+func Test_setfiletype_completion()
+ call feedkeys(":setfiletype java\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"setfiletype java javacc javascript javascriptreact', @:)
+endfunc
+
+" Test for ':filetype detect' command for a buffer without a file
+func Test_emptybuf_ftdetect()
+ new
+ call setline(1, '#!/bin/sh')
+ call assert_equal('', &filetype)
+ filetype detect
+ call assert_equal('sh', &filetype)
+ close!
+endfunc
+
+" Test for ':filetype indent on' and ':filetype indent off' commands
+func Test_filetype_indent_off()
+ new Xtest.vim
+ filetype indent on
+ call assert_equal(1, g:did_indent_on)
+ call assert_equal(['filetype detection:ON plugin:OFF indent:ON'],
+ \ execute('filetype')->split("\n"))
+ filetype indent off
+ call assert_equal(0, exists('g:did_indent_on'))
+ call assert_equal(['filetype detection:ON plugin:OFF indent:OFF'],
+ \ execute('filetype')->split("\n"))
+ close
+endfunc
+
+"""""""""""""""""""""""""""""""""""""""""""""""""
+" Tests for specific extensions and filetypes.
+" Keep sorted.
+"""""""""""""""""""""""""""""""""""""""""""""""""
+
+func Test_bas_file()
+ filetype on
+
+ call writefile(['looks like BASIC'], 'Xfile.bas', 'D')
+ split Xfile.bas
+ call assert_equal('basic', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTbas()
+
+ let g:filetype_bas = 'freebasic'
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
+ unlet g:filetype_bas
+
+ " FreeBASIC
+
+ call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
+
+ call writefile(['#define TESTING'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
+
+ call writefile(['option byval'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
+
+ call writefile(['extern "C"'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
+
+ " QB64
+
+ call writefile(['$LET TESTING = 1'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
+ bwipe!
+
+ call writefile(['OPTION _EXPLICIT'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
+ bwipe!
+
+ " Visual Basic
+
+ call writefile(['Attribute VB_NAME = "Testing"', 'Enum Foo', 'End Enum'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+" Test dist#ft#FTcfg()
+func Test_cfg_file()
+ filetype on
+
+ " *.cfg defaults to cfg
+ call writefile(['looks like cfg'], 'cfgfile.cfg', 'D')
+ split cfgfile.cfg
+ call assert_equal('cfg', &filetype)
+
+ let g:filetype_cfg = 'other'
+ edit
+ call assert_equal('other', &filetype)
+ bwipe!
+ unlet g:filetype_cfg
+
+ " RAPID cfg
+ let ext = 'cfg'
+ for i in ['EIO', 'MMC', 'MOC', 'PROC', 'SIO', 'SYS']
+ call writefile([i .. ':CFG'], 'cfgfile.' .. ext)
+ execute "split cfgfile." .. ext
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('cfgfile.' .. ext)
+ " check different case of file extension
+ let ext = substitute(ext, '\(\l\)', '\u\1', '')
+ endfor
+
+ " clean up
+ filetype off
+endfunc
+
+func Test_d_file()
+ filetype on
+
+ call writefile(['looks like D'], 'Xfile.d', 'D')
+ split Xfile.d
+ call assert_equal('d', &filetype)
+ bwipe!
+
+ call writefile(['#!/some/bin/dtrace'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('dtrace', &filetype)
+ bwipe!
+
+ call writefile(['#pragma D option'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('dtrace', &filetype)
+ bwipe!
+
+ call writefile([':some:thing:'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('dtrace', &filetype)
+ bwipe!
+
+ call writefile(['module this', '#pragma D option'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('d', &filetype)
+ bwipe!
+
+ call writefile(['import that', '#pragma D option'], 'Xfile.d')
+ split Xfile.d
+ call assert_equal('d', &filetype)
+ bwipe!
+
+ " clean up
+ filetype off
+endfunc
+
+func Test_dat_file()
+ filetype on
+
+ " KRL header start with "&WORD", but is not always present.
+ call writefile(['&ACCESS'], 'datfile.dat')
+ split datfile.dat
+ call assert_equal('krl', &filetype)
+ bwipe!
+ call delete('datfile.dat')
+
+ " KRL defdat with leading spaces, for KRL file extension is not case
+ " sensitive.
+ call writefile([' DEFDAT datfile'], 'datfile.Dat')
+ split datfile.Dat
+ call assert_equal('krl', &filetype)
+ bwipe!
+ call delete('datfile.Dat')
+
+ " KRL defdat with embedded spaces, file starts with empty line(s).
+ call writefile(['', 'defdat datfile public'], 'datfile.DAT')
+ split datfile.DAT
+ call assert_equal('krl', &filetype)
+ bwipe!
+
+ " User may overrule file inspection
+ let g:filetype_dat = 'dat'
+ split datfile.DAT
+ call assert_equal('dat', &filetype)
+ bwipe!
+ call delete('datfile.DAT')
+ unlet g:filetype_dat
+
+ filetype off
+endfunc
+
+func Test_dep3patch_file()
+ filetype on
+
+ call assert_true(mkdir('debian/patches', 'pR'))
+
+ " series files are not patches
+ call writefile(['Description: some awesome patch'], 'debian/patches/series')
+ split debian/patches/series
+ call assert_notequal('dep3patch', &filetype)
+ bwipe!
+
+ " diff/patch files without the right headers should still show up as ft=diff
+ call writefile([], 'debian/patches/foo.diff')
+ split debian/patches/foo.diff
+ call assert_equal('diff', &filetype)
+ bwipe!
+
+ " Files with the right headers are detected as dep3patch, even if they don't
+ " have a diff/patch extension
+ call writefile(['Subject: dep3patches'], 'debian/patches/bar')
+ split debian/patches/bar
+ call assert_equal('dep3patch', &filetype)
+ bwipe!
+
+ " Files in sub-directories are detected
+ call assert_true(mkdir('debian/patches/s390x', 'p'))
+ call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
+ split debian/patches/s390x/bar
+ call assert_equal('dep3patch', &filetype)
+ bwipe!
+
+ " The detection stops when seeing the "header end" marker
+ call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
+ split debian/patches/baz
+ call assert_notequal('dep3patch', &filetype)
+ bwipe!
+endfunc
+
+func Test_dsl_file()
+ filetype on
+
+ call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl', 'D')
+ split dslfile.dsl
+ call assert_equal('dsl', &filetype)
+ bwipe!
+
+ call writefile(['workspace {'], 'dslfile.dsl')
+ split dslfile.dsl
+ call assert_equal('structurizr', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_ex_file()
+ filetype on
+
+ call writefile(['arbitrary content'], 'Xfile.ex', 'D')
+ split Xfile.ex
+ call assert_equal('elixir', &filetype)
+ bwipe!
+ let g:filetype_euphoria = 'euphoria4'
+ split Xfile.ex
+ call assert_equal('euphoria4', &filetype)
+ bwipe!
+ unlet g:filetype_euphoria
+
+ call writefile(['-- filetype euphoria comment'], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ call writefile(['--filetype euphoria comment'], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ call writefile(['ifdef '], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ call writefile(['include '], 'Xfile.ex')
+ split Xfile.ex
+ call assert_equal('euphoria3', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_foam_file()
+ filetype on
+ call assert_true(mkdir('0', 'pR'))
+ call assert_true(mkdir('0.orig', 'pR'))
+
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict', 'D')
+ split Xfile1Dict
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something', 'D')
+ split Xfile1Dict.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties', 'D')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something', 'D')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0/Xfile')
+ split 0/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile')
+ split 0.orig/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_frm_file()
+ filetype on
+
+ call writefile(['looks like FORM'], 'Xfile.frm', 'D')
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTfrm()
+
+ let g:filetype_frm = 'form'
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+ unlet g:filetype_frm
+
+ " Visual Basic
+
+ call writefile(['Begin VB.Form Form1'], 'Xfile.frm')
+ split Xfile.frm
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_fs_file()
+ filetype on
+
+ call writefile(['looks like F#'], 'Xfile.fs', 'D')
+ split Xfile.fs
+ call assert_equal('fsharp', &filetype)
+ bwipe!
+
+ let g:filetype_fs = 'forth'
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+ unlet g:filetype_fs
+
+ " Test dist#ft#FTfs()
+
+ " Forth (Gforth)
+
+ call writefile(['( Forth inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\ Forth line comment'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ " empty line comment - no space required
+ call writefile(['\'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\G Forth documentation comment '], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_git_file()
+ filetype on
+
+ call assert_true(mkdir('Xrepo.git', 'pR'))
+
+ call writefile([], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_hook_file()
+ filetype on
+
+ call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook', 'D')
+ split Xfile.hook
+ call assert_equal('conf', &filetype)
+ bwipe!
+
+ call writefile(['not pacman'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_notequal('conf', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_m_file()
+ filetype on
+
+ call writefile(['looks like Matlab'], 'Xfile.m', 'D')
+ split Xfile.m
+ call assert_equal('matlab', &filetype)
+ bwipe!
+
+ let g:filetype_m = 'octave'
+ split Xfile.m
+ call assert_equal('octave', &filetype)
+ bwipe!
+ unlet g:filetype_m
+
+ " Test dist#ft#FTm()
+
+ " Objective-C
+
+ call writefile(['// Objective-C line comment'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('objc', &filetype)
+ bwipe!
+
+ call writefile(['/* Objective-C block comment */'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('objc', &filetype)
+ bwipe!
+
+ call writefile(['#import "test.m"'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('objc', &filetype)
+ bwipe!
+
+ call writefile(['#include <header.h>'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('objc', &filetype)
+ bwipe!
+
+ call writefile(['#define FORTY_TWO'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('objc', &filetype)
+ bwipe!
+
+ " Octave
+
+ call writefile(['# Octave line comment'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('octave', &filetype)
+ bwipe!
+
+ call writefile(['%!test "Octave test"'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('octave', &filetype)
+ bwipe!
+
+ call writefile(['unwind_protect'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('octave', &filetype)
+ bwipe!
+
+ call writefile(['try; 42; end_try_catch'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('octave', &filetype)
+ bwipe!
+
+ " Mathematica
+
+ call writefile(['(* Mathematica comment'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('mma', &filetype)
+ bwipe!
+
+ " MATLAB
+
+ call writefile(['% MATLAB line comment'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('matlab', &filetype)
+ bwipe!
+
+ " Murphi
+
+ call writefile(['-- Murphi comment'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('murphi', &filetype)
+ bwipe!
+
+ call writefile(['/* Murphi block comment */', 'Type'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('murphi', &filetype)
+ bwipe!
+
+ call writefile(['Type'], 'Xfile.m')
+ split Xfile.m
+ call assert_equal('murphi', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_mod_file()
+ filetype on
+
+ " *.mod defaults to Modsim III
+ call writefile(['locks like Modsim III'], 'modfile.mod')
+ split modfile.mod
+ call assert_equal('modsim3', &filetype)
+ bwipe!
+
+ " Users preference set by g:filetype_mod
+ let g:filetype_mod = 'lprolog'
+ split modfile.mod
+ call assert_equal('lprolog', &filetype)
+ unlet g:filetype_mod
+ bwipe!
+
+ " RAPID header start with a line containing only "%%%",
+ " but is not always present.
+ call writefile(['%%%'], 'modfile.mod')
+ split modfile.mod
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('modfile.mod')
+
+ " RAPID supports umlauts in module names, leading spaces,
+ " the .mod extension is not case sensitive.
+ call writefile([' module ÃœmlautModule'], 'modfile.Mod')
+ split modfile.Mod
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('modfile.Mod')
+
+ " RAPID is not case sensitive, embedded spaces, sysmodule,
+ " file starts with empty line(s).
+ call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'modfile.MOD')
+ split modfile.MOD
+ call assert_equal('rapid', &filetype)
+ bwipe!
+
+ " Modula-2 MODULE not start of line
+ call writefile(['IMPLEMENTATION MODULE Module2Mod;'], 'modfile.MOD')
+ split modfile.MOD
+ call assert_equal('modula2', &filetype)
+ bwipe!
+
+ " Modula-2 with comment and empty lines prior MODULE
+ call writefile(['', '(* with', ' comment *)', '', 'MODULE Module2Mod;'], 'modfile.MOD')
+ split modfile.MOD
+ call assert_equal('modula2', &filetype)
+ bwipe!
+ call delete('modfile.MOD')
+
+ " LambdaProlog module
+ call writefile(['module lpromod.'], 'modfile.mod')
+ split modfile.mod
+ call assert_equal('lprolog', &filetype)
+ bwipe!
+
+ " LambdaProlog with comment and empty lines prior module
+ call writefile(['', '% with', '% comment', '', 'module lpromod.'], 'modfile.mod')
+ split modfile.mod
+ call assert_equal('lprolog', &filetype)
+ bwipe!
+ call delete('modfile.mod')
+
+ " go.mod
+ call writefile(['module example.com/M'], 'go.mod')
+ split go.mod
+ call assert_equal('gomod', &filetype)
+ bwipe!
+ call delete('go.mod')
+
+ filetype off
+endfunc
+
+func Test_patch_file()
+ filetype on
+
+ call writefile([], 'Xfile.patch', 'D')
+ split Xfile.patch
+ call assert_equal('diff', &filetype)
+ bwipe!
+
+ call writefile(['From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('gitsendemail', &filetype)
+ bwipe!
+
+ call writefile(['From 0000000000000000000000000000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch')
+ split Xfile.patch
+ call assert_equal('gitsendemail', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_perl_file()
+ filetype on
+
+ " only tests one case, should do more
+ let lines =<< trim END
+
+ use a
+ END
+ call writefile(lines, "Xfile.t", 'D')
+ split Xfile.t
+ call assert_equal('perl', &filetype)
+ bwipe
+
+ filetype off
+endfunc
+
+func Test_pp_file()
+ filetype on
+
+ call writefile(['looks like puppet'], 'Xfile.pp', 'D')
+ split Xfile.pp
+ call assert_equal('puppet', &filetype)
+ bwipe!
+
+ let g:filetype_pp = 'pascal'
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
+ bwipe!
+ unlet g:filetype_pp
+
+ " Test dist#ft#FTpp()
+ call writefile(['{ pascal comment'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
+ bwipe!
+
+ call writefile(['procedure pascal'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+" Test dist#ft#FTprg()
+func Test_prg_file()
+ filetype on
+
+ " *.prg defaults to clipper
+ call writefile(['looks like clipper'], 'prgfile.prg')
+ split prgfile.prg
+ call assert_equal('clipper', &filetype)
+ bwipe!
+
+ " Users preference set by g:filetype_prg
+ let g:filetype_prg = 'eviews'
+ split prgfile.prg
+ call assert_equal('eviews', &filetype)
+ unlet g:filetype_prg
+ bwipe!
+
+ " RAPID header start with a line containing only "%%%",
+ " but is not always present.
+ call writefile(['%%%'], 'prgfile.prg')
+ split prgfile.prg
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('prgfile.prg')
+
+ " RAPID supports umlauts in module names, leading spaces,
+ " the .prg extension is not case sensitive.
+ call writefile([' module ÃœmlautModule'], 'prgfile.Prg')
+ split prgfile.Prg
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('prgfile.Prg')
+
+ " RAPID is not case sensitive, embedded spaces, sysmodule,
+ " file starts with empty line(s).
+ call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'prgfile.PRG')
+ split prgfile.PRG
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('prgfile.PRG')
+
+ filetype off
+endfunc
+
+" Test dist#ft#FTsc()
+func Test_sc_file()
+ filetype on
+
+ " SC classes are defined with '+ Class {}'
+ call writefile(['+ SCNvim {', '*methodArgs {|method|'], 'srcfile.sc')
+ split srcfile.sc
+ call assert_equal('supercollider', &filetype)
+ bwipe!
+ call delete('srcfile.sc')
+
+ " Some SC class files start with comment and define methods many lines later
+ call writefile(['// Query', '//Method','^this {'], 'srcfile.sc')
+ split srcfile.sc
+ call assert_equal('supercollider', &filetype)
+ bwipe!
+ call delete('srcfile.sc')
+
+ " Some SC class files put comments between method declaration after class
+ call writefile(['PingPong {', '//comment','*ar { arg'], 'srcfile.sc')
+ split srcfile.sc
+ call assert_equal('supercollider', &filetype)
+ bwipe!
+ call delete('srcfile.sc')
+
+ filetype off
+endfunc
+
+" Test dist#ft#FTscd()
+func Test_scd_file()
+ filetype on
+
+ call writefile(['ijq(1)'], 'srcfile.scd', 'D')
+ split srcfile.scd
+ call assert_equal('scdoc', &filetype)
+
+ bwipe!
+ filetype off
+endfunc
+
+func Test_src_file()
+ filetype on
+
+ " KRL header start with "&WORD", but is not always present.
+ call writefile(['&ACCESS'], 'srcfile.src')
+ split srcfile.src
+ call assert_equal('krl', &filetype)
+ bwipe!
+ call delete('srcfile.src')
+
+ " KRL def with leading spaces, for KRL file extension is not case sensitive.
+ call writefile([' DEF srcfile()'], 'srcfile.Src')
+ split srcfile.Src
+ call assert_equal('krl', &filetype)
+ bwipe!
+ call delete('srcfile.Src')
+
+ " KRL global deffct with embedded spaces, file starts with empty line(s).
+ for text in ['global def srcfile()', 'global deffct srcfile()']
+ call writefile(['', text], 'srcfile.SRC')
+ split srcfile.SRC
+ call assert_equal('krl', &filetype, text)
+ bwipe!
+ endfor
+
+ " User may overrule file inspection
+ let g:filetype_src = 'src'
+ split srcfile.SRC
+ call assert_equal('src', &filetype)
+ bwipe!
+ call delete('srcfile.SRC')
+ unlet g:filetype_src
+
+ filetype off
+endfunc
+
+func Test_sys_file()
+ filetype on
+
+ " *.sys defaults to Batch file for MSDOS
+ call writefile(['looks like dos batch'], 'sysfile.sys')
+ split sysfile.sys
+ call assert_equal('bat', &filetype)
+ bwipe!
+
+ " Users preference set by g:filetype_sys
+ let g:filetype_sys = 'sys'
+ split sysfile.sys
+ call assert_equal('sys', &filetype)
+ unlet g:filetype_sys
+ bwipe!
+
+ " RAPID header start with a line containing only "%%%",
+ " but is not always present.
+ call writefile(['%%%'], 'sysfile.sys')
+ split sysfile.sys
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('sysfile.sys')
+
+ " RAPID supports umlauts in module names, leading spaces,
+ " the .sys extension is not case sensitive.
+ call writefile([' module ÃœmlautModule'], 'sysfile.Sys')
+ split sysfile.Sys
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('sysfile.Sys')
+
+ " RAPID is not case sensitive, embedded spaces, sysmodule,
+ " file starts with empty line(s).
+ call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'sysfile.SYS')
+ split sysfile.SYS
+ call assert_equal('rapid', &filetype)
+ bwipe!
+ call delete('sysfile.SYS')
+
+ filetype off
+endfunc
+
+func Test_tex_file()
+ filetype on
+
+ call writefile(['%& pdflatex'], 'Xfile.tex')
+ split Xfile.tex
+ call assert_equal('tex', &filetype)
+ bwipe
+
+ call writefile(['\newcommand{\test}{some text}'], 'Xfile.tex')
+ split Xfile.tex
+ call assert_equal('tex', &filetype)
+ bwipe
+
+ " tex_flavor is unset
+ call writefile(['%& plain'], 'Xfile.tex')
+ split Xfile.tex
+ call assert_equal('plaintex', &filetype)
+ bwipe
+
+ let g:tex_flavor = 'plain'
+ call writefile(['just some text'], 'Xfile.tex')
+ split Xfile.tex
+ call assert_equal('plaintex', &filetype)
+ bwipe
+
+ let lines =<< trim END
+ % This is a comment.
+
+ \usemodule[translate]
+ END
+ call writefile(lines, 'Xfile.tex')
+ split Xfile.tex
+ call assert_equal('context', &filetype)
+ bwipe
+
+ let g:tex_flavor = 'context'
+ call writefile(['just some text'], 'Xfile.tex')
+ split Xfile.tex
+ call assert_equal('context', &filetype)
+ bwipe
+ unlet g:tex_flavor
+
+ call delete('Xfile.tex')
+ filetype off
+endfunc
+
+func Test_tf_file()
+ filetype on
+
+ call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf', 'D')
+ split Xfile.tf
+ call assert_equal('tf', &filetype)
+ bwipe!
+
+ call writefile(['provider "azurerm" {'], 'Xfile.tf')
+ split Xfile.tf
+ call assert_equal('terraform', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_ts_file()
+ filetype on
+
+ call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts', 'D')
+ split Xfile.ts
+ call assert_equal('xml', &filetype)
+ bwipe!
+
+ call writefile(['// looks like Typescript'], 'Xfile.ts')
+ split Xfile.ts
+ call assert_equal('typescript', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_ttl_file()
+ filetype on
+
+ call writefile(['@base <http://example.org/> .'], 'Xfile.ttl', 'D')
+ split Xfile.ttl
+ call assert_equal('turtle', &filetype)
+ bwipe!
+
+ call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
+ split Xfile.ttl
+ call assert_equal('teraterm', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_xpm_file()
+ filetype on
+
+ call writefile(['this is XPM2'], 'file.xpm', 'D')
+ split file.xpm
+ call assert_equal('xpm2', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_cls_file()
+ filetype on
+
+ call writefile(['looks like Smalltalk'], 'Xfile.cls', 'D')
+ split Xfile.cls
+ call assert_equal('st', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTcls()
+
+ let g:filetype_cls = 'vb'
+ split Xfile.cls
+ call assert_equal('vb', &filetype)
+ bwipe!
+ unlet g:filetype_cls
+
+ " TeX
+
+ call writefile(['%'], 'Xfile.cls')
+ split Xfile.cls
+ call assert_equal('tex', &filetype)
+ bwipe!
+
+ call writefile(['\NeedsTeXFormat{LaTeX2e}'], 'Xfile.cls')
+ split Xfile.cls
+ call assert_equal('tex', &filetype)
+ bwipe!
+
+ " Rexx
+
+ call writefile(['# rexx'], 'Xfile.cls')
+ split Xfile.cls
+ call assert_equal('rexx', &filetype)
+ bwipe!
+
+ " Visual Basic
+
+ call writefile(['VERSION 1.0 CLASS'], 'Xfile.cls')
+ split Xfile.cls
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_sig_file()
+ filetype on
+
+ call writefile(['this is neither Lambda Prolog nor SML'], 'Xfile.sig', 'D')
+ split Xfile.sig
+ call assert_equal('', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTsig()
+
+ let g:filetype_sig = 'sml'
+ split Xfile.sig
+ call assert_equal('sml', &filetype)
+ bwipe!
+ unlet g:filetype_sig
+
+ " Lambda Prolog
+
+ call writefile(['sig foo.'], 'Xfile.sig')
+ split Xfile.sig
+ call assert_equal('lprolog', &filetype)
+ bwipe!
+
+ call writefile(['/* ... */'], 'Xfile.sig')
+ split Xfile.sig
+ call assert_equal('lprolog', &filetype)
+ bwipe!
+
+ call writefile(['% ...'], 'Xfile.sig')
+ split Xfile.sig
+ call assert_equal('lprolog', &filetype)
+ bwipe!
+
+ " SML signature file
+
+ call writefile(['signature FOO ='], 'Xfile.sig')
+ split Xfile.sig
+ call assert_equal('sml', &filetype)
+ bwipe!
+
+ call writefile(['structure FOO ='], 'Xfile.sig')
+ split Xfile.sig
+ call assert_equal('sml', &filetype)
+ bwipe!
+
+ call writefile(['(* ... *)'], 'Xfile.sig')
+ split Xfile.sig
+ call assert_equal('sml', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+" Test dist#ft#FTsil()
+func Test_sil_file()
+ filetype on
+
+ split Xfile.sil
+ call assert_equal('sil', &filetype)
+ bwipe!
+
+ let lines =<< trim END
+ // valid
+ let protoErasedPathA = \ABCProtocol.a
+
+ // also valid
+ let protoErasedPathA =
+ \ABCProtocol.a
+ END
+ call writefile(lines, 'Xfile.sil', 'D')
+
+ split Xfile.sil
+ call assert_equal('sil', &filetype)
+ bwipe!
+
+ " SILE
+
+ call writefile(['% some comment'], 'Xfile.sil')
+ split Xfile.sil
+ call assert_equal('sile', &filetype)
+ bwipe!
+
+ call writefile(['\begin[papersize=a6]{document}foo\end{document}'], 'Xfile.sil')
+ split Xfile.sil
+ call assert_equal('sile', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_inc_file()
+ filetype on
+
+ call writefile(['this is the fallback'], 'Xfile.inc', 'D')
+ split Xfile.inc
+ call assert_equal('pov', &filetype)
+ bwipe!
+
+ let g:filetype_inc = 'foo'
+ split Xfile.inc
+ call assert_equal('foo', &filetype)
+ bwipe!
+ unlet g:filetype_inc
+
+ " aspperl
+ call writefile(['perlscript'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('aspperl', &filetype)
+ bwipe!
+
+ " aspvbs
+ call writefile(['<% something'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('aspvbs', &filetype)
+ bwipe!
+
+ " php
+ call writefile(['<?php'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('php', &filetype)
+ bwipe!
+
+ " pascal
+ call writefile(['program'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('pascal', &filetype)
+ bwipe!
+
+ " bitbake
+ call writefile(['require foo'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('bitbake', &filetype)
+ bwipe!
+
+ call writefile(['S = "${WORKDIR}"'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('bitbake', &filetype)
+ bwipe!
+
+ call writefile(['DEPENDS:append = " somedep"'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('bitbake', &filetype)
+ bwipe!
+
+ call writefile(['MACHINE ??= "qemu"'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('bitbake', &filetype)
+ bwipe!
+
+ call writefile(['PROVIDES := "test"'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('bitbake', &filetype)
+ bwipe!
+
+ call writefile(['RDEPENDS_${PN} += "bar"'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('bitbake', &filetype)
+ bwipe!
+
+ " asm
+ call writefile(['asmsyntax=foo'], 'Xfile.inc')
+ split Xfile.inc
+ call assert_equal('foo', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_lsl_file()
+ filetype on
+
+ call writefile(['looks like Linden Scripting Language'], 'Xfile.lsl', 'D')
+ split Xfile.lsl
+ call assert_equal('lsl', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTlsl()
+
+ let g:filetype_lsl = 'larch'
+ split Xfile.lsl
+ call assert_equal('larch', &filetype)
+ bwipe!
+ unlet g:filetype_lsl
+
+ " Larch Shared Language
+
+ call writefile(['% larch comment'], 'Xfile.lsl')
+ split Xfile.lsl
+ call assert_equal('larch', &filetype)
+ bwipe!
+
+ call writefile(['foo: trait'], 'Xfile.lsl')
+ split Xfile.lsl
+ call assert_equal('larch', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_filter_cmd.vim b/src/testdir/test_filter_cmd.vim
new file mode 100644
index 0000000..7c84a13
--- /dev/null
+++ b/src/testdir/test_filter_cmd.vim
@@ -0,0 +1,199 @@
+" Test the :filter command modifier
+
+source check.vim
+
+func Test_filter()
+ edit Xdoesnotmatch
+ edit Xwillmatch
+ call assert_equal('"Xwillmatch"', substitute(execute('filter willma ls'), '[^"]*\(".*"\)[^"]*', '\1', ''))
+ bwipe Xdoesnotmatch
+ bwipe Xwillmatch
+
+ new
+ call setline(1, ['foo1', 'foo2', 'foo3', 'foo4', 'foo5'])
+ call assert_equal("\nfoo2\nfoo4", execute('filter /foo[24]/ 1,$print'))
+ call assert_equal("\n 2 foo2\n 4 foo4", execute('filter /foo[24]/ 1,$number'))
+ call assert_equal("\nfoo2$\nfoo4$", execute('filter /foo[24]/ 1,$list'))
+
+ call assert_equal("\nfoo1$\nfoo3$\nfoo5$", execute('filter! /foo[24]/ 1,$list'))
+ bwipe!
+
+ command XTryThis echo 'this'
+ command XTryThat echo 'that'
+ command XDoThat echo 'that'
+ let lines = split(execute('filter XTry command'), "\n")
+ call assert_equal(3, len(lines))
+ call assert_match("XTryThat", lines[1])
+ call assert_match("XTryThis", lines[2])
+ delcommand XTryThis
+ delcommand XTryThat
+ delcommand XDoThat
+
+ map f1 the first key
+ map f2 the second key
+ map f3 not a key
+ let lines = split(execute('filter the map f'), "\n")
+ call assert_equal(2, len(lines))
+ call assert_match("f2", lines[0])
+ call assert_match("f1", lines[1])
+ unmap f1
+ unmap f2
+ unmap f3
+endfunc
+
+func Test_filter_fails()
+ call assert_fails('filter', 'E471:')
+ call assert_fails('filter pat', 'E476:')
+ call assert_fails('filter /pat', 'E476:')
+ call assert_fails('filter /pat/', 'E476:')
+ call assert_fails('filter /pat/ asdf', 'E492:')
+ " Using assert_fails() causes E476 instead of E866. So use a try-catch.
+ let caught_e866 = 0
+ try
+ filter /\@>b/ ls
+ catch /E866:/
+ let caught_e866 = 1
+ endtry
+ call assert_equal(1, caught_e866)
+
+ call assert_fails('filter!', 'E471:')
+ call assert_fails('filter! pat', 'E476:')
+ call assert_fails('filter! /pat', 'E476:')
+ call assert_fails('filter! /pat/', 'E476:')
+ call assert_fails('filter! /pat/ asdf', 'E492:')
+endfunc
+
+function s:complete_filter_cmd(filtcmd)
+ let keystroke = "\<TAB>\<C-R>=execute('let cmdline = getcmdline()')\<CR>\<C-C>"
+ let cmdline = ''
+ call feedkeys(':' . a:filtcmd . keystroke, 'ntx')
+ return cmdline
+endfunction
+
+func Test_filter_cmd_completion()
+ " Do not complete pattern
+ call assert_equal("filter \t", s:complete_filter_cmd('filter '))
+ call assert_equal("filter pat\t", s:complete_filter_cmd('filter pat'))
+ call assert_equal("filter /pat\t", s:complete_filter_cmd('filter /pat'))
+ call assert_equal("filter /pat/\t", s:complete_filter_cmd('filter /pat/'))
+
+ " Complete after string pattern
+ call assert_equal('filter pat print', s:complete_filter_cmd('filter pat pri'))
+
+ " Complete after regexp pattern
+ call assert_equal('filter /pat/ print', s:complete_filter_cmd('filter /pat/ pri'))
+ call assert_equal('filter #pat# print', s:complete_filter_cmd('filter #pat# pri'))
+endfunc
+
+func Test_filter_cmd_with_filter()
+ new
+ set shelltemp
+ %!echo "a|b"
+ let out = getline(1)
+ bw!
+ if has('win32')
+ let out = trim(out, '" ')
+ endif
+ call assert_equal('a|b', out)
+ set shelltemp&
+endfunction
+
+func Test_filter_commands()
+ CheckFeature quickfix
+
+ let g:test_filter_a = 1
+ let b:test_filter_b = 2
+ let test_filter_c = 3
+
+ " Test filtering :let command
+ let res = split(execute("filter /^test_filter/ let"), "\n")
+ call assert_equal(["test_filter_a #1"], res)
+
+ let res = split(execute("filter /\\v^(b:)?test_filter/ let"), "\n")
+ call assert_equal(["test_filter_a #1", "b:test_filter_b #2"], res)
+
+ unlet g:test_filter_a
+ unlet b:test_filter_b
+ unlet test_filter_c
+
+ " Test filtering :set command
+ let helplang=&helplang
+ set helplang=en
+ let res = join(split(execute("filter /^help/ set"), "\n")[1:], " ")
+ call assert_match('^\s*helplang=\w*$', res)
+ let &helplang=helplang
+
+ " Test filtering :llist command
+ call setloclist(0, [{"filename": "/path/vim.c"}, {"filename": "/path/vim.h"}, {"module": "Main.Test"}])
+ let res = split(execute("filter /\\.c$/ llist"), "\n")
+ call assert_equal([" 1 /path/vim.c: "], res)
+
+ let res = split(execute("filter /\\.Test$/ llist"), "\n")
+ call assert_equal([" 3 Main.Test: "], res)
+
+ " Test filtering :jump command
+ e file.c
+ e file.h
+ e file.hs
+ let res = split(execute("filter /\.c$/ jumps"), "\n")[1:]
+ call assert_equal([" 2 1 0 file.c", ">"], res)
+
+ " Test filtering :marks command
+ b file.c
+ mark A
+ b file.h
+ mark B
+ let res = split(execute("filter /\.c$/ marks"), "\n")[1:]
+ call assert_equal([" A 1 0 file.c"], res)
+
+ " Test filtering :highlight command
+ highlight MyHlGroup ctermfg=10
+ let res = split(execute("filter /MyHlGroup/ highlight"), "\n")
+ call assert_equal(["MyHlGroup xxx ctermfg=10"], res)
+
+ call setline(1, ['one', 'two', 'three'])
+ 1mark a
+ 2mark b
+ 3mark c
+ let res = split(execute("filter /two/ marks abc"), "\n")[1:]
+ call assert_equal([" b 2 0 two"], res)
+
+ bwipe! file.c
+ bwipe! file.h
+ bwipe! file.hs
+endfunc
+
+func Test_filter_display()
+ edit Xdoesnotmatch
+ let @a = '!!willmatch'
+ let @b = '!!doesnotmatch'
+ let @c = "oneline\ntwoline\nwillmatch\n"
+ let @/ = '!!doesnotmatch'
+ call feedkeys(":echo '!!doesnotmatch:'\<CR>", 'ntx')
+ let lines = map(split(execute('filter /willmatch/ display'), "\n"), 'v:val[5:6]')
+
+ call assert_true(index(lines, '"a') >= 0)
+ call assert_false(index(lines, '"b') >= 0)
+ call assert_true(index(lines, '"c') >= 0)
+ call assert_false(index(lines, '"/') >= 0)
+ call assert_false(index(lines, '":') >= 0)
+ call assert_false(index(lines, '"%') >= 0)
+
+ let lines = map(split(execute('filter /doesnotmatch/ display'), "\n"), 'v:val[5:6]')
+ call assert_true(index(lines, '"a') < 0)
+ call assert_false(index(lines, '"b') < 0)
+ call assert_true(index(lines, '"c') < 0)
+ call assert_false(index(lines, '"/') < 0)
+ call assert_false(index(lines, '":') < 0)
+ call assert_false(index(lines, '"%') < 0)
+
+ bwipe!
+endfunc
+
+func Test_filter_scriptnames()
+ let lines = split(execute('filter /test_filter_cmd/ scriptnames'), "\n")
+ call assert_equal(1, len(lines))
+ call assert_match('filter_cmd', lines[0])
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_filter_map.vim b/src/testdir/test_filter_map.vim
new file mode 100644
index 0000000..f06059b
--- /dev/null
+++ b/src/testdir/test_filter_map.vim
@@ -0,0 +1,234 @@
+" Test filter() and map()
+
+import './vim9.vim' as v9
+
+" list with expression string
+func Test_filter_map_list_expr_string()
+ " filter()
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
+ call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
+ call assert_equal([], filter([1, 2, 3, 4], 0))
+
+ " map()
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
+ call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
+ call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
+endfunc
+
+" dict with expression string
+func Test_filter_map_dict_expr_string()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
+ call assert_equal({}, filter(copy(dict), 0))
+
+ " map()
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
+ call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
+endfunc
+
+" list with funcref
+func Test_filter_map_list_expr_funcref()
+ " filter()
+ func! s:filter1(index, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
+
+ func! s:filter2(index, val) abort
+ return a:index > 1
+ endfunc
+ call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
+
+ " map()
+ func! s:filter3(index, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
+
+ func! s:filter4(index, val) abort
+ return a:index * 2
+ endfunc
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
+endfunc
+
+func Test_filter_map_nested()
+ let x = {"x":10}
+ let r = map(range(2), 'filter(copy(x), "1")')
+ call assert_equal([x, x], r)
+
+ let r = map(copy(x), 'filter(copy(x), "1")')
+ call assert_equal({"x": x}, r)
+endfunc
+
+" dict with funcref
+func Test_filter_map_dict_expr_funcref()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ func! s:filter1(key, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
+
+ func! s:filter2(key, val) abort
+ return a:key > "bar"
+ endfunc
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
+
+ " map()
+ func! s:filter3(key, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
+
+ func! s:filter4(key, val) abort
+ return a:key[0]
+ endfunc
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
+endfunc
+
+func Test_map_filter_fails()
+ call assert_fails('call map([1], "42 +")', 'E15:')
+ call assert_fails('call filter([1], "42 +")', 'E15:')
+ call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
+ call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
+ call assert_fails("let l = filter([1, 2], {})", 'E731:')
+ call assert_equal(test_null_list(), filter(test_null_list(), 0))
+ call assert_equal(test_null_dict(), filter(test_null_dict(), 0))
+ call assert_equal(test_null_list(), map(test_null_list(), '"> " .. v:val'))
+ call assert_equal(test_null_dict(), map(test_null_dict(), '"> " .. v:val'))
+ call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_function()))
+ call assert_fails("let l = filter([1, 2], function('min'))", 'E118:')
+ call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial()))
+ call assert_fails("let l = filter([1, 2], {a, b, c -> 1})", 'E119:')
+endfunc
+
+func Test_map_and_modify()
+ let l = ["abc"]
+ " cannot change the list halfway a map()
+ call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:')
+
+ let d = #{a: 1, b: 2, c: 3}
+ call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:')
+ call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
+endfunc
+
+func Test_mapnew_dict()
+ let din = #{one: 1, two: 2}
+ let dout = mapnew(din, {k, v -> string(v)})
+ call assert_equal(#{one: 1, two: 2}, din)
+ call assert_equal(#{one: '1', two: '2'}, dout)
+
+ const dconst = #{one: 1, two: 2, three: 3}
+ call assert_equal(#{one: 2, two: 3, three: 4}, mapnew(dconst, {_, v -> v + 1}))
+endfunc
+
+func Test_mapnew_list()
+ let lin = [1, 2, 3]
+ let lout = mapnew(lin, {k, v -> string(v)})
+ call assert_equal([1, 2, 3], lin)
+ call assert_equal(['1', '2', '3'], lout)
+
+ const lconst = [1, 2, 3]
+ call assert_equal([2, 3, 4], mapnew(lconst, {_, v -> v + 1}))
+endfunc
+
+func Test_mapnew_blob()
+ let bin = 0z123456
+ let bout = mapnew(bin, {k, v -> k == 1 ? 0x99 : v})
+ call assert_equal(0z123456, bin)
+ call assert_equal(0z129956, bout)
+endfunc
+
+" Test for using map(), filter() and mapnew() with a string
+func Test_filter_map_string()
+ " filter()
+ let lines =<< trim END
+ VAR s = "abc"
+ call filter(s, '"b" != v:val')
+ call assert_equal('abc', s)
+ call assert_equal('ac', filter('abc', '"b" != v:val'))
+ call assert_equal('ã‚ã„ã†ãˆãŠ', filter('ã‚xã„xã†xãˆxãŠ', '"x" != v:val'))
+ call assert_equal('ã‚a😊💕💕b💕', filter('ã‚xax😊x💕💕b💕x', '"x" != v:val'))
+ call assert_equal('xxxx', filter('ã‚xax😊x💕💕b💕x', '"x" == v:val'))
+ VAR t = "%),:;>?]}’â€â€ â€¡â€¦â€°,‱‼â‡âˆâ‰â„ƒâ„‰,ã€ã€‚〉》ã€,ã€ã€‘〕〗〙〛,ï¼ï¼‰ï¼Œï¼Žï¼š,;?,ï¼½ï½"
+ VAR u = "%):;>?]}’â€â€ â€¡â€¦â€°â€±â€¼â‡âˆâ‰â„ƒâ„‰ã€ã€‚〉》ã€ã€ã€‘〕〗〙〛ï¼ï¼‰ï¼Œï¼Žï¼šï¼›ï¼Ÿï¼½ï½"
+ call assert_equal(u, filter(t, '"," != v:val'))
+ call assert_equal('', filter('abc', '0'))
+ call assert_equal('ac', filter('abc', LSTART i, x LMIDDLE "b" != x LEND))
+ call assert_equal('ã‚ã„ã†ãˆãŠ', filter('ã‚xã„xã†xãˆxãŠ', LSTART i, x LMIDDLE "x" != x LEND))
+ call assert_equal('', filter('abc', LSTART i, x LMIDDLE v:false LEND))
+ call assert_equal('', filter('', "v:val == 'a'"))
+ call assert_equal('', filter(test_null_string(), "v:val == 'a'"))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " map()
+ let lines =<< trim END
+ VAR s = "abc"
+ call map(s, 'nr2char(char2nr(v:val) + 2)')
+ call assert_equal('abc', s)
+ call assert_equal('cde', map('abc', 'nr2char(char2nr(v:val) + 2)'))
+ call assert_equal('[ã‚][i][ã†][ãˆ][ãŠ]', map('ã‚iã†ãˆãŠ', '"[" .. v:val .. "]"'))
+ call assert_equal('[ã‚][a][😊][,][‱][‼][â‡][âˆ][â‰][💕][b][💕][c][💕]', map('ã‚a😊,‱‼â‡âˆâ‰ðŸ’•b💕c💕', '"[" .. v:val .. "]"'))
+ call assert_equal('', map('abc', '""'))
+ call assert_equal('cde', map('abc', LSTART i, x LMIDDLE nr2char(char2nr(x) + 2) LEND))
+ call assert_equal('[ã‚][i][ã†][ãˆ][ãŠ]', map('ã‚iã†ãˆãŠ', LSTART i, x LMIDDLE '[' .. x .. ']' LEND))
+ call assert_equal('', map('abc', LSTART i, x LMIDDLE '' LEND))
+ call assert_equal('', map('', "v:val == 'a'"))
+ call assert_equal('', map(test_null_string(), "v:val == 'a'"))
+ call assert_fails('echo map("abc", "10")', 'E928:')
+ call assert_fails('echo map("abc", "a10")', 'E121:')
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " mapnew()
+ let lines =<< trim END
+ VAR s = "abc"
+ call mapnew(s, 'nr2char(char2nr(v:val) + 2)')
+ call assert_equal('abc', s)
+ call assert_equal('cde', mapnew('abc', 'nr2char(char2nr(v:val) + 2)'))
+ call assert_equal('[ã‚][i][ã†][ãˆ][ãŠ]', mapnew('ã‚iã†ãˆãŠ', '"[" .. v:val .. "]"'))
+ call assert_equal('[ã‚][a][😊][,][‱][‼][â‡][âˆ][â‰][💕][b][💕][c][💕]', mapnew('ã‚a😊,‱‼â‡âˆâ‰ðŸ’•b💕c💕', '"[" .. v:val .. "]"'))
+ call assert_equal('', mapnew('abc', '""'))
+ call assert_equal('cde', mapnew('abc', LSTART i, x LMIDDLE nr2char(char2nr(x) + 2) LEND))
+ call assert_equal('[ã‚][i][ã†][ãˆ][ãŠ]', mapnew('ã‚iã†ãˆãŠ', LSTART i, x LMIDDLE '[' .. x .. ']' LEND))
+ call assert_equal('', mapnew('abc', LSTART i, x LMIDDLE '' LEND))
+ call assert_equal('', mapnew('', "v:val == 'a'"))
+ call assert_equal('', mapnew(test_null_string(), "v:val == 'a'"))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ #" map() and filter()
+ call assert_equal('[ã‚][âˆ][a][😊][â‰][💕][💕][b][💕]', map(filter('ã‚xâˆax😊xâ‰ðŸ’•ðŸ’•b💕x', '"x" != v:val'), '"[" .. v:val .. "]"'))
+
+ #" patterns-composing(\Z)
+ call assert_equal('ॠॠ', filter('ऊॠॡ,ऊॠॡ', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0x0960) LEND))
+ call assert_equal('àà', filter('càt,càt', LSTART i, x LMIDDLE x =~ '\Za' LEND))
+ call assert_equal('ÅÅ', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xc5) LEND))
+ call assert_equal('öö', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xf6) LEND))
+ call assert_equal('ऊ@ॡ', map('ऊॠॡ', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0x0960) ? '@' : x LEND))
+ call assert_equal('c@t', map('càt', LSTART i, x LMIDDLE x =~ '\Za' ? '@' : x LEND))
+ call assert_equal('@ström', map('Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xc5) ? '@' : x LEND))
+ call assert_equal('Åstr@m', map('Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xf6) ? '@' : x LEND))
+
+ #" patterns-composing(\%C)
+ call assert_equal('ॠॠ', filter('ऊॠॡ,ऊॠॡ', LSTART i, x LMIDDLE x =~ nr2char(0x0960) .. '\%C' LEND))
+ call assert_equal('àà', filter('càt,càt', LSTART i, x LMIDDLE x =~ 'a' .. '\%C' LEND))
+ call assert_equal('ÅÅ', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ nr2char(0xc5) .. '\%C' LEND))
+ call assert_equal('öö', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ nr2char(0xf6) .. '\%C' LEND))
+ call assert_equal('ऊ@ॡ', map('ऊॠॡ', LSTART i, x LMIDDLE x =~ nr2char(0x0960) .. '\%C' ? '@' : x LEND))
+ call assert_equal('c@t', map('càt', LSTART i, x LMIDDLE x =~ 'a' .. '\%C' ? '@' : x LEND))
+ call assert_equal('@ström', map('Åström', LSTART i, x LMIDDLE x =~ nr2char(0xc5) .. '\%C' ? '@' : x LEND))
+ call assert_equal('Åstr@m', map('Åström', LSTART i, x LMIDDLE x =~ nr2char(0xf6) .. '\%C' ? '@' : x LEND))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_find_complete.vim b/src/testdir/test_find_complete.vim
new file mode 100644
index 0000000..079fb78
--- /dev/null
+++ b/src/testdir/test_find_complete.vim
@@ -0,0 +1,164 @@
+" Tests for the 'find' command completion.
+
+" Do all the tests in a separate window to avoid E211 when we recursively
+" delete the Xfind directory during cleanup
+func Test_find_complete()
+ set belloff=all
+
+ " On windows a stale "Xfind" directory may exist, remove it so that
+ " we start from a clean state.
+ call delete("Xfind", "rf")
+ let cwd = getcwd()
+ let test_out = cwd . '/test.out'
+ call mkdir('Xfind', 'R')
+ cd Xfind
+
+ new
+ set path=
+ call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
+ close
+
+ new
+ set path=.
+ call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
+ close
+
+ new
+ set path=.,,
+ call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
+ close
+
+ new
+ set path=./**
+ call assert_fails('call feedkeys(":find \t\n", "xt")', 'E471:')
+ close
+
+ " We shouldn't find any file till this point
+
+ call mkdir('in/path', 'p')
+ call chdir(cwd)
+ call writefile(['Holy Grail'], 'Xfind/file.txt')
+ call writefile(['Jimmy Hoffa'], 'Xfind/in/file.txt')
+ call writefile(['Another Holy Grail'], 'Xfind/in/stuff.txt')
+ call writefile(['E.T.'], 'Xfind/in/path/file.txt')
+
+ new
+ set path=Xfind/**
+ call feedkeys(":find file\t\n", "xt")
+ call assert_equal('Holy Grail', getline(1))
+ call feedkeys(":find file\t\t\n", "xt")
+ call assert_equal('Jimmy Hoffa', getline(1))
+ call feedkeys(":find file\t\t\t\n", "xt")
+ call assert_equal('E.T.', getline(1))
+
+ " Rerun the previous three find completions, using fullpath in 'path'
+ exec "set path=" . cwd . "/Xfind/**"
+
+ call feedkeys(":find file\t\n", "xt")
+ call assert_equal('Holy Grail', getline(1))
+ call feedkeys(":find file\t\t\n", "xt")
+ call assert_equal('Jimmy Hoffa', getline(1))
+ call feedkeys(":find file\t\t\t\n", "xt")
+ call assert_equal('E.T.', getline(1))
+
+ " Same steps again, using relative and fullpath items that point to the same
+ " recursive location.
+ " This is to test that there are no duplicates in the completion list.
+ set path+=Xfind/**
+ call feedkeys(":find file\t\n", "xt")
+ call assert_equal('Holy Grail', getline(1))
+ call feedkeys(":find file\t\t\n", "xt")
+ call assert_equal('Jimmy Hoffa', getline(1))
+ call feedkeys(":find file\t\t\t\n", "xt")
+ call assert_equal('E.T.', getline(1))
+ call feedkeys(":find file\t\t\n", "xt")
+
+ " Test find completion for directory of current buffer, which at this point
+ " is Xfind/in/file.txt.
+ set path=.
+ call feedkeys(":find st\t\n", "xt")
+ call assert_equal('Another Holy Grail', getline(1))
+
+ " Test find completion for empty path item ",," which is the current
+ " directory
+ cd Xfind
+ set path=,,
+ call feedkeys(":find f\t\n", "xt")
+ call assert_equal('Holy Grail', getline(1))
+
+ " Test that find completion on directory appends a slash
+ call feedkeys(":find in/pa\tfile.txt\n", "xt")
+ call assert_equal('E.T.', getline(1))
+ call feedkeys(":find ./i\tstuff.txt\n", "xt")
+ call assert_equal('Another Holy Grail', getline(1))
+
+ " Test shortening of
+ "
+ " foo/x/bar/voyager.txt
+ " foo/y/bar/voyager.txt
+ "
+ " When current directory is above foo/ they should be shortened to (in order
+ " of appearance):
+ "
+ " x/bar/voyager.txt
+ " y/bar/voyager.txt
+ call mkdir('foo/x/bar', 'p')
+ call mkdir('foo/y/bar', 'p')
+ call writefile(['Voyager 1'], 'foo/x/bar/voyager.txt')
+ call writefile(['Voyager 2'], 'foo/y/bar/voyager.txt')
+
+ exec "set path=" . cwd . "/Xfind/**"
+ call feedkeys(":find voyager\t\n", "xt")
+ call assert_equal('Voyager 1', getline(1))
+ call feedkeys(":find voyager\t\t\n", "xt")
+ call assert_equal('Voyager 2', getline(1))
+
+ "
+ " When current directory is .../foo/y/bar they should be shortened to (in
+ " order of appearance):
+ "
+ " ./voyager.txt
+ " x/bar/voyager.txt
+ cd foo/y/bar
+ call feedkeys(":find voyager\t\n", "xt")
+ call assert_equal('Voyager 2', getline(1))
+ call feedkeys(":find voyager\t\t\n", "xt")
+ call assert_equal('Voyager 1', getline(1))
+
+ " Check the opposite too:
+ cd ../../x/bar
+ call feedkeys(":find voyager\t\n", "xt")
+ call assert_equal('Voyager 1', getline(1))
+ call feedkeys(":find voyager\t\t\n", "xt")
+ call assert_equal('Voyager 2', getline(1))
+
+ " Check for correct handling of shorten_fname()'s behavior on windows
+ call chdir(cwd .. "/Xfind/in")
+ call feedkeys(":find file\t\n", "xt")
+ call assert_equal('Jimmy Hoffa', getline(1))
+
+ " Test for relative to current buffer 'path' item
+ call chdir(cwd . "/Xfind/")
+ set path=./path
+ " Open the file where Jimmy Hoffa is found
+ e in/file.txt
+ " Find the file containing 'E.T.' in the Xfind/in/path directory
+ call feedkeys(":find file\t\n", "xt")
+ call assert_equal('E.T.', getline(1))
+
+ " Test that completion works when path=.,,
+ set path=.,,
+ " Open Jimmy Hoffa file
+ e in/file.txt
+ call assert_equal('Jimmy Hoffa', getline(1))
+
+ " Search for the file containing Holy Grail in same directory as in/path.txt
+ call feedkeys(":find stu\t\n", "xt")
+ call assert_equal('Another Holy Grail', getline(1))
+
+ enew | only
+ call chdir(cwd)
+ set path&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_findfile.vim b/src/testdir/test_findfile.vim
new file mode 100644
index 0000000..20d5096
--- /dev/null
+++ b/src/testdir/test_findfile.vim
@@ -0,0 +1,249 @@
+" Test findfile() and finddir()
+
+let s:files = [ 'Xfinddir1/foo',
+ \ 'Xfinddir1/bar',
+ \ 'Xfinddir1/Xdir2/foo',
+ \ 'Xfinddir1/Xdir2/foobar',
+ \ 'Xfinddir1/Xdir2/Xdir3/bar',
+ \ 'Xfinddir1/Xdir2/Xdir3/barfoo' ]
+
+func CreateFiles()
+ call mkdir('Xfinddir1/Xdir2/Xdir3/Xdir2', 'p')
+ for f in s:files
+ call writefile([], f)
+ endfor
+endfunc
+
+func CleanFiles()
+ " Safer to delete each file even if it's more verbose
+ " than doing a recursive delete('Xfinddir1', 'rf').
+ for f in s:files
+ call delete(f)
+ endfor
+
+ call delete('Xfinddir1/Xdir2/Xdir3/Xdir2', 'd')
+ call delete('Xfinddir1/Xdir2/Xdir3', 'd')
+ call delete('Xfinddir1/Xdir2', 'd')
+ call delete('Xfinddir1', 'd')
+endfunc
+
+" Test findfile({name} [, {path} [, {count}]])
+func Test_findfile()
+ let save_path = &path
+ let save_shellslash = &shellslash
+ let save_dir = getcwd()
+ set shellslash
+ call CreateFiles()
+ cd Xfinddir1
+ e Xdir2/foo
+
+ " With ,, in path, findfile() searches in current directory.
+ set path=,,
+ call assert_equal('foo', findfile('foo'))
+ call assert_equal('bar', findfile('bar'))
+ call assert_equal('', findfile('foobar'))
+
+ " Directories should not be found (finddir() finds them).
+ call assert_equal('', findfile('Xdir2'))
+
+ " With . in 'path', findfile() searches relatively to current file.
+ set path=.
+ call assert_equal('Xdir2/foo', findfile('foo'))
+ call assert_equal('', findfile('bar'))
+ call assert_equal('Xdir2/foobar', 'foobar'->findfile())
+
+ " Empty {path} 2nd argument is the same as no 2nd argument.
+ call assert_equal('Xdir2/foo', findfile('foo', ''))
+ call assert_equal('', findfile('bar', ''))
+
+ " Test with *
+ call assert_equal('Xdir2/foo', findfile('foo', '*'))
+ call assert_equal('', findfile('bar', '*'))
+ call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '*/*'))
+ call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir2/*'))
+ call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir*/Xdir3'))
+ call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '*2/*3'))
+
+ " Test with **
+ call assert_equal('bar', findfile('bar', '**'))
+ call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '**/Xdir3'))
+ call assert_equal('Xdir2/Xdir3/bar', findfile('bar', 'Xdir2/**'))
+
+ call assert_equal('Xdir2/Xdir3/barfoo', findfile('barfoo', '**2'))
+ call assert_equal('', findfile('barfoo', '**1'))
+ call assert_equal('Xdir2/foobar', findfile('foobar', '**1'))
+
+ " Test with {count} 3rd argument.
+ call assert_equal('bar', findfile('bar', '**', 0))
+ call assert_equal('bar', findfile('bar', '**', 1))
+ call assert_equal('Xdir2/Xdir3/bar', findfile('bar', '**', 2))
+ call assert_equal('', findfile('bar', '**', 3))
+ call assert_equal(['bar', 'Xdir2/Xdir3/bar'], findfile('bar', '**', -1))
+
+ " Test upwards search.
+ cd Xdir2/Xdir3
+ call assert_equal('bar', findfile('bar', ';'))
+ call assert_match('.*/Xfinddir1/Xdir2/foo', findfile('foo', ';'))
+ call assert_match('.*/Xfinddir1/Xdir2/foo', findfile('foo', ';', 1))
+ call assert_match('.*/Xfinddir1/foo', findfile('foo', ';', 2))
+ call assert_match('.*/Xfinddir1/foo', findfile('foo', ';', 2))
+ call assert_match('.*/Xfinddir1/Xdir2/foo', findfile('foo', 'Xdir2;', 1))
+ call assert_equal('', findfile('foo', 'Xdir2;', 2))
+
+ " List l should have at least 2 values (possibly more if foo file
+ " happens to be found upwards above Xfinddir1).
+ let l = findfile('foo', ';', -1)
+ call assert_match('.*/Xfinddir1/Xdir2/foo', l[0])
+ call assert_match('.*/Xfinddir1/foo', l[1])
+
+ " Test upwards search with stop-directory.
+ cd Xdir2
+ let l = findfile('bar', ';' . save_dir . '/Xfinddir1/Xdir2/', -1)
+ call assert_equal(1, len(l))
+ call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0])
+
+ let l = findfile('bar', ';' . save_dir . '/Xfinddir1/', -1)
+ call assert_equal(2, len(l))
+ call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0])
+ call assert_match('.*/Xfinddir1/bar', l[1])
+
+ " Test combined downwards and upwards search from Xdir2/.
+ cd ../..
+ call assert_equal('Xdir3/bar', findfile('bar', '**;', 1))
+ call assert_match('.*/Xfinddir1/bar', findfile('bar', '**;', 2))
+
+ bwipe!
+ call chdir(save_dir)
+ call CleanFiles()
+ let &path = save_path
+ let &shellslash = save_shellslash
+endfunc
+
+func Test_findfile_error()
+ call assert_fails('call findfile([])', 'E730:')
+ call assert_fails('call findfile("x", [])', 'E730:')
+ call assert_fails('call findfile("x", "", [])', 'E745:')
+ call assert_fails('call findfile("x", "**x")', 'E343:')
+ call assert_fails('call findfile("x", repeat("x", 5000))', 'E854:')
+endfunc
+
+" Test finddir({name} [, {path} [, {count}]])
+func Test_finddir()
+ let save_path = &path
+ let save_shellslash = &shellslash
+ let save_dir = getcwd()
+ set path=,,
+ call CreateFiles()
+ cd Xfinddir1
+
+ call assert_equal('Xdir2', finddir('Xdir2'))
+ call assert_equal('', 'Xdir3'->finddir())
+
+ " Files should not be found (findfile() finds them).
+ call assert_equal('', finddir('foo'))
+
+ call assert_equal('Xdir2', finddir('Xdir2', '**'))
+ call assert_equal('Xdir2/Xdir3', finddir('Xdir3', '**'))
+
+ call assert_equal('Xdir2', finddir('Xdir2', '**', 1))
+ call assert_equal('Xdir2/Xdir3/Xdir2', finddir('Xdir2', '**', 2))
+ call assert_equal(['Xdir2',
+ \ 'Xdir2/Xdir3/Xdir2'], finddir('Xdir2', '**', -1))
+
+ call assert_equal('Xdir2', finddir('Xdir2', '**1'))
+ call assert_equal('Xdir2', finddir('Xdir2', '**0'))
+ call assert_equal('Xdir2/Xdir3', finddir('Xdir3', '**1'))
+ call assert_equal('', finddir('Xdir3', '**0'))
+
+ " Test upwards dir search.
+ cd Xdir2/Xdir3
+ call assert_match('.*/Xfinddir1', finddir('Xfinddir1', ';'))
+
+ " Test upwards search with stop-directory.
+ call assert_match('.*/Xfinddir1', finddir('Xfinddir1', ';' . save_dir . '/'))
+ call assert_equal('', finddir('Xfinddir1', ';' . save_dir . '/Xfinddir1/'))
+
+ " Test combined downwards and upwards dir search from Xdir2/.
+ cd ..
+ call assert_match('.*/Xfinddir1', finddir('Xfinddir1', '**;', 1))
+ call assert_equal('Xdir3/Xdir2', finddir('Xdir2', '**;', 1))
+ call assert_match('.*/Xfinddir1/Xdir2', finddir('Xdir2', '**;', 2))
+ call assert_equal('Xdir3', finddir('Xdir3', '**;', 1))
+
+ call chdir(save_dir)
+ call CleanFiles()
+ let &path = save_path
+ let &shellslash = save_shellslash
+endfunc
+
+func Test_finddir_error()
+ call assert_fails('call finddir([])', 'E730:')
+ call assert_fails('call finddir("x", [])', 'E730:')
+ call assert_fails('call finddir("x", "", [])', 'E745:')
+ call assert_fails('call finddir("x", "**x")', 'E343:')
+ call assert_fails('call finddir("x", repeat("x", 5000))', 'E854:')
+endfunc
+
+" Test for the :find, :sfind and :tabfind commands
+func Test_find_cmd()
+ new
+ let save_path = &path
+ let save_dir = getcwd()
+ set path=.,./**/*
+ call CreateFiles()
+ cd Xfinddir1
+
+ " Test for :find
+ find foo
+ call assert_equal('foo', expand('%:.'))
+ 2find foo
+ call assert_equal('Xdir2/foo', expand('%:.'))
+ call assert_fails('3find foo', 'E347:')
+
+ " Test for :sfind
+ enew
+ sfind barfoo
+ call assert_equal('Xdir2/Xdir3/barfoo', expand('%:.'))
+ call assert_equal(3, winnr('$'))
+ close
+ call assert_fails('sfind baz', 'E345:')
+ call assert_equal(2, winnr('$'))
+
+ " Test for :tabfind
+ enew
+ tabfind foobar
+ call assert_equal('Xdir2/foobar', expand('%:.'))
+ call assert_equal(2, tabpagenr('$'))
+ tabclose
+ call assert_fails('tabfind baz', 'E345:')
+ call assert_equal(1, tabpagenr('$'))
+
+ call chdir(save_dir)
+ call CleanFiles()
+ let &path = save_path
+ close
+
+ call assert_fails('find', 'E471:')
+ call assert_fails('sfind', 'E471:')
+ call assert_fails('tabfind', 'E471:')
+endfunc
+
+func Test_find_non_existing_path()
+ new
+ let save_path = &path
+ let save_dir = getcwd()
+ call mkdir('dir1/dir2', 'pR')
+ call writefile([], 'dir1/file.txt')
+ call writefile([], 'dir1/dir2/base.txt')
+ call chdir('dir1/dir2')
+ e base.txt
+ set path=../include
+
+ call assert_fails(':find file.txt', 'E345:')
+
+ call chdir(save_dir)
+ bw!
+ let &path = save_path
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_fixeol.vim b/src/testdir/test_fixeol.vim
new file mode 100644
index 0000000..41d47d6
--- /dev/null
+++ b/src/testdir/test_fixeol.vim
@@ -0,0 +1,118 @@
+" Tests for 'fixeol', 'eof' and 'eol'
+
+func Test_fixeol()
+ " first write two test files – with and without trailing EOL
+ " use Unix fileformat for consistency
+ set ff=unix
+ enew!
+ call setline('.', 'with eol or eof')
+ w! XXEol
+ enew!
+ set noeof noeol nofixeol
+ call setline('.', 'without eol or eof')
+ w! XXNoEol
+ set eol eof fixeol
+ bwipe XXEol XXNoEol
+
+ " try editing files with 'fixeol' disabled
+ e! XXEol
+ normal ostays eol
+ set nofixeol
+ w! XXTestEol
+ e! XXNoEol
+ normal ostays without
+ set nofixeol
+ w! XXTestNoEol
+ bwipe! XXEol XXNoEol XXTestEol XXTestNoEol
+ set fixeol
+
+ " Append "END" to each file so that we can see what the last written char
+ " was.
+ normal ggdGaEND
+ w >>XXEol
+ w >>XXNoEol
+ w >>XXTestEol
+ w >>XXTestNoEol
+
+ call assert_equal(['with eol or eof', 'END'], readfile('XXEol'))
+ call assert_equal(['without eol or eofEND'], readfile('XXNoEol'))
+ call assert_equal(['with eol or eof', 'stays eol', 'END'], readfile('XXTestEol'))
+ call assert_equal(['without eol or eof', 'stays withoutEND'],
+ \ readfile('XXTestNoEol'))
+
+ call delete('XXEol')
+ call delete('XXNoEol')
+ call delete('XXTestEol')
+ call delete('XXTestNoEol')
+ set ff& fixeol& eof& eol&
+ enew!
+endfunc
+
+func Test_eof()
+ let data = 0z68656c6c6f.0d0a.776f726c64 " "hello\r\nworld"
+
+ " 1. Eol, Eof
+ " read
+ call writefile(data + 0z0d0a.1a, 'XXEolEof')
+ e! XXEolEof
+ call assert_equal(['hello', 'world'], getline(1, 2))
+ call assert_equal([1, 1], [&eol, &eof])
+ " write
+ set fixeol
+ w!
+ call assert_equal(data + 0z0d0a, readblob('XXEolEof'))
+ set nofixeol
+ w!
+ call assert_equal(data + 0z0d0a.1a, readblob('XXEolEof'))
+
+ " 2. NoEol, Eof
+ " read
+ call writefile(data + 0z1a, 'XXNoEolEof')
+ e! XXNoEolEof
+ call assert_equal(['hello', 'world'], getline(1, 2))
+ call assert_equal([0, 1], [&eol, &eof])
+ " write
+ set fixeol
+ w!
+ call assert_equal(data + 0z0d0a, readblob('XXNoEolEof'))
+ set nofixeol
+ w!
+ call assert_equal(data + 0z1a, readblob('XXNoEolEof'))
+
+ " 3. Eol, NoEof
+ " read
+ call writefile(data + 0z0d0a, 'XXEolNoEof')
+ e! XXEolNoEof
+ call assert_equal(['hello', 'world'], getline(1, 2))
+ call assert_equal([1, 0], [&eol, &eof])
+ " write
+ set fixeol
+ w!
+ call assert_equal(data + 0z0d0a, readblob('XXEolNoEof'))
+ set nofixeol
+ w!
+ call assert_equal(data + 0z0d0a, readblob('XXEolNoEof'))
+
+ " 4. NoEol, NoEof
+ " read
+ call writefile(data, 'XXNoEolNoEof')
+ e! XXNoEolNoEof
+ call assert_equal(['hello', 'world'], getline(1, 2))
+ call assert_equal([0, 0], [&eol, &eof])
+ " write
+ set fixeol
+ w!
+ call assert_equal(data + 0z0d0a, readblob('XXNoEolNoEof'))
+ set nofixeol
+ w!
+ call assert_equal(data, readblob('XXNoEolNoEof'))
+
+ call delete('XXEolEof')
+ call delete('XXNoEolEof')
+ call delete('XXEolNoEof')
+ call delete('XXNoEolNoEof')
+ set ff& fixeol& eof& eol&
+ enew!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_flatten.vim b/src/testdir/test_flatten.vim
new file mode 100644
index 0000000..a79014d
--- /dev/null
+++ b/src/testdir/test_flatten.vim
@@ -0,0 +1,109 @@
+" Test for flatting list.
+
+func Test_flatten()
+ call assert_fails('call flatten(1)', 'E686:')
+ call assert_fails('call flatten({})', 'E686:')
+ call assert_fails('call flatten("string")', 'E686:')
+ call assert_fails('call flatten([], [])', 'E745:')
+ call assert_fails('call flatten([], -1)', 'E900: maxdepth')
+
+ call assert_equal([], flatten([]))
+ call assert_equal([], flatten([[]]))
+ call assert_equal([], flatten([[[]]]))
+
+ call assert_equal([1, 2, 3], flatten([1, 2, 3]))
+ call assert_equal([1, 2, 3], flatten([[1], 2, 3]))
+ call assert_equal([1, 2, 3], flatten([1, [2], 3]))
+ call assert_equal([1, 2, 3], flatten([1, 2, [3]]))
+ call assert_equal([1, 2, 3], flatten([[1], [2], 3]))
+ call assert_equal([1, 2, 3], flatten([1, [2], [3]]))
+ call assert_equal([1, 2, 3], flatten([[1], 2, [3]]))
+ call assert_equal([1, 2, 3], flatten([[1], [2], [3]]))
+
+ call assert_equal([1, 2, 3], flatten([[1, 2, 3], []]))
+ call assert_equal([1, 2, 3], flatten([[], [1, 2, 3]]))
+ call assert_equal([1, 2, 3], flatten([[1, 2], [], [3]]))
+ call assert_equal([1, 2, 3], flatten([[], [1, 2, 3], []]))
+ call assert_equal([1, 2, 3, 4], flatten(range(1, 4)))
+
+ " example in the help
+ call assert_equal([1, 2, 3, 4, 5], flatten([1, [2, [3, 4]], 5]))
+ call assert_equal([1, 2, [3, 4], 5], flatten([1, [2, [3, 4]], 5], 1))
+
+ call assert_equal([0, [1], 2, [3], 4], flatten([[0, [1]], 2, [[3], 4]], 1))
+ call assert_equal([1, 2, 3], flatten([[[[1]]], [2], [3]], 3))
+ call assert_equal([[1], [2], [3]], flatten([[[1], [2], [3]]], 1))
+ call assert_equal([[1]], flatten([[1]], 0))
+
+ " Make it flatten if the given maxdepth is larger than actual depth.
+ call assert_equal([1, 2, 3], flatten([[1, 2, 3]], 1))
+ call assert_equal([1, 2, 3], flatten([[1, 2, 3]], 2))
+
+ let l:list = [[1], [2], [3]]
+ call assert_equal([1, 2, 3], flatten(l:list))
+ call assert_equal([1, 2, 3], l:list)
+
+ " Tests for checking reference counter works well.
+ let l:x = {'foo': 'bar'}
+ call assert_equal([1, 2, l:x, 3], flatten([1, [2, l:x], 3]))
+ call test_garbagecollect_now()
+ call assert_equal('bar', l:x.foo)
+
+ let l:list = [[1], [2], [3]]
+ call assert_equal([1, 2, 3], flatten(l:list))
+ call test_garbagecollect_now()
+ call assert_equal([1, 2, 3], l:list)
+
+ " Tests for checking circular reference list can be flattened.
+ let l:x = [1]
+ let l:y = [x]
+ let l:z = flatten(l:y)
+ call assert_equal([1], l:z)
+ call test_garbagecollect_now()
+ let l:x[0] = 2
+ call assert_equal([2], l:x)
+ call assert_equal([1], l:z) " NOTE: primitive types are copied.
+ call assert_equal([1], l:y)
+
+ let l:x = [2]
+ let l:y = [1, [l:x], 3] " [1, [[2]], 3]
+ let l:z = flatten(l:y, 1)
+ call assert_equal([1, [2], 3], l:z)
+ let l:x[0] = 9
+ call assert_equal([1, [9], 3], l:z) " Reference to l:x is kept.
+ call assert_equal([1, [9], 3], l:y)
+
+ let l:x = [1]
+ let l:y = [2]
+ call add(x, y) " l:x = [1, [2]]
+ call add(y, x) " l:y = [2, [1, [...]]]
+ call assert_equal([1, 2, 1, 2], flatten(l:x, 2))
+ call assert_equal([2, l:x], l:y)
+
+ let l4 = [ 1, [ 11, [ 101, [ 1001 ] ] ] ]
+ call assert_equal(l4, flatten(deepcopy(l4), 0))
+ call assert_equal([1, 11, [101, [1001]]], flatten(deepcopy(l4), 1))
+ call assert_equal([1, 11, 101, [1001]], flatten(deepcopy(l4), 2))
+ call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4), 3))
+ call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4), 4))
+ call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4)))
+endfunc
+
+func Test_flattennew()
+ let l = [1, [2, [3, 4]], 5]
+ call assert_equal([1, 2, 3, 4, 5], flattennew(l))
+ call assert_equal([1, [2, [3, 4]], 5], l)
+
+ call assert_equal([1, 2, [3, 4], 5], flattennew(l, 1))
+ call assert_equal([1, [2, [3, 4]], 5], l)
+
+ let l4 = [ 1, [ 11, [ 101, [ 1001 ] ] ] ]
+ call assert_equal(l4, flatten(deepcopy(l4), 0))
+ call assert_equal([1, 11, [101, [1001]]], flattennew(l4, 1))
+ call assert_equal([1, 11, 101, [1001]], flattennew(l4, 2))
+ call assert_equal([1, 11, 101, 1001], flattennew(l4, 3))
+ call assert_equal([1, 11, 101, 1001], flattennew(l4, 4))
+ call assert_equal([1, 11, 101, 1001], flattennew(l4))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_float_func.vim b/src/testdir/test_float_func.vim
new file mode 100644
index 0000000..acc2e11
--- /dev/null
+++ b/src/testdir/test_float_func.vim
@@ -0,0 +1,389 @@
+" test float functions
+
+source check.vim
+import './vim9.vim' as v9
+
+func Test_abs()
+ call assert_equal('1.23', string(abs(1.23)))
+ call assert_equal('1.23', string(abs(-1.23)))
+ eval -1.23->abs()->string()->assert_equal('1.23')
+
+ call assert_equal('0.0', string(abs(0.0)))
+ call assert_equal('0.0', string(abs(1.0/(1.0/0.0))))
+ call assert_equal('0.0', string(abs(-1.0/(1.0/0.0))))
+ call assert_equal('inf', string(abs(1.0/0.0)))
+ call assert_equal('inf', string(abs(-1.0/0.0)))
+ call assert_equal('nan', string(abs(0.0/0.0)))
+ call assert_equal('12', string(abs('12abc')))
+ call assert_equal('12', string(abs('-12abc')))
+ call assert_fails("call abs([])", 'E745:')
+ call assert_fails("call abs({})", 'E728:')
+ call assert_fails("call abs(function('string'))", 'E703:')
+endfunc
+
+func Test_sqrt()
+ call assert_equal('0.0', string(sqrt(0.0)))
+ call assert_equal('1.414214', string(sqrt(2.0)))
+ eval 2.0->sqrt()->string()->assert_equal('1.414214')
+ call assert_equal('inf', string(sqrt(1.0/0.0)))
+ call assert_equal('nan', string(sqrt(-1.0)))
+ call assert_equal('nan', string(sqrt(0.0/0.0)))
+ call assert_fails('call sqrt("")', 'E808:')
+endfunc
+
+func Test_log()
+ call assert_equal('0.0', string(log(1.0)))
+ call assert_equal('-0.693147', string(log(0.5)))
+ eval 0.5->log()->string()->assert_equal('-0.693147')
+ call assert_equal('-inf', string(log(0.0)))
+ call assert_equal('nan', string(log(-1.0)))
+ call assert_equal('inf', string(log(1.0/0.0)))
+ call assert_equal('nan', string(log(0.0/0.0)))
+ call assert_fails('call log("")', 'E808:')
+endfunc
+
+func Test_log10()
+ call assert_equal('0.0', string(log10(1.0)))
+ call assert_equal('2.0', string(log10(100.0)))
+ call assert_equal('2.079181', string(log10(120.0)))
+ eval 120.0->log10()->string()->assert_equal('2.079181')
+ call assert_equal('-inf', string(log10(0.0)))
+ call assert_equal('nan', string(log10(-1.0)))
+ call assert_equal('inf', string(log10(1.0/0.0)))
+ call assert_equal('nan', string(log10(0.0/0.0)))
+ call assert_fails('call log10("")', 'E808:')
+endfunc
+
+func Test_exp()
+ call assert_equal('1.0', string(exp(0.0)))
+ call assert_equal('7.389056', string(exp(2.0)))
+ call assert_equal('0.367879', string(exp(-1.0)))
+ eval -1.0->exp()->string()->assert_equal('0.367879')
+ call assert_equal('inf', string(exp(1.0/0.0)))
+ call assert_equal('0.0', string(exp(-1.0/0.0)))
+ call assert_equal('nan', string(exp(0.0/0.0)))
+ call assert_fails('call exp("")', 'E808:')
+endfunc
+
+func Test_sin()
+ call assert_equal('0.0', string(sin(0.0)))
+ call assert_equal('0.841471', string(sin(1.0)))
+ call assert_equal('-0.479426', string(sin(-0.5)))
+ eval -0.5->sin()->string()->assert_equal('-0.479426')
+ call assert_equal('nan', string(sin(0.0/0.0)))
+ call assert_equal('nan', string(sin(1.0/0.0)))
+ call assert_equal('0.0', string(sin(1.0/(1.0/0.0))))
+ call assert_equal('-0.0', string(sin(-1.0/(1.0/0.0))))
+ call assert_fails('call sin("")', 'E808:')
+endfunc
+
+func Test_asin()
+ call assert_equal('0.0', string(asin(0.0)))
+ call assert_equal('1.570796', string(asin(1.0)))
+ eval 1.0->asin()->string()->assert_equal('1.570796')
+
+ call assert_equal('-0.523599', string(asin(-0.5)))
+ call assert_equal('nan', string(asin(1.1)))
+ call assert_equal('nan', string(asin(1.0/0.0)))
+ call assert_equal('nan', string(asin(0.0/0.0)))
+ call assert_fails('call asin("")', 'E808:')
+endfunc
+
+func Test_sinh()
+ call assert_equal('0.0', string(sinh(0.0)))
+ call assert_equal('0.521095', string(sinh(0.5)))
+ call assert_equal('-1.026517', string(sinh(-0.9)))
+ eval -0.9->sinh()->string()->assert_equal('-1.026517')
+ call assert_equal('inf', string(sinh(1.0/0.0)))
+ call assert_equal('-inf', string(sinh(-1.0/0.0)))
+ call assert_equal('nan', string(sinh(0.0/0.0)))
+ call assert_fails('call sinh("")', 'E808:')
+endfunc
+
+func Test_cos()
+ call assert_equal('1.0', string(cos(0.0)))
+ call assert_equal('0.540302', string(cos(1.0)))
+ call assert_equal('0.877583', string(cos(-0.5)))
+ eval -0.5->cos()->string()->assert_equal('0.877583')
+ call assert_equal('nan', string(cos(0.0/0.0)))
+ call assert_equal('nan', string(cos(1.0/0.0)))
+ call assert_fails('call cos("")', 'E808:')
+endfunc
+
+func Test_acos()
+ call assert_equal('1.570796', string(acos(0.0)))
+ call assert_equal('0.0', string(acos(1.0)))
+ call assert_equal('3.141593', string(acos(-1.0)))
+ eval -1.0->acos()->string()->assert_equal('3.141593')
+ call assert_equal('2.094395', string(acos(-0.5)))
+ call assert_equal('nan', string(acos(1.1)))
+ call assert_equal('nan', string(acos(1.0/0.0)))
+ call assert_equal('nan', string(acos(0.0/0.0)))
+ call assert_fails('call acos("")', 'E808:')
+endfunc
+
+func Test_cosh()
+ call assert_equal('1.0', string(cosh(0.0)))
+ call assert_equal('1.127626', string(cosh(0.5)))
+ eval 0.5->cosh()->string()->assert_equal('1.127626')
+ call assert_equal('inf', string(cosh(1.0/0.0)))
+ call assert_equal('inf', string(cosh(-1.0/0.0)))
+ call assert_equal('nan', string(cosh(0.0/0.0)))
+ call assert_fails('call cosh("")', 'E808:')
+endfunc
+
+func Test_tan()
+ call assert_equal('0.0', string(tan(0.0)))
+ call assert_equal('0.546302', string(tan(0.5)))
+ call assert_equal('-0.546302', string(tan(-0.5)))
+ eval -0.5->tan()->string()->assert_equal('-0.546302')
+ call assert_equal('nan', string(tan(1.0/0.0)))
+ call assert_equal('nan', string(cos(0.0/0.0)))
+ call assert_equal('0.0', string(tan(1.0/(1.0/0.0))))
+ call assert_equal('-0.0', string(tan(-1.0/(1.0/0.0))))
+ call assert_fails('call tan("")', 'E808:')
+endfunc
+
+func Test_atan()
+ call assert_equal('0.0', string(atan(0.0)))
+ call assert_equal('0.463648', string(atan(0.5)))
+ call assert_equal('-0.785398', string(atan(-1.0)))
+ eval -1.0->atan()->string()->assert_equal('-0.785398')
+ call assert_equal('1.570796', string(atan(1.0/0.0)))
+ call assert_equal('-1.570796', string(atan(-1.0/0.0)))
+ call assert_equal('nan', string(atan(0.0/0.0)))
+ call assert_fails('call atan("")', 'E808:')
+endfunc
+
+func Test_atan2()
+ call assert_equal('-2.356194', string(atan2(-1, -1)))
+ call assert_equal('2.356194', string(atan2(1, -1)))
+ call assert_equal('0.0', string(atan2(1.0, 1.0/0.0)))
+ eval 1.0->atan2(1.0/0.0)->string()->assert_equal('0.0')
+ call assert_equal('1.570796', string(atan2(1.0/0.0, 1.0)))
+ call assert_equal('nan', string(atan2(0.0/0.0, 1.0)))
+ call assert_fails('call atan2("", -1)', 'E808:')
+ call assert_fails('call atan2(-1, "")', 'E808:')
+endfunc
+
+func Test_tanh()
+ call assert_equal('0.0', string(tanh(0.0)))
+ call assert_equal('0.462117', string(tanh(0.5)))
+ call assert_equal('-0.761594', string(tanh(-1.0)))
+ eval -1.0->tanh()->string()->assert_equal('-0.761594')
+ call assert_equal('1.0', string(tanh(1.0/0.0)))
+ call assert_equal('-1.0', string(tanh(-1.0/0.0)))
+ call assert_equal('nan', string(tanh(0.0/0.0)))
+ call assert_fails('call tanh("")', 'E808:')
+endfunc
+
+func Test_fmod()
+ call assert_equal('0.13', string(fmod(12.33, 1.22)))
+ call assert_equal('-0.13', string(fmod(-12.33, 1.22)))
+ call assert_equal('nan', string(fmod(1.0/0.0, 1.0)))
+ eval (1.0/0.0)->fmod(1.0)->string()->assert_equal('nan')
+ " On Windows we get "nan" instead of 1.0, accept both.
+ let res = string(fmod(1.0, 1.0/0.0))
+ if res != 'nan'
+ call assert_equal('1.0', res)
+ endif
+ call assert_equal('nan', string(fmod(1.0, 0.0)))
+ call assert_fails("call fmod('', 1.22)", 'E808:')
+ call assert_fails("call fmod(12.33, '')", 'E808:')
+endfunc
+
+func Test_pow()
+ call assert_equal('1.0', string(pow(0.0, 0.0)))
+ call assert_equal('8.0', string(pow(2.0, 3.0)))
+ eval 2.0->pow(3.0)->string()->assert_equal('8.0')
+ call assert_equal('nan', string(pow(2.0, 0.0/0.0)))
+ call assert_equal('nan', string(pow(0.0/0.0, 3.0)))
+ call assert_equal('nan', string(pow(0.0/0.0, 3.0)))
+ call assert_equal('inf', string(pow(2.0, 1.0/0.0)))
+ call assert_equal('inf', string(pow(1.0/0.0, 3.0)))
+ call assert_fails("call pow('', 2.0)", 'E808:')
+ call assert_fails("call pow(2.0, '')", 'E808:')
+endfunc
+
+func Test_str2float()
+ call assert_equal('1.0', string(str2float('1')))
+ call assert_equal('1.0', string(str2float(' 1 ')))
+ call assert_equal('1.0', string(str2float(' 1.0 ')))
+ call assert_equal('1.23', string(str2float('1.23')))
+ call assert_equal('1.23', string(str2float('1.23abc')))
+ eval '1.23abc'->str2float()->string()->assert_equal('1.23')
+ call assert_equal('1.0e40', string(str2float('1e40')))
+ call assert_equal('-1.23', string(str2float('-1.23')))
+ call assert_equal('1.23', string(str2float(' + 1.23 ')))
+
+ call assert_equal('1.0', string(str2float('+1')))
+ call assert_equal('1.0', string(str2float('+1')))
+ call assert_equal('1.0', string(str2float(' +1 ')))
+ call assert_equal('1.0', string(str2float(' + 1 ')))
+
+ call assert_equal('-1.0', string(str2float('-1')))
+ call assert_equal('-1.0', string(str2float('-1')))
+ call assert_equal('-1.0', string(str2float(' -1 ')))
+ call assert_equal('-1.0', string(str2float(' - 1 ')))
+
+ call assert_equal('0.0', string(str2float('+0.0')))
+ call assert_equal('-0.0', string(str2float('-0.0')))
+ call assert_equal('inf', string(str2float('1e1000')))
+ call assert_equal('inf', string(str2float('inf')))
+ call assert_equal('-inf', string(str2float('-inf')))
+ call assert_equal('inf', string(str2float('+inf')))
+ call assert_equal('inf', string(str2float('Inf')))
+ call assert_equal('inf', string(str2float(' +inf ')))
+ call assert_equal('nan', string(str2float('nan')))
+ call assert_equal('nan', string(str2float('NaN')))
+ call assert_equal('nan', string(str2float(' nan ')))
+
+ call assert_equal('123456.789', string(str2float("123'456.789", 1)))
+ call assert_equal('123456.789', string(str2float("12'34'56.789", 1)))
+ call assert_equal('123456.789', string(str2float("1'2'3'4'5'6.789", 1)))
+ call assert_equal('1.0', string(str2float("1''2.3", 1)))
+ call assert_equal('123456.7', string(str2float("123'456.7'89", 1)))
+
+ call assert_equal(1.2, str2float(1.2, 0))
+ call v9.CheckDefAndScriptFailure(['str2float(1.2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1174: String required for argument 1'])
+ call assert_fails("call str2float([])", 'E730:')
+ call assert_fails("call str2float({})", 'E731:')
+ call assert_fails("call str2float(function('string'))", 'E729:')
+endfunc
+
+def Test_float_quotes()
+ call assert_equal('123456.789', string(123'456.789))
+ call assert_equal('123456.789', string(12'34'56.789))
+ call assert_equal('123456.789', string(1'2'3'4'5'6.789))
+
+ call assert_fails("echo string(1''2.3)", 'E116:')
+ call assert_fails("echo string(123'456.7'89)", 'E116:')
+enddef
+
+func Test_float_quotes_from_legacy()
+ call assert_equal("\n123456.789", execute("vim9 echo 12'34'56.789"))
+endfunc
+
+func Test_float2nr()
+ call assert_equal(1, float2nr(1.234))
+ call assert_equal(123, float2nr(1.234e2))
+ call assert_equal(12, float2nr(123.4e-1))
+ eval 123.4e-1->float2nr()->assert_equal(12)
+ let max_number = 1/0
+ let min_number = -max_number
+ call assert_equal(max_number/2+1, float2nr(pow(2, 62)))
+ call assert_equal(max_number, float2nr(pow(2, 63)))
+ call assert_equal(max_number, float2nr(pow(2, 64)))
+ call assert_equal(min_number/2-1, float2nr(-pow(2, 62)))
+ call assert_equal(min_number, float2nr(-pow(2, 63)))
+ call assert_equal(min_number, float2nr(-pow(2, 64)))
+endfunc
+
+func Test_floor()
+ call assert_equal('2.0', string(floor(2.0)))
+ call assert_equal('2.0', string(floor(2.11)))
+ call assert_equal('2.0', string(floor(2.99)))
+ eval 2.99->floor()->string()->assert_equal('2.0')
+ call assert_equal('-3.0', string(floor(-2.11)))
+ call assert_equal('-3.0', string(floor(-2.99)))
+ call assert_equal('nan', string(floor(0.0/0.0)))
+ call assert_equal('inf', string(floor(1.0/0.0)))
+ call assert_equal('-inf', string(floor(-1.0/0.0)))
+ call assert_fails("call floor('')", 'E808:')
+endfunc
+
+func Test_ceil()
+ call assert_equal('2.0', string(ceil(2.0)))
+ call assert_equal('3.0', string(ceil(2.11)))
+ call assert_equal('3.0', string(ceil(2.99)))
+ call assert_equal('-2.0', string(ceil(-2.11)))
+ eval -2.11->ceil()->string()->assert_equal('-2.0')
+ call assert_equal('-2.0', string(ceil(-2.99)))
+ call assert_equal('nan', string(ceil(0.0/0.0)))
+ call assert_equal('inf', string(ceil(1.0/0.0)))
+ call assert_equal('-inf', string(ceil(-1.0/0.0)))
+ call assert_fails("call ceil('')", 'E808:')
+endfunc
+
+func Test_round()
+ call assert_equal('2.0', string(round(2.1)))
+ call assert_equal('3.0', string(round(2.5)))
+ call assert_equal('3.0', string(round(2.9)))
+ eval 2.9->round()->string()->assert_equal('3.0')
+ call assert_equal('-2.0', string(round(-2.1)))
+ call assert_equal('-3.0', string(round(-2.5)))
+ call assert_equal('-3.0', string(round(-2.9)))
+ call assert_equal('nan', string(round(0.0/0.0)))
+ call assert_equal('inf', string(round(1.0/0.0)))
+ call assert_equal('-inf', string(round(-1.0/0.0)))
+ call assert_fails("call round('')", 'E808:')
+endfunc
+
+func Test_trunc()
+ call assert_equal('2.0', string(trunc(2.1)))
+ call assert_equal('2.0', string(trunc(2.5)))
+ call assert_equal('2.0', string(trunc(2.9)))
+ eval 2.9->trunc()->string()->assert_equal('2.0')
+ call assert_equal('-2.0', string(trunc(-2.1)))
+ call assert_equal('-2.0', string(trunc(-2.5)))
+ call assert_equal('-2.0', string(trunc(-2.9)))
+ call assert_equal('nan', string(trunc(0.0/0.0)))
+ call assert_equal('inf', string(trunc(1.0/0.0)))
+ call assert_equal('-inf', string(trunc(-1.0/0.0)))
+ call assert_fails("call trunc('')", 'E808:')
+endfunc
+
+func Test_isinf()
+ call assert_equal(1, isinf(1.0/0.0))
+ call assert_equal(-1, isinf(-1.0/0.0))
+ eval (-1.0/0.0)->isinf()->assert_equal(-1)
+ call assert_false(isinf(1.0))
+ call assert_false(isinf(0.0/0.0))
+ call assert_false(isinf('a'))
+ call assert_false(isinf([]))
+ call assert_false(isinf({}))
+endfunc
+
+func Test_isnan()
+ call assert_true(isnan(0.0/0.0))
+ call assert_false(isnan(1.0))
+ call assert_false(isnan(1.0/0.0))
+ eval (1.0/0.0)->isnan()->assert_false()
+ call assert_false(isnan(-1.0/0.0))
+ call assert_false(isnan('a'))
+ call assert_false(isnan([]))
+ call assert_false(isnan({}))
+endfunc
+
+" This was converted from test65
+func Test_float_misc()
+ call assert_equal('123.456000', printf('%f', 123.456))
+ call assert_equal('1.234560e+02', printf('%e', 123.456))
+ call assert_equal('123.456', printf('%g', 123.456))
+ " +=
+ let v = 1.234
+ let v += 6.543
+ call assert_equal('7.777', printf('%g', v))
+ let v = 1.234
+ let v += 5
+ call assert_equal('6.234', printf('%g', v))
+ let v = 5
+ let v += 3.333
+ call assert_equal('8.333', string(v))
+ " ==
+ let v = 1.234
+ call assert_true(v == 1.234)
+ call assert_false(v == 1.2341)
+ " add-subtract
+ call assert_equal('5.234', printf('%g', 4 + 1.234))
+ call assert_equal('-6.766', printf('%g', 1.234 - 8))
+ " mult-div
+ call assert_equal('4.936', printf('%g', 4 * 1.234))
+ call assert_equal('0.003241', printf('%g', 4.0 / 1234))
+ " dict
+ call assert_equal("{'x': 1.234, 'y': -2.0e20}", string({'x': 1.234, 'y': -2.0e20}))
+ " list
+ call assert_equal('[-123.4, 2.0e-20]', string([-123.4, 2.0e-20]))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_fnameescape.vim b/src/testdir/test_fnameescape.vim
new file mode 100644
index 0000000..e24d64d
--- /dev/null
+++ b/src/testdir/test_fnameescape.vim
@@ -0,0 +1,27 @@
+" Test if fnameescape is correct for special chars like !
+
+func Test_fnameescape()
+ let fname = 'Xspa ce'
+ let status = v:false
+ try
+ exe "w! " . fnameescape(fname)
+ let status = v:true
+ endtry
+ call assert_true(status, "Space")
+ call delete(fname)
+
+ let fname = 'Xemark!'
+ let status = v:false
+ try
+ exe "w! " . fname->fnameescape()
+ let status = v:true
+ endtry
+ call assert_true(status, "ExclamationMark")
+ call delete(fname)
+
+ call assert_equal('\-', fnameescape('-'))
+ call assert_equal('\+', fnameescape('+'))
+ call assert_equal('\>', fnameescape('>'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_fnamemodify.vim b/src/testdir/test_fnamemodify.vim
new file mode 100644
index 0000000..c19f464
--- /dev/null
+++ b/src/testdir/test_fnamemodify.vim
@@ -0,0 +1,106 @@
+" Test filename modifiers.
+
+func Test_fnamemodify()
+ let save_home = $HOME
+ let save_shell = &shell
+ let save_shellslash = &shellslash
+ let $HOME = fnamemodify('.', ':p:h:h')
+ set shell=sh
+ set shellslash
+
+ call assert_equal('/', fnamemodify('.', ':p')[-1:])
+ call assert_equal('r', fnamemodify('.', ':p:h')[-1:])
+ call assert_equal('t', fnamemodify('test.out', ':p')[-1:])
+ call assert_equal($HOME .. "/foo" , fnamemodify('~/foo', ':p'))
+ call assert_equal(fnamemodify('.', ':p:h:h:h') .. '/', fnamemodify($HOME .. '/../', ':p'))
+ call assert_equal(fnamemodify('.', ':p:h:h:h') .. '/', fnamemodify($HOME .. '/..', ':p'))
+ call assert_equal('test.out', fnamemodify('test.out', ':.'))
+ call assert_equal('a', fnamemodify('../testdir/a', ':.'))
+ call assert_equal('~/testdir/test.out', fnamemodify('test.out', ':~'))
+ call assert_equal('~/testdir/a', fnamemodify('../testdir/a', ':~'))
+ call assert_equal('a', '../testdir/a'->fnamemodify(':t'))
+ call assert_equal('', fnamemodify('.', ':p:t'))
+ call assert_equal('test.out', fnamemodify('test.out', ':p:t'))
+ call assert_equal('out', fnamemodify('test.out', ':p:e'))
+ call assert_equal('out', fnamemodify('test.out', ':p:t:e'))
+ call assert_equal('abc.fb2.tar', fnamemodify('abc.fb2.tar.gz', ':r'))
+ call assert_equal('abc.fb2', fnamemodify('abc.fb2.tar.gz', ':r:r'))
+ call assert_equal('abc', fnamemodify('abc.fb2.tar.gz', ':r:r:r'))
+ call assert_equal('testdir/abc.fb2', substitute(fnamemodify('abc.fb2.tar.gz', ':p:r:r'), '.*\(testdir/.*\)', '\1', ''))
+ call assert_equal('gz', fnamemodify('abc.fb2.tar.gz', ':e'))
+ call assert_equal('tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e'))
+ call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e'))
+ call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e'))
+ call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r'))
+ call assert_equal(getcwd(), fnamemodify('', ':p:h'))
+
+ let cwd = getcwd()
+ call chdir($HOME)
+ call assert_equal('foobar', fnamemodify('~/foobar', ':~:.'))
+ call chdir(cwd)
+ call mkdir($HOME . '/XXXXXXXX/a', 'p')
+ call mkdir($HOME . '/XXXXXXXX/b', 'p')
+ call chdir($HOME . '/XXXXXXXX/a/')
+ call assert_equal('foo', fnamemodify($HOME . '/XXXXXXXX/a/foo', ':p:~:.'))
+ call assert_equal('~/XXXXXXXX/b/foo', fnamemodify($HOME . '/XXXXXXXX/b/foo', ':p:~:.'))
+ call mkdir($HOME . '/XXXXXXXX/a.ext', 'p')
+ call assert_equal('~/XXXXXXXX/a.ext/foo', fnamemodify($HOME . '/XXXXXXXX/a.ext/foo', ':p:~:.'))
+ call chdir(cwd)
+ call delete($HOME . '/XXXXXXXX', 'rf')
+
+ call assert_equal('''abc def''', fnamemodify('abc def', ':S'))
+ call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S'))
+ call assert_equal('''abc"%"def''', fnamemodify('abc"%"def', ':S'))
+ call assert_equal('''abc''\'''' ''\''''def''', fnamemodify('abc'' ''def', ':S'))
+ call assert_equal('''abc''\''''%''\''''def''', fnamemodify('abc''%''def', ':S'))
+ sp test_alot.vim
+ call assert_equal(expand('%:r:S'), shellescape(expand('%:r')))
+ call assert_equal('test_alot,''test_alot'',test_alot.vim', join([expand('%:r'), expand('%:r:S'), expand('%')], ','))
+ quit
+
+ call assert_equal("'abc\ndef'", fnamemodify("abc\ndef", ':S'))
+ set shell=tcsh
+ call assert_equal("'abc\\\ndef'", fnamemodify("abc\ndef", ':S'))
+
+ let $HOME = save_home
+ let &shell = save_shell
+ let &shellslash = save_shellslash
+endfunc
+
+func Test_fnamemodify_er()
+ call assert_equal("with", fnamemodify("path/to/file.with.extensions", ':e:e:r:r'))
+
+ call assert_equal('c', fnamemodify('a.c', ':e'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e:r'))
+ call assert_equal('c', fnamemodify('a.c', ':e:e:r:r'))
+
+ call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r'))
+ call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r'))
+ call assert_equal('spec.rb', fnamemodify('a.spec.rb', ':e:e'))
+ call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r'))
+ call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r:r'))
+ call assert_equal('spec', fnamemodify('a.b.spec.rb', ':e:e:r'))
+ call assert_equal('b.spec', fnamemodify('a.b.spec.rb', ':e:e:e:r'))
+ call assert_equal('b', fnamemodify('a.b.spec.rb', ':e:e:e:r:r'))
+
+ call assert_equal('spec', fnamemodify('a.b.spec.rb', ':r:e'))
+ call assert_equal('b', fnamemodify('a.b.spec.rb', ':r:r:e'))
+
+ call assert_equal('c', fnamemodify('a.b.c.d.e', ':r:r:e'))
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e'))
+
+ " :e never includes the whole filename, so "a.b":e:e:e --> "b"
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e'))
+ call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e'))
+
+ call assert_equal('', fnamemodify('', ':p:t'))
+ call assert_equal('', fnamemodify(test_null_string(), test_null_string()))
+endfunc
+
+func Test_fnamemodify_fail()
+ call assert_fails('call fnamemodify({}, ":p")', 'E731:')
+ call assert_fails('call fnamemodify("x", {})', 'E731:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_fold.vim b/src/testdir/test_fold.vim
new file mode 100644
index 0000000..e0119c4
--- /dev/null
+++ b/src/testdir/test_fold.vim
@@ -0,0 +1,1758 @@
+" Test for folding
+
+source check.vim
+source view_util.vim
+source screendump.vim
+
+func PrepIndent(arg)
+ return [a:arg] + repeat(["\t".a:arg], 5)
+endfu
+
+func Test_address_fold()
+ new
+ call setline(1, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
+ \ 'after fold 1', 'after fold 2', 'after fold 3'])
+ setl fen fdm=marker
+ " The next commands should all copy the same part of the buffer,
+ " regardless of the addressing type, since the part to be copied
+ " is folded away
+ :1y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+ :.y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+ :.+y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+ :.,.y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+ :sil .1,.y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+ " use silent to make E493 go away
+ :sil .+,.y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+ :,y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+ :,+y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/','after fold 1'], getreg(0,1,1))
+ " using .+3 as second address should copy the whole folded line + the next 3
+ " lines
+ :.,+3y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/',
+ \ 'after fold 1', 'after fold 2', 'after fold 3'], getreg(0,1,1))
+ :sil .,-2y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3', '4', '5', '}/*}}}*/'], getreg(0,1,1))
+
+ " now test again with folding disabled
+ set nofoldenable
+ :1y
+ call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
+ :.y
+ call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
+ :.+y
+ call assert_equal(['1'], getreg(0,1,1))
+ :.,.y
+ call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
+ " use silent to make E493 go away
+ :sil .1,.y
+ call assert_equal(['int FuncName() {/*{{{*/', '1'], getreg(0,1,1))
+ " use silent to make E493 go away
+ :sil .+,.y
+ call assert_equal(['int FuncName() {/*{{{*/', '1'], getreg(0,1,1))
+ :,y
+ call assert_equal(['int FuncName() {/*{{{*/'], getreg(0,1,1))
+ :,+y
+ call assert_equal(['int FuncName() {/*{{{*/', '1'], getreg(0,1,1))
+ " using .+3 as second address should copy the whole folded line + the next 3
+ " lines
+ :.,+3y
+ call assert_equal(['int FuncName() {/*{{{*/', '1', '2', '3'], getreg(0,1,1))
+ :7
+ :sil .,-2y
+ call assert_equal(['4', '5', '}/*}}}*/'], getreg(0,1,1))
+
+ quit!
+endfunc
+
+func Test_address_offsets()
+ " check the help for :range-closed-fold
+ enew
+ call setline(1, [
+ \ '1 one',
+ \ '2 two',
+ \ '3 three',
+ \ '4 four FOLDED',
+ \ '5 five FOLDED',
+ \ '6 six',
+ \ '7 seven',
+ \ '8 eight',
+ \])
+ set foldmethod=manual
+ normal 4Gvjzf
+ 3,4+2yank
+ call assert_equal([
+ \ '3 three',
+ \ '4 four FOLDED',
+ \ '5 five FOLDED',
+ \ '6 six',
+ \ '7 seven',
+ \ ], getreg(0,1,1))
+
+ enew!
+ call setline(1, [
+ \ '1 one',
+ \ '2 two',
+ \ '3 three FOLDED',
+ \ '4 four FOLDED',
+ \ '5 five FOLDED',
+ \ '6 six FOLDED',
+ \ '7 seven',
+ \ '8 eight',
+ \])
+ normal 3Gv3jzf
+ 2,4-1yank
+ call assert_equal([
+ \ '2 two',
+ \ '3 three FOLDED',
+ \ '4 four FOLDED',
+ \ '5 five FOLDED',
+ \ '6 six FOLDED',
+ \ ], getreg(0,1,1))
+
+ bwipe!
+endfunc
+
+func Test_indent_fold()
+ new
+ call setline(1, ['', 'a', ' b', ' c'])
+ setl fen fdm=indent
+ 2
+ norm! >>
+ let a=map(range(1,4), 'foldclosed(v:val)')
+ call assert_equal([-1,-1,-1,-1], a)
+ bw!
+endfunc
+
+func Test_indent_fold2()
+ new
+ call setline(1, ['', '{{{', '}}}', '{{{', '}}}'])
+ setl fen fdm=marker
+ 2
+ norm! >>
+ let a=map(range(1,5), 'v:val->foldclosed()')
+ call assert_equal([-1,-1,-1,4,4], a)
+ bw!
+endfunc
+
+" Test for fold indent with indents greater than 'foldnestmax'
+func Test_indent_fold_max()
+ new
+ setlocal foldmethod=indent
+ setlocal shiftwidth=2
+ " 'foldnestmax' default value is 20
+ call setline(1, "\t\t\t\t\t\ta")
+ call assert_equal(20, foldlevel(1))
+ setlocal foldnestmax=10
+ call assert_equal(10, foldlevel(1))
+ setlocal foldnestmax=-1
+ call assert_equal(0, foldlevel(1))
+ bw!
+endfunc
+
+func Test_indent_fold_tabstop()
+ call setline(1, ['0', ' 1', ' 1', "\t2", "\t2"])
+ setlocal shiftwidth=4
+ setlocal foldcolumn=1
+ setlocal foldlevel=2
+ setlocal foldmethod=indent
+ redraw
+ call assert_equal('2 2', ScreenLines(5, 10)[0])
+ vsplit
+ windo diffthis
+ botright new
+ " This 'tabstop' value should not be used for folding in other buffers.
+ setlocal tabstop=4
+ diffoff!
+ redraw
+ call assert_equal('2 2', ScreenLines(5, 10)[0])
+
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_manual_fold_with_filter()
+ CheckExecutable cat
+ for type in ['manual', 'marker']
+ exe 'set foldmethod=' . type
+ new
+ call setline(1, range(1, 20))
+ 4,$fold
+ %foldopen
+ 10,$fold
+ %foldopen
+ " This filter command should not have an effect
+ 1,8! cat
+ call feedkeys('5ggzdzMGdd', 'xt')
+ call assert_equal(['1', '2', '3', '4', '5', '6', '7', '8', '9'], getline(1, '$'))
+
+ bwipe!
+ set foldmethod&
+ endfor
+endfunc
+
+func Test_indent_fold_with_read()
+ new
+ set foldmethod=indent
+ call setline(1, repeat(["\<Tab>a"], 4))
+ for n in range(1, 4)
+ call assert_equal(1, foldlevel(n))
+ endfor
+
+ call writefile(["a", "", "\<Tab>a"], 'Xinfofile', 'D')
+ foldopen
+ 2read Xinfofile
+ %foldclose
+ call assert_equal(1, foldlevel(1))
+ call assert_equal(2, foldclosedend(1))
+ call assert_equal(0, foldlevel(3))
+ call assert_equal(0, foldlevel(4))
+ call assert_equal(1, foldlevel(5))
+ call assert_equal(7, 5->foldclosedend())
+
+ bwipe!
+ set foldmethod&
+endfunc
+
+func Test_combining_folds_indent()
+ new
+ let one = "\<Tab>a"
+ let zero = 'a'
+ call setline(1, [one, one, zero, zero, zero, one, one, one])
+ set foldmethod=indent
+ 3,5d
+ %foldclose
+ call assert_equal(5, foldclosedend(1))
+
+ set foldmethod&
+ bwipe!
+endfunc
+
+func Test_combining_folds_marker()
+ new
+ call setline(1, ['{{{', '}}}', '', '', '', '{{{', '', '}}}'])
+ set foldmethod=marker
+ 3,5d
+ %foldclose
+ call assert_equal(2, foldclosedend(1))
+
+ set foldmethod&
+ bwipe!
+endfunc
+
+func Test_folds_marker_in_comment()
+ new
+ call setline(1, ['" foo', 'bar', 'baz'])
+ setl fen fdm=marker
+ setl com=sO:\"\ -,mO:\"\ \ ,eO:\"\",:\" cms=\"%s
+ norm! zf2j
+ setl nofen
+ :1y
+ call assert_equal(['" foo{{{'], getreg(0,1,1))
+ :+2y
+ call assert_equal(['baz"}}}'], getreg(0,1,1))
+
+ set foldmethod&
+ bwipe!
+endfunc
+
+func s:TestFoldExpr(lnum)
+ let thisline = getline(a:lnum)
+ if thisline == 'a'
+ return 1
+ elseif thisline == 'b'
+ return 0
+ elseif thisline == 'c'
+ return '<1'
+ elseif thisline == 'd'
+ return '>1'
+ endif
+ return 0
+endfunction
+
+func Test_update_folds_expr_read()
+ new
+ call setline(1, ['a', 'a', 'a', 'a', 'a', 'a'])
+ set foldmethod=expr
+ set foldexpr=s:TestFoldExpr(v:lnum)
+ 2
+ foldopen
+ call writefile(['b', 'b', 'a', 'a', 'd', 'a', 'a', 'c'], 'Xupfofile', 'D')
+ read Xupfofile
+ %foldclose
+ call assert_equal(2, foldclosedend(1))
+ call assert_equal(0, foldlevel(3))
+ call assert_equal(0, 4->foldlevel())
+ call assert_equal(6, foldclosedend(5))
+ call assert_equal(10, foldclosedend(7))
+ call assert_equal(14, foldclosedend(11))
+
+ bwipe!
+ set foldmethod& foldexpr&
+endfunc
+
+" Test for what patch 8.1.0535 fixes.
+func Test_foldexpr_no_interrupt_addsub()
+ new
+ func! FoldFunc()
+ call setpos('.', getcurpos())
+ return '='
+ endfunc
+
+ set foldmethod=expr
+ set foldexpr=FoldFunc()
+ call setline(1, '1.2')
+
+ exe "norm! $\<C-A>"
+ call assert_equal('1.3', getline(1))
+
+ bwipe!
+ delfunc FoldFunc
+ set foldmethod& foldexpr&
+endfunc
+
+" Fold function defined in another script
+func Test_foldexpr_compiled()
+ new
+ let lines =<< trim END
+ vim9script
+ def FoldFunc(): number
+ return v:lnum
+ enddef
+
+ set foldmethod=expr
+ set foldexpr=s:FoldFunc()
+ END
+ call writefile(lines, 'XfoldExpr', 'D')
+ source XfoldExpr
+
+ call setline(1, ['one', 'two', 'three'])
+ redraw
+ call assert_equal(1, foldlevel(1))
+ call assert_equal(2, foldlevel(2))
+ call assert_equal(3, foldlevel(3))
+
+ bwipe!
+ set foldmethod& foldexpr&
+endfunc
+
+func Check_foldlevels(expected)
+ call assert_equal(a:expected, map(range(1, line('$')), 'foldlevel(v:val)'))
+endfunc
+
+func Test_move_folds_around_manual()
+ new
+ let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")
+ call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+ let folds=[-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14]
+ " all folds closed
+ set foldenable foldlevel=0 fdm=indent
+ " needs a forced redraw
+ redraw!
+ set fdm=manual
+ call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+ call assert_equal(input, getline(1, '$'))
+ 7,12m0
+ call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+ call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+ 10,12m0
+ call assert_equal(PrepIndent("a")[1:] + PrepIndent("b") + ["a"] + PrepIndent("c"), getline(1, '$'))
+ call assert_equal([1, 1, 1, 1, 1, -1, 7, 7, 7, 7, 7, -1, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+ " moving should not close the folds
+ %d
+ call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+ set fdm=indent
+ redraw!
+ set fdm=manual
+ call cursor(2, 1)
+ %foldopen
+ 7,12m0
+ let folds=repeat([-1], 18)
+ call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+ call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+ norm! zM
+ " folds are not corrupted and all have been closed
+ call assert_equal([-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+ %d
+ call setline(1, ["a", "\tb", "\tc", "\td", "\te"])
+ set fdm=indent
+ redraw!
+ set fdm=manual
+ %foldopen
+ 3m4
+ %foldclose
+ call assert_equal(["a", "\tb", "\td", "\tc", "\te"], getline(1, '$'))
+ call assert_equal([-1, 5, 5, 5, 5], map(range(1, line('$')), 'foldclosedend(v:val)'))
+ %d
+ call setline(1, ["a", "\tb", "\tc", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"])
+ set fdm=indent foldlevel=0
+ set fdm=manual
+ %foldopen
+ 3m1
+ %foldclose
+ call assert_equal(["a", "\tc", "\tb", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"], getline(1, '$'))
+ call assert_equal(0, foldlevel(2))
+ call assert_equal(5, foldclosedend(3))
+ call assert_equal([-1, -1, 3, 3, 3, -1, 7, 7, 7, 7], map(range(1, line('$')), 'foldclosed(v:val)'))
+ 2,6m$
+ %foldclose
+ call assert_equal(5, foldclosedend(2))
+ call assert_equal(0, foldlevel(6))
+ call assert_equal(9, foldclosedend(7))
+ call assert_equal([-1, 2, 2, 2, 2, -1, 7, 7, 7, -1], map(range(1, line('$')), 'foldclosed(v:val)'))
+
+ %d
+ " Ensure moving around the edges still works.
+ call setline(1, PrepIndent("a") + repeat(["a"], 3) + ["\ta"])
+ set fdm=indent foldlevel=0
+ set fdm=manual
+ %foldopen
+ 6m$
+ " The first fold has been truncated to the 5'th line.
+ " Second fold has been moved up because the moved line is now below it.
+ call Check_foldlevels([0, 1, 1, 1, 1, 0, 0, 0, 1, 0])
+
+ %delete
+ set fdm=indent foldlevel=0
+ call setline(1, [
+ \ "a",
+ \ "\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "a",
+ \ "a"])
+ set fdm=manual
+ %foldopen!
+ 4,5m6
+ call Check_foldlevels([0, 1, 2, 0, 0, 0, 0])
+
+ %delete
+ set fdm=indent
+ call setline(1, [
+ \ "\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\t\ta",
+ \ "\ta",
+ \ "a"])
+ set fdm=manual
+ %foldopen!
+ 13m7
+ call Check_foldlevels([1, 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 2, 1, 0])
+
+ bw!
+endfunc
+
+func Test_move_folds_around_indent()
+ new
+ let input = PrepIndent("a") + PrepIndent("b") + PrepIndent("c")
+ call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+ let folds=[-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14]
+ " all folds closed
+ set fdm=indent
+ call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+ call assert_equal(input, getline(1, '$'))
+ 7,12m0
+ call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+ call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+ 10,12m0
+ call assert_equal(PrepIndent("a")[1:] + PrepIndent("b") + ["a"] + PrepIndent("c"), getline(1, '$'))
+ call assert_equal([1, 1, 1, 1, 1, -1, 7, 7, 7, 7, 7, -1, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+ " moving should not close the folds
+ %d
+ call setline(1, PrepIndent("a") + PrepIndent("b") + PrepIndent("c"))
+ set fdm=indent
+ call cursor(2, 1)
+ %foldopen
+ 7,12m0
+ let folds=repeat([-1], 18)
+ call assert_equal(PrepIndent("b") + PrepIndent("a") + PrepIndent("c"), getline(1, '$'))
+ call assert_equal(folds, map(range(1, line('$')), 'foldclosed(v:val)'))
+ norm! zM
+ " folds are not corrupted and all have been closed
+ call assert_equal([-1, 2, 2, 2, 2, 2, -1, 8, 8, 8, 8, 8, -1, 14, 14, 14, 14, 14], map(range(1, line('$')), 'foldclosed(v:val)'))
+ %d
+ call setline(1, ["a", "\tb", "\tc", "\td", "\te"])
+ set fdm=indent
+ %foldopen
+ 3m4
+ %foldclose
+ call assert_equal(["a", "\tb", "\td", "\tc", "\te"], getline(1, '$'))
+ call assert_equal([-1, 5, 5, 5, 5], map(range(1, line('$')), 'foldclosedend(v:val)'))
+ %d
+ call setline(1, ["a", "\tb", "\tc", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"])
+ set fdm=indent foldlevel=0
+ %foldopen
+ 3m1
+ %foldclose
+ call assert_equal(["a", "\tc", "\tb", "\td", "\te", "z", "\ty", "\tx", "\tw", "\tv"], getline(1, '$'))
+ call assert_equal(1, foldlevel(2))
+ call assert_equal(5, foldclosedend(3))
+ call assert_equal([-1, 2, 2, 2, 2, -1, 7, 7, 7, 7], map(range(1, line('$')), 'foldclosed(v:val)'))
+ 2,6m$
+ %foldclose
+ call assert_equal(9, foldclosedend(2))
+ call assert_equal(1, foldlevel(6))
+ call assert_equal(9, foldclosedend(7))
+ call assert_equal([-1, 2, 2, 2, 2, 2, 2, 2, 2, -1], map(range(1, line('$')), 'foldclosed(v:val)'))
+ " Ensure moving around the edges still works.
+ %d
+ call setline(1, PrepIndent("a") + repeat(["a"], 3) + ["\ta"])
+ set fdm=indent foldlevel=0
+ %foldopen
+ 6m$
+ " The first fold has been truncated to the 5'th line.
+ " Second fold has been moved up because the moved line is now below it.
+ call Check_foldlevels([0, 1, 1, 1, 1, 0, 0, 0, 1, 1])
+ bw!
+endfunc
+
+func Test_folddoopen_folddoclosed()
+ new
+ call setline(1, range(1, 9))
+ set foldmethod=manual
+ 1,3 fold
+ 6,8 fold
+
+ " Test without range.
+ folddoopen s/$/o/
+ folddoclosed s/$/c/
+ call assert_equal(['1c', '2c', '3c',
+ \ '4o', '5o',
+ \ '6c', '7c', '8c',
+ \ '9o'], getline(1, '$'))
+
+ " Test with range.
+ call setline(1, range(1, 9))
+ 1,8 folddoopen s/$/o/
+ 4,$ folddoclosed s/$/c/
+ call assert_equal(['1', '2', '3',
+ \ '4o', '5o',
+ \ '6c', '7c', '8c',
+ \ '9'], getline(1, '$'))
+
+ set foldmethod&
+ bw!
+endfunc
+
+func Test_fold_error()
+ new
+ call setline(1, [1, 2])
+
+ for fm in ['indent', 'expr', 'syntax', 'diff']
+ exe 'set foldmethod=' . fm
+ call assert_fails('norm zf', 'E350:')
+ call assert_fails('norm zd', 'E351:')
+ call assert_fails('norm zE', 'E352:')
+ endfor
+
+ set foldmethod=manual
+ call assert_fails('norm zd', 'E490:')
+ call assert_fails('norm zo', 'E490:')
+ call assert_fails('3fold', 'E16:')
+
+ set foldmethod=marker
+ set nomodifiable
+ call assert_fails('1,2fold', 'E21:')
+
+ set modifiable&
+ set foldmethod&
+ bw!
+endfunc
+
+func Test_foldtext_recursive()
+ new
+ call setline(1, ['{{{', 'some text', '}}}'])
+ setlocal foldenable foldmethod=marker foldtext=foldtextresult(v\:foldstart)
+ " This was crashing because of endless recursion.
+ 2foldclose
+ redraw
+ call assert_equal(1, foldlevel(2))
+ call assert_equal(1, foldclosed(2))
+ call assert_equal(3, foldclosedend(2))
+ bwipe!
+endfunc
+
+" Various fold related tests
+
+" Basic test if a fold can be created, opened, moving to the end and closed
+func Test_fold_manual()
+ new
+ set fdm=manual
+
+ let content = ['1 aa', '2 bb', '3 cc']
+ call append(0, content)
+ call cursor(1, 1)
+ normal zf2j
+ call assert_equal('1 aa', getline(foldclosed('.')))
+ normal zo
+ call assert_equal(-1, foldclosed('.'))
+ normal ]z
+ call assert_equal('3 cc', getline('.'))
+ normal zc
+ call assert_equal('1 aa', getline(foldclosed('.')))
+
+ " Create a fold inside a closed fold after setting 'foldlevel'
+ %d _
+ call setline(1, range(1, 5))
+ 1,5fold
+ normal zR
+ 2,4fold
+ set foldlevel=1
+ 3fold
+ call assert_equal([1, 3, 3, 3, 1], map(range(1, 5), {->foldlevel(v:val)}))
+ set foldlevel&
+
+ " Create overlapping folds (at the start and at the end)
+ normal zE
+ 2,3fold
+ normal zR
+ 3,4fold
+ call assert_equal([0, 2, 2, 1, 0], map(range(1, 5), {->foldlevel(v:val)}))
+ normal zE
+ 3,4fold
+ normal zR
+ 2,3fold
+ call assert_equal([0, 1, 2, 2, 0], map(range(1, 5), {->foldlevel(v:val)}))
+
+ " Create a nested fold across two non-adjoining folds
+ %d _
+ call setline(1, range(1, 7))
+ 1,2fold
+ normal zR
+ 4,5fold
+ normal zR
+ 6,7fold
+ normal zR
+ 1,5fold
+ call assert_equal([2, 2, 1, 2, 2, 1, 1],
+ \ map(range(1, 7), {->foldlevel(v:val)}))
+
+ " A newly created nested fold should be closed
+ %d _
+ call setline(1, range(1, 6))
+ 1,6fold
+ normal zR
+ 3,4fold
+ normal zR
+ 2,5fold
+ call assert_equal([1, 2, 3, 3, 2, 1], map(range(1, 6), {->foldlevel(v:val)}))
+ call assert_equal(2, foldclosed(4))
+ call assert_equal(5, foldclosedend(4))
+
+ " Test zO, zC and zA on a line with no folds.
+ normal zE
+ call assert_fails('normal zO', 'E490:')
+ call assert_fails('normal zC', 'E490:')
+ call assert_fails('normal zA', 'E490:')
+
+ set fdm&
+ bw!
+endfunc
+
+" test folding with markers.
+func Test_fold_marker()
+ new
+ set fdm=marker fdl=1 fdc=3
+
+ let content = ['4 dd {{{', '5 ee {{{ }}}', '6 ff }}}']
+ call append(0, content)
+ call cursor(2, 1)
+ call assert_equal(2, foldlevel('.'))
+ normal [z
+ call assert_equal(1, foldlevel('.'))
+ exe "normal jo{{ \<Esc>r{jj"
+ call assert_equal(1, foldlevel('.'))
+ normal kYpj
+ call assert_equal(0, foldlevel('.'))
+
+ " Use only closing fold marker (without and with a count)
+ set fdl&
+ %d _
+ call setline(1, ['one }}}', 'two'])
+ call assert_equal([0, 0], [foldlevel(1), foldlevel(2)])
+ %d _
+ call setline(1, ['one }}}4', 'two'])
+ call assert_equal([4, 3], [foldlevel(1), foldlevel(2)])
+
+ set fdm& fdl& fdc&
+ bw!
+endfunc
+
+" test create fold markers with C filetype
+func Test_fold_create_marker_in_C()
+ bw!
+ set fdm=marker fdl=9
+ set filetype=c
+
+ let content =<< trim [CODE]
+ /*
+ * comment
+ *
+ *
+ */
+ int f(int* p) {
+ *p = 3;
+ return 0;
+ }
+ [CODE]
+
+ for c in range(len(content) - 1)
+ bw!
+ call append(0, content)
+ call cursor(c + 1, 1)
+ norm! zfG
+ call assert_equal(content[c] . (c < 4 ? '{{{' : '/*{{{*/'), getline(c + 1))
+ endfor
+
+ set fdm& fdl&
+ bw!
+endfunc
+
+" test folding with indent
+func Test_fold_indent()
+ new
+ set fdm=indent sw=2
+
+ let content = ['1 aa', '2 bb', '3 cc']
+ call append(0, content)
+ call cursor(2, 1)
+ exe "normal i \<Esc>jI "
+ call assert_equal(2, foldlevel('.'))
+ normal k
+ call assert_equal(1, foldlevel('.'))
+
+ set fdm& sw&
+ bw!
+endfunc
+
+" test syntax folding
+func Test_fold_syntax()
+ CheckFeature syntax
+
+ new
+ set fdm=syntax fdl=0
+
+ syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3
+ syn region Fd1 start="ee" end="ff" fold contained
+ syn region Fd2 start="gg" end="hh" fold contained
+ syn region Fd3 start="commentstart" end="commentend" fold contained
+ let content = ['3 cc', '4 dd {{{', '5 ee {{{ }}}', '{{{{', '6 ff }}}',
+ \ '6 ff }}}', '7 gg', '8 hh', '9 ii']
+ call append(0, content)
+ normal Gzk
+ call assert_equal('9 ii', getline('.'))
+ normal k
+ call assert_equal('3 cc', getline('.'))
+ exe "normal jAcommentstart \<Esc>Acommentend"
+ set fdl=1
+ normal 3j
+ call assert_equal('7 gg', getline('.'))
+ set fdl=0
+ exe "normal zO\<C-L>j"
+ call assert_equal('8 hh', getline('.'))
+ syn clear Fd1 Fd2 Fd3 Hup
+
+ set fdm& fdl&
+ bw!
+endfunc
+
+func Flvl()
+ let l = getline(v:lnum)
+ if l =~ "bb$"
+ return 2
+ elseif l =~ "gg$"
+ return "s1"
+ elseif l =~ "ii$"
+ return ">2"
+ elseif l =~ "kk$"
+ return "0"
+ endif
+ return "="
+endfun
+
+" test expression folding
+func Test_fold_expr()
+ new
+ set fdm=expr fde=Flvl()
+
+ let content = ['1 aa',
+ \ '2 bb',
+ \ '3 cc',
+ \ '4 dd {{{commentstart commentend',
+ \ '5 ee {{{ }}}',
+ \ '{{{',
+ \ '6 ff }}}',
+ \ '6 ff }}}',
+ \ ' 7 gg',
+ \ ' 8 hh',
+ \ '9 ii',
+ \ 'a jj',
+ \ 'b kk']
+ call append(0, content)
+ call cursor(1, 1)
+ exe "normal /bb$\<CR>"
+ call assert_equal(2, foldlevel('.'))
+ exe "normal /hh$\<CR>"
+ call assert_equal(1, foldlevel('.'))
+ exe "normal /ii$\<CR>"
+ call assert_equal(2, foldlevel('.'))
+ exe "normal /kk$\<CR>"
+ call assert_equal(0, foldlevel('.'))
+
+ set fdm& fde&
+ bw!
+endfunc
+
+" Bug with fdm=indent and moving folds
+" Moving a fold a few times, messes up the folds below the moved fold.
+" Fixed by 7.4.700
+func Test_fold_move()
+ new
+ set fdm=indent sw=2 fdl=0
+
+ let content = ['', '', 'Line1', ' Line2', ' Line3',
+ \ 'Line4', ' Line5', ' Line6',
+ \ 'Line7', ' Line8', ' Line9']
+ call append(0, content)
+ normal zM
+ call cursor(4, 1)
+ move 2
+ move 1
+ call assert_equal(7, foldclosed(7))
+ call assert_equal(8, foldclosedend(7))
+ call assert_equal(0, foldlevel(9))
+ call assert_equal(10, foldclosed(10))
+ call assert_equal(11, foldclosedend(10))
+ call assert_equal('+-- 2 lines: Line2', foldtextresult(2))
+ call assert_equal('+-- 2 lines: Line8', 10->foldtextresult())
+
+ set fdm& sw& fdl&
+ bw!
+endfunc
+
+" test for patch 7.3.637
+" Cannot catch the error caused by a foldopen when there is no fold.
+func Test_foldopen_exception()
+ new
+ let a = 'No error caught'
+ try
+ foldopen
+ catch
+ let a = matchstr(v:exception,'^[^ ]*')
+ endtry
+ call assert_equal('Vim(foldopen):E490:', a)
+
+ let a = 'No error caught'
+ try
+ foobar
+ catch
+ let a = matchstr(v:exception,'^[^ ]*')
+ endtry
+ call assert_match('E492:', a)
+ bw!
+endfunc
+
+func Test_fold_last_line_with_pagedown()
+ new
+ set fdm=manual
+
+ let expect = '+-- 11 lines: 9---'
+ let content = range(1,19)
+ call append(0, content)
+ normal dd9G
+ normal zfG
+ normal zt
+ call assert_equal('9', getline(foldclosed('.')))
+ call assert_equal('19', getline(foldclosedend('.')))
+ call assert_equal(expect, ScreenLines(1, len(expect))[0])
+ call feedkeys("\<C-F>", 'xt')
+ call assert_equal(expect, ScreenLines(1, len(expect))[0])
+ call feedkeys("\<C-F>", 'xt')
+ call assert_equal(expect, ScreenLines(1, len(expect))[0])
+ call feedkeys("\<C-B>\<C-F>\<C-F>", 'xt')
+ call assert_equal(expect, ScreenLines(1, len(expect))[0])
+
+ set fdm&
+ bw!
+endfunc
+
+func Test_folds_with_rnu()
+ CheckScreendump
+
+ call writefile([
+ \ 'set fdm=marker rnu foldcolumn=2',
+ \ 'call setline(1, ["{{{1", "nline 1", "{{{1", "line 2"])',
+ \ ], 'Xtest_folds_with_rnu', 'D')
+ let buf = RunVimInTerminal('-S Xtest_folds_with_rnu', {})
+
+ call VerifyScreenDump(buf, 'Test_folds_with_rnu_01', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_folds_with_rnu_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_folds_marker_in_comment2()
+ new
+ call setline(1, ['Lorem ipsum dolor sit', 'Lorem ipsum dolor sit', 'Lorem ipsum dolor sit'])
+ setl fen fdm=marker
+ setl commentstring=<!--%s-->
+ setl comments=s:<!--,m:\ \ \ \ ,e:-->
+ norm! zf2j
+ setl nofen
+ :1y
+ call assert_equal(['Lorem ipsum dolor sit<!--{{{-->'], getreg(0,1,1))
+ :+2y
+ call assert_equal(['Lorem ipsum dolor sit<!--}}}-->'], getreg(0,1,1))
+
+ set foldmethod&
+ bwipe!
+endfunc
+
+func Test_fold_delete_with_marker()
+ new
+ call setline(1, ['func Func() {{{1', 'endfunc'])
+ 1,2yank
+ new
+ set fdm=marker
+ call setline(1, 'x')
+ normal! Vp
+ normal! zd
+ call assert_equal(['func Func() ', 'endfunc'], getline(1, '$'))
+
+ set fdm&
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_fold_delete_with_marker_and_whichwrap()
+ new
+ let content1 = ['']
+ let content2 = ['folded line 1 "{{{1', ' test', ' test2', ' test3', '', 'folded line 2 "{{{1', ' test', ' test2', ' test3']
+ call setline(1, content1 + content2)
+ set fdm=marker ww+=l
+ normal! x
+ call assert_equal(content2, getline(1, '$'))
+ set fdm& ww&
+ bwipe!
+endfunc
+
+func Test_fold_delete_first_line()
+ new
+ call setline(1, [
+ \ '" x {{{1',
+ \ '" a',
+ \ '" aa',
+ \ '" x {{{1',
+ \ '" b',
+ \ '" bb',
+ \ '" x {{{1',
+ \ '" c',
+ \ '" cc',
+ \ ])
+ set foldmethod=marker
+ 1
+ normal dj
+ call assert_equal([
+ \ '" x {{{1',
+ \ '" c',
+ \ '" cc',
+ \ ], getline(1,'$'))
+ bwipe!
+ set foldmethod&
+endfunc
+
+" Add a test for deleting the outer fold of a nested fold and promoting the
+" inner folds to one level up with already a fold at that level following the
+" nested fold.
+func Test_fold_delete_recursive_fold()
+ new
+ call setline(1, range(1, 7))
+ 2,3fold
+ normal zR
+ 4,5fold
+ normal zR
+ 1,5fold
+ normal zR
+ 6,7fold
+ normal zR
+ normal 1Gzd
+ normal 1Gzj
+ call assert_equal(2, line('.'))
+ normal zj
+ call assert_equal(4, line('.'))
+ normal zj
+ call assert_equal(6, line('.'))
+ bw!
+endfunc
+
+" Test for errors in 'foldexpr'
+func Test_fold_expr_error()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ " In a window with no folds, foldlevel() should return 0
+ call assert_equal(0, foldlevel(1))
+
+ " Return a list from the expression
+ set foldexpr=[]
+ set foldmethod=expr
+ for i in range(3)
+ call assert_equal(0, foldlevel(i))
+ endfor
+
+ " expression error
+ set foldexpr=[{]
+ set foldmethod=expr
+ for i in range(3)
+ call assert_equal(0, foldlevel(i))
+ endfor
+
+ set foldmethod& foldexpr&
+ close!
+endfunc
+
+func Test_undo_fold_deletion()
+ new
+ set fdm=marker
+ let lines =<< trim END
+ " {{{
+ " }}}1
+ " {{{
+ END
+ call setline(1, lines)
+ 3d
+ g/"/d
+ undo
+ redo
+ eval getline(1, '$')->assert_equal([''])
+
+ set fdm&vim
+ bwipe!
+endfunc
+
+" this was crashing
+func Test_move_no_folds()
+ new
+ fold
+ setlocal fdm=expr
+ normal zj
+ bwipe!
+endfunc
+
+" this was crashing
+func Test_fold_create_delete_create()
+ new
+ fold
+ fold
+ normal zd
+ fold
+ bwipe!
+endfunc
+
+" this was crashing
+func Test_fold_create_delete()
+ new
+ norm zFzFzdzj
+ bwipe!
+endfunc
+
+func Test_fold_relative_move()
+ new
+ set fdm=indent sw=2 wrap tw=80
+
+ let longtext = repeat('x', &columns + 1)
+ let content = [ ' foo', ' ' .. longtext, ' baz',
+ \ longtext,
+ \ ' foo', ' ' .. longtext, ' baz'
+ \ ]
+ call append(0, content)
+
+ normal zM
+
+ for lnum in range(1, 3)
+ call cursor(lnum, 1)
+ call assert_true(foldclosed(line('.')))
+ normal gj
+ call assert_equal(2, winline())
+ endfor
+
+ call cursor(2, 1)
+ call assert_true(foldclosed(line('.')))
+ normal 2gj
+ call assert_equal(3, winline())
+
+ for lnum in range(5, 7)
+ call cursor(lnum, 1)
+ call assert_true(foldclosed(line('.')))
+ normal gk
+ call assert_equal(3, winline())
+ endfor
+
+ call cursor(6, 1)
+ call assert_true(foldclosed(line('.')))
+ normal 2gk
+ call assert_equal(2, winline())
+
+ set fdm& sw& wrap& tw&
+ bw!
+endfunc
+
+" Test for using multibyte characters as 'foldopen', 'foldclose' and
+" 'foldsetp' items in 'fillchars'
+func s:mbyte_fillchar_tests(fo, fc, fs)
+ setlocal foldcolumn=3
+
+ normal zE
+ 1,2fold
+ call assert_equal([a:fc .. ' +-- 2 ', ' three '],
+ \ ScreenLines([1, 2], 10))
+ 1,2foldopen
+ call assert_equal([a:fo .. ' one ', a:fs .. ' two '],
+ \ ScreenLines([1, 2], 7))
+ 1,2foldclose
+ redraw!
+ call assert_equal([a:fc .. ' +-- 2 ', ' three '],
+ \ ScreenLines([1, 2], 10))
+
+ " Two level fold
+ normal zE
+ 2,3fold
+ 1,4fold
+ call assert_equal([a:fc .. ' +-- 4 ', ' five '],
+ \ ScreenLines([1, 2], 10))
+ 1,4foldopen
+ call assert_equal([a:fo .. ' one ', a:fs .. a:fc .. ' +--- 2'],
+ \ ScreenLines([1, 2], 10))
+ 1,4foldopen
+ call assert_equal([a:fo .. ' one ', a:fs .. a:fo .. ' two ',
+ \ a:fs .. a:fs .. ' three '], ScreenLines([1, 3], 10))
+ 2,3foldclose
+ call assert_equal([a:fo .. ' one ', a:fs .. a:fc .. ' +--- 2'],
+ \ ScreenLines([1, 2], 10))
+ 1,4foldclose
+ call assert_equal([a:fc .. ' +-- 4 ', ' five '],
+ \ ScreenLines([1, 2], 10))
+
+ " Three level fold
+ normal zE
+ 3,4fold
+ 2,5fold
+ 1,6fold
+ call assert_equal([a:fc .. ' +-- 6 '], ScreenLines(1, 10))
+ " open all the folds
+ normal zR
+ call assert_equal([
+ \ a:fo .. ' one ',
+ \ a:fs .. a:fo .. ' two ',
+ \ '2' .. a:fo .. ' three ',
+ \ '23 four ',
+ \ a:fs .. a:fs .. ' five ',
+ \ a:fs .. ' six ',
+ \ ], ScreenLines([1, 6], 10))
+ " close the innermost fold
+ 3,4foldclose
+ call assert_equal([
+ \ a:fo .. ' one ',
+ \ a:fs .. a:fo .. ' two ',
+ \ a:fs .. a:fs .. a:fc .. '+---- ',
+ \ a:fs .. a:fs .. ' five ',
+ \ a:fs .. ' six ',
+ \ ], ScreenLines([1, 5], 10))
+ " close the next fold
+ 2,5foldclose
+ call assert_equal([
+ \ a:fo .. ' one ',
+ \ a:fs .. a:fc .. ' +--- 4',
+ \ a:fs .. ' six ',
+ \ ], ScreenLines([1, 3], 10))
+
+ " set the fold column size to 2
+ setlocal fdc=2
+ normal zR
+ call assert_equal([
+ \ a:fo .. ' one ',
+ \ a:fo .. ' two ',
+ \ a:fo .. ' three',
+ \ '3 four ',
+ \ '2 five ',
+ \ a:fs .. ' six ',
+ \ ], ScreenLines([1, 6], 7))
+
+ " set the fold column size to 1
+ setlocal fdc=1
+ normal zR
+ call assert_equal([
+ \ a:fo .. 'one ',
+ \ a:fo .. 'two ',
+ \ a:fo .. 'three ',
+ \ '3four ',
+ \ '2five ',
+ \ a:fs .. 'six ',
+ \ ], ScreenLines([1, 6], 7))
+
+ " Enable number and sign columns and place some signs
+ setlocal fdc=3
+ setlocal number
+ setlocal signcolumn=auto
+ sign define S1 text=->
+ sign place 10 line=3 name=S1
+ call assert_equal([
+ \ a:fo .. ' 1 one ',
+ \ a:fs .. a:fo .. ' 2 two ',
+ \ '2' .. a:fo .. ' -> 3 three',
+ \ '23 4 four ',
+ \ a:fs .. a:fs .. ' 5 five ',
+ \ a:fs .. ' 6 six '
+ \ ], ScreenLines([1, 6], 14))
+
+ " Test with 'rightleft'
+ if has('rightleft')
+ setlocal rightleft
+ let lines = ScreenLines([1, 6], winwidth(0))
+ call assert_equal('o 1 ' .. a:fo,
+ \ strcharpart(lines[0], strchars(lines[0]) - 10, 10))
+ call assert_equal('t 2 ' .. a:fo .. a:fs,
+ \ strcharpart(lines[1], strchars(lines[1]) - 10, 10))
+ call assert_equal('t 3 >- ' .. a:fo .. '2',
+ \ strcharpart(lines[2], strchars(lines[2]) - 10, 10))
+ call assert_equal('f 4 32',
+ \ strcharpart(lines[3], strchars(lines[3]) - 10, 10))
+ call assert_equal('f 5 ' .. a:fs .. a:fs,
+ \ strcharpart(lines[4], strchars(lines[4]) - 10, 10))
+ call assert_equal('s 6 ' .. a:fs,
+ \ strcharpart(lines[5], strchars(lines[5]) - 10, 10))
+ setlocal norightleft
+ endif
+
+ sign unplace *
+ sign undefine S1
+ setlocal number& signcolumn&
+
+ " Add a test with more than 9 folds (and then delete some folds)
+ normal zE
+ for i in range(1, 10)
+ normal zfGzo
+ endfor
+ normal zR
+ call assert_equal([
+ \ a:fo .. a:fo .. ' one ',
+ \ '9> two '
+ \ ], ScreenLines([1, 2], 7))
+ normal 1Gzd
+ call assert_equal([
+ \ a:fo .. a:fo .. ' one ',
+ \ '89 two '
+ \ ], ScreenLines([1, 2], 7))
+ normal 1Gzdzdzdzdzdzdzd
+ call assert_equal([
+ \ a:fo .. a:fo .. ' one ',
+ \ a:fs .. a:fs .. ' two '
+ \ ], ScreenLines([1, 2], 7))
+
+ setlocal foldcolumn& number& signcolumn&
+endfunc
+
+func Test_foldcolumn_multibyte_char()
+ new
+ call setline(1, ['one', 'two', 'three', 'four', 'five', 'six'])
+ setlocal foldenable foldmethod=manual
+
+ " First test with the default setting
+ call s:mbyte_fillchar_tests('-', '+', '|')
+
+ " Use multi-byte characters
+ set fillchars+=foldopen:▾,foldsep:│,foldclose:▸
+ call s:mbyte_fillchar_tests('▾', '▸', '│')
+
+ " Use a mix of multi-byte and single-byte characters
+ set fillchars+=foldopen:¬,foldsep:\|,foldclose:+
+ call s:mbyte_fillchar_tests('¬', '+', '|')
+ set fillchars+=foldopen:+,foldsep:\|,foldclose:¬
+ call s:mbyte_fillchar_tests('+', '¬', '|')
+
+ bw!
+ set foldenable& fdc& fdm& fillchars&
+endfunc
+
+" Test for calling foldlevel() from a fold expression
+let g:FoldLevels = []
+func FoldExpr1(lnum)
+ let f = [a:lnum]
+ for i in range(1, line('$'))
+ call add(f, foldlevel(i))
+ endfor
+ call add(g:FoldLevels, f)
+ return getline(a:lnum)[0] == "\t"
+endfunc
+
+func Test_foldexpr_foldlevel()
+ new
+ call setline(1, ['one', "\ttwo", "\tthree"])
+ setlocal foldmethod=expr
+ setlocal foldexpr=FoldExpr1(v:lnum)
+ setlocal foldenable
+ setlocal foldcolumn=3
+ redraw!
+ call assert_equal([[1, -1, -1, -1], [2, -1, -1, -1], [3, 0, 1, -1]],
+ \ g:FoldLevels)
+ set foldmethod& foldexpr& foldenable& foldcolumn&
+ bw!
+endfunc
+
+" Test for returning different values from a fold expression
+func FoldExpr2(lnum)
+ if a:lnum == 1 || a:lnum == 4
+ return -2
+ elseif a:lnum == 2
+ return 'a1'
+ elseif a:lnum == 3
+ return 's4'
+ endif
+ return '='
+endfunc
+
+func Test_foldexpr_2()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ setlocal foldexpr=FoldExpr2(v:lnum)
+ setlocal foldmethod=expr
+ call assert_equal([0, 1, 1, 0], [foldlevel(1), foldlevel(2), foldlevel(3),
+ \ foldlevel(4)])
+ bw!
+endfunc
+
+" Test for the 'foldclose' option
+func Test_foldclose_opt()
+ CheckScreendump
+
+ let lines =<< trim END
+ set foldmethod=manual foldclose=all foldopen=all
+ call setline(1, ['one', 'two', 'three', 'four'])
+ 2,3fold
+ func XsaveFoldLevels()
+ redraw!
+ call writefile([json_encode([foldclosed(1), foldclosed(2), foldclosed(3),
+ \ foldclosed(4)])], 'Xoutput', 'a')
+ endfunc
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let rows = 10
+ let buf = RunVimInTerminal('-S Xscript', {'rows': rows})
+ call term_wait(buf)
+ call term_sendkeys(buf, ":set noruler\n")
+ call term_wait(buf)
+ call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
+ call term_sendkeys(buf, "2G")
+ call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))})
+ call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
+ call term_sendkeys(buf, "4G")
+ call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))})
+ call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
+ call term_sendkeys(buf, "3G")
+ call WaitForAssert({-> assert_equal('three', term_getline(buf, 3))})
+ call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
+ call term_sendkeys(buf, "1G")
+ call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))})
+ call term_sendkeys(buf, ":call XsaveFoldLevels()\n")
+ call term_sendkeys(buf, "2G")
+ call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))})
+ call term_sendkeys(buf, "k")
+ call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+
+ call assert_equal(['[-1,2,2,-1]', '[-1,-1,-1,-1]', '[-1,2,2,-1]',
+ \ '[-1,-1,-1,-1]', '[-1,2,2,-1]'], readfile('Xoutput'))
+ call delete('Xoutput')
+endfunc
+
+" Test for foldtextresult()
+func Test_foldtextresult()
+ new
+ call assert_equal('', foldtextresult(-1))
+ call assert_equal('', foldtextresult(0))
+ call assert_equal('', foldtextresult(1))
+ call setline(1, ['one', 'two', 'three', 'four'])
+ 2,3fold
+ call assert_equal('', foldtextresult(1))
+ call assert_equal('+-- 2 lines: two', foldtextresult(2))
+ setlocal foldtext=
+ call assert_equal('+-- 2 lines folded ', foldtextresult(2))
+
+ " Fold text for a C comment fold
+ %d _
+ setlocal foldtext&
+ call setline(1, ['', '/*', ' * Comment', ' */', ''])
+ 2,4fold
+ call assert_equal('+-- 3 lines: Comment', foldtextresult(2))
+
+ bw!
+endfunc
+
+" Test for merging two recursive folds when an intermediate line with no fold
+" is removed
+func Test_fold_merge_recursive()
+ new
+ call setline(1, [' one', ' two', 'xxxx', ' three',
+ \ ' four', "\tfive"])
+ setlocal foldmethod=indent shiftwidth=2
+ 3d_
+ %foldclose
+ call assert_equal([1, 5], [foldclosed(5), foldclosedend(1)])
+ bw!
+endfunc
+
+" Test for moving a line which is the start of a fold from a recursive fold to
+" outside. The fold length should reduce.
+func Test_fold_move_foldlevel()
+ new
+ call setline(1, ['a{{{', 'b{{{', 'c{{{', 'd}}}', 'e}}}', 'f}}}', 'g'])
+ setlocal foldmethod=marker
+ normal zR
+ call assert_equal([3, 2, 1], [foldlevel(4), foldlevel(5), foldlevel(6)])
+ 3move 7
+ call assert_equal([2, 1, 0], [foldlevel(3), foldlevel(4), foldlevel(5)])
+ call assert_equal(1, foldlevel(7))
+
+ " Move a line from outside a fold to inside the fold.
+ %d _
+ call setline(1, ['a', 'b{{{', 'c}}}'])
+ normal zR
+ 1move 2
+ call assert_equal([1, 1, 1], [foldlevel(1), foldlevel(2), foldlevel(3)])
+
+ " Move the start of one fold to inside another fold
+ %d _
+ call setline(1, ['a', 'b{{{', 'c}}}', 'd{{{', 'e}}}'])
+ normal zR
+ call assert_equal([0, 1, 1, 1, 1], [foldlevel(1), foldlevel(2),
+ \ foldlevel(3), foldlevel(4), foldlevel(5)])
+ 1,2move 4
+ call assert_equal([0, 1, 1, 2, 2], [foldlevel(1), foldlevel(2),
+ \ foldlevel(3), foldlevel(4), foldlevel(5)])
+
+ bw!
+endfunc
+
+" Test for using zj and zk to move downwards and upwards to the start and end
+" of the next fold.
+" Test for using [z and ]z in a closed fold to jump to the beginning and end
+" of the fold.
+func Test_fold_jump()
+ new
+ call setline(1, ["\t1", "\t2", "\t\t3", "\t\t4", "\t\t\t5", "\t\t\t6", "\t\t7", "\t\t8", "\t9", "\t10"])
+ setlocal foldmethod=indent
+ normal zR
+ normal zj
+ call assert_equal(3, line('.'))
+ normal zj
+ call assert_equal(5, line('.'))
+ call assert_beeps('normal zj')
+ call assert_equal(5, line('.'))
+ call assert_beeps('normal 9Gzj')
+ call assert_equal(9, line('.'))
+ normal Gzk
+ call assert_equal(8, line('.'))
+ normal zk
+ call assert_equal(6, line('.'))
+ call assert_beeps('normal zk')
+ call assert_equal(6, line('.'))
+ call assert_beeps('normal 2Gzk')
+ call assert_equal(2, line('.'))
+
+ " Using [z or ]z in a closed fold should not move the cursor
+ %d _
+ call setline(1, ["1", "\t2", "\t3", "\t4", "\t5", "\t6", "7"])
+ normal zR4Gzc
+ call assert_equal(4, line('.'))
+ call assert_beeps('normal [z')
+ call assert_equal(4, line('.'))
+ call assert_beeps('normal ]z')
+ call assert_equal(4, line('.'))
+ bw!
+endfunc
+
+" Test for using a script-local function for 'foldexpr'
+func Test_foldexpr_scriptlocal_func()
+ func! s:FoldFunc()
+ let g:FoldLnum = v:lnum
+ endfunc
+ new | only
+ call setline(1, 'abc')
+ let g:FoldLnum = 0
+ set foldmethod=expr foldexpr=s:FoldFunc()
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &g:foldexpr)
+ call assert_equal(1, g:FoldLnum)
+ set foldmethod& foldexpr=
+ bw!
+ new | only
+ call setline(1, 'abc')
+ let g:FoldLnum = 0
+ set foldmethod=expr foldexpr=<SID>FoldFunc()
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &g:foldexpr)
+ call assert_equal(1, g:FoldLnum)
+ bw!
+ call setline(1, 'abc')
+ setlocal foldmethod& foldexpr&
+ setglobal foldmethod=expr foldexpr=s:FoldFunc()
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &g:foldexpr)
+ call assert_equal('0', &foldexpr)
+ enew!
+ call setline(1, 'abc')
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
+ call assert_equal(1, g:FoldLnum)
+ bw!
+ call setline(1, 'abc')
+ setlocal foldmethod& foldexpr&
+ setglobal foldmethod=expr foldexpr=<SID>FoldFunc()
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &g:foldexpr)
+ call assert_equal('0', &foldexpr)
+ enew!
+ call setline(1, 'abc')
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr)
+ call assert_equal(1, g:FoldLnum)
+ set foldmethod& foldexpr&
+ delfunc s:FoldFunc
+ bw!
+endfunc
+
+" Test for using a script-local function for 'foldtext'
+func Test_foldtext_scriptlocal_func()
+ func! s:FoldText()
+ let g:FoldTextArgs = [v:foldstart, v:foldend]
+ return foldtext()
+ endfunc
+ new | only
+ call setline(1, range(50))
+ let g:FoldTextArgs = []
+ set foldtext=s:FoldText()
+ norm! 4Gzf4j
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext)
+ call assert_equal(expand('<SID>') .. 'FoldText()', &g:foldtext)
+ call assert_equal([4, 8], g:FoldTextArgs)
+ set foldtext&
+ bw!
+ new | only
+ call setline(1, range(50))
+ let g:FoldTextArgs = []
+ set foldtext=<SID>FoldText()
+ norm! 8Gzf4j
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext)
+ call assert_equal(expand('<SID>') .. 'FoldText()', &g:foldtext)
+ call assert_equal([8, 12], g:FoldTextArgs)
+ set foldtext&
+ bw!
+ call setline(1, range(50))
+ let g:FoldTextArgs = []
+ setlocal foldtext&
+ setglobal foldtext=s:FoldText()
+ call assert_equal(expand('<SID>') .. 'FoldText()', &g:foldtext)
+ call assert_equal('foldtext()', &foldtext)
+ enew!
+ call setline(1, range(50))
+ norm! 12Gzf4j
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext)
+ call assert_equal([12, 16], g:FoldTextArgs)
+ set foldtext&
+ bw!
+ call setline(1, range(50))
+ let g:FoldTextArgs = []
+ setlocal foldtext&
+ setglobal foldtext=<SID>FoldText()
+ call assert_equal(expand('<SID>') .. 'FoldText()', &g:foldtext)
+ call assert_equal('foldtext()', &foldtext)
+ enew!
+ call setline(1, range(50))
+ norm! 16Gzf4j
+ redraw!
+ call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext)
+ call assert_equal([16, 20], g:FoldTextArgs)
+ set foldtext&
+ bw!
+ delfunc s:FoldText
+endfunc
+
+" Make sure a fold containing a nested fold is split correctly when using
+" foldmethod=indent
+func Test_fold_split()
+ new
+ let lines =<< trim END
+ line 1
+ line 2
+ line 3
+ line 4
+ line 5
+ END
+ call setline(1, lines)
+ setlocal sw=2
+ setlocal foldmethod=indent foldenable
+ call assert_equal([0, 1, 1, 2, 2], range(1, 5)->map('foldlevel(v:val)'))
+ call append(2, 'line 2.5')
+ call assert_equal([0, 1, 0, 1, 2, 2], range(1, 6)->map('foldlevel(v:val)'))
+ 3d
+ call assert_equal([0, 1, 1, 2, 2], range(1, 5)->map('foldlevel(v:val)'))
+ bw!
+endfunc
+
+" Make sure that when you append under a blank line that is under a fold with
+" the same indent level as your appended line, the fold expands across the
+" blank line
+func Test_indent_append_under_blank_line()
+ new
+ let lines =<< trim END
+ line 1
+ line 2
+ line 3
+ END
+ call setline(1, lines)
+ setlocal sw=2
+ setlocal foldmethod=indent foldenable
+ call assert_equal([0, 1, 1], range(1, 3)->map('foldlevel(v:val)'))
+ call append(3, '')
+ call append(4, ' line 5')
+ call assert_equal([0, 1, 1, 1, 1], range(1, 5)->map('foldlevel(v:val)'))
+ bw!
+endfunc
+
+" Make sure that when you delete 1 line of a fold whose length is 2 lines, the
+" fold can't be closed since its length (1) is now less than foldminlines.
+func Test_indent_one_line_fold_close()
+ let lines =<< trim END
+ line 1
+ line 2
+ line 3
+ END
+
+ new
+ setlocal sw=2 foldmethod=indent
+ call setline(1, lines)
+ " open all folds, delete line, then close all folds
+ normal zR
+ 3delete
+ normal zM
+ call assert_equal(-1, foldclosed(2)) " the fold should not be closed
+
+ " Now do the same, but delete line 2 this time; this covers different code.
+ " (Combining this code with the above code doesn't expose both bugs.)
+ 1,$delete
+ call setline(1, lines)
+ normal zR
+ 2delete
+ normal zM
+ call assert_equal(-1, foldclosed(2))
+ bw!
+endfunc
+
+" Make sure that when appending [an indented line then a blank line] right
+" before a single indented line, the resulting extended fold can be closed
+func Test_indent_append_blank_small_fold_close()
+ new
+ setlocal sw=2 foldmethod=indent
+ " at first, the fold at the second line can't be closed since it's smaller
+ " than foldminlines
+ let lines =<< trim END
+ line 1
+ line 4
+ END
+ call setline(1, lines)
+ call append(1, [' line 2', ''])
+ " close all folds
+ normal zM
+ call assert_notequal(-1, foldclosed(2)) " the fold should be closed now
+ bw!
+endfunc
+
+func Test_sort_closed_fold()
+ CheckExecutable sort
+
+ call setline(1, [
+ \ 'Section 1',
+ \ ' how',
+ \ ' now',
+ \ ' brown',
+ \ ' cow',
+ \ 'Section 2',
+ \ ' how',
+ \ ' now',
+ \ ' brown',
+ \ ' cow',
+ \])
+ setlocal foldmethod=indent sw=3
+ normal 2G
+
+ " The "!!" expands to ".,.+3" and must only sort four lines
+ call feedkeys("!!sort\<CR>", 'xt')
+ call assert_equal([
+ \ 'Section 1',
+ \ ' brown',
+ \ ' cow',
+ \ ' how',
+ \ ' now',
+ \ 'Section 2',
+ \ ' how',
+ \ ' now',
+ \ ' brown',
+ \ ' cow',
+ \ ], getline(1, 10))
+
+ bwipe!
+endfunc
+
+func Test_indent_with_L_command()
+ " The "L" command moved the cursor to line zero, causing the text saved for
+ " undo to use line number -1, which caused trouble for undo later.
+ new
+ sil! norm 8R V{zf8=Lu
+ bwipe!
+endfunc
+
+" Make sure that when there is a fold at the bottom of the buffer and a newline
+" character is appended to the line, the fold gets expanded (instead of the new
+" line not being part of the fold).
+func Test_expand_fold_at_bottom_of_buffer()
+ new
+ " create a fold on the only line
+ fold
+ execute "normal A\<CR>"
+ call assert_equal([1, 1], range(1, 2)->map('foldlevel(v:val)'))
+
+ bwipe!
+endfunc
+
+func Test_fold_screenrow_motion()
+ call setline(1, repeat(['aaaa'], 5))
+ 1,4fold
+ norm Ggkzo
+ call assert_equal(1, line('.'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_function_lists.vim b/src/testdir/test_function_lists.vim
new file mode 100644
index 0000000..dd69b83
--- /dev/null
+++ b/src/testdir/test_function_lists.vim
@@ -0,0 +1,107 @@
+" Test to verify that the three function lists:
+"
+" - global_functions[] in src/evalfunc.c
+" - *functions* in runtime/doc/builtin.txt
+" - *function-list* in runtime/doc/usr_41.txt
+"
+" contain the same functions and that the global_functions and
+" ":help functions" lists are in ASCII order.
+
+func Test_function_lists()
+
+ " Delete any files left over from an earlier run of this test.
+ call delete("Xglobal_functions.diff")
+ call delete("Xfunctions.diff")
+ call delete("Xfunction-list.diff")
+
+ " Create a file of the functions in evalfunc.c:global_functions[].
+ enew!
+ read ../evalfunc.c
+ 1,/^static funcentry_T global_functions\[\] =$/d
+ call search('^};$')
+ .,$d
+ v/^ {/d
+ %s/^ {"//
+ %s/".*//
+ w! Xglobal_functions
+
+ " Verify that those functions are in ASCII order.
+ sort u
+ w! Xsorted_global_functions
+ let l:unequal = assert_equalfile("Xsorted_global_functions", "Xglobal_functions",
+ \ "global_functions[] not sorted")
+ if l:unequal && executable("diff")
+ call system("diff -u Xsorted_global_functions Xglobal_functions > Xglobal_functions.diff")
+ endif
+
+ " Create a file of the functions in evalfunc.c:global_functions[] that are
+ " not obsolete, sorted in ASCII order.
+ enew!
+ read ../evalfunc.c
+ 1,/^static funcentry_T global_functions\[\] =$/d
+ call search('^};$')
+ .,$d
+ v/^ {/d
+ g/\/\/ obsolete$/d
+ %s/^ {"//
+ %s/".*//
+ sort u
+ w! ++ff=unix Xsorted_current_global_functions
+
+ " Verify that the ":help functions" list is complete and in ASCII order.
+ enew!
+ if filereadable('../../doc/builtin.txt')
+ " unpacked MS-Windows zip archive
+ read ../../doc/builtin.txt
+ else
+ read ../../runtime/doc/builtin.txt
+ endif
+ call search('^USAGE')
+ 1,.d
+ call search('^==========')
+ .,$d
+ v/^\S/d
+ %s/(.*//
+ let l:lines = getline(1, '$')
+ call uniq(l:lines)
+ call writefile(l:lines, "Xfunctions")
+ let l:unequal = assert_equalfile("Xsorted_current_global_functions", "Xfunctions",
+ \ "\":help functions\" not sorted or incomplete")
+ if l:unequal && executable("diff")
+ call system("diff -u Xsorted_current_global_functions Xfunctions > Xfunctions.diff")
+ endif
+
+ " Verify that the ":help function-list" list is complete.
+ enew!
+ if filereadable('../../doc/usr_41.txt')
+ " unpacked MS-Windows zip archive
+ read ../../doc/usr_41.txt
+ else
+ read ../../runtime/doc/usr_41.txt
+ endif
+ call search('\*function-list\*$')
+ 1,.d
+ call search('^==*$')
+ .,$d
+ v/^\t\S/d
+ %s/(.*//
+ %left
+ sort u
+ w! ++ff=unix Xfunction-list
+ let l:unequal = assert_equalfile("Xsorted_current_global_functions", "Xfunction-list",
+ \ "\":help function-list\" incomplete")
+ if l:unequal && executable("diff")
+ call system("diff -u Xsorted_current_global_functions Xfunction-list > Xfunction-list.diff")
+ endif
+
+ " Clean up.
+ call delete("Xglobal_functions")
+ call delete("Xsorted_global_functions")
+ call delete("Xsorted_current_global_functions")
+ call delete("Xfunctions")
+ call delete("Xfunction-list")
+ enew!
+
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
new file mode 100644
index 0000000..3bea88d
--- /dev/null
+++ b/src/testdir/test_functions.vim
@@ -0,0 +1,3078 @@
+" Tests for various functions.
+
+source shared.vim
+source check.vim
+source term_util.vim
+source screendump.vim
+import './vim9.vim' as v9
+
+" Must be done first, since the alternate buffer must be unset.
+func Test_00_bufexists()
+ call assert_equal(0, bufexists('does_not_exist'))
+ call assert_equal(1, bufexists(bufnr('%')))
+ call assert_equal(0, bufexists(0))
+ new Xfoo
+ let bn = bufnr('%')
+ call assert_equal(1, bufexists(bn))
+ call assert_equal(1, bufexists('Xfoo'))
+ call assert_equal(1, bufexists(getcwd() . '/Xfoo'))
+ call assert_equal(1, bufexists(0))
+ bw
+ call assert_equal(0, bufexists(bn))
+ call assert_equal(0, bufexists('Xfoo'))
+endfunc
+
+func Test_has()
+ call assert_equal(1, has('eval'))
+ call assert_equal(1, has('eval', 1))
+
+ if has('unix')
+ call assert_equal(1, or(has('ttyin'), 1))
+ call assert_equal(0, and(has('ttyout'), 0))
+ call assert_equal(1, has('multi_byte_encoding'))
+ endif
+ call assert_equal(1, has('vcon', 1))
+ call assert_equal(1, has('mouse_gpm_enabled', 1))
+
+ call assert_equal(0, has('nonexistent'))
+ call assert_equal(0, has('nonexistent', 1))
+
+ " Will we ever have patch 9999?
+ let ver = 'patch-' .. v:version / 100 .. '.' .. v:version % 100 .. '.9999'
+ call assert_equal(0, has(ver))
+
+ " There actually isn't a patch 9.0.0, but this is more consistent.
+ call assert_equal(1, has('patch-9.0.0'))
+endfunc
+
+func Test_empty()
+ call assert_equal(1, empty(''))
+ call assert_equal(0, empty('a'))
+
+ call assert_equal(1, empty(0))
+ call assert_equal(1, empty(-0))
+ call assert_equal(0, empty(1))
+ call assert_equal(0, empty(-1))
+
+ call assert_equal(1, empty(0.0))
+ call assert_equal(1, empty(-0.0))
+ call assert_equal(0, empty(1.0))
+ call assert_equal(0, empty(-1.0))
+ call assert_equal(0, empty(1.0/0.0))
+ call assert_equal(0, empty(0.0/0.0))
+
+ call assert_equal(1, empty([]))
+ call assert_equal(0, empty(['a']))
+
+ call assert_equal(1, empty({}))
+ call assert_equal(0, empty({'a':1}))
+
+ call assert_equal(1, empty(v:null))
+ call assert_equal(1, empty(v:none))
+ call assert_equal(1, empty(v:false))
+ call assert_equal(0, empty(v:true))
+
+ if has('channel')
+ call assert_equal(1, empty(test_null_channel()))
+ endif
+ if has('job')
+ call assert_equal(1, empty(test_null_job()))
+ endif
+
+ call assert_equal(0, empty(function('Test_empty')))
+ call assert_equal(0, empty(function('Test_empty', [0])))
+
+ call assert_fails("call empty(test_void())", 'E685:')
+ call assert_fails("call empty(test_unknown())", 'E685:')
+endfunc
+
+func Test_test_void()
+ call assert_fails('echo 1 == test_void()', 'E1031:')
+ call assert_fails('echo 1.0 == test_void()', 'E1031:')
+ call assert_fails('let x = json_encode(test_void())', 'E685:')
+ call assert_fails('let x = copy(test_void())', 'E685:')
+ call assert_fails('let x = copy([test_void()])', 'E1031:')
+endfunc
+
+func Test_islocked()
+ call assert_fails('call islocked(99)', 'E475:')
+ call assert_fails('call islocked("s: x")', 'E488:')
+endfunc
+
+func Test_len()
+ call assert_equal(1, len(0))
+ call assert_equal(2, len(12))
+
+ call assert_equal(0, len(''))
+ call assert_equal(2, len('ab'))
+
+ call assert_equal(0, len([]))
+ call assert_equal(0, len(test_null_list()))
+ call assert_equal(2, len([2, 1]))
+
+ call assert_equal(0, len({}))
+ call assert_equal(0, len(test_null_dict()))
+ call assert_equal(2, len({'a': 1, 'b': 2}))
+
+ call assert_fails('call len(v:none)', 'E701:')
+ call assert_fails('call len({-> 0})', 'E701:')
+endfunc
+
+func Test_max()
+ call assert_equal(0, max([]))
+ call assert_equal(2, max([2]))
+ call assert_equal(2, max([1, 2]))
+ call assert_equal(2, max([1, 2, v:null]))
+
+ call assert_equal(0, max({}))
+ call assert_equal(2, max({'a':1, 'b':2}))
+
+ call assert_fails('call max(1)', 'E712:')
+ call assert_fails('call max(v:none)', 'E712:')
+
+ " check we only get one error
+ call assert_fails('call max([#{}, [1]])', ['E728:', 'E728:'])
+ call assert_fails('call max(#{a: {}, b: [1]})', ['E728:', 'E728:'])
+endfunc
+
+func Test_min()
+ call assert_equal(0, min([]))
+ call assert_equal(2, min([2]))
+ call assert_equal(1, min([1, 2]))
+ call assert_equal(0, min([1, 2, v:null]))
+
+ call assert_equal(0, min({}))
+ call assert_equal(1, min({'a':1, 'b':2}))
+
+ call assert_fails('call min(1)', 'E712:')
+ call assert_fails('call min(v:none)', 'E712:')
+ call assert_fails('call min([1, {}])', 'E728:')
+
+ " check we only get one error
+ call assert_fails('call min([[1], #{}])', ['E745:', 'E745:'])
+ call assert_fails('call min(#{a: [1], b: #{}})', ['E745:', 'E745:'])
+endfunc
+
+func Test_strwidth()
+ for aw in ['single', 'double']
+ exe 'set ambiwidth=' . aw
+ call assert_equal(0, strwidth(''))
+ call assert_equal(1, strwidth("\t"))
+ call assert_equal(3, strwidth('Vim'))
+ call assert_equal(4, strwidth(1234))
+ call assert_equal(5, strwidth(-1234))
+
+ call assert_equal(2, strwidth('😉'))
+ call assert_equal(17, strwidth('EÄ¥oÅanÄo ĉiuĵaÅ­de'))
+ call assert_equal((aw == 'single') ? 6 : 7, strwidth('Straße'))
+
+ call assert_fails('call strwidth({->0})', 'E729:')
+ call assert_fails('call strwidth([])', 'E730:')
+ call assert_fails('call strwidth({})', 'E731:')
+ endfor
+
+ call assert_equal(3, strwidth(1.2))
+ call v9.CheckDefAndScriptFailure(['echo strwidth(1.2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1174: String required for argument 1'])
+
+ set ambiwidth&
+endfunc
+
+func Test_str2nr()
+ call assert_equal(0, str2nr(''))
+ call assert_equal(1, str2nr('1'))
+ call assert_equal(1, str2nr(' 1 '))
+
+ call assert_equal(1, str2nr('+1'))
+ call assert_equal(1, str2nr('+ 1'))
+ call assert_equal(1, str2nr(' + 1 '))
+
+ call assert_equal(-1, str2nr('-1'))
+ call assert_equal(-1, str2nr('- 1'))
+ call assert_equal(-1, str2nr(' - 1 '))
+
+ call assert_equal(123456789, str2nr('123456789'))
+ call assert_equal(-123456789, str2nr('-123456789'))
+
+ call assert_equal(5, str2nr('101', 2))
+ call assert_equal(5, '0b101'->str2nr(2))
+ call assert_equal(5, str2nr('0B101', 2))
+ call assert_equal(-5, str2nr('-101', 2))
+ call assert_equal(-5, str2nr('-0b101', 2))
+ call assert_equal(-5, str2nr('-0B101', 2))
+
+ call assert_equal(65, str2nr('101', 8))
+ call assert_equal(65, str2nr('0101', 8))
+ call assert_equal(-65, str2nr('-101', 8))
+ call assert_equal(-65, str2nr('-0101', 8))
+ call assert_equal(65, str2nr('0o101', 8))
+ call assert_equal(65, str2nr('0O0101', 8))
+ call assert_equal(-65, str2nr('-0O101', 8))
+ call assert_equal(-65, str2nr('-0o0101', 8))
+
+ call assert_equal(11259375, str2nr('abcdef', 16))
+ call assert_equal(11259375, str2nr('ABCDEF', 16))
+ call assert_equal(-11259375, str2nr('-ABCDEF', 16))
+ call assert_equal(11259375, str2nr('0xabcdef', 16))
+ call assert_equal(11259375, str2nr('0Xabcdef', 16))
+ call assert_equal(11259375, str2nr('0XABCDEF', 16))
+ call assert_equal(-11259375, str2nr('-0xABCDEF', 16))
+
+ call assert_equal(1, str2nr("1'000'000", 10, 0))
+ call assert_equal(256, str2nr("1'0000'0000", 2, 1))
+ call assert_equal(262144, str2nr("1'000'000", 8, 1))
+ call assert_equal(1000000, str2nr("1'000'000", 10, 1))
+ call assert_equal(1000, str2nr("1'000''000", 10, 1))
+ call assert_equal(65536, str2nr("1'00'00", 16, 1))
+
+ call assert_equal(0, str2nr('0x10'))
+ call assert_equal(0, str2nr('0b10'))
+ call assert_equal(0, str2nr('0o10'))
+ call assert_equal(1, str2nr('12', 2))
+ call assert_equal(1, str2nr('18', 8))
+ call assert_equal(1, str2nr('1g', 16))
+
+ call assert_equal(0, str2nr(v:null))
+ call assert_equal(0, str2nr(v:none))
+
+ call assert_fails('call str2nr([])', 'E730:')
+ call assert_fails('call str2nr({->2})', 'E729:')
+ call assert_equal(1, str2nr(1.2))
+ call v9.CheckDefAndScriptFailure(['echo str2nr(1.2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1174: String required for argument 1'])
+ call assert_fails('call str2nr(10, [])', 'E745:')
+endfunc
+
+func Test_strftime()
+ CheckFunction strftime
+
+ " Format of strftime() depends on system. We assume
+ " that basic formats tested here are available and
+ " identical on all systems which support strftime().
+ "
+ " The 2nd parameter of strftime() is a local time, so the output day
+ " of strftime() can be 17 or 18, depending on timezone.
+ call assert_match('^2017-01-1[78]$', strftime('%Y-%m-%d', 1484695512))
+ "
+ call assert_match('^\d\d\d\d-\(0\d\|1[012]\)-\([012]\d\|3[01]\) \([01]\d\|2[0-3]\):[0-5]\d:\([0-5]\d\|60\)$', '%Y-%m-%d %H:%M:%S'->strftime())
+
+ call assert_fails('call strftime([])', 'E730:')
+ call assert_fails('call strftime("%Y", [])', 'E745:')
+
+ " Check that the time changes after we change the timezone
+ " Save previous timezone value, if any
+ if exists('$TZ')
+ let tz = $TZ
+ endif
+
+ " Force EST and then UTC, save the current hour (24-hour clock) for each
+ let $TZ = 'EST' | let est = strftime('%H')
+ let $TZ = 'UTC' | let utc = strftime('%H')
+
+ " Those hours should be two bytes long, and should not be the same; if they
+ " are, a tzset(3) call may have failed somewhere
+ call assert_equal(strlen(est), 2)
+ call assert_equal(strlen(utc), 2)
+ " TODO: this fails on MS-Windows
+ if has('unix')
+ call assert_notequal(est, utc)
+ endif
+
+ " If we cached a timezone value, put it back, otherwise clear it
+ if exists('tz')
+ let $TZ = tz
+ else
+ unlet $TZ
+ endif
+endfunc
+
+func Test_strptime()
+ CheckFunction strptime
+
+ if exists('$TZ')
+ let tz = $TZ
+ endif
+ let $TZ = 'UTC'
+
+ call assert_equal(1484653763, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23'))
+
+ " Force DST and check that it's considered
+ let $TZ = 'WINTER0SUMMER,J1,J365'
+ call assert_equal(1484653763 - 3600, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23'))
+
+ call assert_fails('call strptime()', 'E119:')
+ call assert_fails('call strptime("xxx")', 'E119:')
+ call assert_equal(0, strptime("%Y", ''))
+ call assert_equal(0, strptime("%Y", "xxx"))
+
+ if exists('tz')
+ let $TZ = tz
+ else
+ unlet $TZ
+ endif
+endfunc
+
+func Test_resolve_unix()
+ CheckUnix
+
+ " Xlink1 -> Xlink2
+ " Xlink2 -> Xlink3
+ silent !ln -s -f Xlink2 Xlink1
+ silent !ln -s -f Xlink3 Xlink2
+ call assert_equal('Xlink3', resolve('Xlink1'))
+ call assert_equal('./Xlink3', resolve('./Xlink1'))
+ call assert_equal('Xlink3/', resolve('Xlink2/'))
+ " FIXME: these tests result in things like "Xlink2/" instead of "Xlink3/"?!
+ "call assert_equal('Xlink3/', resolve('Xlink1/'))
+ "call assert_equal('./Xlink3/', resolve('./Xlink1/'))
+ "call assert_equal(getcwd() . '/Xlink3/', resolve(getcwd() . '/Xlink1/'))
+ call assert_equal(getcwd() . '/Xlink3', resolve(getcwd() . '/Xlink1'))
+
+ " Test resolve() with a symlink cycle.
+ " Xlink1 -> Xlink2
+ " Xlink2 -> Xlink3
+ " Xlink3 -> Xlink1
+ silent !ln -s -f Xlink1 Xlink3
+ call assert_fails('call resolve("Xlink1")', 'E655:')
+ call assert_fails('call resolve("./Xlink1")', 'E655:')
+ call assert_fails('call resolve("Xlink2")', 'E655:')
+ call assert_fails('call resolve("Xlink3")', 'E655:')
+ call delete('Xlink1')
+ call delete('Xlink2')
+ call delete('Xlink3')
+
+ silent !ln -s -f Xresolvedir//Xfile Xresolvelink
+ call assert_equal('Xresolvedir/Xfile', resolve('Xresolvelink'))
+ call delete('Xresolvelink')
+
+ silent !ln -s -f Xlink2/ Xlink1
+ call assert_equal('Xlink2', 'Xlink1'->resolve())
+ call assert_equal('Xlink2/', resolve('Xlink1/'))
+ call delete('Xlink1')
+
+ silent !ln -s -f ./Xlink2 Xlink1
+ call assert_equal('Xlink2', resolve('Xlink1'))
+ call assert_equal('./Xlink2', resolve('./Xlink1'))
+ call delete('Xlink1')
+
+ call assert_equal('/', resolve('/'))
+endfunc
+
+func s:normalize_fname(fname)
+ let ret = substitute(a:fname, '\', '/', 'g')
+ let ret = substitute(ret, '//', '/', 'g')
+ return ret->tolower()
+endfunc
+
+func Test_resolve_win32()
+ CheckMSWindows
+
+ " test for shortcut file
+ if executable('cscript')
+ new Xresfile
+ wq
+ let lines =<< trim END
+ Set fs = CreateObject("Scripting.FileSystemObject")
+ Set ws = WScript.CreateObject("WScript.Shell")
+ Set shortcut = ws.CreateShortcut("Xlink.lnk")
+ shortcut.TargetPath = fs.BuildPath(ws.CurrentDirectory, "Xresfile")
+ shortcut.Save
+ END
+ call writefile(lines, 'link.vbs')
+ silent !cscript link.vbs
+ call delete('link.vbs')
+ call assert_equal(s:normalize_fname(getcwd() . '\Xresfile'), s:normalize_fname(resolve('./Xlink.lnk')))
+ call delete('Xresfile')
+
+ call assert_equal(s:normalize_fname(getcwd() . '\Xresfile'), s:normalize_fname(resolve('./Xlink.lnk')))
+ call delete('Xlink.lnk')
+ else
+ echomsg 'skipped test for shortcut file'
+ endif
+
+ " remove files
+ call delete('Xlink')
+ call delete('Xdir', 'd')
+ call delete('Xresfile')
+
+ " test for symbolic link to a file
+ new Xresfile
+ wq
+ call assert_equal('Xresfile', resolve('Xresfile'))
+ silent !mklink Xlink Xresfile
+ if !v:shell_error
+ call assert_equal(s:normalize_fname(getcwd() . '\Xresfile'), s:normalize_fname(resolve('./Xlink')))
+ call delete('Xlink')
+ else
+ echomsg 'skipped test for symbolic link to a file'
+ endif
+ call delete('Xresfile')
+
+ " test for junction to a directory
+ call mkdir('Xdir')
+ silent !mklink /J Xlink Xdir
+ if !v:shell_error
+ call assert_equal(s:normalize_fname(getcwd() . '\Xdir'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+
+ call delete('Xdir', 'd')
+
+ " test for junction already removed
+ call assert_equal(s:normalize_fname(getcwd() . '\Xlink'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+ call delete('Xlink')
+ else
+ echomsg 'skipped test for junction to a directory'
+ call delete('Xdir', 'd')
+ endif
+
+ " test for symbolic link to a directory
+ call mkdir('Xdir')
+ silent !mklink /D Xlink Xdir
+ if !v:shell_error
+ call assert_equal(s:normalize_fname(getcwd() . '\Xdir'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+
+ call delete('Xdir', 'd')
+
+ " test for symbolic link already removed
+ call assert_equal(s:normalize_fname(getcwd() . '\Xlink'), s:normalize_fname(resolve(getcwd() . '/Xlink')))
+ call delete('Xlink')
+ else
+ echomsg 'skipped test for symbolic link to a directory'
+ call delete('Xdir', 'd')
+ endif
+
+ " test for buffer name
+ new Xbuffile
+ wq
+ silent !mklink Xlink Xbuffile
+ if !v:shell_error
+ edit Xlink
+ call assert_equal('Xlink', bufname('%'))
+ call delete('Xlink')
+ bw!
+ else
+ echomsg 'skipped test for buffer name'
+ endif
+ call delete('Xbuffile')
+
+ " test for reparse point
+ call mkdir('Xdir')
+ call assert_equal('Xdir', resolve('Xdir'))
+ silent !mklink /D Xdirlink Xdir
+ if !v:shell_error
+ w Xdir/text.txt
+ call assert_equal('Xdir/text.txt', resolve('Xdir/text.txt'))
+ call assert_equal(s:normalize_fname(getcwd() . '\Xdir\text.txt'), s:normalize_fname(resolve('Xdirlink\text.txt')))
+ call assert_equal(s:normalize_fname(getcwd() . '\Xdir'), s:normalize_fname(resolve('Xdirlink')))
+ call delete('Xdirlink')
+ else
+ echomsg 'skipped test for reparse point'
+ endif
+
+ call delete('Xdir', 'rf')
+endfunc
+
+func Test_simplify()
+ call assert_equal('', simplify(''))
+ call assert_equal('/', simplify('/'))
+ call assert_equal('/', simplify('/.'))
+ call assert_equal('/', simplify('/..'))
+ call assert_equal('/...', simplify('/...'))
+ call assert_equal('//path', simplify('//path'))
+ if has('unix')
+ call assert_equal('/path', simplify('///path'))
+ call assert_equal('/path', simplify('////path'))
+ endif
+
+ call assert_equal('./dir/file', './dir/file'->simplify())
+ call assert_equal('./dir/file', simplify('.///dir//file'))
+ call assert_equal('./dir/file', simplify('./dir/./file'))
+ call assert_equal('./file', simplify('./dir/../file'))
+ call assert_equal('../dir/file', simplify('dir/../../dir/file'))
+ call assert_equal('./file', simplify('dir/.././file'))
+ call assert_equal('../dir', simplify('./../dir'))
+ call assert_equal('..', simplify('../testdir/..'))
+ call mkdir('Xsimpdir')
+ call assert_equal('.', simplify('Xsimpdir/../.'))
+ call delete('Xsimpdir', 'd')
+
+ call assert_fails('call simplify({->0})', 'E729:')
+ call assert_fails('call simplify([])', 'E730:')
+ call assert_fails('call simplify({})', 'E731:')
+ call assert_equal('1.2', simplify(1.2))
+ call v9.CheckDefAndScriptFailure(['echo simplify(1.2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1174: String required for argument 1'])
+endfunc
+
+func Test_pathshorten()
+ call assert_equal('', pathshorten(''))
+ call assert_equal('foo', pathshorten('foo'))
+ call assert_equal('/foo', '/foo'->pathshorten())
+ call assert_equal('f/', pathshorten('foo/'))
+ call assert_equal('f/bar', pathshorten('foo/bar'))
+ call assert_equal('f/b/foobar', 'foo/bar/foobar'->pathshorten())
+ call assert_equal('/f/b/foobar', pathshorten('/foo/bar/foobar'))
+ call assert_equal('.f/bar', pathshorten('.foo/bar'))
+ call assert_equal('~f/bar', pathshorten('~foo/bar'))
+ call assert_equal('~.f/bar', pathshorten('~.foo/bar'))
+ call assert_equal('.~f/bar', pathshorten('.~foo/bar'))
+ call assert_equal('~/f/bar', pathshorten('~/foo/bar'))
+ call assert_fails('call pathshorten([])', 'E730:')
+
+ " test pathshorten with optional variable to set preferred size of shortening
+ call assert_equal('', pathshorten('', 2))
+ call assert_equal('foo', pathshorten('foo', 2))
+ call assert_equal('/foo', pathshorten('/foo', 2))
+ call assert_equal('fo/', pathshorten('foo/', 2))
+ call assert_equal('fo/bar', pathshorten('foo/bar', 2))
+ call assert_equal('fo/ba/foobar', pathshorten('foo/bar/foobar', 2))
+ call assert_equal('/fo/ba/foobar', pathshorten('/foo/bar/foobar', 2))
+ call assert_equal('.fo/bar', pathshorten('.foo/bar', 2))
+ call assert_equal('~fo/bar', pathshorten('~foo/bar', 2))
+ call assert_equal('~.fo/bar', pathshorten('~.foo/bar', 2))
+ call assert_equal('.~fo/bar', pathshorten('.~foo/bar', 2))
+ call assert_equal('~/fo/bar', pathshorten('~/foo/bar', 2))
+ call assert_fails('call pathshorten([],2)', 'E730:')
+ call assert_notequal('~/fo/bar', pathshorten('~/foo/bar', 3))
+ call assert_equal('~/foo/bar', pathshorten('~/foo/bar', 3))
+ call assert_equal('~/f/bar', pathshorten('~/foo/bar', 0))
+endfunc
+
+func Test_strpart()
+ call assert_equal('de', strpart('abcdefg', 3, 2))
+ call assert_equal('ab', strpart('abcdefg', -2, 4))
+ call assert_equal('abcdefg', 'abcdefg'->strpart(-2))
+ call assert_equal('fg', strpart('abcdefg', 5, 4))
+ call assert_equal('defg', strpart('abcdefg', 3))
+ call assert_equal('', strpart('abcdefg', 10))
+ call assert_fails("let s=strpart('abcdef', [])", 'E745:')
+
+ call assert_equal('lép', strpart('éléphant', 2, 4))
+ call assert_equal('léphant', strpart('éléphant', 2))
+
+ call assert_equal('é', strpart('éléphant', 0, 1, 1))
+ call assert_equal('ép', strpart('éléphant', 3, 2, v:true))
+ call assert_equal('oÌ', strpart('coÌmposed', 1, 1, 1))
+endfunc
+
+func Test_tolower()
+ call assert_equal("", tolower(""))
+
+ " Test with all printable ASCII characters.
+ call assert_equal(' !"#$%&''()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~',
+ \ tolower(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'))
+
+ " Test with a few uppercase diacritics.
+ call assert_equal("aàáâãäåÄăąǎǟǡả", tolower("AÀÃÂÃÄÅĀĂĄÇǞǠẢ"))
+ call assert_equal("bḃḇ", tolower("BḂḆ"))
+ call assert_equal("cçćĉċÄ", tolower("CÇĆĈĊČ"))
+ call assert_equal("dÄđḋá¸á¸‘", tolower("DÄŽÄḊḎá¸"))
+ call assert_equal("eèéêëēĕėęěẻẽ", tolower("EÈÉÊËĒĔĖĘĚẺẼ"))
+ call assert_equal("fḟ ", tolower("FḞ "))
+ call assert_equal("gÄğġģǥǧǵḡ", tolower("GĜĞĠĢǤǦǴḠ"))
+ call assert_equal("hĥħḣḧḩ", tolower("HĤĦḢḦḨ"))
+ call assert_equal("iìíîïĩīĭįiÇỉ", tolower("IÃŒÃÃŽÃĨĪĬĮİÇỈ"))
+ call assert_equal("jĵ", tolower("JĴ"))
+ call assert_equal("kķǩḱḵ", tolower("KĶǨḰḴ"))
+ call assert_equal("lĺļľŀłḻ", tolower("LĹĻĽĿÅḺ"))
+ call assert_equal("mḿá¹", tolower("MḾṀ"))
+ call assert_equal("nñńņňṅṉ", tolower("NÑŃŅŇṄṈ"))
+ call assert_equal("oòóôõöøÅÅÅ‘Æ¡Ç’Ç«Ç­á»", tolower("OÒÓÔÕÖØŌŎÅƠǑǪǬỎ"))
+ call assert_equal("pṕṗ", tolower("PṔṖ"))
+ call assert_equal("q", tolower("Q"))
+ call assert_equal("rŕŗřṙṟ", tolower("RŔŖŘṘṞ"))
+ call assert_equal("sÅ›Åşšṡ", tolower("SŚŜŞŠṠ"))
+ call assert_equal("tţťŧṫṯ", tolower("TŢŤŦṪṮ"))
+ call assert_equal("uùúûüũūŭůűųưǔủ", tolower("UÙÚÛÜŨŪŬŮŰŲƯǓỦ"))
+ call assert_equal("vá¹½", tolower("Vá¹¼"))
+ call assert_equal("wŵáºáºƒáº…ẇ", tolower("WŴẀẂẄẆ"))
+ call assert_equal("xẋáº", tolower("XẊẌ"))
+ call assert_equal("yýŷÿáºá»³á»·á»¹", tolower("YÃŶŸẎỲỶỸ"))
+ call assert_equal("zźżžƶẑẕ", tolower("ZŹŻŽƵáºáº”"))
+
+ " Test with a few lowercase diacritics, which should remain unchanged.
+ call assert_equal("aàáâãäåÄăąǎǟǡả", tolower("aàáâãäåÄăąǎǟǡả"))
+ call assert_equal("bḃḇ", tolower("bḃḇ"))
+ call assert_equal("cçćĉċÄ", tolower("cçćĉċÄ"))
+ call assert_equal("dÄđḋá¸á¸‘", tolower("dÄđḋá¸á¸‘"))
+ call assert_equal("eèéêëēĕėęěẻẽ", tolower("eèéêëēĕėęěẻẽ"))
+ call assert_equal("fḟ", tolower("fḟ"))
+ call assert_equal("gÄğġģǥǧǵḡ", tolower("gÄğġģǥǧǵḡ"))
+ call assert_equal("hĥħḣḧḩẖ", tolower("hĥħḣḧḩẖ"))
+ call assert_equal("iìíîïĩīĭįÇỉ", tolower("iìíîïĩīĭįÇỉ"))
+ call assert_equal("jĵǰ", tolower("jĵǰ"))
+ call assert_equal("kķǩḱḵ", tolower("kķǩḱḵ"))
+ call assert_equal("lĺļľŀłḻ", tolower("lĺļľŀłḻ"))
+ call assert_equal("mḿṠ", tolower("mḿṠ"))
+ call assert_equal("nñńņňʼnṅṉ", tolower("nñńņňʼnṅṉ"))
+ call assert_equal("oòóôõöøÅÅÅ‘Æ¡Ç’Ç«Ç­á»", tolower("oòóôõöøÅÅÅ‘Æ¡Ç’Ç«Ç­á»"))
+ call assert_equal("pṕṗ", tolower("pṕṗ"))
+ call assert_equal("q", tolower("q"))
+ call assert_equal("rŕŗřṙṟ", tolower("rŕŗřṙṟ"))
+ call assert_equal("sÅ›Åşšṡ", tolower("sÅ›Åşšṡ"))
+ call assert_equal("tţťŧṫṯẗ", tolower("tţťŧṫṯẗ"))
+ call assert_equal("uùúûüũūŭůűųưǔủ", tolower("uùúûüũūŭůűųưǔủ"))
+ call assert_equal("vá¹½", tolower("vá¹½"))
+ call assert_equal("wŵáºáºƒáº…ẇẘ", tolower("wŵáºáºƒáº…ẇẘ"))
+ call assert_equal("ẋáº", tolower("ẋáº"))
+ call assert_equal("yýÿŷáºáº™á»³á»·á»¹", tolower("yýÿŷáºáº™á»³á»·á»¹"))
+ call assert_equal("zźżžƶẑẕ", tolower("zźżžƶẑẕ"))
+
+ " According to https://twitter.com/jifa/status/625776454479970304
+ " Ⱥ (U+023A) and Ⱦ (U+023E) are the *only* code points to increase
+ " in length (2 to 3 bytes) when lowercased. So let's test them.
+ call assert_equal("ⱥ ⱦ", tolower("Ⱥ Ⱦ"))
+
+ " This call to tolower with invalid utf8 sequence used to cause access to
+ " invalid memory.
+ call tolower("\xC0\x80\xC0")
+ call tolower("123\xC0\x80\xC0")
+
+ " Test in latin1 encoding
+ let save_enc = &encoding
+ set encoding=latin1
+ call assert_equal("abc", tolower("ABC"))
+ let &encoding = save_enc
+endfunc
+
+func Test_toupper()
+ call assert_equal("", toupper(""))
+
+ " Test with all printable ASCII characters.
+ call assert_equal(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~',
+ \ toupper(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'))
+
+ " Test with a few lowercase diacritics.
+ call assert_equal("AÀÃÂÃÄÅĀĂĄÇǞǠẢ", "aàáâãäåÄăąǎǟǡả"->toupper())
+ call assert_equal("BḂḆ", toupper("bḃḇ"))
+ call assert_equal("CÇĆĈĊČ", toupper("cçćĉċÄ"))
+ call assert_equal("DÄŽÄḊḎá¸", toupper("dÄđḋá¸á¸‘"))
+ call assert_equal("EÈÉÊËĒĔĖĘĚẺẼ", toupper("eèéêëēĕėęěẻẽ"))
+ call assert_equal("FḞ", toupper("fḟ"))
+ call assert_equal("GĜĞĠĢǤǦǴḠ", toupper("gÄğġģǥǧǵḡ"))
+ call assert_equal("HĤĦḢḦḨẖ", toupper("hĥħḣḧḩẖ"))
+ call assert_equal("IÃŒÃÃŽÃĨĪĬĮÇỈ", toupper("iìíîïĩīĭįÇỉ"))
+ call assert_equal("JĴǰ", toupper("jĵǰ"))
+ call assert_equal("KĶǨḰḴ", toupper("kķǩḱḵ"))
+ call assert_equal("LĹĻĽĿÅḺ", toupper("lĺļľŀłḻ"))
+ call assert_equal("MḾṀ ", toupper("mḿṠ"))
+ call assert_equal("NÑŃŅŇʼnṄṈ", toupper("nñńņňʼnṅṉ"))
+ call assert_equal("OÒÓÔÕÖØŌŎÅƠǑǪǬỎ", toupper("oòóôõöøÅÅÅ‘Æ¡Ç’Ç«Ç­á»"))
+ call assert_equal("PṔṖ", toupper("pṕṗ"))
+ call assert_equal("Q", toupper("q"))
+ call assert_equal("RŔŖŘṘṞ", toupper("rŕŗřṙṟ"))
+ call assert_equal("SŚŜŞŠṠ", toupper("sÅ›Åşšṡ"))
+ call assert_equal("TŢŤŦṪṮẗ", toupper("tţťŧṫṯẗ"))
+ call assert_equal("UÙÚÛÜŨŪŬŮŰŲƯǓỦ", toupper("uùúûüũūŭůűųưǔủ"))
+ call assert_equal("Vá¹¼", toupper("vá¹½"))
+ call assert_equal("WŴẀẂẄẆẘ", toupper("wŵáºáºƒáº…ẇẘ"))
+ call assert_equal("ẊẌ", toupper("ẋáº"))
+ call assert_equal("YßŶẎẙỲỶỸ", toupper("yýÿŷáºáº™á»³á»·á»¹"))
+ call assert_equal("ZŹŻŽƵáºáº”", toupper("zźżžƶẑẕ"))
+
+ " Test that uppercase diacritics, which should remain unchanged.
+ call assert_equal("AÀÃÂÃÄÅĀĂĄÇǞǠẢ", toupper("AÀÃÂÃÄÅĀĂĄÇǞǠẢ"))
+ call assert_equal("BḂḆ", toupper("BḂḆ"))
+ call assert_equal("CÇĆĈĊČ", toupper("CÇĆĈĊČ"))
+ call assert_equal("DÄŽÄḊḎá¸", toupper("DÄŽÄḊḎá¸"))
+ call assert_equal("EÈÉÊËĒĔĖĘĚẺẼ", toupper("EÈÉÊËĒĔĖĘĚẺẼ"))
+ call assert_equal("FḞ ", toupper("FḞ "))
+ call assert_equal("GĜĞĠĢǤǦǴḠ", toupper("GĜĞĠĢǤǦǴḠ"))
+ call assert_equal("HĤĦḢḦḨ", toupper("HĤĦḢḦḨ"))
+ call assert_equal("IÃŒÃÃŽÃĨĪĬĮİÇỈ", toupper("IÃŒÃÃŽÃĨĪĬĮİÇỈ"))
+ call assert_equal("JÄ´", toupper("JÄ´"))
+ call assert_equal("KĶǨḰḴ", toupper("KĶǨḰḴ"))
+ call assert_equal("LĹĻĽĿÅḺ", toupper("LĹĻĽĿÅḺ"))
+ call assert_equal("MḾṀ", toupper("MḾṀ"))
+ call assert_equal("NÑŃŅŇṄṈ", toupper("NÑŃŅŇṄṈ"))
+ call assert_equal("OÒÓÔÕÖØŌŎÅƠǑǪǬỎ", toupper("OÒÓÔÕÖØŌŎÅƠǑǪǬỎ"))
+ call assert_equal("PṔṖ", toupper("PṔṖ"))
+ call assert_equal("Q", toupper("Q"))
+ call assert_equal("RŔŖŘṘṞ", toupper("RŔŖŘṘṞ"))
+ call assert_equal("SŚŜŞŠṠ", toupper("SŚŜŞŠṠ"))
+ call assert_equal("TŢŤŦṪṮ", toupper("TŢŤŦṪṮ"))
+ call assert_equal("UÙÚÛÜŨŪŬŮŰŲƯǓỦ", toupper("UÙÚÛÜŨŪŬŮŰŲƯǓỦ"))
+ call assert_equal("Vá¹¼", toupper("Vá¹¼"))
+ call assert_equal("WŴẀẂẄẆ", toupper("WŴẀẂẄẆ"))
+ call assert_equal("XẊẌ", toupper("XẊẌ"))
+ call assert_equal("YÃŶŸẎỲỶỸ", toupper("YÃŶŸẎỲỶỸ"))
+ call assert_equal("ZŹŻŽƵáºáº”", toupper("ZŹŻŽƵáºáº”"))
+
+ call assert_equal("Ⱥ Ⱦ", toupper("ⱥ ⱦ"))
+
+ " This call to toupper with invalid utf8 sequence used to cause access to
+ " invalid memory.
+ call toupper("\xC0\x80\xC0")
+ call toupper("123\xC0\x80\xC0")
+
+ " Test in latin1 encoding
+ let save_enc = &encoding
+ set encoding=latin1
+ call assert_equal("ABC", toupper("abc"))
+ let &encoding = save_enc
+endfunc
+
+func Test_tr()
+ call assert_equal('foo', tr('bar', 'bar', 'foo'))
+ call assert_equal('zxy', 'cab'->tr('abc', 'xyz'))
+ call assert_fails("let s=tr([], 'abc', 'def')", 'E730:')
+ call assert_fails("let s=tr('abc', [], 'def')", 'E730:')
+ call assert_fails("let s=tr('abc', 'abc', [])", 'E730:')
+ call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:')
+ set encoding=latin1
+ call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:')
+ call assert_equal('hEllO', tr('hello', 'eo', 'EO'))
+ call assert_equal('hello', tr('hello', 'xy', 'ab'))
+ call assert_fails('call tr("abc", "123", "â‚â‚‚")', 'E475:')
+ set encoding=utf8
+endfunc
+
+" Tests for the mode() function
+let current_modes = ''
+func Save_mode()
+ let g:current_modes = mode(0) . '-' . mode(1)
+ return ''
+endfunc
+
+" Test for the mode() function
+func Test_mode()
+ new
+ call append(0, ["Blue Ball Black", "Brown Band Bowl", ""])
+
+ " Only complete from the current buffer.
+ set complete=.
+
+ inoremap <F2> <C-R>=Save_mode()<CR>
+ xnoremap <F2> <Cmd>call Save_mode()<CR>
+
+ normal! 3G
+ exe "normal i\<F2>\<Esc>"
+ call assert_equal('i-i', g:current_modes)
+ " i_CTRL-P: Multiple matches
+ exe "normal i\<C-G>uBa\<C-P>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-P: Single match
+ exe "normal iBro\<C-P>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-X
+ exe "normal iBa\<C-X>\<F2>\<Esc>u"
+ call assert_equal('i-ix', g:current_modes)
+ " i_CTRL-X CTRL-P: Multiple matches
+ exe "normal iBa\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-X CTRL-P: Single match
+ exe "normal iBro\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-X CTRL-P + CTRL-P: Single match
+ exe "normal iBro\<C-X>\<C-P>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-X CTRL-L: Multiple matches
+ exe "normal i\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-X CTRL-L: Single match
+ exe "normal iBlu\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-P: No match
+ exe "normal iCom\<C-P>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-X CTRL-P: No match
+ exe "normal iCom\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+ " i_CTRL-X CTRL-L: No match
+ exe "normal iabc\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('i-ic', g:current_modes)
+
+ exe "normal R\<F2>\<Esc>"
+ call assert_equal('R-R', g:current_modes)
+ " R_CTRL-P: Multiple matches
+ exe "normal RBa\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-P: Single match
+ exe "normal RBro\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-X
+ exe "normal RBa\<C-X>\<F2>\<Esc>u"
+ call assert_equal('R-Rx', g:current_modes)
+ " R_CTRL-X CTRL-P: Multiple matches
+ exe "normal RBa\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-X CTRL-P: Single match
+ exe "normal RBro\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-X CTRL-P + CTRL-P: Single match
+ exe "normal RBro\<C-X>\<C-P>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-X CTRL-L: Multiple matches
+ exe "normal R\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-X CTRL-L: Single match
+ exe "normal RBlu\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-P: No match
+ exe "normal RCom\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-X CTRL-P: No match
+ exe "normal RCom\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+ " R_CTRL-X CTRL-L: No match
+ exe "normal Rabc\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('R-Rc', g:current_modes)
+
+ exe "normal gR\<F2>\<Esc>"
+ call assert_equal('R-Rv', g:current_modes)
+ " gR_CTRL-P: Multiple matches
+ exe "normal gRBa\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-P: Single match
+ exe "normal gRBro\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-X
+ exe "normal gRBa\<C-X>\<F2>\<Esc>u"
+ call assert_equal('R-Rvx', g:current_modes)
+ " gR_CTRL-X CTRL-P: Multiple matches
+ exe "normal gRBa\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-X CTRL-P: Single match
+ exe "normal gRBro\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-X CTRL-P + CTRL-P: Single match
+ exe "normal gRBro\<C-X>\<C-P>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-X CTRL-L: Multiple matches
+ exe "normal gR\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-X CTRL-L: Single match
+ exe "normal gRBlu\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-P: No match
+ exe "normal gRCom\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-X CTRL-P: No match
+ exe "normal gRCom\<C-X>\<C-P>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+ " gR_CTRL-X CTRL-L: No match
+ exe "normal gRabc\<C-X>\<C-L>\<F2>\<Esc>u"
+ call assert_equal('R-Rvc', g:current_modes)
+
+ call assert_equal('n', 0->mode())
+ call assert_equal('n', 1->mode())
+
+ " i_CTRL-O
+ exe "normal i\<C-O>:call Save_mode()\<Cr>\<Esc>"
+ call assert_equal("n-niI", g:current_modes)
+
+ " R_CTRL-O
+ exe "normal R\<C-O>:call Save_mode()\<Cr>\<Esc>"
+ call assert_equal("n-niR", g:current_modes)
+
+ " gR_CTRL-O
+ exe "normal gR\<C-O>:call Save_mode()\<Cr>\<Esc>"
+ call assert_equal("n-niV", g:current_modes)
+
+ " How to test operator-pending mode?
+
+ call feedkeys("v", 'xt')
+ call assert_equal('v', mode())
+ call assert_equal('v', mode(1))
+ call feedkeys("\<Esc>V", 'xt')
+ call assert_equal('V', mode())
+ call assert_equal('V', mode(1))
+ call feedkeys("\<Esc>\<C-V>", 'xt')
+ call assert_equal("\<C-V>", mode())
+ call assert_equal("\<C-V>", mode(1))
+ call feedkeys("\<Esc>", 'xt')
+
+ call feedkeys("gh", 'xt')
+ call assert_equal('s', mode())
+ call assert_equal('s', mode(1))
+ call feedkeys("\<Esc>gH", 'xt')
+ call assert_equal('S', mode())
+ call assert_equal('S', mode(1))
+ call feedkeys("\<Esc>g\<C-H>", 'xt')
+ call assert_equal("\<C-S>", mode())
+ call assert_equal("\<C-S>", mode(1))
+ call feedkeys("\<Esc>", 'xt')
+
+ " v_CTRL-O
+ exe "normal gh\<C-O>\<F2>\<Esc>"
+ call assert_equal("v-vs", g:current_modes)
+ exe "normal gH\<C-O>\<F2>\<Esc>"
+ call assert_equal("V-Vs", g:current_modes)
+ exe "normal g\<C-H>\<C-O>\<F2>\<Esc>"
+ call assert_equal("\<C-V>-\<C-V>s", g:current_modes)
+
+ call feedkeys(":echo \<C-R>=Save_mode()\<C-U>\<CR>", 'xt')
+ call assert_equal('c-c', g:current_modes)
+ call feedkeys("gQecho \<C-R>=Save_mode()\<CR>\<CR>vi\<CR>", 'xt')
+ call assert_equal('c-cv', g:current_modes)
+ call feedkeys("Qcall Save_mode()\<CR>vi\<CR>", 'xt')
+ call assert_equal('c-ce', g:current_modes)
+ " How to test Ex mode?
+
+ " Test mode in operatorfunc (it used to be Operator-pending).
+ set operatorfunc=OperatorFunc
+ function OperatorFunc(_)
+ call Save_mode()
+ endfunction
+ execute "normal! g@l\<Esc>"
+ call assert_equal('n-n', g:current_modes)
+ execute "normal! i\<C-o>g@l\<Esc>"
+ call assert_equal('n-niI', g:current_modes)
+ execute "normal! R\<C-o>g@l\<Esc>"
+ call assert_equal('n-niR', g:current_modes)
+ execute "normal! gR\<C-o>g@l\<Esc>"
+ call assert_equal('n-niV', g:current_modes)
+
+ if has('terminal')
+ term
+ call feedkeys("\<C-W>N", 'xt')
+ call assert_equal('n', mode())
+ call assert_equal('nt', mode(1))
+ call feedkeys("aexit\<CR>", 'xt')
+ endif
+
+ bwipe!
+ iunmap <F2>
+ xunmap <F2>
+ set complete&
+ set operatorfunc&
+ delfunction OperatorFunc
+endfunc
+
+" Test for append()
+func Test_append()
+ enew!
+ split
+ call assert_equal(0, append(1, []))
+ call assert_equal(0, append(1, test_null_list()))
+ call assert_equal(0, append(0, ["foo"]))
+ call assert_equal(0, append(1, []))
+ call assert_equal(0, append(1, test_null_list()))
+ call assert_equal(0, append(8, []))
+ call assert_equal(0, append(9, test_null_list()))
+ call assert_equal(['foo', ''], getline(1, '$'))
+ split
+ only
+ undo
+ undo
+
+ " Using $ instead of '$' must give an error
+ call assert_fails("call append($, 'foobar')", 'E116:')
+
+ call assert_fails("call append({}, '')", ['E728:', 'E728:'])
+endfunc
+
+" Test for setline()
+func Test_setline()
+ new
+ call setline(0, ["foo"])
+ call setline(0, [])
+ call setline(0, test_null_list())
+ call setline(1, ["bar"])
+ call setline(1, [])
+ call setline(1, test_null_list())
+ call setline(2, [])
+ call setline(2, test_null_list())
+ call setline(3, [])
+ call setline(3, test_null_list())
+ call setline(2, ["baz"])
+ call assert_equal(['bar', 'baz'], getline(1, '$'))
+ close!
+endfunc
+
+func Test_getbufvar()
+ let bnr = bufnr('%')
+ let b:var_num = '1234'
+ let def_num = '5678'
+ call assert_equal('1234', getbufvar(bnr, 'var_num'))
+ call assert_equal('1234', getbufvar(bnr, 'var_num', def_num))
+
+ let bd = getbufvar(bnr, '')
+ call assert_equal('1234', bd['var_num'])
+ call assert_true(exists("bd['changedtick']"))
+ call assert_equal(2, len(bd))
+
+ let bd2 = getbufvar(bnr, '', def_num)
+ call assert_equal(bd, bd2)
+
+ unlet b:var_num
+ call assert_equal(def_num, getbufvar(bnr, 'var_num', def_num))
+ call assert_equal('', getbufvar(bnr, 'var_num'))
+
+ let bd = getbufvar(bnr, '')
+ call assert_equal(1, len(bd))
+ let bd = getbufvar(bnr, '',def_num)
+ call assert_equal(1, len(bd))
+
+ call assert_equal('', getbufvar(9999, ''))
+ call assert_equal(def_num, getbufvar(9999, '', def_num))
+ unlet def_num
+
+ call assert_equal(0, getbufvar(bnr, '&autoindent'))
+ call assert_equal(0, getbufvar(bnr, '&autoindent', 1))
+
+ " Set and get a buffer-local variable
+ call setbufvar(bnr, 'bufvar_test', ['one', 'two'])
+ call assert_equal(['one', 'two'], getbufvar(bnr, 'bufvar_test'))
+
+ " Open new window with forced option values
+ set fileformats=unix,dos
+ new ++ff=dos ++bin ++enc=iso-8859-2
+ call assert_equal('dos', getbufvar(bufnr('%'), '&fileformat'))
+ call assert_equal(1, getbufvar(bufnr('%'), '&bin'))
+ call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc'))
+ close
+
+ " Get the b: dict.
+ let b:testvar = 'one'
+ new
+ let b:testvar = 'two'
+ let thebuf = bufnr()
+ wincmd w
+ call assert_equal('two', getbufvar(thebuf, 'testvar'))
+ call assert_equal('two', getbufvar(thebuf, '').testvar)
+ bwipe!
+
+ set fileformats&
+endfunc
+
+func Test_last_buffer_nr()
+ call assert_equal(bufnr('$'), last_buffer_nr())
+endfunc
+
+func Test_stridx()
+ call assert_equal(-1, stridx('', 'l'))
+ call assert_equal(0, stridx('', ''))
+ call assert_equal(0, 'hello'->stridx(''))
+ call assert_equal(-1, stridx('hello', 'L'))
+ call assert_equal(2, stridx('hello', 'l', -1))
+ call assert_equal(2, stridx('hello', 'l', 0))
+ call assert_equal(2, 'hello'->stridx('l', 1))
+ call assert_equal(3, stridx('hello', 'l', 3))
+ call assert_equal(-1, stridx('hello', 'l', 4))
+ call assert_equal(-1, stridx('hello', 'l', 10))
+ call assert_equal(2, stridx('hello', 'll'))
+ call assert_equal(-1, stridx('hello', 'hello world'))
+ call assert_fails("let n=stridx('hello', [])", 'E730:')
+ call assert_fails("let n=stridx([], 'l')", 'E730:')
+endfunc
+
+func Test_strridx()
+ call assert_equal(-1, strridx('', 'l'))
+ call assert_equal(0, strridx('', ''))
+ call assert_equal(5, strridx('hello', ''))
+ call assert_equal(-1, strridx('hello', 'L'))
+ call assert_equal(3, 'hello'->strridx('l'))
+ call assert_equal(3, strridx('hello', 'l', 10))
+ call assert_equal(3, strridx('hello', 'l', 3))
+ call assert_equal(2, strridx('hello', 'l', 2))
+ call assert_equal(-1, strridx('hello', 'l', 1))
+ call assert_equal(-1, strridx('hello', 'l', 0))
+ call assert_equal(-1, strridx('hello', 'l', -1))
+ call assert_equal(2, strridx('hello', 'll'))
+ call assert_equal(-1, strridx('hello', 'hello world'))
+ call assert_fails("let n=strridx('hello', [])", 'E730:')
+ call assert_fails("let n=strridx([], 'l')", 'E730:')
+endfunc
+
+func Test_match_func()
+ call assert_equal(4, match('testing', 'ing'))
+ call assert_equal(4, 'testing'->match('ing', 2))
+ call assert_equal(-1, match('testing', 'ing', 5))
+ call assert_equal(-1, match('testing', 'ing', 8))
+ call assert_equal(1, match(['vim', 'testing', 'execute'], 'ing'))
+ call assert_equal(-1, match(['vim', 'testing', 'execute'], 'img'))
+ call assert_fails("let x=match('vim', [])", 'E730:')
+ call assert_equal(3, match(['a', 'b', 'c', 'a'], 'a', 1))
+ call assert_equal(-1, match(['a', 'b', 'c', 'a'], 'a', 5))
+ call assert_equal(4, match('testing', 'ing', -1))
+ call assert_fails("let x=match('testing', 'ing', 0, [])", 'E745:')
+ call assert_equal(-1, match(test_null_list(), 2))
+ call assert_equal(-1, match('abc', '\\%('))
+endfunc
+
+func Test_matchend()
+ call assert_equal(7, matchend('testing', 'ing'))
+ call assert_equal(7, 'testing'->matchend('ing', 2))
+ call assert_equal(-1, matchend('testing', 'ing', 5))
+ call assert_equal(-1, matchend('testing', 'ing', 8))
+ call assert_equal(match(['vim', 'testing', 'execute'], 'ing'), matchend(['vim', 'testing', 'execute'], 'ing'))
+ call assert_equal(match(['vim', 'testing', 'execute'], 'img'), matchend(['vim', 'testing', 'execute'], 'img'))
+endfunc
+
+func Test_matchlist()
+ call assert_equal(['acd', 'a', '', 'c', 'd', '', '', '', '', ''], matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)'))
+ call assert_equal(['d', '', '', '', 'd', '', '', '', '', ''], 'acd'->matchlist('\(a\)\?\(b\)\?\(c\)\?\(.*\)', 2))
+ call assert_equal([], matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)', 4))
+endfunc
+
+func Test_matchstr()
+ call assert_equal('ing', matchstr('testing', 'ing'))
+ call assert_equal('ing', 'testing'->matchstr('ing', 2))
+ call assert_equal('', matchstr('testing', 'ing', 5))
+ call assert_equal('', matchstr('testing', 'ing', 8))
+ call assert_equal('testing', matchstr(['vim', 'testing', 'execute'], 'ing'))
+ call assert_equal('', matchstr(['vim', 'testing', 'execute'], 'img'))
+endfunc
+
+func Test_matchstrpos()
+ call assert_equal(['ing', 4, 7], matchstrpos('testing', 'ing'))
+ call assert_equal(['ing', 4, 7], 'testing'->matchstrpos('ing', 2))
+ call assert_equal(['', -1, -1], matchstrpos('testing', 'ing', 5))
+ call assert_equal(['', -1, -1], matchstrpos('testing', 'ing', 8))
+ call assert_equal(['ing', 1, 4, 7], matchstrpos(['vim', 'testing', 'execute'], 'ing'))
+ call assert_equal(['', -1, -1, -1], matchstrpos(['vim', 'testing', 'execute'], 'img'))
+ call assert_equal(['', -1, -1], matchstrpos(test_null_list(), '\a'))
+endfunc
+
+func Test_nextnonblank_prevnonblank()
+ new
+insert
+This
+
+
+is
+
+a
+Test
+.
+ call assert_equal(0, nextnonblank(-1))
+ call assert_equal(0, nextnonblank(0))
+ call assert_equal(1, nextnonblank(1))
+ call assert_equal(4, 2->nextnonblank())
+ call assert_equal(4, nextnonblank(3))
+ call assert_equal(4, nextnonblank(4))
+ call assert_equal(6, nextnonblank(5))
+ call assert_equal(6, nextnonblank(6))
+ call assert_equal(7, nextnonblank(7))
+ call assert_equal(0, 8->nextnonblank())
+
+ call assert_equal(0, prevnonblank(-1))
+ call assert_equal(0, prevnonblank(0))
+ call assert_equal(1, 1->prevnonblank())
+ call assert_equal(1, prevnonblank(2))
+ call assert_equal(1, prevnonblank(3))
+ call assert_equal(4, prevnonblank(4))
+ call assert_equal(4, 5->prevnonblank())
+ call assert_equal(6, prevnonblank(6))
+ call assert_equal(7, prevnonblank(7))
+ call assert_equal(0, prevnonblank(8))
+ bw!
+endfunc
+
+func Test_byte2line_line2byte()
+ new
+ set endofline
+ call setline(1, ['a', 'bc', 'd'])
+
+ set fileformat=unix
+ call assert_equal([-1, -1, 1, 1, 2, 2, 2, 3, 3, -1],
+ \ map(range(-1, 8), 'byte2line(v:val)'))
+ call assert_equal([-1, -1, 1, 3, 6, 8, -1],
+ \ map(range(-1, 5), 'line2byte(v:val)'))
+
+ set fileformat=mac
+ call assert_equal([-1, -1, 1, 1, 2, 2, 2, 3, 3, -1],
+ \ map(range(-1, 8), 'v:val->byte2line()'))
+ call assert_equal([-1, -1, 1, 3, 6, 8, -1],
+ \ map(range(-1, 5), 'v:val->line2byte()'))
+
+ set fileformat=dos
+ call assert_equal([-1, -1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, -1],
+ \ map(range(-1, 11), 'byte2line(v:val)'))
+ call assert_equal([-1, -1, 1, 4, 8, 11, -1],
+ \ map(range(-1, 5), 'line2byte(v:val)'))
+
+ bw!
+ set noendofline nofixendofline
+ normal a-
+ for ff in ["unix", "mac", "dos"]
+ let &fileformat = ff
+ call assert_equal(1, line2byte(1))
+ call assert_equal(2, line2byte(2)) " line2byte(line("$") + 1) is the buffer size plus one (as per :help line2byte).
+ endfor
+
+ set endofline& fixendofline& fileformat&
+ bw!
+endfunc
+
+" Test for byteidx() and byteidxcomp() functions
+func Test_byteidx()
+ let a = '.é.' " one char of two bytes
+ call assert_equal(0, byteidx(a, 0))
+ call assert_equal(0, byteidxcomp(a, 0))
+ call assert_equal(1, byteidx(a, 1))
+ call assert_equal(1, byteidxcomp(a, 1))
+ call assert_equal(3, byteidx(a, 2))
+ call assert_equal(3, byteidxcomp(a, 2))
+ call assert_equal(4, byteidx(a, 3))
+ call assert_equal(4, byteidxcomp(a, 3))
+ call assert_equal(-1, byteidx(a, 4))
+ call assert_equal(-1, byteidxcomp(a, 4))
+
+ let b = '.eÌ.' " normal e with composing char
+ call assert_equal(0, b->byteidx(0))
+ call assert_equal(1, b->byteidx(1))
+ call assert_equal(4, b->byteidx(2))
+ call assert_equal(5, b->byteidx(3))
+ call assert_equal(-1, b->byteidx(4))
+ call assert_fails("call byteidx([], 0)", 'E730:')
+
+ call assert_equal(0, b->byteidxcomp(0))
+ call assert_equal(1, b->byteidxcomp(1))
+ call assert_equal(2, b->byteidxcomp(2))
+ call assert_equal(4, b->byteidxcomp(3))
+ call assert_equal(5, b->byteidxcomp(4))
+ call assert_equal(-1, b->byteidxcomp(5))
+ call assert_fails("call byteidxcomp([], 0)", 'E730:')
+endfunc
+
+" Test for charidx()
+func Test_charidx()
+ let a = 'xaÌbÌy'
+ call assert_equal(0, charidx(a, 0))
+ call assert_equal(1, charidx(a, 3))
+ call assert_equal(2, charidx(a, 4))
+ call assert_equal(3, charidx(a, 7))
+ call assert_equal(-1, charidx(a, 8))
+ call assert_equal(-1, charidx(a, -1))
+ call assert_equal(-1, charidx('', 0))
+ call assert_equal(-1, charidx(test_null_string(), 0))
+
+ " count composing characters
+ call assert_equal(0, charidx(a, 0, 1))
+ call assert_equal(2, charidx(a, 2, 1))
+ call assert_equal(3, charidx(a, 4, 1))
+ call assert_equal(5, charidx(a, 7, 1))
+ call assert_equal(-1, charidx(a, 8, 1))
+ call assert_equal(-1, charidx('', 0, 1))
+
+ call assert_fails('let x = charidx([], 1)', 'E1174:')
+ call assert_fails('let x = charidx("abc", [])', 'E1210:')
+ call assert_fails('let x = charidx("abc", 1, [])', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, -1)', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, 2)', 'E1212:')
+endfunc
+
+func Test_count()
+ let l = ['a', 'a', 'A', 'b']
+ call assert_equal(2, count(l, 'a'))
+ call assert_equal(1, count(l, 'A'))
+ call assert_equal(1, count(l, 'b'))
+ call assert_equal(0, count(l, 'B'))
+
+ call assert_equal(2, count(l, 'a', 0))
+ call assert_equal(1, count(l, 'A', 0))
+ call assert_equal(1, count(l, 'b', 0))
+ call assert_equal(0, count(l, 'B', 0))
+
+ call assert_equal(3, count(l, 'a', 1))
+ call assert_equal(3, count(l, 'A', 1))
+ call assert_equal(1, count(l, 'b', 1))
+ call assert_equal(1, count(l, 'B', 1))
+ call assert_equal(0, count(l, 'c', 1))
+
+ call assert_equal(1, count(l, 'a', 0, 1))
+ call assert_equal(2, count(l, 'a', 1, 1))
+ call assert_fails('call count(l, "a", 0, 10)', 'E684:')
+ call assert_fails('call count(l, "a", [])', 'E745:')
+
+ let d = {1: 'a', 2: 'a', 3: 'A', 4: 'b'}
+ call assert_equal(2, count(d, 'a'))
+ call assert_equal(1, count(d, 'A'))
+ call assert_equal(1, count(d, 'b'))
+ call assert_equal(0, count(d, 'B'))
+
+ call assert_equal(2, count(d, 'a', 0))
+ call assert_equal(1, count(d, 'A', 0))
+ call assert_equal(1, count(d, 'b', 0))
+ call assert_equal(0, count(d, 'B', 0))
+
+ call assert_equal(3, count(d, 'a', 1))
+ call assert_equal(3, count(d, 'A', 1))
+ call assert_equal(1, count(d, 'b', 1))
+ call assert_equal(1, count(d, 'B', 1))
+ call assert_equal(0, count(d, 'c', 1))
+
+ call assert_fails('call count(d, "a", 0, 1)', 'E474:')
+
+ call assert_equal(0, count("foo", "bar"))
+ call assert_equal(1, count("foo", "oo"))
+ call assert_equal(2, count("foo", "o"))
+ call assert_equal(0, count("foo", "O"))
+ call assert_equal(2, count("foo", "O", 1))
+ call assert_equal(2, count("fooooo", "oo"))
+ call assert_equal(0, count("foo", ""))
+
+ call assert_fails('call count(0, 0)', 'E712:')
+endfunc
+
+func Test_changenr()
+ new Xchangenr
+ call assert_equal(0, changenr())
+ norm ifoo
+ call assert_equal(1, changenr())
+ set undolevels=10
+ norm Sbar
+ call assert_equal(2, changenr())
+ undo
+ call assert_equal(1, changenr())
+ redo
+ call assert_equal(2, changenr())
+ bw!
+ set undolevels&
+endfunc
+
+func Test_filewritable()
+ new Xfilewritable
+ write!
+ call assert_equal(1, filewritable('Xfilewritable'))
+
+ call assert_notequal(0, setfperm('Xfilewritable', 'r--r-----'))
+ call assert_equal(0, filewritable('Xfilewritable'))
+
+ call assert_notequal(0, setfperm('Xfilewritable', 'rw-r-----'))
+ call assert_equal(1, 'Xfilewritable'->filewritable())
+
+ call assert_equal(0, filewritable('doesnotexist'))
+
+ call mkdir('Xwritedir', 'D')
+ call assert_equal(2, filewritable('Xwritedir'))
+
+ call delete('Xfilewritable')
+ bw!
+endfunc
+
+func Test_Executable()
+ if has('win32')
+ call assert_equal(1, executable('notepad'))
+ call assert_equal(1, 'notepad.exe'->executable())
+ call assert_equal(0, executable('notepad.exe.exe'))
+ call assert_equal(0, executable('shell32.dll'))
+ call assert_equal(0, executable('win.ini'))
+
+ " get "notepad" path and remove the leading drive and sep. (ex. 'C:\')
+ let notepadcmd = exepath('notepad.exe')
+ let driveroot = notepadcmd[:2]
+ let notepadcmd = notepadcmd[3:]
+ new
+ " check that the relative path works in /
+ execute 'lcd' driveroot
+ call assert_equal(1, executable(notepadcmd))
+ call assert_equal(driveroot .. notepadcmd, notepadcmd->exepath())
+ bwipe
+
+ " create "notepad.bat"
+ call mkdir('Xnotedir')
+ let notepadbat = fnamemodify('Xnotedir/notepad.bat', ':p')
+ call writefile([], notepadbat)
+ new
+ " check that the path and the pathext order is valid
+ lcd Xnotedir
+ let [pathext, $PATHEXT] = [$PATHEXT, '.com;.exe;.bat;.cmd']
+ call assert_equal(notepadbat, exepath('notepad'))
+ let $PATHEXT = pathext
+ bwipe
+ eval 'Xnotedir'->delete('rf')
+ elseif has('unix')
+ call assert_equal(1, 'cat'->executable())
+ call assert_equal(0, executable('nodogshere'))
+
+ " get "cat" path and remove the leading /
+ let catcmd = exepath('cat')[1:]
+ new
+ " check that the relative path works in /
+ lcd /
+ call assert_equal(1, executable(catcmd))
+ let result = catcmd->exepath()
+ " when using chroot looking for sbin/cat can return bin/cat, that is OK
+ if catcmd =~ '\<sbin\>' && result =~ '\<bin\>'
+ call assert_equal('/' .. substitute(catcmd, '\<sbin\>', 'bin', ''), result)
+ else
+ " /bin/cat and /usr/bin/cat may be hard linked, we could get either
+ let result = substitute(result, '/usr/bin/cat', '/bin/cat', '')
+ let catcmd = substitute(catcmd, 'usr/bin/cat', 'bin/cat', '')
+ call assert_equal('/' .. catcmd, result)
+ endif
+ bwipe
+ else
+ throw 'Skipped: does not work on this platform'
+ endif
+endfunc
+
+func Test_executable_windows_store_apps()
+ CheckMSWindows
+
+ " Windows Store apps install some 'decoy' .exe that require some careful
+ " handling as they behave similarly to symlinks.
+ let app_dir = expand("$LOCALAPPDATA\\Microsoft\\WindowsApps")
+ if !isdirectory(app_dir)
+ return
+ endif
+
+ let save_path = $PATH
+ let $PATH = app_dir
+ " Ensure executable() finds all the app .exes
+ for entry in readdir(app_dir)
+ if entry =~ '\.exe$'
+ call assert_true(executable(entry))
+ endif
+ endfor
+
+ let $PATH = save_path
+endfunc
+
+func Test_executable_longname()
+ CheckMSWindows
+
+ " Create a temporary .bat file with 205 characters in the name.
+ " Maximum length of a filename (including the path) on MS-Windows is 259
+ " characters.
+ " See https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
+ let len = 259 - getcwd()->len() - 6
+ if len > 200
+ let len = 200
+ endif
+
+ let fname = 'X' . repeat('ã‚', len) . '.bat'
+ call writefile([], fname)
+ call assert_equal(1, executable(fname))
+ call delete(fname)
+endfunc
+
+func Test_hostname()
+ let hostname_vim = hostname()
+ if has('unix')
+ let hostname_system = systemlist('uname -n')[0]
+ call assert_equal(hostname_vim, hostname_system)
+ endif
+endfunc
+
+func Test_getpid()
+ " getpid() always returns the same value within a vim instance.
+ call assert_equal(getpid(), getpid())
+ if has('unix')
+ call assert_equal(systemlist('echo $PPID')[0], string(getpid()))
+ endif
+endfunc
+
+func Test_hlexists()
+ call assert_equal(0, hlexists('does_not_exist'))
+ call assert_equal(0, 'Number'->hlexists())
+ call assert_equal(0, highlight_exists('does_not_exist'))
+ call assert_equal(0, highlight_exists('Number'))
+ syntax on
+ call assert_equal(0, hlexists('does_not_exist'))
+ call assert_equal(1, hlexists('Number'))
+ call assert_equal(0, highlight_exists('does_not_exist'))
+ call assert_equal(1, highlight_exists('Number'))
+ syntax off
+endfunc
+
+" Test for the col() function
+func Test_col()
+ new
+ call setline(1, 'abcdef')
+ norm gg4|mx6|mY2|
+ call assert_equal(2, col('.'))
+ call assert_equal(7, col('$'))
+ call assert_equal(2, col('v'))
+ call assert_equal(4, col("'x"))
+ call assert_equal(6, col("'Y"))
+ call assert_equal(2, [1, 2]->col())
+ call assert_equal(7, col([1, '$']))
+
+ call assert_equal(0, col(''))
+ call assert_equal(0, col('x'))
+ call assert_equal(0, col([2, '$']))
+ call assert_equal(0, col([1, 100]))
+ call assert_equal(0, col([1]))
+ call assert_equal(0, col(test_null_list()))
+ call assert_fails('let c = col({})', 'E1222:')
+ call assert_fails('let c = col(".", [])', 'E1210:')
+
+ " test for getting the visual start column
+ func T()
+ let g:Vcol = col('v')
+ return ''
+ endfunc
+ let g:Vcol = 0
+ xmap <expr> <F2> T()
+ exe "normal gg3|ve\<F2>"
+ call assert_equal(3, g:Vcol)
+ xunmap <F2>
+ delfunc T
+
+ " Test for the visual line start and end marks '< and '>
+ call setline(1, ['one', 'one two', 'one two three'])
+ "normal! ggVG
+ call feedkeys("ggVG\<Esc>", 'xt')
+ call assert_equal(1, col("'<"))
+ call assert_equal(14, col("'>"))
+ " Delete the last line of the visually selected region
+ $d
+ call assert_notequal(14, col("'>"))
+
+ " Test with 'virtualedit'
+ set virtualedit=all
+ call cursor(1, 10)
+ call assert_equal(4, col('.'))
+ set virtualedit&
+
+ " Test for getting the column number in another window
+ let winid = win_getid()
+ new
+ call win_execute(winid, 'normal 1G$')
+ call assert_equal(3, col('.', winid))
+ call win_execute(winid, 'normal 2G')
+ call assert_equal(8, col('$', winid))
+ call assert_equal(0, col('.', 5001))
+
+ bw!
+endfunc
+
+" Test for input()
+func Test_input_func()
+ " Test for prompt with multiple lines
+ redir => v
+ call feedkeys(":let c = input(\"A\\nB\\nC\\n? \")\<CR>B\<CR>", 'xt')
+ redir END
+ call assert_equal("B", c)
+ call assert_equal(['A', 'B', 'C'], split(v, "\n"))
+
+ " Test for default value
+ call feedkeys(":let c = input('color? ', 'red')\<CR>\<CR>", 'xt')
+ call assert_equal('red', c)
+
+ " Test for completion at the input prompt
+ func! Tcomplete(arglead, cmdline, pos)
+ return "item1\nitem2\nitem3"
+ endfunc
+ call feedkeys(":let c = input('Q? ', '', 'custom,Tcomplete')\<CR>"
+ \ .. "\<C-A>\<CR>", 'xt')
+ delfunc Tcomplete
+ call assert_equal('item1 item2 item3', c)
+
+ " Test for using special characters as default input
+ call feedkeys(":let c = input('name? ', \"x\\<BS>y\")\<CR>\<CR>", 'xt')
+ call assert_equal('y', c)
+
+ " Test for using text with composing characters as default input
+ call feedkeys(":let c = input('name? ', \"ã̳\")\<CR>\<CR>", 'xt')
+ call assert_equal('ã̳', c)
+
+ " Test for using <CR> as default input
+ call feedkeys(":let c = input('name? ', \"\\<CR>\")\<CR>x\<CR>", 'xt')
+ call assert_equal(' x', c)
+
+ call assert_fails("call input('F:', '', 'invalid')", 'E180:')
+ call assert_fails("call input('F:', '', [])", 'E730:')
+endfunc
+
+" Test for the inputdialog() function
+func Test_inputdialog()
+ set timeout timeoutlen=10
+ if has('gui_running')
+ call assert_fails('let v=inputdialog([], "xx")', 'E730:')
+ call assert_fails('let v=inputdialog("Q", [])', 'E730:')
+ else
+ call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt')
+ call assert_equal('xx', v)
+ call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt')
+ call assert_equal('yy', v)
+ endif
+ set timeout& timeoutlen&
+endfunc
+
+" Test for inputlist()
+func Test_inputlist()
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>1\<cr>", 'tx')
+ call assert_equal(1, c)
+ call feedkeys(":let c = ['Select color:', '1. red', '2. green', '3. blue']->inputlist()\<cr>2\<cr>", 'tx')
+ call assert_equal(2, c)
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>3\<cr>", 'tx')
+ call assert_equal(3, c)
+
+ " CR to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<cr>", 'tx')
+ call assert_equal(0, c)
+
+ " Esc to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<Esc>", 'tx')
+ call assert_equal(0, c)
+
+ " q to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>q", 'tx')
+ call assert_equal(0, c)
+
+ " Cancel after inputting a number
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>5q", 'tx')
+ call assert_equal(0, c)
+
+ " Use backspace to delete characters in the prompt
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>1\<BS>3\<BS>2\<cr>", 'tx')
+ call assert_equal(2, c)
+
+ " Use mouse to make a selection
+ call test_setmouse(&lines - 3, 2)
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<LeftMouse>", 'tx')
+ call assert_equal(1, c)
+ " Mouse click outside of the list
+ call test_setmouse(&lines - 6, 2)
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<LeftMouse>", 'tx')
+ call assert_equal(-2, c)
+
+ call assert_fails('call inputlist("")', 'E686:')
+ call assert_fails('call inputlist(test_null_list())', 'E686:')
+endfunc
+
+func Test_range_inputlist()
+ " flush out any garbage left in the buffer
+ while getchar(0)
+ endwhile
+
+ call feedkeys(":let result = inputlist(range(10))\<CR>1\<CR>", 'x')
+ call assert_equal(1, result)
+ call feedkeys(":let result = inputlist(range(3, 10))\<CR>1\<CR>", 'x')
+ call assert_equal(1, result)
+
+ unlet result
+endfunc
+
+func Test_balloon_show()
+ CheckFeature balloon_eval
+
+ " This won't do anything but must not crash either.
+ call balloon_show('hi!')
+ if !has('gui_running')
+ call balloon_show(range(3))
+ call balloon_show([])
+ endif
+endfunc
+
+func Test_setbufvar_options()
+ " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the
+ " window layout and cursor position.
+ call assert_equal(1, winnr('$'))
+ split dummy_preview
+ resize 2
+ set winfixheight winfixwidth
+ let prev_id = win_getid()
+
+ wincmd j
+ let wh = winheight(0)
+ let dummy_buf = bufnr('dummy_buf1', v:true)
+ call setbufvar(dummy_buf, '&buftype', 'nofile')
+ execute 'belowright vertical split #' . dummy_buf
+ call assert_equal(wh, winheight(0))
+ let dum1_id = win_getid()
+ call setline(1, 'foo')
+ normal! V$
+ call assert_equal(4, col('.'))
+ call setbufvar('dummy_preview', '&buftype', 'nofile')
+ call assert_equal(4, col('.'))
+
+ wincmd h
+ let wh = winheight(0)
+ call setline(1, 'foo')
+ normal! V$
+ call assert_equal(4, col('.'))
+ let dummy_buf = bufnr('dummy_buf2', v:true)
+ eval 'nofile'->setbufvar(dummy_buf, '&buftype')
+ call assert_equal(4, col('.'))
+ execute 'belowright vertical split #' . dummy_buf
+ call assert_equal(wh, winheight(0))
+
+ bwipe!
+ call win_gotoid(prev_id)
+ bwipe!
+ call win_gotoid(dum1_id)
+ bwipe!
+endfunc
+
+func Test_setbufvar_keep_window_title()
+ CheckRunVimInTerminal
+ if !has('title') || empty(&t_ts)
+ throw "Skipped: can't get/set title"
+ endif
+
+ let lines =<< trim END
+ set title
+ edit Xa.txt
+ let g:buf = bufadd('Xb.txt')
+ inoremap <F2> <C-R>=setbufvar(g:buf, '&autoindent', 1) ?? ''<CR>
+ END
+ call writefile(lines, 'Xsetbufvar', 'D')
+ let buf = RunVimInTerminal('-S Xsetbufvar', {})
+ call WaitForAssert({-> assert_match('Xa.txt', term_gettitle(buf))}, 1000)
+
+ call term_sendkeys(buf, "i\<F2>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "\<Esc>")
+ call TermWait(buf)
+ call assert_match('Xa.txt', term_gettitle(buf))
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_redo_in_nested_functions()
+ nnoremap g. :set opfunc=Operator<CR>g@
+ function Operator( type, ... )
+ let @x = 'XXX'
+ execute 'normal! g`[' . (a:type ==# 'line' ? 'V' : 'v') . 'g`]' . '"xp'
+ endfunction
+
+ function! Apply()
+ 5,6normal! .
+ endfunction
+
+ new
+ call setline(1, repeat(['some "quoted" text', 'more "quoted" text'], 3))
+ 1normal g.i"
+ call assert_equal('some "XXX" text', getline(1))
+ 3,4normal .
+ call assert_equal('some "XXX" text', getline(3))
+ call assert_equal('more "XXX" text', getline(4))
+ call Apply()
+ call assert_equal('some "XXX" text', getline(5))
+ call assert_equal('more "XXX" text', getline(6))
+ bwipe!
+
+ nunmap g.
+ delfunc Operator
+ delfunc Apply
+endfunc
+
+func Test_trim()
+ call assert_equal("Testing", trim(" \t\r\r\x0BTesting \t\n\r\n\t\x0B\x0B"))
+ call assert_equal("Testing", " \t \r\r\n\n\x0BTesting \t\n\r\n\t\x0B\x0B"->trim())
+ call assert_equal("RESERVE", trim("xyz \twwRESERVEzyww \t\t", " wxyz\t"))
+ call assert_equal("wRE \tSERVEzyww", trim("wRE \tSERVEzyww"))
+ call assert_equal("abcd\t xxxx tail", trim(" \tabcd\t xxxx tail"))
+ call assert_equal("\tabcd\t xxxx tail", trim(" \tabcd\t xxxx tail", " "))
+ call assert_equal(" \tabcd\t xxxx tail", trim(" \tabcd\t xxxx tail", "abx"))
+ call assert_equal("RESERVE", trim("你RESERVE好", "你好"))
+ call assert_equal("您R E SER V E早", trim("你好您R E SER V E早好你你", "你好"))
+ call assert_equal("你好您R E SER V E早好你你", trim(" \n\r\r 你好您R E SER V E早好你你 \t \x0B", ))
+ call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" 你好您R E SER V E早好你你 \t \x0B", " 你好"))
+ call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你好tes"))
+ call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你你你好好好tttsses"))
+ call assert_equal("留下", trim("这些些ä¸è¦è¿™äº›ç•™ä¸‹è¿™äº›", "这些ä¸è¦"))
+ call assert_equal("", trim("", ""))
+ call assert_equal("a", trim("a", ""))
+ call assert_equal("", trim("", "a"))
+
+ call assert_equal("vim", trim(" vim ", " ", 0))
+ call assert_equal("vim ", trim(" vim ", " ", 1))
+ call assert_equal(" vim", trim(" vim ", " ", 2))
+ call assert_fails('eval trim(" vim ", " ", [])', 'E745:')
+ call assert_fails('eval trim(" vim ", " ", -1)', 'E475:')
+ call assert_fails('eval trim(" vim ", " ", 3)', 'E475:')
+ call assert_fails('eval trim(" vim ", 0)', 'E1174:')
+
+ let chars = join(map(range(1, 0x20) + [0xa0], {n -> n->nr2char()}), '')
+ call assert_equal("x", trim(chars . "x" . chars))
+
+ call assert_fails('let c=trim([])', 'E730:')
+endfunc
+
+" Test for reg_recording() and reg_executing()
+func Test_reg_executing_and_recording()
+ let s:reg_stat = ''
+ func s:save_reg_stat()
+ let s:reg_stat = reg_recording() . ':' . reg_executing()
+ return ''
+ endfunc
+
+ new
+ call s:save_reg_stat()
+ call assert_equal(':', s:reg_stat)
+ call feedkeys("qa\"=s:save_reg_stat()\<CR>pq", 'xt')
+ call assert_equal('a:', s:reg_stat)
+ call feedkeys("@a", 'xt')
+ call assert_equal(':a', s:reg_stat)
+ call feedkeys("qb@aq", 'xt')
+ call assert_equal('b:a', s:reg_stat)
+ call feedkeys("q\"\"=s:save_reg_stat()\<CR>pq", 'xt')
+ call assert_equal('":', s:reg_stat)
+
+ " :normal command saves and restores reg_executing
+ let s:reg_stat = ''
+ let @q = ":call TestFunc()\<CR>:call s:save_reg_stat()\<CR>"
+ func TestFunc() abort
+ normal! ia
+ endfunc
+ call feedkeys("@q", 'xt')
+ call assert_equal(':q', s:reg_stat)
+ delfunc TestFunc
+
+ " getchar() command saves and restores reg_executing
+ map W :call TestFunc()<CR>
+ let @q = "W"
+ let g:typed = ''
+ let g:regs = []
+ func TestFunc() abort
+ let g:regs += [reg_executing()]
+ let g:typed = getchar(0)
+ let g:regs += [reg_executing()]
+ endfunc
+ call feedkeys("@qy", 'xt')
+ call assert_equal(char2nr("y"), g:typed)
+ call assert_equal(['q', 'q'], g:regs)
+ delfunc TestFunc
+ unmap W
+ unlet g:typed
+ unlet g:regs
+
+ " input() command saves and restores reg_executing
+ map W :call TestFunc()<CR>
+ let @q = "W"
+ let g:typed = ''
+ let g:regs = []
+ func TestFunc() abort
+ let g:regs += [reg_executing()]
+ let g:typed = '?'->input()
+ let g:regs += [reg_executing()]
+ endfunc
+ call feedkeys("@qy\<CR>", 'xt')
+ call assert_equal("y", g:typed)
+ call assert_equal(['q', 'q'], g:regs)
+ delfunc TestFunc
+ unmap W
+ unlet g:typed
+ unlet g:regs
+
+ bwipe!
+ delfunc s:save_reg_stat
+ unlet s:reg_stat
+endfunc
+
+func Test_inputsecret()
+ map W :call TestFunc()<CR>
+ let @q = "W"
+ let g:typed1 = ''
+ let g:typed2 = ''
+ let g:regs = []
+ func TestFunc() abort
+ let g:typed1 = '?'->inputsecret()
+ let g:typed2 = inputsecret('password: ')
+ endfunc
+ call feedkeys("@qsomething\<CR>else\<CR>", 'xt')
+ call assert_equal("something", g:typed1)
+ call assert_equal("else", g:typed2)
+ delfunc TestFunc
+ unmap W
+ unlet g:typed1
+ unlet g:typed2
+endfunc
+
+func Test_getchar()
+ call feedkeys('a', '')
+ call assert_equal(char2nr('a'), getchar())
+ call assert_equal(0, getchar(0))
+ call assert_equal(0, getchar(1))
+
+ call feedkeys('a', '')
+ call assert_equal('a', getcharstr())
+ call assert_equal('', getcharstr(0))
+ call assert_equal('', getcharstr(1))
+
+ call feedkeys("\<M-F2>", '')
+ call assert_equal("\<M-F2>", getchar(0))
+ call assert_equal(0, getchar(0))
+
+ call setline(1, 'xxxx')
+ call test_setmouse(1, 3)
+ let v:mouse_win = 9
+ let v:mouse_winid = 9
+ let v:mouse_lnum = 9
+ let v:mouse_col = 9
+ call feedkeys("\<S-LeftMouse>", '')
+ call assert_equal("\<S-LeftMouse>", getchar())
+ call assert_equal(1, v:mouse_win)
+ call assert_equal(win_getid(1), v:mouse_winid)
+ call assert_equal(1, v:mouse_lnum)
+ call assert_equal(3, v:mouse_col)
+ enew!
+endfunc
+
+func Test_libcall_libcallnr()
+ CheckFeature libcall
+
+ if has('win32')
+ let libc = 'msvcrt.dll'
+ elseif has('mac')
+ let libc = 'libSystem.B.dylib'
+ elseif executable('ldd')
+ let libc = matchstr(split(system('ldd ' . GetVimProg())), '/libc\.so\>')
+ endif
+ if get(l:, 'libc', '') ==# ''
+ " On Unix, libc.so can be in various places.
+ if has('linux')
+ " There is not documented but regarding the 1st argument of glibc's
+ " dlopen an empty string and nullptr are equivalent, so using an empty
+ " string for the 1st argument of libcall allows to call functions.
+ let libc = ''
+ elseif has('sun')
+ " Set the path to libc.so according to the architecture.
+ let test_bits = system('file ' . GetVimProg())
+ let test_arch = system('uname -p')
+ if test_bits =~ '64-bit' && test_arch =~ 'sparc'
+ let libc = '/usr/lib/sparcv9/libc.so'
+ elseif test_bits =~ '64-bit' && test_arch =~ 'i386'
+ let libc = '/usr/lib/amd64/libc.so'
+ else
+ let libc = '/usr/lib/libc.so'
+ endif
+ else
+ " Unfortunately skip this test until a good way is found.
+ return
+ endif
+ endif
+
+ if has('win32')
+ call assert_equal($USERPROFILE, 'USERPROFILE'->libcall(libc, 'getenv'))
+ else
+ call assert_equal($HOME, 'HOME'->libcall(libc, 'getenv'))
+ endif
+
+ " If function returns NULL, libcall() should return an empty string.
+ call assert_equal('', libcall(libc, 'getenv', 'X_ENV_DOES_NOT_EXIT'))
+
+ " Test libcallnr() with string and integer argument.
+ call assert_equal(4, 'abcd'->libcallnr(libc, 'strlen'))
+ call assert_equal(char2nr('A'), char2nr('a')->libcallnr(libc, 'toupper'))
+
+ call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", ['', 'E364:'])
+ call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", ['', 'E364:'])
+
+ call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", ['', 'E364:'])
+ call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", ['', 'E364:'])
+endfunc
+
+sandbox function Fsandbox()
+ normal ix
+endfunc
+
+func Test_func_sandbox()
+ sandbox let F = {-> 'hello'}
+ call assert_equal('hello', F())
+
+ sandbox let F = {-> "normal ix\<Esc>"->execute()}
+ call assert_fails('call F()', 'E48:')
+ unlet F
+
+ call assert_fails('call Fsandbox()', 'E48:')
+ delfunc Fsandbox
+
+ " From a sandbox try to set a predefined variable (which cannot be modified
+ " from a sandbox)
+ call assert_fails('sandbox let v:lnum = 10', 'E794:')
+endfunc
+
+func EditAnotherFile()
+ let word = expand('<cword>')
+ edit Xfuncrange2
+endfunc
+
+func Test_func_range_with_edit()
+ " Define a function that edits another buffer, then call it with a range that
+ " is invalid in that buffer.
+ call writefile(['just one line'], 'Xfuncrange2', 'D')
+ new
+ eval 10->range()->setline(1)
+ write Xfuncrange1
+ call assert_fails('5,8call EditAnotherFile()', 'E16:')
+
+ call delete('Xfuncrange1')
+ bwipe!
+endfunc
+
+func Test_func_exists_on_reload()
+ call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists', 'D')
+ call assert_equal(0, exists('*ExistingFunction'))
+ source Xfuncexists
+ call assert_equal(1, '*ExistingFunction'->exists())
+ " Redefining a function when reloading a script is OK.
+ source Xfuncexists
+ call assert_equal(1, exists('*ExistingFunction'))
+
+ " But redefining in another script is not OK.
+ call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2', 'D')
+ call assert_fails('source Xfuncexists2', 'E122:')
+
+ " Defining a new function from the cmdline should fail if the function is
+ " already defined
+ call assert_fails('call feedkeys(":func ExistingFunction()\<CR>", "xt")', 'E122:')
+
+ delfunc ExistingFunction
+ call assert_equal(0, exists('*ExistingFunction'))
+ call writefile([
+ \ 'func ExistingFunction()', 'echo "yes"', 'endfunc',
+ \ 'func ExistingFunction()', 'echo "no"', 'endfunc',
+ \ ], 'Xfuncexists')
+ call assert_fails('source Xfuncexists', 'E122:')
+ call assert_equal(1, exists('*ExistingFunction'))
+
+ delfunc ExistingFunction
+endfunc
+
+" Test confirm({msg} [, {choices} [, {default} [, {type}]]])
+func Test_confirm()
+ CheckUnix
+ CheckNotGui
+
+ call feedkeys('o', 'L')
+ let a = confirm('Press O to proceed')
+ call assert_equal(1, a)
+
+ call feedkeys('y', 'L')
+ let a = 'Are you sure?'->confirm("&Yes\n&No")
+ call assert_equal(1, a)
+
+ call feedkeys('n', 'L')
+ let a = confirm('Are you sure?', "&Yes\n&No")
+ call assert_equal(2, a)
+
+ " confirm() should return 0 when pressing CTRL-C.
+ call feedkeys("\<C-C>", 'L')
+ let a = confirm('Are you sure?', "&Yes\n&No")
+ call assert_equal(0, a)
+
+ " <Esc> requires another character to avoid it being seen as the start of an
+ " escape sequence. Zero should be harmless.
+ eval "\<Esc>0"->feedkeys('L')
+ let a = confirm('Are you sure?', "&Yes\n&No")
+ call assert_equal(0, a)
+
+ " Default choice is returned when pressing <CR>.
+ call feedkeys("\<CR>", 'L')
+ let a = confirm('Are you sure?', "&Yes\n&No")
+ call assert_equal(1, a)
+
+ call feedkeys("\<CR>", 'L')
+ let a = confirm('Are you sure?', "&Yes\n&No", 2)
+ call assert_equal(2, a)
+
+ call feedkeys("\<CR>", 'L')
+ let a = confirm('Are you sure?', "&Yes\n&No", 0)
+ call assert_equal(0, a)
+
+ " Test with the {type} 4th argument
+ for type in ['Error', 'Question', 'Info', 'Warning', 'Generic']
+ call feedkeys('y', 'L')
+ let a = confirm('Are you sure?', "&Yes\n&No\n", 1, type)
+ call assert_equal(1, a)
+ endfor
+
+ call assert_fails('call confirm([])', 'E730:')
+ call assert_fails('call confirm("Are you sure?", [])', 'E730:')
+ call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", [])', 'E745:')
+ call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", 0, [])', 'E730:')
+endfunc
+
+func Test_platform_name()
+ " The system matches at most only one name.
+ let names = ['amiga', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix']
+ call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)')))
+
+ " Is Unix?
+ call assert_equal(has('bsd'), has('bsd') && has('unix'))
+ call assert_equal(has('hpux'), has('hpux') && has('unix'))
+ call assert_equal(has('linux'), has('linux') && has('unix'))
+ call assert_equal(has('mac'), has('mac') && has('unix'))
+ call assert_equal(has('qnx'), has('qnx') && has('unix'))
+ call assert_equal(has('sun'), has('sun') && has('unix'))
+ call assert_equal(has('win32'), has('win32') && !has('unix'))
+ call assert_equal(has('win32unix'), has('win32unix') && has('unix'))
+
+ if has('unix') && executable('uname')
+ let uname = system('uname')
+ " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined
+ call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd'))
+ call assert_equal(uname =~? 'HP-UX', has('hpux'))
+ call assert_equal(uname =~? 'Linux', has('linux'))
+ call assert_equal(uname =~? 'Darwin', has('mac'))
+ call assert_equal(uname =~? 'QNX', has('qnx'))
+ call assert_equal(uname =~? 'SunOS', has('sun'))
+ call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix'))
+ endif
+endfunc
+
+func Test_readdir()
+ call mkdir('Xreaddir', 'R')
+ call writefile([], 'Xreaddir/foo.txt')
+ call writefile([], 'Xreaddir/bar.txt')
+ call mkdir('Xreaddir/dir')
+
+ " All results
+ let files = readdir('Xreaddir')
+ call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files))
+
+ " Only results containing "f"
+ let files = 'Xreaddir'->readdir({ x -> stridx(x, 'f') != -1 })
+ call assert_equal(['foo.txt'], sort(files))
+
+ " Only .txt files
+ let files = readdir('Xreaddir', { x -> x =~ '.txt$' })
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Only .txt files with string
+ let files = readdir('Xreaddir', 'v:val =~ ".txt$"')
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Limit to 1 result.
+ let l = []
+ let files = readdir('Xreaddir', {x -> len(add(l, x)) == 2 ? -1 : 1})
+ call assert_equal(1, len(files))
+
+ " Nested readdir() must not crash
+ let files = readdir('Xreaddir', 'readdir("Xreaddir", "1") != []')
+ call sort(files)->assert_equal(['bar.txt', 'dir', 'foo.txt'])
+endfunc
+
+func Test_readdirex()
+ call mkdir('Xexdir', 'R')
+ call writefile(['foo'], 'Xexdir/foo.txt')
+ call writefile(['barbar'], 'Xexdir/bar.txt')
+ call mkdir('Xexdir/dir')
+
+ " All results
+ let files = readdirex('Xexdir')->map({-> v:val.name})
+ call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files))
+ let sizes = readdirex('Xexdir')->map({-> v:val.size})
+ call assert_equal([0, 4, 7], sort(sizes))
+
+ " Only results containing "f"
+ let files = 'Xexdir'->readdirex({ e -> stridx(e.name, 'f') != -1 })
+ \ ->map({-> v:val.name})
+ call assert_equal(['foo.txt'], sort(files))
+
+ " Only .txt files
+ let files = readdirex('Xexdir', { e -> e.name =~ '.txt$' })
+ \ ->map({-> v:val.name})
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Only .txt files with string
+ let files = readdirex('Xexdir', 'v:val.name =~ ".txt$"')
+ \ ->map({-> v:val.name})
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Limit to 1 result.
+ let l = []
+ let files = readdirex('Xexdir', {e -> len(add(l, e.name)) == 2 ? -1 : 1})
+ \ ->map({-> v:val.name})
+ call assert_equal(1, len(files))
+
+ " Nested readdirex() must not crash
+ let files = readdirex('Xexdir', 'readdirex("Xexdir", "1") != []')
+ \ ->map({-> v:val.name})
+ call sort(files)->assert_equal(['bar.txt', 'dir', 'foo.txt'])
+
+ " report broken link correctly
+ if has("unix")
+ call writefile([], 'Xexdir/abc.txt')
+ call system("ln -s Xexdir/abc.txt Xexdir/link")
+ call delete('Xexdir/abc.txt')
+ let files = readdirex('Xexdir', 'readdirex("Xexdir", "1") != []')
+ \ ->map({-> v:val.name .. '_' .. v:val.type})
+ call sort(files)->assert_equal(
+ \ ['bar.txt_file', 'dir_dir', 'foo.txt_file', 'link_link'])
+ endif
+
+ call assert_fails('call readdirex("doesnotexist")', 'E484:')
+endfunc
+
+func Test_readdirex_sort()
+ CheckUnix
+ " Skip tests on Mac OS X and Cygwin (does not allow several files with different casing)
+ if has("osxdarwin") || has("osx") || has("macunix") || has("win32unix")
+ throw 'Skipped: Test_readdirex_sort on systems that do not allow this using the default filesystem'
+ endif
+ let _collate = v:collate
+ call mkdir('Xsortdir2', 'R')
+ call writefile(['1'], 'Xsortdir2/README.txt')
+ call writefile(['2'], 'Xsortdir2/Readme.txt')
+ call writefile(['3'], 'Xsortdir2/readme.txt')
+
+ " 1) default
+ let files = readdirex('Xsortdir2')->map({-> v:val.name})
+ let default = copy(files)
+ call assert_equal(['README.txt', 'Readme.txt', 'readme.txt'], files, 'sort using default')
+
+ " 2) no sorting
+ let files = readdirex('Xsortdir2', 1, #{sort: 'none'})->map({-> v:val.name})
+ let unsorted = copy(files)
+ call assert_equal(['README.txt', 'Readme.txt', 'readme.txt'], sort(files), 'unsorted')
+ call assert_fails("call readdirex('Xsortdir2', 1, #{slort: 'none'})", 'E857: Dictionary key "sort" required')
+
+ " 3) sort by case (same as default)
+ let files = readdirex('Xsortdir2', 1, #{sort: 'case'})->map({-> v:val.name})
+ call assert_equal(default, files, 'sort by case')
+
+ " 4) sort by ignoring case
+ let files = readdirex('Xsortdir2', 1, #{sort: 'icase'})->map({-> v:val.name})
+ call assert_equal(unsorted->sort('i'), files, 'sort by icase')
+
+ " 5) Default Collation
+ let collate = v:collate
+ lang collate C
+ let files = readdirex('Xsortdir2', 1, #{sort: 'collate'})->map({-> v:val.name})
+ call assert_equal(['README.txt', 'Readme.txt', 'readme.txt'], files, 'sort by C collation')
+
+ " 6) Collation de_DE
+ " Switch locale, this may not work on the CI system, if the locale isn't
+ " available
+ try
+ lang collate de_DE
+ let files = readdirex('Xsortdir2', 1, #{sort: 'collate'})->map({-> v:val.name})
+ call assert_equal(['readme.txt', 'Readme.txt', 'README.txt'], files, 'sort by de_DE collation')
+ catch
+ throw 'Skipped: de_DE collation is not available'
+
+ finally
+ exe 'lang collate' collate
+ endtry
+endfunc
+
+func Test_readdir_sort()
+ " some more cases for testing sorting for readdirex
+ let dir = 'Xsortdir3'
+ call mkdir(dir, 'R')
+ call writefile(['1'], dir .. '/README.txt')
+ call writefile(['2'], dir .. '/Readm.txt')
+ call writefile(['3'], dir .. '/read.txt')
+ call writefile(['4'], dir .. '/Z.txt')
+ call writefile(['5'], dir .. '/a.txt')
+ call writefile(['6'], dir .. '/b.txt')
+
+ " 1) default
+ let files = readdir(dir)
+ let default = copy(files)
+ call assert_equal(default->sort(), files, 'sort using default')
+
+ " 2) sort by case (same as default)
+ let files = readdir(dir, '1', #{sort: 'case'})
+ call assert_equal(default, files, 'sort using default')
+
+ " 3) sort by ignoring case
+ let files = readdir(dir, '1', #{sort: 'icase'})
+ call assert_equal(default->sort('i'), files, 'sort by ignoring case')
+
+ " 4) collation
+ let collate = v:collate
+ lang collate C
+ let files = readdir(dir, 1, #{sort: 'collate'})
+ call assert_equal(default->sort(), files, 'sort by C collation')
+ exe "lang collate" collate
+
+ " 5) Errors
+ call assert_fails('call readdir(dir, 1, 1)', 'E1206:')
+ call assert_fails('call readdir(dir, 1, #{sorta: 1})')
+ call assert_fails('call readdir(dir, 1, test_null_dict())', 'E1297:')
+ call assert_fails('call readdirex(dir, 1, 1)', 'E1206:')
+ call assert_fails('call readdirex(dir, 1, #{sorta: 1})')
+ call assert_fails('call readdirex(dir, 1, test_null_dict())', 'E1297:')
+
+ " 6) ignore other values in dict
+ let files = readdir(dir, '1', #{sort: 'c'})
+ call assert_equal(default, files, 'sort using default2')
+
+ " Cleanup
+ exe "lang collate" collate
+endfunc
+
+func Test_delete_rf()
+ call mkdir('Xrfdir')
+ call writefile([], 'Xrfdir/foo.txt')
+ call writefile([], 'Xrfdir/bar.txt')
+ call mkdir('Xrfdir/[a-1]') " issue #696
+ call writefile([], 'Xrfdir/[a-1]/foo.txt')
+ call writefile([], 'Xrfdir/[a-1]/bar.txt')
+ call assert_true(filereadable('Xrfdir/foo.txt'))
+ call assert_true('Xrfdir/[a-1]/foo.txt'->filereadable())
+
+ call assert_equal(0, delete('Xrfdir', 'rf'))
+ call assert_false(filereadable('Xrfdir/foo.txt'))
+ call assert_false(filereadable('Xrfdir/[a-1]/foo.txt'))
+
+ if has('unix')
+ call mkdir('Xrfdir/Xdir2', 'p')
+ silent !chmod 555 Xrfdir
+ call assert_equal(-1, delete('Xrfdir/Xdir2', 'rf'))
+ call assert_equal(-1, delete('Xrfdir', 'rf'))
+ silent !chmod 755 Xrfdir
+ call assert_equal(0, delete('Xrfdir', 'rf'))
+ endif
+endfunc
+
+func Test_call()
+ call assert_equal(3, call('len', [123]))
+ call assert_equal(3, 'len'->call([123]))
+ call assert_fails("call call('len', 123)", 'E1211:')
+ call assert_equal(0, call('', []))
+ call assert_equal(0, call('len', test_null_list()))
+
+ function Mylen() dict
+ return len(self.data)
+ endfunction
+ let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")}
+ eval mydict.len->call([], mydict)->assert_equal(4)
+ call assert_fails("call call('Mylen', [], 0)", 'E1206:')
+ call assert_fails('call foo', 'E107:')
+
+ " These once caused a crash.
+ call call(test_null_function(), [])
+ call call(test_null_partial(), [])
+ call assert_fails('call test_null_function()()', 'E1192:')
+ call assert_fails('call test_null_partial()()', 'E117:')
+
+ let lines =<< trim END
+ let Time = 'localtime'
+ call Time()
+ END
+ call v9.CheckScriptFailure(lines, 'E1085:')
+endfunc
+
+func Test_char2nr()
+ call assert_equal(12354, char2nr('ã‚', 1))
+ call assert_equal(120, 'x'->char2nr())
+ set encoding=latin1
+ call assert_equal(120, 'x'->char2nr())
+ set encoding=utf-8
+endfunc
+
+func Test_charclass()
+ call assert_equal(0, charclass(' '))
+ call assert_equal(1, charclass('.'))
+ call assert_equal(2, charclass('x'))
+ call assert_equal(3, charclass("\u203c"))
+ " this used to crash vim
+ call assert_equal(0, "xxx"[-1]->charclass())
+endfunc
+
+func Test_eventhandler()
+ call assert_equal(0, eventhandler())
+endfunc
+
+func Test_bufadd_bufload()
+ call assert_equal(0, bufexists('someName'))
+ let buf = bufadd('someName')
+ call assert_notequal(0, buf)
+ call assert_equal(1, bufexists('someName'))
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ call bufload(buf)
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal([''], getbufline(buf, 1, '$'))
+
+ let curbuf = bufnr('')
+ eval ['some', 'text']->writefile('XotherName')
+ let buf = 'XotherName'->bufadd()
+ call assert_notequal(0, buf)
+ eval 'XotherName'->bufexists()->assert_equal(1)
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ eval buf->bufload()
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
+ call assert_equal(curbuf, bufnr(''))
+
+ let buf1 = bufadd('')
+ let buf2 = bufadd('')
+ call assert_notequal(0, buf1)
+ call assert_notequal(0, buf2)
+ call assert_notequal(buf1, buf2)
+ call assert_equal(1, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ call assert_equal(0, bufloaded(buf1))
+ exe 'bwipe ' .. buf1
+ call assert_equal(0, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ exe 'bwipe ' .. buf2
+ call assert_equal(0, bufexists(buf2))
+
+ " When 'buftype' is "nofile" then bufload() does not read the file.
+ " Other values too.
+ for val in [['nofile', 0],
+ \ ['nowrite', 1],
+ \ ['acwrite', 1],
+ \ ['quickfix', 0],
+ \ ['help', 1],
+ \ ['terminal', 0],
+ \ ['prompt', 0],
+ \ ['popup', 0],
+ \ ]
+ bwipe! XotherName
+ let buf = bufadd('XotherName')
+ call setbufvar(buf, '&bt', val[0])
+ call bufload(buf)
+ call assert_equal(val[1] ? ['some', 'text'] : [''], getbufline(buf, 1, '$'), val[0])
+ endfor
+
+ bwipe someName
+ bwipe XotherName
+ call assert_equal(0, bufexists('someName'))
+ call delete('XotherName')
+endfunc
+
+func Test_state()
+ CheckRunVimInTerminal
+
+ let getstate = ":echo 'state: ' .. g:state .. '; mode: ' .. g:mode\<CR>"
+
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three'])
+ map ;; gg
+ set complete=.
+ func RunTimer()
+ call timer_start(10, {id -> execute('let g:state = state()') .. execute('let g:mode = mode()')})
+ endfunc
+ au Filetype foobar let g:state = state()|let g:mode = mode()
+ END
+ call writefile(lines, 'XState')
+ let buf = RunVimInTerminal('-S XState', #{rows: 6})
+
+ " Using a ":" command Vim is busy, thus "S" is returned
+ call term_sendkeys(buf, ":echo 'state: ' .. state() .. '; mode: ' .. mode()\<CR>")
+ call WaitForAssert({-> assert_match('state: S; mode: n', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, ":\<CR>")
+
+ " Using a timer callback
+ call term_sendkeys(buf, ":call RunTimer()\<CR>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: c; mode: n', term_getline(buf, 6))}, 1000)
+
+ " Halfway a mapping
+ call term_sendkeys(buf, ":call RunTimer()\<CR>;")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, ";")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: mSc; mode: n', term_getline(buf, 6))}, 1000)
+
+ " Insert mode completion (bit slower on Mac)
+ call term_sendkeys(buf, ":call RunTimer()\<CR>Got\<C-N>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: aSc; mode: i', term_getline(buf, 6))}, 1000)
+
+ " Autocommand executing
+ call term_sendkeys(buf, ":set filetype=foobar\<CR>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: xS; mode: n', term_getline(buf, 6))}, 1000)
+
+ " Todo: "w" - waiting for ch_evalexpr()
+
+ " messages scrolled
+ call term_sendkeys(buf, ":call RunTimer()\<CR>:echo \"one\\ntwo\\nthree\"\<CR>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, "\<CR>")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: Scs; mode: r', term_getline(buf, 6))}, 1000)
+
+ call StopVimInTerminal(buf)
+ call delete('XState')
+endfunc
+
+func Test_range()
+ " destructuring
+ let [x, y] = range(2)
+ call assert_equal([0, 1], [x, y])
+
+ " index
+ call assert_equal(4, range(1, 10)[3])
+
+ " add()
+ call assert_equal([0, 1, 2, 3], add(range(3), 3))
+ call assert_equal([0, 1, 2, [0, 1, 2]], add([0, 1, 2], range(3)))
+ call assert_equal([0, 1, 2, [0, 1, 2]], add(range(3), range(3)))
+
+ " append()
+ new
+ call append('.', range(5))
+ call assert_equal(['', '0', '1', '2', '3', '4'], getline(1, '$'))
+ bwipe!
+
+ " appendbufline()
+ new
+ call appendbufline(bufnr(''), '.', range(5))
+ call assert_equal(['0', '1', '2', '3', '4', ''], getline(1, '$'))
+ bwipe!
+
+ " call()
+ func TwoArgs(a, b)
+ return [a:a, a:b]
+ endfunc
+ call assert_equal([0, 1], call('TwoArgs', range(2)))
+
+ " col()
+ new
+ call setline(1, ['foo', 'bar'])
+ call assert_equal(2, col(range(1, 2)))
+ bwipe!
+
+ " complete()
+ execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>"
+ " complete_info()
+ execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>\<C-r>=[complete_info(range(5)), ''][1]\<CR>"
+
+ " copy()
+ call assert_equal([1, 2, 3], copy(range(1, 3)))
+
+ " count()
+ call assert_equal(0, count(range(0), 3))
+ call assert_equal(0, count(range(2), 3))
+ call assert_equal(1, count(range(5), 3))
+
+ " cursor()
+ new
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ call cursor(range(1, 2))
+ call assert_equal([2, 1], [col('.'), line('.')])
+ bwipe!
+
+ " deepcopy()
+ call assert_equal([1, 2, 3], deepcopy(range(1, 3)))
+
+ " empty()
+ call assert_true(empty(range(0)))
+ call assert_false(empty(range(2)))
+
+ " execute()
+ new
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ call execute(range(3))
+ call assert_equal(2, line('.'))
+ bwipe!
+
+ " extend()
+ call assert_equal([1, 2, 3, 4], extend([1], range(2, 4)))
+ call assert_equal([1, 2, 3, 4], extend(range(1, 1), range(2, 4)))
+ call assert_equal([1, 2, 3, 4], extend(range(1, 1), [2, 3, 4]))
+
+ " filter()
+ call assert_equal([1, 3], filter(range(5), 'v:val % 2'))
+ call assert_equal([1, 5, 7, 11, 13], filter(filter(range(15), 'v:val % 2'), 'v:val % 3'))
+
+ " funcref()
+ call assert_equal([0, 1], funcref('TwoArgs', range(2))())
+
+ " function()
+ call assert_equal([0, 1], function('TwoArgs', range(2))())
+
+ " garbagecollect()
+ let thelist = [1, range(2), 3]
+ let otherlist = range(3)
+ call test_garbagecollect_now()
+
+ " get()
+ call assert_equal(4, get(range(1, 10), 3))
+ call assert_equal(-1, get(range(1, 10), 42, -1))
+
+ " index()
+ call assert_equal(1, index(range(1, 5), 2))
+ call assert_fails("echo index([1, 2], 1, [])", 'E745:')
+
+ " insert()
+ call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42))
+ call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42, 0))
+ call assert_equal([1, 42, 2, 3, 4, 5], insert(range(1, 5), 42, 1))
+ call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, 4))
+ call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, -1))
+ call assert_equal([1, 2, 3, 4, 5, 42], insert(range(1, 5), 42, 5))
+
+ " join()
+ call assert_equal('0 1 2 3 4', join(range(5)))
+
+ " json_encode()
+ call assert_equal('[0,1,2,3]', json_encode(range(4)))
+
+ " len()
+ call assert_equal(0, len(range(0)))
+ call assert_equal(2, len(range(2)))
+ call assert_equal(5, len(range(0, 12, 3)))
+ call assert_equal(4, len(range(3, 0, -1)))
+
+ " list2str()
+ call assert_equal('ABC', list2str(range(65, 67)))
+ call assert_fails('let s = list2str(5)', 'E1211:')
+
+ " lock()
+ let thelist = range(5)
+ lockvar thelist
+
+ " map()
+ call assert_equal([0, 2, 4, 6, 8], map(range(5), 'v:val * 2'))
+ call assert_equal([3, 5, 7, 9, 11], map(map(range(5), 'v:val * 2'), 'v:val + 3'))
+ call assert_equal([2, 6], map(filter(range(5), 'v:val % 2'), 'v:val * 2'))
+ call assert_equal([2, 4, 8], filter(map(range(5), 'v:val * 2'), 'v:val % 3'))
+
+ " match()
+ call assert_equal(3, match(range(5), 3))
+
+ " matchaddpos()
+ highlight MyGreenGroup ctermbg=green guibg=green
+ call matchaddpos('MyGreenGroup', range(line('.'), line('.')))
+
+ " matchend()
+ call assert_equal(4, matchend(range(5), '4'))
+ call assert_equal(3, matchend(range(1, 5), '4'))
+ call assert_equal(-1, matchend(range(1, 5), '42'))
+
+ " matchstrpos()
+ call assert_equal(['4', 4, 0, 1], matchstrpos(range(5), '4'))
+ call assert_equal(['4', 3, 0, 1], matchstrpos(range(1, 5), '4'))
+ call assert_equal(['', -1, -1, -1], matchstrpos(range(1, 5), '42'))
+
+ " max() reverse()
+ call assert_equal(0, max(range(0)))
+ call assert_equal(0, max(range(10, 9)))
+ call assert_equal(9, max(range(10)))
+ call assert_equal(18, max(range(0, 20, 3)))
+ call assert_equal(20, max(range(20, 0, -3)))
+ call assert_equal(99999, max(range(100000)))
+ call assert_equal(99999, max(range(99999, 0, -1)))
+ call assert_equal(99999, max(reverse(range(100000))))
+ call assert_equal(99999, max(reverse(range(99999, 0, -1))))
+
+ " min() reverse()
+ call assert_equal(0, min(range(0)))
+ call assert_equal(0, min(range(10, 9)))
+ call assert_equal(5, min(range(5, 10)))
+ call assert_equal(5, min(range(5, 10, 3)))
+ call assert_equal(2, min(range(20, 0, -3)))
+ call assert_equal(0, min(range(100000)))
+ call assert_equal(0, min(range(99999, 0, -1)))
+ call assert_equal(0, min(reverse(range(100000))))
+ call assert_equal(0, min(reverse(range(99999, 0, -1))))
+
+ " remove()
+ call assert_equal(1, remove(range(1, 10), 0))
+ call assert_equal(2, remove(range(1, 10), 1))
+ call assert_equal(9, remove(range(1, 10), 8))
+ call assert_equal(10, remove(range(1, 10), 9))
+ call assert_equal(10, remove(range(1, 10), -1))
+ call assert_equal([3, 4, 5], remove(range(1, 10), 2, 4))
+
+ " repeat()
+ call assert_equal([0, 1, 2, 0, 1, 2], repeat(range(3), 2))
+ call assert_equal([0, 1, 2], repeat(range(3), 1))
+ call assert_equal([], repeat(range(3), 0))
+ call assert_equal([], repeat(range(5, 4), 2))
+ call assert_equal([], repeat(range(5, 4), 0))
+
+ " reverse()
+ call assert_equal([2, 1, 0], reverse(range(3)))
+ call assert_equal([0, 1, 2, 3], reverse(range(3, 0, -1)))
+ call assert_equal([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], reverse(range(10)))
+ call assert_equal([20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10], reverse(range(10, 20)))
+ call assert_equal([16, 13, 10], reverse(range(10, 18, 3)))
+ call assert_equal([19, 16, 13, 10], reverse(range(10, 19, 3)))
+ call assert_equal([19, 16, 13, 10], reverse(range(10, 20, 3)))
+ call assert_equal([11, 14, 17, 20], reverse(range(20, 10, -3)))
+ call assert_equal([], reverse(range(0)))
+
+ " TODO: setpos()
+ " new
+ " call setline(1, repeat([''], bufnr('')))
+ " call setline(bufnr('') + 1, repeat('x', bufnr('') * 2 + 6))
+ " call setpos('x', range(bufnr(''), bufnr('') + 3))
+ " bwipe!
+
+ " setreg()
+ call setreg('a', range(3))
+ call assert_equal("0\n1\n2\n", getreg('a'))
+
+ " settagstack()
+ call settagstack(1, #{items : range(4)})
+
+ " sign_define()
+ call assert_fails("call sign_define(range(5))", "E715:")
+ call assert_fails("call sign_placelist(range(5))", "E715:")
+
+ " sign_undefine()
+ call assert_fails("call sign_undefine(range(5))", "E908:")
+
+ " sign_unplacelist()
+ call assert_fails("call sign_unplacelist(range(5))", "E715:")
+
+ " sort()
+ call assert_equal([0, 1, 2, 3, 4, 5], sort(range(5, 0, -1)))
+
+ " string()
+ call assert_equal('[0, 1, 2, 3, 4]', string(range(5)))
+
+ " taglist() with 'tagfunc'
+ func TagFunc(pattern, flags, info)
+ return range(10)
+ endfunc
+ set tagfunc=TagFunc
+ call assert_fails("call taglist('asdf')", 'E987:')
+ set tagfunc=
+
+ " term_start()
+ if has('terminal') && has('termguicolors')
+ call assert_fails('call term_start(range(3, 4))', 'E474:')
+ let g:terminal_ansi_colors = range(16)
+ if has('win32')
+ let cmd = "cmd /c dir"
+ else
+ let cmd = "ls"
+ endif
+ call assert_fails('call term_start("' .. cmd .. '", #{term_finish: "close"'
+ \ .. ', ansi_colors: range(16)})', 'E475:')
+ unlet g:terminal_ansi_colors
+ endif
+
+ " type()
+ call assert_equal(v:t_list, type(range(5)))
+
+ " uniq()
+ call assert_equal([0, 1, 2, 3, 4], uniq(range(5)))
+
+ " errors
+ call assert_fails('let x=range(2, 8, 0)', 'E726:')
+ call assert_fails('let x=range(3, 1)', 'E727:')
+ call assert_fails('let x=range(1, 3, -2)', 'E727:')
+ call assert_fails('let x=range([])', 'E745:')
+ call assert_fails('let x=range(1, [])', 'E745:')
+ call assert_fails('let x=range(1, 4, [])', 'E745:')
+endfunc
+
+func Test_garbagecollect_now_fails()
+ let v:testing = 0
+ call assert_fails('call test_garbagecollect_now()', 'E1142:')
+ let v:testing = 1
+endfunc
+
+func Test_echoraw()
+ CheckScreendump
+
+ " Normally used for escape codes, but let's test with a CR.
+ let lines =<< trim END
+ call echoraw("hello\<CR>x")
+ END
+ call writefile(lines, 'XTest_echoraw')
+ let buf = RunVimInTerminal('-S XTest_echoraw', {'rows': 5, 'cols': 40})
+ call VerifyScreenDump(buf, 'Test_functions_echoraw', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_echoraw')
+endfunc
+
+" Test for echo highlighting
+func Test_echohl()
+ echohl Search
+ echo 'Vim'
+ call assert_equal('Vim', Screenline(&lines))
+ " TODO: How to check the highlight group used by echohl?
+ " ScreenAttrs() returns all zeros.
+ echohl None
+endfunc
+
+" Test for the eval() function
+func Test_eval()
+ call assert_fails("call eval('5 a')", 'E488:')
+endfunc
+
+" Test for the keytrans() function
+func Test_keytrans()
+ call assert_equal('<Space>', keytrans(' '))
+ call assert_equal('<lt>', keytrans('<'))
+ call assert_equal('<lt>Tab>', keytrans('<Tab>'))
+ call assert_equal('<Tab>', keytrans("\<Tab>"))
+ call assert_equal('<C-V>', keytrans("\<C-V>"))
+ call assert_equal('<BS>', keytrans("\<BS>"))
+ call assert_equal('<Home>', keytrans("\<Home>"))
+ call assert_equal('<C-Home>', keytrans("\<C-Home>"))
+ call assert_equal('<M-Home>', keytrans("\<M-Home>"))
+ call assert_equal('<C-Space>', keytrans("\<C-Space>"))
+ call assert_equal('<M-Space>', keytrans("\<*M-Space>"))
+ call assert_equal('<M-x>', "\<*M-x>"->keytrans())
+ call assert_equal('<C-I>', "\<*C-I>"->keytrans())
+ call assert_equal('<S-3>', "\<*S-3>"->keytrans())
+ call assert_equal('Ï€', 'Ï€'->keytrans())
+ call assert_equal('<M-Ï€>', "\<M-Ï€>"->keytrans())
+ call assert_equal('Ä›', 'Ä›'->keytrans())
+ call assert_equal('<M-Ä›>', "\<M-Ä›>"->keytrans())
+ call assert_equal('', ''->keytrans())
+ call assert_equal('', test_null_string()->keytrans())
+ call assert_fails('call keytrans(1)', 'E1174:')
+ call assert_fails('call keytrans()', 'E119:')
+endfunc
+
+" Test for the nr2char() function
+func Test_nr2char()
+ set encoding=latin1
+ call assert_equal('@', nr2char(64))
+ set encoding=utf8
+ call assert_equal('a', nr2char(97, 1))
+ call assert_equal('a', nr2char(97, 0))
+
+ call assert_equal("\x80\xfc\b" .. nr2char(0x100000), eval('"\<M-' .. nr2char(0x100000) .. '>"'))
+ call assert_equal("\x80\xfc\b" .. nr2char(0x40000000), eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
+endfunc
+
+" Test for screenattr(), screenchar() and screenchars() functions
+func Test_screen_functions()
+ call assert_equal(-1, screenattr(-1, -1))
+ call assert_equal(-1, screenchar(-1, -1))
+ call assert_equal([], screenchars(-1, -1))
+endfunc
+
+" Test for getcurpos() and setpos()
+func Test_getcurpos_setpos()
+ new
+ call setline(1, ['012345678', '012345678'])
+ normal gg6l
+ let sp = getcurpos()
+ normal 0
+ call setpos('.', sp)
+ normal jyl
+ call assert_equal('6', @")
+ call assert_equal(-1, setpos('.', test_null_list()))
+ call assert_equal(-1, setpos('.', {}))
+
+ let winid = win_getid()
+ normal G$
+ let pos = getcurpos()
+ wincmd w
+ call assert_equal(pos, getcurpos(winid))
+
+ wincmd w
+ close!
+
+ call assert_equal(getcurpos(), getcurpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(1999))
+endfunc
+
+func Test_getmousepos()
+ enew!
+ call setline(1, "\t\t\t1234")
+ call test_setmouse(1, 1)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 1,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 1,
+ \ line: 1,
+ \ column: 1,
+ \ }, getmousepos())
+ call test_setmouse(1, 25)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 25,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 25,
+ \ line: 1,
+ \ column: 4,
+ \ }, getmousepos())
+ call test_setmouse(1, 50)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 50,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 50,
+ \ line: 1,
+ \ column: 8,
+ \ }, getmousepos())
+
+ " If the mouse is positioned past the last buffer line, "line" and "column"
+ " should act like it's positioned on the last buffer line.
+ call test_setmouse(2, 25)
+ call assert_equal(#{
+ \ screenrow: 2,
+ \ screencol: 25,
+ \ winid: win_getid(),
+ \ winrow: 2,
+ \ wincol: 25,
+ \ line: 1,
+ \ column: 4,
+ \ }, getmousepos())
+ call test_setmouse(2, 50)
+ call assert_equal(#{
+ \ screenrow: 2,
+ \ screencol: 50,
+ \ winid: win_getid(),
+ \ winrow: 2,
+ \ wincol: 50,
+ \ line: 1,
+ \ column: 8,
+ \ }, getmousepos())
+ bwipe!
+endfunc
+
+func Test_getmouseshape()
+ CheckFeature mouseshape
+
+ call assert_equal('arrow', getmouseshape())
+endfunc
+
+" Test for glob()
+func Test_glob()
+ call assert_equal('', glob(test_null_string()))
+ call assert_equal('', globpath(test_null_string(), test_null_string()))
+ call assert_fails("let x = globpath(&rtp, 'syntax/c.vim', [])", 'E745:')
+
+ call writefile([], 'Xglob1')
+ call writefile([], 'XGLOB2')
+ set wildignorecase
+ " Sort output of glob() otherwise we end up with different
+ " ordering depending on whether file system is case-sensitive.
+ call assert_equal(['XGLOB2', 'Xglob1'], sort(glob('Xglob[12]', 0, 1)))
+ " wildignorecase shall be applied even when the pattern contains no wildcards.
+ call assert_equal('XGLOB2', glob('xglob2'))
+ set wildignorecase&
+
+ call delete('Xglob1')
+ call delete('XGLOB2')
+
+ call assert_fails("call glob('*', 0, {})", 'E728:')
+endfunc
+
+" Test for browse()
+func Test_browse()
+ CheckFeature browse
+ call assert_fails('call browse([], "open", "x", "a.c")', 'E745:')
+endfunc
+
+" Test for browsedir()
+func Test_browsedir()
+ CheckFeature browse
+ call assert_fails('call browsedir("open", [])', 'E730:')
+endfunc
+
+func HasDefault(msg = 'msg')
+ return a:msg
+endfunc
+
+func Test_default_arg_value()
+ call assert_equal('msg', HasDefault())
+endfunc
+
+" Test for gettext()
+func Test_gettext()
+ call assert_fails('call gettext(1)', 'E1174:')
+endfunc
+
+func Test_builtin_check()
+ call assert_fails('let g:["trim"] = {x -> " " .. x}', 'E704:')
+ call assert_fails('let g:.trim = {x -> " " .. x}', 'E704:')
+ call assert_fails('let l:["trim"] = {x -> " " .. x}', 'E704:')
+ call assert_fails('let l:.trim = {x -> " " .. x}', 'E704:')
+ let lines =<< trim END
+ vim9script
+ var trim = (x) => " " .. x
+ END
+ call v9.CheckScriptFailure(lines, 'E704:')
+
+ call assert_fails('call extend(g:, #{foo: { -> "foo" }})', 'E704:')
+ let g:bar = 123
+ call extend(g:, #{bar: { -> "foo" }}, "keep")
+ call assert_fails('call extend(g:, #{bar: { -> "foo" }}, "force")', 'E704:')
+ unlet g:bar
+
+ call assert_fails('call extend(l:, #{foo: { -> "foo" }})', 'E704:')
+ let bar = 123
+ call extend(l:, #{bar: { -> "foo" }}, "keep")
+ call assert_fails('call extend(l:, #{bar: { -> "foo" }}, "force")', 'E704:')
+ unlet bar
+
+ call assert_fails('call extend(g:, #{foo: function("extend")})', 'E704:')
+ let g:bar = 123
+ call extend(g:, #{bar: function("extend")}, "keep")
+ call assert_fails('call extend(g:, #{bar: function("extend")}, "force")', 'E704:')
+ unlet g:bar
+
+ call assert_fails('call extend(l:, #{foo: function("extend")})', 'E704:')
+ let bar = 123
+ call extend(l:, #{bar: function("extend")}, "keep")
+ call assert_fails('call extend(l:, #{bar: function("extend")}, "force")', 'E704:')
+ unlet bar
+endfunc
+
+func Test_funcref_to_string()
+ let Fn = funcref('g:Test_funcref_to_string')
+ call assert_equal("function('g:Test_funcref_to_string')", string(Fn))
+endfunc
+
+" Test for isabsolutepath()
+func Test_isabsolutepath()
+ call assert_false(isabsolutepath(''))
+ call assert_false(isabsolutepath('.'))
+ call assert_false(isabsolutepath('../Foo'))
+ call assert_false(isabsolutepath('Foo/'))
+ if has('win32')
+ call assert_true(isabsolutepath('A:\'))
+ call assert_true(isabsolutepath('A:\Foo'))
+ call assert_true(isabsolutepath('A:/Foo'))
+ call assert_false(isabsolutepath('A:Foo'))
+ call assert_false(isabsolutepath('\Windows'))
+ call assert_true(isabsolutepath('\\Server2\Share\Test\Foo.txt'))
+ else
+ call assert_true(isabsolutepath('/'))
+ call assert_true(isabsolutepath('/usr/share/'))
+ endif
+endfunc
+
+" Test for exepath()
+func Test_exepath()
+ if has('win32')
+ call assert_notequal(exepath('cmd'), '')
+
+ let oldNoDefaultCurrentDirectoryInExePath = $NoDefaultCurrentDirectoryInExePath
+ call writefile(['@echo off', 'echo Evil'], 'vim-test-evil.bat')
+ let $NoDefaultCurrentDirectoryInExePath = ''
+ call assert_notequal(exepath("vim-test-evil.bat"), '')
+ let $NoDefaultCurrentDirectoryInExePath = '1'
+ call assert_equal(exepath("vim-test-evil.bat"), '')
+ let $NoDefaultCurrentDirectoryInExePath = oldNoDefaultCurrentDirectoryInExePath
+ call delete('vim-test-evil.bat')
+ else
+ call assert_notequal(exepath('sh'), '')
+ endif
+endfunc
+
+" Test for virtcol()
+func Test_virtcol()
+ enew!
+ call setline(1, "the\tquick\tbrown\tfox")
+ norm! 4|
+ call assert_equal(8, virtcol('.'))
+ call assert_equal(8, virtcol('.', v:false))
+ call assert_equal([4, 8], virtcol('.', v:true))
+ bwipe!
+endfunc
+
+func Test_delfunc_while_listing()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nocompatible
+ for i in range(1, 999)
+ exe 'func ' .. 'MyFunc' .. i .. '()'
+ endfunc
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('delfunc MyFunc622')})
+ END
+ call writefile(lines, 'Xfunctionclear', 'D')
+ let buf = RunVimInTerminal('-S Xfunctionclear', {'rows': 12})
+
+ " This was using freed memory. The height of the terminal must be so that
+ " the next function to be listed with "j" is the one that is deleted in the
+ " timer callback, tricky!
+ call term_sendkeys(buf, ":func /MyFunc\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "j")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ga.vim b/src/testdir/test_ga.vim
new file mode 100644
index 0000000..a0050fa
--- /dev/null
+++ b/src/testdir/test_ga.vim
@@ -0,0 +1,44 @@
+" Test ga normal command, and :ascii Ex command.
+
+func Do_ga(c)
+ call setline(1, a:c)
+ let l:a = execute("norm 1goga")
+ let l:b = execute("ascii")
+ call assert_equal(l:a, l:b)
+ return l:a
+endfunc
+
+func Test_ga_command()
+ new
+ set display=uhex
+ call assert_equal("\nNUL", Do_ga(''))
+ call assert_equal("\n<<01>> 1, Hex 01, Oct 001, Digr SH", Do_ga("\x01"))
+ call assert_equal("\n<<09>> 9, Hex 09, Oct 011, Digr HT", Do_ga("\t"))
+
+ set display=
+ call assert_equal("\nNUL", Do_ga(''))
+ call assert_equal("\n<^A> 1, Hex 01, Oct 001, Digr SH", Do_ga("\x01"))
+ call assert_equal("\n<^I> 9, Hex 09, Oct 011, Digr HT", Do_ga("\t"))
+ call assert_equal("\n<^@> 0, Hex 00, Octal 000", Do_ga("\n"))
+
+ call assert_equal("\n<e> 101, Hex 65, Octal 145", Do_ga('e'))
+
+ " Test a few multi-bytes characters.
+ call assert_equal("\n<é> 233, Hex 00e9, Oct 351, Digr e'", Do_ga('é'))
+ call assert_equal("\n<ẻ> 7867, Hex 1ebb, Oct 17273, Digr e2", Do_ga('ẻ'))
+ call assert_equal("\n<\U00012345> 74565, Hex 00012345, Octal 221505", Do_ga("\U00012345"))
+
+ " Test with combining characters.
+ call assert_equal("\n<e> 101, Hex 65, Octal 145 < Ì> 769, Hex 0301, Octal 1401", Do_ga("e\u0301"))
+ call assert_equal("\n<e> 101, Hex 65, Octal 145 < Ì> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461", Do_ga("e\u0301\u0331"))
+ call assert_equal("\n<e> 101, Hex 65, Octal 145 < Ì> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461 < ̸> 824, Hex 0338, Octal 1470", Do_ga("e\u0301\u0331\u0338"))
+
+ " When using Mac fileformat, CR instead of NL is used for line termination
+ enew!
+ set fileformat=mac
+ call assert_equal("\n<^J> 10, Hex 0a, Oct 012, Digr NU", Do_ga("\r"))
+
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_getcwd.vim b/src/testdir/test_getcwd.vim
new file mode 100644
index 0000000..b67dadb
--- /dev/null
+++ b/src/testdir/test_getcwd.vim
@@ -0,0 +1,268 @@
+" Test for getcwd()
+
+func GetCwdInfo(win, tab)
+ let tab_changed = 0
+ let mod = ":t"
+ if a:tab > 0 && a:tab != tabpagenr()
+ let tab_changed = 1
+ exec "tabnext " . a:tab
+ endif
+ let bufname = fnamemodify(bufname(winbufnr(a:win)), mod)
+ if tab_changed
+ tabprevious
+ endif
+ if a:win == 0 && a:tab == 0
+ let dirname = fnamemodify(getcwd(), mod)
+ let lflag = haslocaldir()
+ elseif a:tab == 0
+ let dirname = fnamemodify(getcwd(a:win), mod)
+ let lflag = haslocaldir(a:win)
+ else
+ let dirname = fnamemodify(getcwd(a:win, a:tab), mod)
+ let lflag = a:win->haslocaldir(a:tab)
+ endif
+ return bufname . ' ' . dirname . ' ' . lflag
+endfunc
+
+" Do all test in a separate window to avoid E211 when we recursively
+" delete the Xtopdir directory during cleanup
+func SetUp()
+ set visualbell
+ set nocp viminfo+=nviminfo
+
+ " On windows a swapfile in Xtopdir prevents it from being cleaned up.
+ set noswapfile
+
+ " On windows a stale "Xtopdir" directory may exist, remove it so that
+ " we start from a clean state.
+ call delete("Xtopdir", "rf")
+ new
+ eval 'Xtopdir'->mkdir()
+ cd Xtopdir
+ let g:topdir = getcwd()
+ call mkdir('Xcwdir1')
+ call mkdir('Xcwdir2')
+ call mkdir('Xcwdir3')
+endfunction
+
+let g:cwd=getcwd()
+function TearDown()
+ q
+ call chdir(g:cwd)
+ call delete("Xtopdir", "rf")
+endfunction
+
+function Test_GetCwd()
+ new a
+ new b
+ new c
+ 3wincmd w
+ lcd Xcwdir1
+ call assert_equal("a Xcwdir1 1", GetCwdInfo(0, 0))
+ call assert_equal(g:topdir, getcwd(-1))
+ wincmd W
+ call assert_equal("b Xtopdir 0", GetCwdInfo(0, 0))
+ call assert_equal(g:topdir, getcwd(-1))
+ wincmd W
+ lcd Xcwdir3
+ call assert_equal("c Xcwdir3 1", GetCwdInfo(0, 0))
+ call assert_equal("a Xcwdir1 1", GetCwdInfo(bufwinnr("a"), 0))
+ call assert_equal("b Xtopdir 0", GetCwdInfo(bufwinnr("b"), 0))
+ call assert_equal("c Xcwdir3 1", GetCwdInfo(bufwinnr("c"), 0))
+ call assert_equal(g:topdir, getcwd(-1))
+ wincmd W
+ call assert_equal("a Xcwdir1 1", GetCwdInfo(bufwinnr("a"), tabpagenr()))
+ call assert_equal("b Xtopdir 0", GetCwdInfo(bufwinnr("b"), tabpagenr()))
+ call assert_equal("c Xcwdir3 1", GetCwdInfo(bufwinnr("c"), tabpagenr()))
+ call assert_equal(g:topdir, getcwd(-1))
+
+ tabnew x
+ new y
+ new z
+ 3wincmd w
+ call assert_equal("x Xtopdir 0", GetCwdInfo(0, 0))
+ call assert_equal(g:topdir, getcwd(-1))
+ wincmd W
+ lcd Xcwdir2
+ call assert_equal("y Xcwdir2 1", GetCwdInfo(0, 0))
+ call assert_equal(g:topdir, getcwd(-1))
+ wincmd W
+ lcd Xcwdir3
+ call assert_equal("z Xcwdir3 1", GetCwdInfo(0, 0))
+ call assert_equal("x Xtopdir 0", GetCwdInfo(bufwinnr("x"), 0))
+ call assert_equal("y Xcwdir2 1", GetCwdInfo(bufwinnr("y"), 0))
+ call assert_equal("z Xcwdir3 1", GetCwdInfo(bufwinnr("z"), 0))
+ call assert_equal(g:topdir, getcwd(-1))
+ let tp_nr = tabpagenr()
+ tabrewind
+ call assert_equal("x Xtopdir 0", GetCwdInfo(3, tp_nr))
+ call assert_equal("y Xcwdir2 1", GetCwdInfo(2, tp_nr))
+ call assert_equal("z Xcwdir3 1", GetCwdInfo(1, tp_nr))
+ call assert_equal(g:topdir, getcwd(-1))
+ " Non existing windows and tab pages
+ call assert_equal('', getcwd(100))
+ call assert_equal(0, haslocaldir(100))
+ call assert_equal('', getcwd(10, 1))
+ call assert_equal(0, haslocaldir(10, 1))
+ call assert_equal('', getcwd(1, 5))
+ call assert_equal(0, haslocaldir(1, 5))
+ call assert_fails('call getcwd([])', 'E745:')
+ call assert_fails('call getcwd(1, [])', 'E745:')
+ call assert_fails('call haslocaldir([])', 'E745:')
+ call assert_fails('call haslocaldir(1, [])', 'E745:')
+endfunc
+
+function Test_GetCwd_lcd_shellslash()
+ new
+ let root = fnamemodify('/', ':p')
+ exe 'lcd '.root
+ let cwd = getcwd()
+ if !exists('+shellslash') || &shellslash
+ call assert_equal(cwd[-1:], '/')
+ else
+ call assert_equal(cwd[-1:], '\')
+ endif
+endfunc
+
+" Test for :tcd
+function Test_Tab_Local_Cwd()
+ enew | only | tabonly
+
+ call mkdir('Xtabdir1')
+ call mkdir('Xtabdir2')
+ call mkdir('Xwindir1')
+ call mkdir('Xwindir2')
+ call mkdir('Xwindir3')
+
+ " Create three tabpages with three windows each
+ edit a
+ botright new b
+ botright new c
+ tabnew m
+ botright new n
+ botright new o
+ tabnew x
+ botright new y
+ botright new z
+
+ " Setup different directories for the tab pages and windows
+ tabrewind
+ 1wincmd w
+ lcd Xwindir1
+ tabnext
+ tcd Xtabdir1
+ 2wincmd w
+ lcd ../Xwindir2
+ tabnext
+ tcd Xtabdir2
+ 3wincmd w
+ lcd ../Xwindir3
+
+ " Check the directories of various windows
+ call assert_equal("a Xwindir1 1", GetCwdInfo(1, 1))
+ call assert_equal("b Xtopdir 0", GetCwdInfo(2, 1))
+ call assert_equal("c Xtopdir 0", GetCwdInfo(3, 1))
+ call assert_equal("m Xtabdir1 2", GetCwdInfo(1, 2))
+ call assert_equal("n Xwindir2 1", GetCwdInfo(2, 2))
+ call assert_equal("o Xtabdir1 2", GetCwdInfo(3, 2))
+ call assert_equal("x Xtabdir2 2", GetCwdInfo(1, 3))
+ call assert_equal("y Xtabdir2 2", GetCwdInfo(2, 3))
+ call assert_equal("z Xwindir3 1", GetCwdInfo(3, 3))
+
+ " Check the tabpage directories
+ call assert_equal('Xtopdir', fnamemodify(getcwd(-1, 1), ':t'))
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(-1, 2), ':t'))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(-1, 3), ':t'))
+ call assert_equal('', fnamemodify(getcwd(-1, 4), ':t'))
+
+ " Jump to different windows in the tab pages and check the current directory
+ tabrewind | 1wincmd w
+ call assert_equal('Xwindir1', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xwindir1', fnamemodify(getcwd(0), ':t'))
+ call assert_equal('Xwindir1', fnamemodify(getcwd(0, 0), ':t'))
+ call assert_true(haslocaldir(0))
+ call assert_equal(0, haslocaldir(-1, 0))
+ call assert_equal('Xtopdir', fnamemodify(getcwd(-1, 0), ':t'))
+ call assert_equal(g:topdir, getcwd(-1))
+ 2wincmd w
+ call assert_equal('Xtopdir', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xtopdir', fnamemodify(getcwd(0), ':t'))
+ call assert_equal('Xtopdir', fnamemodify(getcwd(0, 0), ':t'))
+ call assert_false(haslocaldir(0))
+ call assert_equal(0, haslocaldir(-1, 0))
+ call assert_equal('Xtopdir', fnamemodify(getcwd(-1, 0), ':t'))
+ call assert_equal(g:topdir, getcwd(-1))
+ tabnext | 1wincmd w
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(0), ':t'))
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(0, 0), ':t'))
+ call assert_true(haslocaldir(0))
+ call assert_equal(2, haslocaldir(-1, 0))
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(-1, 0), ':t'))
+ call assert_equal(g:topdir, getcwd(-1))
+ 2wincmd w
+ call assert_equal('Xwindir2', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xwindir2', fnamemodify(getcwd(0), ':t'))
+ call assert_equal('Xwindir2', fnamemodify(getcwd(0, 0), ':t'))
+ call assert_true(haslocaldir(0))
+ call assert_equal(2, haslocaldir(-1, 0))
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(-1, 0), ':t'))
+ call assert_equal(g:topdir, getcwd(-1))
+ tabnext | 1wincmd w
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(0), ':t'))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(0, 0), ':t'))
+ call assert_true(haslocaldir(0))
+ call assert_equal(2, haslocaldir(-1, 0))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(-1, 0), ':t'))
+ call assert_equal(g:topdir, getcwd(-1))
+ 3wincmd w
+ call assert_equal('Xwindir3', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xwindir3', fnamemodify(getcwd(0), ':t'))
+ call assert_equal('Xwindir3', fnamemodify(getcwd(0, 0), ':t'))
+ call assert_true(haslocaldir(0))
+ call assert_equal(2, haslocaldir(-1, 0))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(-1, 0), ':t'))
+ call assert_equal(g:topdir, getcwd(-1))
+
+ " A new tab page should inherit the directory of the current tab page
+ tabrewind | 1wincmd w
+ tabnew g
+ call assert_equal("g Xwindir1 1", GetCwdInfo(0, 0))
+ tabclose | tabrewind
+ 2wincmd w
+ tabnew h
+ call assert_equal("h Xtopdir 0", GetCwdInfo(0, 0))
+ tabclose
+ tabnext 2 | 1wincmd w
+ tabnew j
+ call assert_equal("j Xtabdir1 2", GetCwdInfo(0, 0))
+ tabclose
+
+ " Change the global directory for the first tab page
+ tabrewind | 1wincmd w
+ cd ../Xcwdir1
+ call assert_equal("a Xcwdir1 0", GetCwdInfo(1, 1))
+ call assert_equal("b Xcwdir1 0", GetCwdInfo(2, 1))
+ call assert_equal("m Xtabdir1 2", GetCwdInfo(1, 2))
+ call assert_equal("n Xwindir2 1", GetCwdInfo(2, 2))
+
+ " Change the global directory for the second tab page
+ tabnext | 1wincmd w
+ cd ../Xcwdir3
+ call assert_equal("m Xcwdir3 0", GetCwdInfo(1, 2))
+ call assert_equal("n Xwindir2 1", GetCwdInfo(2, 2))
+ call assert_equal("o Xcwdir3 0", GetCwdInfo(3, 2))
+
+ " Change the tab-local directory for the third tab page
+ tabnext | 1wincmd w
+ cd ../Xcwdir1
+ call assert_equal("x Xcwdir1 0", GetCwdInfo(1, 3))
+ call assert_equal("y Xcwdir1 0", GetCwdInfo(2, 3))
+ call assert_equal("z Xwindir3 1", GetCwdInfo(3, 3))
+
+ enew | only | tabonly
+ new
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_getvar.vim b/src/testdir/test_getvar.vim
new file mode 100644
index 0000000..2065186
--- /dev/null
+++ b/src/testdir/test_getvar.vim
@@ -0,0 +1,163 @@
+" Tests for getwinvar(), gettabvar(), gettabwinvar() and get().
+
+func Test_var()
+ " Use strings to test for memory leaks. First, check that in an empty
+ " window, gettabvar() returns the correct value
+ let t:testvar='abcd'
+ call assert_equal('abcd', gettabvar(1, 'testvar'))
+ call assert_equal('abcd', gettabvar(1, 'testvar'))
+
+ " test for getwinvar()
+ let w:var_str = "Dance"
+ let def_str = "Chance"
+ call assert_equal('Dance', getwinvar(1, 'var_str'))
+ call assert_equal('Dance', getwinvar(1, 'var_str', def_str))
+ call assert_equal({'var_str': 'Dance'}, 1->getwinvar(''))
+ call assert_equal({'var_str': 'Dance'}, 1->getwinvar('', def_str))
+ unlet w:var_str
+ call assert_equal('Chance', getwinvar(1, 'var_str', def_str))
+ call assert_equal({}, getwinvar(1, ''))
+ call assert_equal({}, getwinvar(1, '', def_str))
+ call assert_equal('', getwinvar(9, ''))
+ call assert_equal('Chance', getwinvar(9, '', def_str))
+ call assert_equal(0, getwinvar(1, '&nu'))
+ call assert_equal(0, getwinvar(1, '&nu', 1))
+ unlet def_str
+
+ " test for gettabvar()
+ tabnew
+ tabnew
+ let t:var_list = [1, 2, 3]
+ let t:other = 777
+ let def_list = [4, 5, 6, 7]
+ tabrewind
+ call assert_equal([1, 2, 3], 3->gettabvar('var_list'))
+ call assert_equal([1, 2, 3], gettabvar(3, 'var_list', def_list))
+ call assert_equal({'var_list': [1, 2, 3], 'other': 777}, gettabvar(3, ''))
+ call assert_equal({'var_list': [1, 2, 3], 'other': 777},
+ \ gettabvar(3, '', def_list))
+
+ tablast
+ unlet t:var_list
+ tabrewind
+ call assert_equal([4, 5, 6, 7], gettabvar(3, 'var_list', def_list))
+ call assert_equal('', gettabvar(9, ''))
+ call assert_equal([4, 5, 6, 7], gettabvar(9, '', def_list))
+ call assert_equal('', gettabvar(3, '&nu'))
+ call assert_equal([4, 5, 6, 7], gettabvar(3, '&nu', def_list))
+ unlet def_list
+ tabonly
+
+ " test for gettabwinvar()
+ tabnew
+ tabnew
+ tabprev
+ split
+ split
+ wincmd w
+ vert split
+ wincmd w
+ let w:var_dict = {'dict': 'tabwin'}
+ let def_dict = {'dict2': 'newval'}
+ wincmd b
+ tabrewind
+ call assert_equal({'dict': 'tabwin'}, 2->gettabwinvar(3, 'var_dict'))
+ call assert_equal({'dict': 'tabwin'},
+ \ gettabwinvar(2, 3, 'var_dict', def_dict))
+ call assert_equal({'var_dict': {'dict': 'tabwin'}}, gettabwinvar(2, 3, ''))
+ call assert_equal({'var_dict': {'dict': 'tabwin'}},
+ \ gettabwinvar(2, 3, '', def_dict))
+
+ tabnext
+ 3wincmd w
+ unlet w:var_dict
+ tabrewind
+ call assert_equal({'dict2': 'newval'},
+ \ gettabwinvar(2, 3, 'var_dict', def_dict))
+ call assert_equal({}, gettabwinvar(2, 3, ''))
+ call assert_equal({}, gettabwinvar(2, 3, '', def_dict))
+ call assert_equal("", gettabwinvar(2, 9, ''))
+ call assert_equal({'dict2': 'newval'}, gettabwinvar(2, 9, '', def_dict))
+ call assert_equal('', gettabwinvar(9, 3, ''))
+ call assert_equal({'dict2': 'newval'}, gettabwinvar(9, 3, '', def_dict))
+
+ unlet def_dict
+
+ call assert_equal("", gettabwinvar(9, 2020, ''))
+ call assert_equal('', gettabwinvar(2, 3, '&nux'))
+ call assert_equal(1, gettabwinvar(2, 3, '&nux', 1))
+ tabonly
+endfunc
+
+" It was discovered that "gettabvar()" would fail if called from within the
+" tabline when the user closed a window. This test confirms the fix.
+func Test_gettabvar_in_tabline()
+ let t:var_str = 'value'
+
+ set tabline=%{assert_equal('value',gettabvar(1,'var_str'))}
+ set showtabline=2
+
+ " Simulate the user opening a split (which becomes window #1) and then
+ " closing the split, which triggers the redrawing of the tabline.
+ leftabove split
+ redrawstatus!
+ close
+ redrawstatus!
+endfunc
+
+" Test get() function using default value.
+
+" get({dict}, {key} [, {default}])
+func Test_get_dict()
+ let d = {'foo': 42}
+ call assert_equal(42, get(d, 'foo', 99))
+ call assert_equal(999, get(d, 'bar', 999))
+endfunc
+
+" get({list}, {idx} [, {default}])
+func Test_get_list()
+ let l = [1,2,3]
+ call assert_equal(1, get(l, 0, 999))
+ call assert_equal(3, get(l, -1, 999))
+ call assert_equal(999, get(l, 3, 999))
+endfunc
+
+" get({blob}, {idx} [, {default}]) - in test_blob.vim
+
+" get({lambda}, {what} [, {default}])
+func Test_get_lambda()
+ let l:L = {-> 42}
+ call assert_match('^<lambda>', get(l:L, 'name'))
+ call assert_equal(l:L, get(l:L, 'func'))
+ call assert_equal({'lambda has': 'no dict'}, get(l:L, 'dict', {'lambda has': 'no dict'}))
+ call assert_equal(0, get(l:L, 'dict'))
+ call assert_equal([], get(l:L, 'args'))
+endfunc
+
+func s:FooBar()
+endfunc
+
+" get({func}, {what} [, {default}])
+func Test_get_func()
+ let l:F = function('tr')
+ call assert_equal('tr', get(l:F, 'name'))
+ call assert_equal(l:F, get(l:F, 'func'))
+
+ let Fb_func = function('s:FooBar')
+ call assert_match('<SNR>\d\+_FooBar', get(Fb_func, 'name'))
+ let Fb_ref = funcref('s:FooBar')
+ call assert_match('<SNR>\d\+_FooBar', get(Fb_ref, 'name'))
+
+ call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'}))
+ call assert_equal(0, get(l:F, 'dict'))
+ call assert_equal([], get(l:F, 'args'))
+ let NF = test_null_function()
+ call assert_equal('', get(NF, 'name'))
+ call assert_equal(NF, get(NF, 'func'))
+ call assert_equal(0, get(NF, 'dict'))
+ call assert_equal([], get(NF, 'args'))
+endfunc
+
+" get({partial}, {what} [, {default}]) - in test_partial.vim
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_gf.vim b/src/testdir/test_gf.vim
new file mode 100644
index 0000000..8317cb5
--- /dev/null
+++ b/src/testdir/test_gf.vim
@@ -0,0 +1,295 @@
+" Test for the gf and gF (goto file) commands
+
+" This is a test if a URL is recognized by "gf", with the cursor before and
+" after the "://". Also test ":\\".
+func Test_gf_url()
+ enew!
+ call append(0, [
+ \ "first test for URL://machine.name/tmp/vimtest2a and other text",
+ \ "second test for URL://machine.name/tmp/vimtest2b. And other text",
+ \ "third test for URL:\\\\machine.name\\vimtest2c and other text",
+ \ "fourth test for URL:\\\\machine.name\\tmp\\vimtest2d, and other text",
+ \ "fifth test for URL://machine.name/tmp?q=vim&opt=yes and other text",
+ \ "sixth test for URL://machine.name:1234?q=vim and other text",
+ \ ])
+ call cursor(1,1)
+ call search("^first")
+ call search("tmp")
+ call assert_equal("URL://machine.name/tmp/vimtest2a", expand("<cfile>"))
+ call search("^second")
+ call search("URL")
+ call assert_equal("URL://machine.name/tmp/vimtest2b", expand("<cfile>"))
+ set isf=@,48-57,/,.,-,_,+,,,$,~,\
+ call search("^third")
+ call search("name")
+ call assert_equal("URL:\\\\machine.name\\vimtest2c", expand("<cfile>"))
+ call search("^fourth")
+ call search("URL")
+ call assert_equal("URL:\\\\machine.name\\tmp\\vimtest2d", expand("<cfile>"))
+
+ call search("^fifth")
+ call search("URL")
+ call assert_equal("URL://machine.name/tmp?q=vim&opt=yes", expand("<cfile>"))
+
+ call search("^sixth")
+ call search("URL")
+ call assert_equal("URL://machine.name:1234?q=vim", expand("<cfile>"))
+
+ %d
+ call setline(1, "demo://remote_file")
+ wincmd f
+ call assert_equal('demo://remote_file', @%)
+ call assert_equal(2, winnr('$'))
+ close!
+
+ set isf&vim
+ enew!
+endfunc
+
+func Test_gF()
+ new
+ call setline(1, ['111', '222', '333', '444'])
+ w! Xgffile
+ close
+ new
+ set isfname-=:
+ call setline(1, ['one', 'Xgffile:3', 'three'])
+ 2
+ call assert_fails('normal gF', 'E37:')
+ call assert_equal(2, getcurpos()[1])
+ w! Xgffile2
+ normal gF
+ call assert_equal('Xgffile', bufname('%'))
+ call assert_equal(3, getcurpos()[1])
+
+ enew!
+ call setline(1, ['one', 'the Xgffile line 2, and more', 'three'])
+ w! Xgffile2
+ normal 2GfX
+ normal gF
+ call assert_equal('Xgffile', bufname('%'))
+ call assert_equal(2, getcurpos()[1])
+
+ " jumping to the file/line with CTRL-W_F
+ %bw!
+ edit Xfile1
+ call setline(1, ['one', 'Xgffile:4', 'three'])
+ exe "normal 2G\<C-W>F"
+ call assert_equal('Xgffile', bufname('%'))
+ call assert_equal(4, getcurpos()[1])
+
+ set isfname&
+ call delete('Xgffile')
+ call delete('Xgffile2')
+ %bw!
+endfunc
+
+" Test for invoking 'gf' on a ${VAR} variable
+func Test_gf()
+ set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
+
+ call writefile(["Test for gf command"], "Xtest1", 'D')
+ if has("unix")
+ call writefile([" ${CDIR}/Xtest1"], "Xtestgf", 'D')
+ else
+ call writefile([" $TDIR/Xtest1"], "Xtestgf", 'D')
+ endif
+ new Xtestgf
+ if has("unix")
+ let $CDIR = "."
+ /CDIR
+ else
+ if has("amiga")
+ let $TDIR = "/testdir"
+ else
+ let $TDIR = "."
+ endif
+ /TDIR
+ endif
+
+ normal gf
+ call assert_equal('Xtest1', fnamemodify(bufname(''), ":t"))
+
+ close!
+endfunc
+
+func Test_gf_visual()
+ call writefile(['one', 'two', 'three', 'four'], "Xtest_gf_visual", 'D')
+ new
+ call setline(1, 'XXXtest_gf_visualXXX')
+ set hidden
+
+ " Visually select Xtest_gf_visual and use gf to go to that file
+ norm! ttvtXgf
+ call assert_equal('Xtest_gf_visual', bufname('%'))
+
+ " if multiple lines are selected, then gf should fail
+ call setline(1, ["one", "two"])
+ normal VGgf
+ call assert_equal('Xtest_gf_visual', @%)
+
+ " following line number is used for gF
+ bwipe!
+ new
+ call setline(1, 'XXXtest_gf_visual:3XXX')
+ norm! 0ttvt:gF
+ call assert_equal('Xtest_gf_visual', bufname('%'))
+ call assert_equal(3, getcurpos()[1])
+
+ " do not include the NUL at the end
+ call writefile(['x'], 'Xvisual', 'D')
+ let save_enc = &enc
+ for enc in ['latin1', 'utf-8']
+ exe "set enc=" .. enc
+ new
+ call setline(1, 'Xvisual')
+ set nomodified
+ exe "normal \<C-V>$gf"
+ call assert_equal('Xvisual', bufname())
+ bwipe!
+ endfor
+ let &enc = save_enc
+
+ " line number in visual area is used for file name
+ if has('unix')
+ bwipe!
+ call writefile([], "Xtest_gf_visual:3", 'D')
+ new
+ call setline(1, 'XXXtest_gf_visual:3XXX')
+ norm! 0ttvtXgF
+ call assert_equal('Xtest_gf_visual:3', bufname('%'))
+ endif
+
+ bwipe!
+ set hidden&
+endfunc
+
+func Test_gf_error()
+ new
+ call assert_fails('normal gf', 'E446:')
+ call assert_fails('normal gF', 'E446:')
+ call setline(1, '/doesnotexist')
+ call assert_fails('normal gf', 'E447:')
+ call assert_fails('normal gF', 'E447:')
+ call assert_fails('normal [f', 'E447:')
+
+ " gf is not allowed when text is locked
+ au InsertCharPre <buffer> normal! gF<CR>
+ let caught_e565 = 0
+ try
+ call feedkeys("ix\<esc>", 'xt')
+ catch /^Vim\%((\a\+)\)\=:E565/ " catch E565
+ let caught_e565 = 1
+ endtry
+ call assert_equal(1, caught_e565)
+ au! InsertCharPre
+
+ bwipe!
+
+ " gf is not allowed when buffer is locked
+ new
+ augroup Test_gf
+ au!
+ au OptionSet diff norm! gf
+ augroup END
+ call setline(1, ['Xfile1', 'line2', 'line3', 'line4'])
+ call test_override('starting', 1)
+ call assert_fails('diffthis', 'E788:')
+ call test_override('starting', 0)
+ augroup Test_gf
+ au!
+ augroup END
+ bw!
+endfunc
+
+" If a file is not found by 'gf', then 'includeexpr' should be used to locate
+" the file.
+func Test_gf_includeexpr()
+ new
+ let g:Inc_fname = ''
+ func IncFunc()
+ let g:Inc_fname = v:fname
+ return v:fname
+ endfunc
+ setlocal includeexpr=IncFunc()
+ call setline(1, 'somefile.java')
+ call assert_fails('normal gf', 'E447:')
+ call assert_equal('somefile.java', g:Inc_fname)
+ close!
+ delfunc IncFunc
+endfunc
+
+" Test for using a script-local function for 'includeexpr'
+func Test_includeexpr_scriptlocal_func()
+ func! s:IncludeFunc()
+ let g:IncludeFname = v:fname
+ return ''
+ endfunc
+ set includeexpr=s:IncludeFunc()
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &g:includeexpr)
+ new | only
+ call setline(1, 'TestFile1')
+ let g:IncludeFname = ''
+ call assert_fails('normal! gf', 'E447:')
+ call assert_equal('TestFile1', g:IncludeFname)
+ bw!
+ set includeexpr=<SID>IncludeFunc()
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &g:includeexpr)
+ new | only
+ call setline(1, 'TestFile2')
+ let g:IncludeFname = ''
+ call assert_fails('normal! gf', 'E447:')
+ call assert_equal('TestFile2', g:IncludeFname)
+ bw!
+ setlocal includeexpr=
+ setglobal includeexpr=s:IncludeFunc()
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &g:includeexpr)
+ call assert_equal('', &includeexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
+ call setline(1, 'TestFile3')
+ let g:IncludeFname = ''
+ call assert_fails('normal! gf', 'E447:')
+ call assert_equal('TestFile3', g:IncludeFname)
+ bw!
+ setlocal includeexpr=
+ setglobal includeexpr=<SID>IncludeFunc()
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &g:includeexpr)
+ call assert_equal('', &includeexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr)
+ call setline(1, 'TestFile4')
+ let g:IncludeFname = ''
+ call assert_fails('normal! gf', 'E447:')
+ call assert_equal('TestFile4', g:IncludeFname)
+ bw!
+ set includeexpr&
+ delfunc s:IncludeFunc
+ bw!
+endfunc
+
+" Check that expanding directories can handle more than 255 entries.
+func Test_gf_subdirs_wildcard()
+ let cwd = getcwd()
+ let dir = 'Xtestgf_dir'
+ call mkdir(dir, 'R')
+ call chdir(dir)
+ for i in range(300)
+ call mkdir(i)
+ call writefile([], i .. '/' .. i, 'S')
+ endfor
+ set path=./**
+
+ new | only
+ call setline(1, '99')
+ w! Xtest1
+ normal gf
+ call assert_equal('99', fnamemodify(bufname(''), ":t"))
+
+ call chdir(cwd)
+ set path&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_glob2regpat.vim b/src/testdir/test_glob2regpat.vim
new file mode 100644
index 0000000..965ca5c
--- /dev/null
+++ b/src/testdir/test_glob2regpat.vim
@@ -0,0 +1,35 @@
+" Test glob2regpat()
+
+import './vim9.vim' as v9
+
+func Test_glob2regpat_invalid()
+ call assert_equal('^1\.33$', glob2regpat(1.33))
+ call v9.CheckDefAndScriptFailure(['echo glob2regpat(1.2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1174: String required for argument 1'])
+ call assert_fails('call glob2regpat("}")', 'E219:')
+ call assert_fails('call glob2regpat("{")', 'E220:')
+endfunc
+
+func Test_glob2regpat_valid()
+ call assert_equal('^foo\.', glob2regpat('foo.*'))
+ call assert_equal('^foo.$', 'foo?'->glob2regpat())
+ call assert_equal('\.vim$', glob2regpat('*.vim'))
+ call assert_equal('^[abc]$', glob2regpat('[abc]'))
+ call assert_equal('^foo bar$', glob2regpat('foo\ bar'))
+ call assert_equal('^foo,bar$', glob2regpat('foo,bar'))
+ call assert_equal('^\(foo\|bar\)$', glob2regpat('{foo,bar}'))
+ call assert_equal('.*', glob2regpat('**'))
+
+ if exists('+shellslash')
+ call assert_equal('^foo[\/].$', glob2regpat('foo\?'))
+ call assert_equal('^\(foo[\/]\|bar\|foobar\)$', glob2regpat('{foo\,bar,foobar}'))
+ call assert_equal('^[\/]\(foo\|bar[\/]\)$', glob2regpat('\{foo,bar\}'))
+ call assert_equal('^[\/][\/]\(foo\|bar[\/][\/]\)$', glob2regpat('\\{foo,bar\\}'))
+ else
+ call assert_equal('^foo?$', glob2regpat('foo\?'))
+ call assert_equal('^\(foo,bar\|foobar\)$', glob2regpat('{foo\,bar,foobar}'))
+ call assert_equal('^{foo,bar}$', glob2regpat('\{foo,bar\}'))
+ call assert_equal('^\\\(foo\|bar\\\)$', glob2regpat('\\{foo,bar\\}'))
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_global.vim b/src/testdir/test_global.vim
new file mode 100644
index 0000000..34857b2
--- /dev/null
+++ b/src/testdir/test_global.vim
@@ -0,0 +1,150 @@
+" Test for :global and :vglobal
+
+source check.vim
+source term_util.vim
+
+func Test_yank_put_clipboard()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ set clipboard=unnamed
+ g/^/normal yyp
+ call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
+ set clipboard=unnamed,unnamedplus
+ call setline(1, ['a', 'b', 'c'])
+ g/^/normal yyp
+ call assert_equal(['a', 'a', 'b', 'b', 'c', 'c'], getline(1, 6))
+ set clipboard&
+ bwipe!
+endfunc
+
+func Test_global_set_clipboard()
+ CheckFeature clipboard_working
+ new
+ set clipboard=unnamedplus
+ let @+='clipboard' | g/^/set cb= | let @" = 'unnamed' | put
+ call assert_equal(['','unnamed'], getline(1, '$'))
+ set clipboard&
+ bwipe!
+endfunc
+
+func Test_nested_global()
+ new
+ call setline(1, ['nothing', 'found', 'found bad', 'bad'])
+ call assert_fails('g/found/3v/bad/s/^/++/', 'E147:')
+ g/found/v/bad/s/^/++/
+ call assert_equal(['nothing', '++found', 'found bad', 'bad'], getline(1, 4))
+ bwipe!
+endfunc
+
+func Test_global_error()
+ call assert_fails('g\\a', 'E10:')
+ call assert_fails('g', 'E148:')
+ call assert_fails('g/\(/y', 'E54:')
+endfunc
+
+" Test for printing lines using :g with different search patterns
+func Test_global_print()
+ new
+ call setline(1, ['foo', 'bar', 'foo', 'foo'])
+ let @/ = 'foo'
+ let t = execute("g/")->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ " Test for Vi compatible patterns
+ let @/ = 'bar'
+ let t = execute('g\/')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ normal gg
+ s/foo/foo/
+ let t = execute('g\&')->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ let @/ = 'bar'
+ let t = execute('g?')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ " Test for the 'Pattern found in every line' message
+ let v:statusmsg = ''
+ v/foo\|bar/p
+ call assert_notequal('', v:statusmsg)
+
+ " In Vim9 script this is an error
+ let caught = 'no'
+ try
+ vim9cmd v/foo\|bar/p
+ catch /E538/
+ let caught = 'yes'
+ call assert_match('E538: Pattern found in every line: foo\|bar', v:exception)
+ endtry
+ call assert_equal('yes', caught)
+
+ " In Vim9 script not matching is an error
+ let caught = 'no'
+ try
+ vim9cmd g/foobarnotfound/p
+ catch /E486/
+ let caught = 'yes'
+ call assert_match('E486: Pattern not found: foobarnotfound', v:exception)
+ endtry
+ call assert_equal('yes', caught)
+
+ close!
+endfunc
+
+func Test_global_empty_pattern()
+ " populate history
+ silent g/hello/
+
+ redir @a
+ g//
+ redir END
+
+ call assert_match('Pattern not found: hello', @a)
+ " ^~~~~ this was previously empty
+endfunc
+
+" Test for global command with newline character
+func Test_global_newline()
+ new
+ call setline(1, ['foo'])
+ exe "g/foo/s/f/h/\<NL>s/o$/w/"
+ call assert_equal('how', getline(1))
+ call setline(1, ["foo\<NL>bar"])
+ exe "g/foo/s/foo\\\<NL>bar/xyz/"
+ call assert_equal('xyz', getline(1))
+ close!
+endfunc
+
+func Test_wrong_delimiter()
+ call assert_fails('g x^bxd', 'E146:')
+endfunc
+
+" Test for interrupting :global using Ctrl-C
+func Test_interrupt_global()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ cnoremap ; <Cmd>sleep 10<CR>
+ call setline(1, repeat(['foo'], 5))
+ END
+ call writefile(lines, 'Xtest_interrupt_global', 'D')
+ let buf = RunVimInTerminal('-S Xtest_interrupt_global', {'rows': 6})
+
+ call term_sendkeys(buf, ":g/foo/norm :\<C-V>;\<CR>")
+ " Wait for :sleep to start
+ call TermWait(buf, 100)
+ call term_sendkeys(buf, "\<C-C>")
+ call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 6))}, 1000)
+
+ " Also test in Ex mode
+ call term_sendkeys(buf, "gQg/foo/norm :\<C-V>;\<CR>")
+ " Wait for :sleep to start
+ call TermWait(buf, 100)
+ call term_sendkeys(buf, "\<C-C>")
+ call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 5))}, 1000)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_gn.vim b/src/testdir/test_gn.vim
new file mode 100644
index 0000000..eb237a2
--- /dev/null
+++ b/src/testdir/test_gn.vim
@@ -0,0 +1,221 @@
+" Test for gn command
+
+func Test_gn_command()
+ noautocmd new
+ " replace a single char by itself quoted:
+ call setline('.', 'abc x def x ghi x jkl')
+ let @/ = 'x'
+ exe "norm! cgn'x'\<esc>.."
+ call assert_equal("abc 'x' def 'x' ghi 'x' jkl", getline('.'))
+ sil! %d_
+
+ " simple search match
+ call setline('.', 'foobar')
+ let @/ = 'foobar'
+ exe "norm! gncsearchmatch"
+ call assert_equal('searchmatch', getline('.'))
+ sil! %d _
+
+ " replace a multi-line match
+ call setline('.', ['', 'one', 'two'])
+ let @/ = 'one\_s*two\_s'
+ exe "norm! gnceins\<CR>zwei"
+ call assert_equal(['','eins','zwei'], getline(1,'$'))
+ sil! %d _
+
+ " test count argument
+ call setline('.', ['', 'abcdx | abcdx | abcdx'])
+ let @/ = '[a]bcdx'
+ exe "norm! 2gnd"
+ call assert_equal(['','abcdx | | abcdx'], getline(1,'$'))
+ sil! %d _
+
+ " join lines
+ call setline('.', ['join ', 'lines'])
+ let @/ = '$'
+ exe "norm! 0gnd"
+ call assert_equal(['join lines'], getline(1,'$'))
+ sil! %d _
+
+ " zero-width match
+ call setline('.', ['', 'zero width pattern'])
+ let @/ = '\>\zs'
+ exe "norm! 0gnd"
+ call assert_equal(['', 'zerowidth pattern'], getline(1,'$'))
+ sil! %d _
+
+ " delete first and last chars
+ call setline('.', ['delete first and last chars'])
+ let @/ = '^'
+ exe "norm! 0gnd$"
+ let @/ = '\zs'
+ exe "norm! gnd"
+ call assert_equal(['elete first and last char'], getline(1,'$'))
+ sil! %d _
+
+ " using visual mode
+ call setline('.', ['', 'uniquepattern uniquepattern'])
+ exe "norm! /[u]niquepattern/s\<cr>vlgnd"
+ call assert_equal(['', ' uniquepattern'], getline(1,'$'))
+ sil! %d _
+
+ " backwards search
+ call setline('.', ['my very excellent mother just served us nachos'])
+ let @/ = 'mother'
+ exe "norm! $cgNmongoose"
+ call assert_equal(['my very excellent mongoose just served us nachos'], getline(1,'$'))
+ sil! %d _
+
+ " search for single char
+ call setline('.', ['','for (i=0; i<=10; i++)'])
+ let @/ = 'i'
+ exe "norm! cgnj"
+ call assert_equal(['','for (j=0; i<=10; i++)'], getline(1,'$'))
+ sil! %d _
+
+ " search hex char
+ call setline('.', ['','Y'])
+ set noignorecase
+ let @/ = '\%x59'
+ exe "norm! gnd"
+ call assert_equal(['',''], getline(1,'$'))
+ sil! %d _
+
+ " test repeating gdn
+ call setline('.', ['', '1', 'Johnny', '2', 'Johnny', '3'])
+ let @/ = 'Johnny'
+ exe "norm! dgn."
+ call assert_equal(['','1', '', '2', '', '3'], getline(1,'$'))
+ sil! %d _
+
+ " test repeating gUgn
+ call setline('.', ['', '1', 'Depp', '2', 'Depp', '3'])
+ let @/ = 'Depp'
+ exe "norm! gUgn."
+ call assert_equal(['', '1', 'DEPP', '2', 'DEPP', '3'], getline(1,'$'))
+ sil! %d _
+
+ " test using look-ahead assertions
+ call setline('.', ['a:10', '', 'a:1', '', 'a:20'])
+ let @/ = 'a:0\@!\zs\d\+'
+ exe "norm! 2nygno\<esc>p"
+ call assert_equal(['a:10', '', 'a:1', '1', '', 'a:20'], getline(1,'$'))
+ sil! %d _
+
+ " test using nowrapscan
+ set nowrapscan
+ call setline(1, 'foo bar baz')
+ exe "norm! /bar/e\<cr>"
+ exe "norm! gnd"
+ call assert_equal(['foo baz'], getline(1,'$'))
+ sil! %d_
+
+ " search upwards with nowrapscan set
+ call setline('.', ['foo', 'bar', 'foo', 'baz'])
+ set nowrapscan
+ let @/ = 'foo'
+ $
+ norm! dgN
+ call assert_equal(['foo', 'bar', '', 'baz'], getline(1,'$'))
+ sil! %d_
+
+ " search using the \zs atom
+ call setline(1, [' nnoremap', '', 'nnoremap'])
+ set wrapscan&vim
+ let @/ = '\_s\zsnnoremap'
+ $
+ norm! cgnmatch
+ call assert_equal([' nnoremap', '', 'match'], getline(1,'$'))
+ sil! %d_
+
+ " make sure it works correctly for one-char wide search items
+ call setline('.', ['abcdefghi'])
+ let @/ = 'a'
+ exe "norm! 0fhvhhgNgU"
+ call assert_equal(['ABCDEFGHi'], getline(1,'$'))
+ call setline('.', ['abcdefghi'])
+ let @/ = 'b'
+ " this gn wraps around the end of the file
+ exe "norm! 0fhvhhgngU"
+ call assert_equal(['aBCDEFGHi'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['abcdefghi'])
+ let @/ = 'f'
+ exe "norm! 0vllgngU"
+ call assert_equal(['ABCDEFghi'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['12345678'])
+ let @/ = '5'
+ norm! gg0f7vhhhhgnd
+ call assert_equal(['12348'], getline(1,'$'))
+ sil! %d _
+ call setline('.', ['12345678'])
+ let @/ = '5'
+ norm! gg0f2vf7gNd
+ call assert_equal(['1678'], getline(1,'$'))
+ sil! %d _
+ set wrapscan&vim
+
+ " Without 'wrapscan', in visual mode, running gn without a match should fail
+ " but the visual mode should be kept.
+ set nowrapscan
+ call setline('.', 'one two')
+ let @/ = 'one'
+ call assert_beeps('normal 0wvlgn')
+ exe "normal y"
+ call assert_equal('tw', @")
+
+ " with exclusive selection, run gn and gN
+ set selection=exclusive
+ normal 0gny
+ call assert_equal('one', @")
+ normal 0wgNy
+ call assert_equal('one', @")
+ set selection&
+endfunc
+
+func Test_gN_repeat()
+ new
+ call setline(1, 'this list is a list with a list of a list.')
+ /list
+ normal $gNgNgNx
+ call assert_equal('list with a list of a list', @")
+ bwipe!
+endfunc
+
+func Test_gN_then_gn()
+ new
+
+ call setline(1, 'this list is a list with a list of a last.')
+ /l.st
+ normal $gNgNgnx
+ call assert_equal('last', @")
+
+ call setline(1, 'this list is a list with a lust of a last.')
+ /l.st
+ normal $gNgNgNgnx
+ call assert_equal('lust of a last', @")
+
+ bwipe!
+endfunc
+
+func Test_gn_multi_line()
+ new
+ call setline(1, [
+ \ 'func Tm1()',
+ \ ' echo "one"',
+ \ 'endfunc',
+ \ 'func Tm2()',
+ \ ' echo "two"',
+ \ 'endfunc',
+ \ 'func Tm3()',
+ \ ' echo "three"',
+ \ 'endfunc',
+ \])
+ /\v^func Tm\d\(\)\n.*\zs".*"\ze$
+ normal jgnrx
+ call assert_equal(' echo xxxxx', getline(5))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_goto.vim b/src/testdir/test_goto.vim
new file mode 100644
index 0000000..85c156a
--- /dev/null
+++ b/src/testdir/test_goto.vim
@@ -0,0 +1,441 @@
+" Test commands that jump somewhere.
+
+" Create a new buffer using "lines" and place the cursor on the word after the
+" first occurrence of return and invoke "cmd". The cursor should now be
+" positioned at the given line and col.
+func XTest_goto_decl(cmd, lines, line, col)
+ new
+ call setline(1, a:lines)
+ /return/
+ normal! W
+ execute 'norm! ' . a:cmd
+ call assert_equal(a:line, line('.'))
+ call assert_equal(a:col, col('.'))
+ quit!
+endfunc
+
+func Test_gD()
+ let lines =<< trim [CODE]
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gD', lines, 1, 5)
+endfunc
+
+func Test_gD_too()
+ let lines =<< trim [CODE]
+ Filename x;
+
+ int Filename
+ int func() {
+ Filename x;
+ return x;
+ [CODE]
+
+ call XTest_goto_decl('gD', lines, 1, 10)
+endfunc
+
+func Test_gD_comment()
+ let lines =<< trim [CODE]
+ /* int x; */
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gD', lines, 2, 5)
+endfunc
+
+func Test_gD_inline_comment()
+ let lines =<< trim [CODE]
+ int y /* , x */;
+ int x;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gD', lines, 2, 5)
+endfunc
+
+func Test_gD_string()
+ let lines =<< trim [CODE]
+ char *s[] = "x";
+ int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gD', lines, 2, 5)
+endfunc
+
+func Test_gD_string_same_line()
+ let lines =<< trim [CODE]
+ char *s[] = "x", int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gD', lines, 1, 22)
+endfunc
+
+func Test_gD_char()
+ let lines =<< trim [CODE]
+ char c = 'x';
+ int x = 1;
+
+ int func(void)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gD', lines, 2, 5)
+endfunc
+
+func Test_gd()
+ let lines =<< trim [CODE]
+ int x;
+
+ int func(int x)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 3, 14)
+endfunc
+
+" Using gd to jump to a declaration in a fold
+func Test_gd_with_fold()
+ new
+ let lines =<< trim END
+ #define ONE 1
+ #define TWO 2
+ #define THREE 3
+
+ TWO
+ END
+ call setline(1, lines)
+ 1,3fold
+ call feedkeys('Ggd', 'xt')
+ call assert_equal(2, line('.'))
+ call assert_equal(-1, foldclosedend(2))
+ bw!
+endfunc
+
+func Test_gd_not_local()
+ let lines =<< trim [CODE]
+ int func1(void)
+ {
+ return x;
+ }
+
+ int func2(int x)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 3, 10)
+endfunc
+
+func Test_gd_kr_style()
+ let lines =<< trim [CODE]
+ int func(x)
+ int x;
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 2, 7)
+endfunc
+
+func Test_gd_missing_braces()
+ let lines =<< trim [CODE]
+ def func1(a)
+ a + 1
+ end
+
+ a = 1
+
+ def func2()
+ return a
+ end
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 1, 11)
+endfunc
+
+func Test_gd_comment()
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ /* int x; */
+ int x;
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 4, 7)
+endfunc
+
+func Test_gd_comment_in_string()
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s ="//"; int x;
+ int x;
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 3, 22)
+endfunc
+
+func Test_gd_string_in_comment()
+ set comments=
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ /* " */ int x;
+ int x;
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 3, 15)
+ set comments&
+endfunc
+
+func Test_gd_inline_comment()
+ let lines =<< trim [CODE]
+ int func(/* x is an int */ int x)
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 1, 32)
+endfunc
+
+func Test_gd_inline_comment_only()
+ let lines =<< trim [CODE]
+ int func(void) /* one lonely x */
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 3, 10)
+endfunc
+
+func Test_gd_inline_comment_body()
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ int y /* , x */;
+
+ for (/* int x = 0 */; y < 2; y++);
+
+ int x = 0;
+
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 7, 7)
+endfunc
+
+func Test_gd_trailing_multiline_comment()
+ let lines =<< trim [CODE]
+ int func(int x) /* x is an int */
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 1, 14)
+endfunc
+
+func Test_gd_trailing_comment()
+ let lines =<< trim [CODE]
+ int func(int x) // x is an int
+ {
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 1, 14)
+endfunc
+
+func Test_gd_string()
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s = "x";
+ int x = 1;
+
+ return x;
+ }
+ [CODE]
+ call XTest_goto_decl('gd', lines, 4, 7)
+endfunc
+
+func Test_gd_string_only()
+ let lines =<< trim [CODE]
+ int func(void)
+ {
+ char *s = "x";
+
+ return x;
+ }
+ [CODE]
+
+ call XTest_goto_decl('gd', lines, 5, 10)
+endfunc
+
+" Check that setting 'cursorline' does not change curswant
+func Test_cursorline_keep_col()
+ new
+ call setline(1, ['long long long line', 'short line'])
+ normal ggfi
+ let pos = getcurpos()
+ normal j
+ set cursorline
+ normal k
+ call assert_equal(pos, getcurpos())
+ bwipe!
+ set nocursorline
+endfunc
+
+func Test_gd_local_block()
+ let lines =<< trim [CODE]
+ int main()
+ {
+ char *a = "NOT NULL";
+ if(a)
+ {
+ char *b = a;
+ printf("%s\n", b);
+ }
+ else
+ {
+ char *b = "NULL";
+ return b;
+ }
+
+ return 0;
+ }
+ [CODE]
+
+ call XTest_goto_decl('1gd', lines, 11, 11)
+endfunc
+
+func Test_motion_if_elif_else_endif()
+ new
+ let lines =<< trim END
+ /* Test pressing % on #if, #else #elsif and #endif,
+ * with nested #if
+ */
+ #if FOO
+ /* ... */
+ # if BAR
+ /* ... */
+ # endif
+ #elif BAR
+ /* ... */
+ #else
+ /* ... */
+ #endif
+
+ #define FOO 1
+ END
+ call setline(1, lines)
+ /#if FOO
+ norm %
+ call assert_equal([9, 1], getpos('.')[1:2])
+ norm %
+ call assert_equal([11, 1], getpos('.')[1:2])
+ norm %
+ call assert_equal([13, 1], getpos('.')[1:2])
+ norm %
+ call assert_equal([4, 1], getpos('.')[1:2])
+ /# if BAR
+ norm $%
+ call assert_equal([8, 1], getpos('.')[1:2])
+ norm $%
+ call assert_equal([6, 1], getpos('.')[1:2])
+
+ " Test for [# and ]# command
+ call cursor(5, 1)
+ normal [#
+ call assert_equal([4, 1], getpos('.')[1:2])
+ call cursor(5, 1)
+ normal ]#
+ call assert_equal([9, 1], getpos('.')[1:2])
+ call cursor(10, 1)
+ normal [#
+ call assert_equal([9, 1], getpos('.')[1:2])
+ call cursor(10, 1)
+ normal ]#
+ call assert_equal([11, 1], getpos('.')[1:2])
+
+ " Finding a match before the first line or after the last line should fail
+ normal gg
+ call assert_beeps('normal [#')
+ normal G
+ call assert_beeps('normal ]#')
+
+ " Finding a match for a macro definition (#define) should fail
+ normal G
+ call assert_beeps('normal %')
+
+ bw!
+endfunc
+
+func Test_motion_c_comment()
+ new
+ a
+/*
+ * Test pressing % on beginning/end
+ * of C comments.
+ */
+/* Another comment */
+.
+ norm gg0%
+ call assert_equal([4, 3], getpos('.')[1:2])
+ norm %
+ call assert_equal([1, 1], getpos('.')[1:2])
+ norm gg0l%
+ call assert_equal([4, 3], getpos('.')[1:2])
+ norm h%
+ call assert_equal([1, 1], getpos('.')[1:2])
+
+ norm G^
+ norm %
+ call assert_equal([5, 21], getpos('.')[1:2])
+ norm %
+ call assert_equal([5, 1], getpos('.')[1:2])
+
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim
new file mode 100644
index 0000000..6b5e12c
--- /dev/null
+++ b/src/testdir/test_gui.vim
@@ -0,0 +1,1684 @@
+" Tests specifically for the GUI
+
+source shared.vim
+source check.vim
+CheckCanRunGui
+
+source setup_gui.vim
+
+func Setup()
+ call GUISetUpCommon()
+endfunc
+
+func TearDown()
+ call GUITearDownCommon()
+endfunc
+
+" Test for resetting "secure" flag after GUI has started.
+" Must be run first, since it starts the GUI on Unix.
+func Test_1_set_secure()
+ set exrc secure
+ gui -f
+ call assert_equal(1, has('gui_running'))
+endfunc
+
+" As for non-GUI, a balloon_show() test was already added with patch 8.0.0401
+func Test_balloon_show()
+ CheckFeature balloon_eval
+ " This won't do anything but must not crash either.
+ call balloon_show('hi!')
+endfunc
+
+func Test_colorscheme()
+ call assert_equal('16777216', &t_Co)
+
+ let colorscheme_saved = exists('g:colors_name') ? g:colors_name : 'default'
+ let g:color_count = 0
+ augroup TestColors
+ au!
+ au ColorScheme * let g:color_count += 1
+ \ | let g:after_colors = g:color_count
+ \ | let g:color_after = expand('<amatch>')
+ au ColorSchemePre * let g:color_count += 1
+ \ | let g:before_colors = g:color_count
+ \ | let g:color_pre = expand('<amatch>')
+ augroup END
+
+ colorscheme torte
+ redraw!
+ call assert_equal('dark', &background)
+ call assert_equal(1, g:before_colors)
+ call assert_equal(2, g:after_colors)
+ call assert_equal('torte', g:color_pre)
+ call assert_equal('torte', g:color_after)
+ call assert_equal("\ntorte", execute('colorscheme'))
+
+ let a = substitute(execute('hi Search'), "\n\\s\\+", ' ', 'g')
+ " FIXME: temporarily check less while the colorscheme changes
+ " call assert_match("\nSearch xxx term=reverse cterm=reverse ctermfg=196 ctermbg=16 gui=reverse guifg=#ff0000 guibg=#000000", a)
+ call assert_match("\nSearch xxx term=reverse ", a)
+
+ call assert_fails('colorscheme does_not_exist', 'E185:')
+ call assert_equal('does_not_exist', g:color_pre)
+ call assert_equal('torte', g:color_after)
+
+ exec 'colorscheme' colorscheme_saved
+ augroup TestColors
+ au!
+ augroup END
+ unlet g:color_count g:after_colors g:before_colors
+ redraw!
+endfunc
+
+func Test_getfontname_with_arg()
+ CheckX11BasedGui
+
+ if has('gui_motif')
+ " Invalid font name. The result should be an empty string.
+ call assert_equal('', getfontname('notexist'))
+
+ " Valid font name. This is usually the real name of 7x13 by default.
+ let fname = '-Misc-Fixed-Medium-R-Normal--13-120-75-75-C-70-ISO8859-1'
+ call assert_match(fname, getfontname(fname))
+
+ elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " Invalid font name. The result should be the name plus the default size.
+ call assert_equal('notexist 10', getfontname('notexist'))
+ call assert_equal('', getfontname('*'))
+
+ " Valid font name. This is usually the real name of Monospace by default.
+ let fname = 'Bitstream Vera Sans Mono 12'
+ call assert_equal(fname, getfontname(fname))
+ endif
+endfunc
+
+func Test_getfontname_without_arg()
+ CheckX11BasedGui
+
+ let fname = getfontname()
+
+ if has('gui_kde')
+ " 'expected' is the value specified by SetUp() above.
+ call assert_equal('Courier 10 Pitch/8/-1/5/50/0/0/0/0/0', fname)
+ elseif has('gui_motif')
+ " 'expected' is DFLT_FONT of gui_x11.c or its real name.
+ let pat = '\(7x13\)\|\(\c-Misc-Fixed-Medium-R-Normal--13-120-75-75-C-70-ISO8859-1\)'
+ call assert_match(pat, fname)
+ elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " 'expected' is DEFAULT_FONT of gui_gtk_x11.c.
+ call assert_equal('Monospace 10', fname)
+ endif
+endfunc
+
+func Test_getwinpos()
+ call assert_match('Window position: X \d\+, Y \d\+', execute('winpos'))
+ call assert_true(getwinposx() >= 0)
+ call assert_true(getwinposy() >= 0)
+ call assert_equal([getwinposx(), getwinposy()], getwinpos())
+endfunc
+
+func Test_quoteplus()
+ CheckX11BasedGui
+
+ let g:test_is_flaky = 1
+
+ let quoteplus_saved = @+
+
+ let test_call = 'Can you hear me?'
+ let test_response = 'Yes, I can.'
+ let testee = 'VIMRUNTIME=' .. $VIMRUNTIME .. '; export VIMRUNTIME;'
+ \ .. GetVimCommand() .. ' --noplugin --not-a-term -c ''%s'''
+ " Ignore the "failed to create input context" error.
+ let cmd = 'call test_ignore_error("E285") | '
+ \ . 'gui -f | '
+ \ . 'call feedkeys("'
+ \ . '\"+p'
+ \ . ':s/' . test_call . '/' . test_response . '/\<CR>'
+ \ . '\"+yis'
+ \ . ':q!\<CR>", "tx")'
+ let run_vimtest = printf(testee, cmd)
+
+ " Set the quoteplus register to test_call, and another gvim will launched.
+ " Then, it first tries to paste the content of its own quotedplus register
+ " onto it. Second, it tries to substitute test_response for the pasted
+ " sentence. If the sentence is identical to test_call, the substitution
+ " should succeed. Third, it tries to yank the result of the substitution
+ " to its own quoteplus register, and last it quits. When system()
+ " returns, the content of the quoteplus register should be identical to
+ " test_response if those quoteplus registers are synchronized properly
+ " with/through the X11 clipboard.
+ let @+ = test_call
+ call system(run_vimtest)
+ call assert_equal(test_response, @+)
+
+ let @+ = quoteplus_saved
+endfunc
+
+func Test_gui_read_stdin()
+ CheckUnix
+
+ call writefile(['some', 'lines'], 'Xstdin', 'D')
+ let script =<< trim END
+ call writefile(getline(1, '$'), 'XstdinOK')
+ qa!
+ END
+ call writefile(script, 'Xscript', 'D')
+
+ " Cannot use --not-a-term here, the "reading from stdin" message would not be
+ " displayed.
+ " However, when using XIM we might get E285, do use it then.
+ if has('xim')
+ let vimcmd = GetVimCommand()
+ else
+ let vimcmd = substitute(GetVimCommand(), '--not-a-term', '', '')
+ endif
+
+ call system('cat Xstdin | ' .. vimcmd .. ' -f -g -S Xscript -')
+ call assert_equal(['some', 'lines'], readfile('XstdinOK'))
+
+ call delete('XstdinOK')
+endfunc
+
+func Test_set_background()
+ let background_saved = &background
+
+ set background&
+ call assert_equal('light', &background)
+
+ set background=dark
+ call assert_equal('dark', &background)
+
+ let &background = background_saved
+endfunc
+
+func Test_set_balloondelay()
+ CheckOption balloondelay
+
+ let balloondelay_saved = &balloondelay
+
+ " Check if the default value is identical to that described in the manual.
+ set balloondelay&
+ call assert_equal(600, &balloondelay)
+
+ " Edge cases
+
+ " XXX This fact should be hidden so that people won't be tempted to write
+ " plugin/TimeMachine.vim. TODO Add reasonable range checks to the source
+ " code.
+ set balloondelay=-1
+ call assert_equal(-1, &balloondelay)
+
+ " Though it's possible to interpret the zero delay to be 'as soon as
+ " possible' or even 'indefinite', its actual meaning depends on the GUI
+ " toolkit in use after all.
+ set balloondelay=0
+ call assert_equal(0, &balloondelay)
+
+ set balloondelay=1
+ call assert_equal(1, &balloondelay)
+
+ " Since p_bdelay is of type long currently, the upper bound can be
+ " impractically huge and machine-dependent. Practically, it's sufficient
+ " to check if balloondelay works with 0x7fffffff (32 bits) for now.
+ set balloondelay=2147483647
+ call assert_equal(2147483647, &balloondelay)
+
+ let &balloondelay = balloondelay_saved
+endfunc
+
+func Test_set_ballooneval()
+ CheckOption ballooneval
+
+ let ballooneval_saved = &ballooneval
+
+ set ballooneval&
+ call assert_equal(0, &ballooneval)
+
+ set ballooneval
+ call assert_notequal(0, &ballooneval)
+
+ set noballooneval
+ call assert_equal(0, &ballooneval)
+
+ let &ballooneval = ballooneval_saved
+endfunc
+
+func Test_set_balloonexpr()
+ CheckOption balloonexpr
+
+ let balloonexpr_saved = &balloonexpr
+
+ " Default value
+ set balloonexpr&
+ call assert_equal('', &balloonexpr)
+
+ " User-defined function
+ new
+ func MyBalloonExpr()
+ return 'Cursor is at line ' . v:beval_lnum .
+ \', column ' . v:beval_col .
+ \ ' of file ' . bufname(v:beval_bufnr) .
+ \ ' on word "' . v:beval_text . '"' .
+ \ ' in window ' . v:beval_winid . ' (#' . v:beval_winnr . ')'
+ endfunc
+ setl balloonexpr=MyBalloonExpr()
+ setl ballooneval
+ call assert_equal('MyBalloonExpr()', &balloonexpr)
+ " TODO Read non-empty text, place the pointer at a character of a word,
+ " and check if the content of the balloon is the same as what is expected.
+ " Also, check if textlock works as expected.
+ setl balloonexpr&
+ call assert_equal('', &balloonexpr)
+ delfunc MyBalloonExpr
+
+ " Using a script-local function
+ func s:NewBalloonExpr()
+ endfunc
+ set balloonexpr=s:NewBalloonExpr()
+ call assert_equal(expand('<SID>') .. 'NewBalloonExpr()', &balloonexpr)
+ set balloonexpr=<SID>NewBalloonExpr()
+ call assert_equal(expand('<SID>') .. 'NewBalloonExpr()', &balloonexpr)
+ delfunc s:NewBalloonExpr
+ bwipe!
+
+ " Multiline support
+ if has('balloon_multiline')
+ " Multiline balloon using NL
+ new
+ func MyBalloonFuncForMultilineUsingNL()
+ return "Multiline\nSupported\nBalloon\nusing NL"
+ endfunc
+ setl balloonexpr=MyBalloonFuncForMultilineUsingNL()
+ setl ballooneval
+ call assert_equal('MyBalloonFuncForMultilineUsingNL()', &balloonexpr)
+ " TODO Read non-empty text, place the pointer at a character of a word,
+ " and check if the content of the balloon is the same as what is
+ " expected. Also, check if textlock works as expected.
+ setl balloonexpr&
+ delfunc MyBalloonFuncForMultilineUsingNL
+ bwipe!
+
+ " Multiline balloon using List
+ new
+ func MyBalloonFuncForMultilineUsingList()
+ return [ 'Multiline', 'Supported', 'Balloon', 'using List' ]
+ endfunc
+ setl balloonexpr=MyBalloonFuncForMultilineUsingList()
+ setl ballooneval
+ call assert_equal('MyBalloonFuncForMultilineUsingList()', &balloonexpr)
+ " TODO Read non-empty text, place the pointer at a character of a word,
+ " and check if the content of the balloon is the same as what is
+ " expected. Also, check if textlock works as expected.
+ setl balloonexpr&
+ delfunc MyBalloonFuncForMultilineUsingList
+ bwipe!
+ endif
+
+ let &balloonexpr = balloonexpr_saved
+endfunc
+
+" Invalid arguments are tested with test_options in conjunction with segfaults
+" caused by them (Patch 8.0.0357, 24922ec233).
+func Test_set_guicursor()
+ let guicursor_saved = &guicursor
+
+ let default = [
+ \ "n-v-c:block-Cursor/lCursor",
+ \ "ve:ver35-Cursor",
+ \ "o:hor50-Cursor",
+ \ "i-ci:ver25-Cursor/lCursor",
+ \ "r-cr:hor20-Cursor/lCursor",
+ \ "sm:block-Cursor-blinkwait175-blinkoff150-blinkon175"
+ \ ]
+
+ " Default Value
+ set guicursor&
+ call assert_equal(join(default, ','), &guicursor)
+
+ " Argument List Example 1
+ let opt_list = copy(default)
+ let opt_list[0] = "n-c-v:block-nCursor"
+ exec "set guicursor=" . join(opt_list, ',')
+ call assert_equal(join(opt_list, ','), &guicursor)
+ unlet opt_list
+
+ " Argument List Example 2
+ let opt_list = copy(default)
+ let opt_list[3] = "i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150"
+ exec "set guicursor=" . join(opt_list, ',')
+ call assert_equal(join(opt_list, ','), &guicursor)
+ unlet opt_list
+
+ " 'a' Mode
+ set guicursor&
+ let &guicursor .= ',a:blinkon0'
+ call assert_equal(join(default, ',') . ",a:blinkon0", &guicursor)
+
+ let &guicursor = guicursor_saved
+endfunc
+
+func Test_set_guifont_errors()
+ if has('win32')
+ " Invalid font names are accepted in GTK GUI
+ call assert_fails('set guifont=xa1bc23d7f', 'E596:')
+ endif
+
+ " This only works if 'renderoptions' exists and does not work for Windows XP
+ " and older.
+ if exists('+renderoptions') && windowsversion() !~ '^[345]\.'
+ " doing this four times used to cause a crash
+ set renderoptions=type:directx
+ for i in range(5)
+ set guifont=
+ endfor
+ set renderoptions=
+ for i in range(5)
+ set guifont=
+ endfor
+ endif
+endfunc
+
+func Test_set_guifont()
+ CheckX11BasedGui
+
+ let guifont_saved = &guifont
+ if has('xfontset')
+ " Prevent 'guifontset' from canceling 'guifont'.
+ let guifontset_saved = &guifontset
+ set guifontset=
+ endif
+
+ if has('gui_motif')
+ " Non-empty font list with invalid font names.
+ "
+ " This test is twofold: (1) It checks if the command fails as expected
+ " when there are no loadable fonts found in the list. (2) It checks if
+ " 'guifont' remains the same after the command loads none of the fonts
+ " listed.
+ let flist = &guifont
+ call assert_fails('set guifont=-notexist1-*,-notexist2-*')
+ call assert_equal(flist, &guifont)
+
+ " Non-empty font list with a valid font name. Should pick up the first
+ " valid font.
+ set guifont=-notexist1-*,fixed,-notexist2-*
+ let pat = '\(fixed\)\|\(\c-Misc-Fixed-Medium-R-SemiCondensed--13-120-75-75-C-60-ISO8859-1\)'
+ call assert_match(pat, getfontname())
+
+ " Empty list. Should fallback to the built-in default.
+ set guifont=
+ let pat = '\(7x13\)\|\(\c-Misc-Fixed-Medium-R-Normal--13-120-75-75-C-70-ISO8859-1\)'
+ call assert_match(pat, getfontname())
+
+ elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " For GTK, what we refer to as 'font names' in our manual are actually
+ " 'initial font patterns'. A valid font which matches the 'canonical font
+ " pattern' constructed from a given 'initial pattern' is to be looked up
+ " and loaded. That explains why the GTK GUIs appear to accept 'invalid
+ " font names'.
+ "
+ " Non-empty list. Should always pick up the first element, no matter how
+ " strange it is, as explained above.
+ set guifont=(´・ω・`)\ 12,Courier\ 12
+ call assert_equal('(´・ω・`) 12', getfontname())
+
+ " Empty list. Should fallback to the built-in default.
+ set guifont=
+ call assert_equal('Monospace 10', getfontname())
+ endif
+
+ if has('xfontset')
+ let &guifontset = guifontset_saved
+ endif
+ let &guifont = guifont_saved
+endfunc
+
+func Test_set_guifontset()
+ CheckFeature xfontset
+ let skipped = ''
+
+ call assert_fails('set guifontset=*', 'E597:')
+
+ let ctype_saved = v:ctype
+
+ " First, since XCreateFontSet(3) is very sensitive to locale, fonts must
+ " be chosen meticulously.
+ let font_head = '-misc-fixed-medium-r-normal--14'
+
+ let font_aw70 = font_head . '-130-75-75-c-70'
+ let font_aw140 = font_head . '-130-75-75-c-140'
+
+ let font_jisx0201 = font_aw70 . '-jisx0201.1976-0'
+ let font_jisx0208 = font_aw140 . '-jisx0208.1983-0'
+
+ let full_XLFDs = join([ font_jisx0208, font_jisx0201 ], ',')
+ let short_XLFDs = join([ font_aw140, font_aw70 ], ',')
+ let singleton = font_head . '-*'
+ let aliases = 'k14,r14'
+
+ " Second, among 'locales', look up such a locale that gets 'set
+ " guifontset=' to work successfully with every fontset specified with
+ " 'fontsets'.
+ let locales = [ 'ja_JP.UTF-8', 'ja_JP.eucJP', 'ja_JP.SJIS' ]
+ let fontsets = [ full_XLFDs, short_XLFDs, singleton, aliases ]
+
+ let feasible = 0
+ for locale in locales
+ try
+ exec 'language ctype' locale
+ catch /^Vim\%((\a\+)\)\=:E197/
+ continue
+ endtry
+ let done = 0
+ for fontset in fontsets
+ try
+ exec 'set guifontset=' . fontset
+ catch /^Vim\%((\a\+)\)\=:E\%(250\|252\|234\|597\|598\)/
+ break
+ endtry
+ let done += 1
+ endfor
+ if done == len(fontsets)
+ let feasible = 1
+ break
+ endif
+ endfor
+
+ " Third, give a set of tests if it is found feasible.
+ if !feasible
+ let skipped = g:not_hosted
+ else
+ " N.B. 'v:ctype' has already been set to an appropriate value in the
+ " previous loop.
+ for fontset in fontsets
+ exec 'set guifontset=' . fontset
+ call assert_equal(fontset, &guifontset)
+ endfor
+ endif
+
+ " Finally, restore ctype.
+ exec 'language ctype' ctype_saved
+
+ if !empty(skipped)
+ throw skipped
+ endif
+endfunc
+
+func Test_set_guifontwide()
+ CheckX11BasedGui
+
+ call assert_fails('set guifontwide=*', 'E533:')
+
+ if has('gui_gtk')
+ let guifont_saved = &guifont
+ let guifontwide_saved = &guifontwide
+
+ let fc_match = exepath('fc-match')
+ if empty(fc_match)
+ let skipped = g:not_hosted
+ else
+ let &guifont = system('fc-match -f "%{family[0]} %{size}" monospace:size=10:lang=en')
+ let wide = system('fc-match -f "%{family[0]} %{size}" monospace:size=10:lang=ja')
+ exec 'set guifontwide=' . fnameescape(wide)
+ call assert_equal(wide, &guifontwide)
+ endif
+
+ let &guifontwide = guifontwide_saved
+ let &guifont = guifont_saved
+
+ elseif has('gui_motif')
+ " guifontwide is premised upon the xfontset feature.
+ if !has('xfontset')
+ let skipped = g:not_supported . 'xfontset'
+ else
+ let encoding_saved = &encoding
+ let guifont_saved = &guifont
+ let guifontset_saved = &guifontset
+ let guifontwide_saved = &guifontwide
+
+ let nfont = '-misc-fixed-medium-r-normal-*-18-120-100-100-c-90-iso10646-1'
+ let wfont = '-misc-fixed-medium-r-normal-*-18-120-100-100-c-180-iso10646-1'
+
+ set encoding=utf-8
+
+ " Case 1: guifontset is empty
+ set guifontset=
+
+ " Case 1-1: Automatic selection
+ set guifontwide=
+ exec 'set guifont=' . nfont
+ call assert_equal(wfont, &guifontwide)
+
+ " Case 1-2: Manual selection
+ exec 'set guifontwide=' . wfont
+ exec 'set guifont=' . nfont
+ call assert_equal(wfont, &guifontwide)
+
+ " Case 2: guifontset is invalid
+ try
+ set guifontset=-*-notexist-*
+ call assert_report("'set guifontset=-*-notexist-*' should have failed")
+ catch
+ call assert_exception('E598:')
+ endtry
+
+ " Case 2-1: Automatic selection
+ set guifontwide=
+ exec 'set guifont=' . nfont
+ call assert_equal(wfont, &guifontwide)
+
+ " Case 2-2: Manual selection
+ exec 'set guifontwide=' . wfont
+ exec 'set guifont=' . nfont
+ call assert_equal(wfont, &guifontwide)
+
+ let &guifontwide = guifontwide_saved
+ let &guifontset = guifontset_saved
+ let &guifont = guifont_saved
+ let &encoding = encoding_saved
+ endif
+ endif
+endfunc
+
+func Test_set_guiligatures()
+ CheckX11BasedGui
+
+ if has('gui_gtk') || has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " Try correct value
+ set guiligatures=<>=ab
+ call assert_equal("<>=ab", &guiligatures)
+ " Try to throw error
+ try
+ set guiligatures=<>=Å¡ab
+ call assert_report("'set guiligatures=<>=Å¡ab should have failed")
+ catch
+ call assert_exception('E1243:')
+ endtry
+ endif
+endfunc
+
+func Test_set_guiheadroom()
+ CheckX11BasedGui
+
+ " Since this script is to be read together with '-U NONE', the default
+ " value must be preserved.
+ call assert_equal(50, &guiheadroom)
+endfunc
+
+func Test_set_guioptions()
+ let guioptions_saved = &guioptions
+ let duration = '200m'
+
+ if has('win32')
+ " Default Value
+ set guioptions&
+ call assert_equal('egmrLtT', &guioptions)
+
+ else
+ " Default Value
+ set guioptions&
+ call assert_equal('aegimrLtT', &guioptions)
+
+ " To activate scrollbars of type 'L' or 'R'.
+ wincmd v
+ redraw!
+
+ " Remove all default GUI ornaments
+ set guioptions-=T
+ exec 'sleep' . duration
+ call assert_equal('aegimrLt', &guioptions)
+ set guioptions-=t
+ exec 'sleep' . duration
+ call assert_equal('aegimrL', &guioptions)
+ set guioptions-=L
+ exec 'sleep' . duration
+ call assert_equal('aegimr', &guioptions)
+ set guioptions-=r
+ exec 'sleep' . duration
+ call assert_equal('aegim', &guioptions)
+ set guioptions-=m
+ exec 'sleep' . duration
+ call assert_equal('aegi', &guioptions)
+
+ " Try non-default GUI ornaments
+ set guioptions+=l
+ exec 'sleep' . duration
+ call assert_equal('aegil', &guioptions)
+ set guioptions-=l
+ exec 'sleep' . duration
+ call assert_equal('aegi', &guioptions)
+
+ set guioptions+=R
+ exec 'sleep' . duration
+ call assert_equal('aegiR', &guioptions)
+ set guioptions-=R
+ exec 'sleep' . duration
+ call assert_equal('aegi', &guioptions)
+
+ set guioptions+=b
+ exec 'sleep' . duration
+ call assert_equal('aegib', &guioptions)
+ set guioptions+=h
+ exec 'sleep' . duration
+ call assert_equal('aegibh', &guioptions)
+ set guioptions-=h
+ exec 'sleep' . duration
+ call assert_equal('aegib', &guioptions)
+ set guioptions-=b
+ exec 'sleep' . duration
+ call assert_equal('aegi', &guioptions)
+
+ set guioptions+=v
+ exec 'sleep' . duration
+ call assert_equal('aegiv', &guioptions)
+ set guioptions-=v
+ exec 'sleep' . duration
+ call assert_equal('aegi', &guioptions)
+
+ if has('gui_motif')
+ set guioptions+=F
+ exec 'sleep' . duration
+ call assert_equal('aegiF', &guioptions)
+ set guioptions-=F
+ exec 'sleep' . duration
+ call assert_equal('aegi', &guioptions)
+ endif
+
+ if has('gui_gtk3')
+ set guioptions+=d
+ exec 'sleep' . duration
+ call assert_equal('aegid', &guioptions)
+ set guioptions-=d
+ exec 'sleep' . duration
+ call assert_equal('aegi', &guioptions)
+ endif
+
+ " Restore GUI ornaments to the default state.
+ set guioptions+=m
+ exec 'sleep' . duration
+ call assert_equal('aegim', &guioptions)
+ set guioptions+=r
+ exec 'sleep' . duration
+ call assert_equal('aegimr', &guioptions)
+ set guioptions+=L
+ exec 'sleep' . duration
+ call assert_equal('aegimrL', &guioptions)
+ set guioptions+=t
+ exec 'sleep' . duration
+ call assert_equal('aegimrLt', &guioptions)
+ set guioptions+=T
+ exec 'sleep' . duration
+ call assert_equal("aegimrLtT", &guioptions)
+
+ wincmd o
+ redraw!
+ endif
+
+ let &guioptions = guioptions_saved
+endfunc
+
+func Test_scrollbars()
+ new
+ " buffer with 200 lines
+ call setline(1, repeat(['one', 'two'], 100))
+ set guioptions+=rlb
+
+ " scroll to move line 11 at top, moves the cursor there
+ let args = #{which: 'left', value: 10, dragging: 0}
+ call test_gui_event('scrollbar', args)
+ redraw
+ call assert_equal(1, winline())
+ call assert_equal(11, line('.'))
+
+ " scroll to move line 1 at top, cursor stays in line 11
+ let args = #{which: 'right', value: 0, dragging: 0}
+ call test_gui_event('scrollbar', args)
+ redraw
+ call assert_equal(11, winline())
+ call assert_equal(11, line('.'))
+
+ set nowrap
+ call setline(11, repeat('x', 150))
+ redraw
+ call assert_equal(1, wincol())
+ set number
+ redraw
+ call assert_equal(5, wincol())
+ set nonumber
+ redraw
+ call assert_equal(1, col('.'))
+
+ " scroll to character 11, cursor is moved
+ let args = #{which: 'hor', value: 10, dragging: 0}
+ call test_gui_event('scrollbar', args)
+ redraw
+ call assert_equal(1, wincol())
+ set number
+ redraw
+ call assert_equal(5, wincol())
+ set nonumber
+ redraw
+ call assert_equal(11, col('.'))
+
+ " Invalid arguments
+ call assert_false(test_gui_event('scrollbar', {}))
+ call assert_false(test_gui_event('scrollbar', #{value: 10, dragging: 0}))
+ call assert_false(test_gui_event('scrollbar', #{which: 'hor', dragging: 0}))
+ call assert_false(test_gui_event('scrollbar', #{which: 'hor', value: 1}))
+ call assert_fails("call test_gui_event('scrollbar', #{which: 'a', value: 1, dragging: 0})", 'E475:')
+
+ set guioptions&
+ set wrap&
+ bwipe!
+endfunc
+
+func Test_menu()
+ CheckFeature quickfix
+
+ " Check Help menu exists
+ let help_menu = execute('menu Help')
+ call assert_match('Overview', help_menu)
+
+ " Check Help menu works
+ emenu Help.Overview
+ call assert_equal('help', &buftype)
+ close
+
+ " Check deleting menu doesn't cause trouble.
+ aunmenu Help
+ if exists(':tlmenu')
+ tlunmenu Help
+ endif
+ call assert_fails('menu Help', 'E329:')
+endfunc
+
+func Test_set_guipty()
+ let guipty_saved = &guipty
+
+ " Default Value
+ set guipty&
+ call assert_equal(1, &guipty)
+
+ set noguipty
+ call assert_equal(0, &guipty)
+
+ let &guipty = guipty_saved
+endfunc
+
+func Test_encoding_conversion()
+ " GTK supports conversion between 'encoding' and "utf-8"
+ CheckFeature gui_gtk
+ let encoding_saved = &encoding
+ set encoding=latin1
+
+ " would be nice if we could take a screenshot
+ intro
+ " sets the window title
+ edit SomeFile
+
+ let &encoding = encoding_saved
+endfunc
+
+func Test_shell_command()
+ new
+ r !echo hello
+ call assert_equal('hello', substitute(getline(2), '\W', '', 'g'))
+ bwipe!
+endfunc
+
+func Test_syntax_colortest()
+ runtime syntax/colortest.vim
+ redraw!
+ sleep 200m
+ bwipe!
+endfunc
+
+func Test_set_term()
+ " It's enough to check the current value since setting 'term' to anything
+ " other than builtin_gui makes no sense at all.
+ call assert_equal('builtin_gui', &term)
+ call assert_fails('set term=xterm', 'E530:')
+endfunc
+
+func Test_windowid_variable()
+ if g:x11_based_gui || has('win32')
+ call assert_true(v:windowid > 0)
+ else
+ call assert_equal(0, v:windowid)
+ endif
+endfunc
+
+" Test "vim -g" and also the GUIEnter autocommand.
+func Test_gui_dash_g()
+ let cmd = GetVimCommand('Xscriptgui')
+ call writefile([""], "Xtestgui", 'D')
+ let lines =<< trim END
+ au GUIEnter * call writefile(["insertmode: " . &insertmode], "Xtestgui")
+ au GUIEnter * qall
+ END
+ call writefile(lines, 'Xscriptgui', 'D')
+ call system(cmd . ' -g')
+ call WaitForAssert({-> assert_equal(['insertmode: 0'], readfile('Xtestgui'))})
+endfunc
+
+" Test "vim -7" and also the GUIEnter autocommand.
+func Test_gui_dash_y()
+ let cmd = GetVimCommand('Xscriptgui')
+ call writefile([""], "Xtestgui", 'D')
+ let lines =<< trim END
+ au GUIEnter * call writefile(["insertmode: " . &insertmode], "Xtestgui")
+ au GUIEnter * qall
+ END
+ call writefile(lines, 'Xscriptgui', 'D')
+ call system(cmd . ' -y')
+ call WaitForAssert({-> assert_equal(['insertmode: 1'], readfile('Xtestgui'))})
+endfunc
+
+" Test for "!" option in 'guioptions'. Use a terminal for running external
+" commands
+func Test_gui_run_cmd_in_terminal()
+ CheckFeature terminal
+ let save_guioptions = &guioptions
+ set guioptions+=!
+ if has('win32')
+ let cmd = 'type'
+ else
+ " assume all the other systems have a cat command
+ let cmd = 'cat'
+ endif
+ exe "silent !" . cmd . " test_gui.vim"
+ " TODO: how to check that the command ran in a separate terminal?
+ " Maybe check for $TERM (dumb vs xterm) in the spawned shell?
+ let &guioptions = save_guioptions
+endfunc
+
+func Test_gui_recursive_mapping()
+ nmap ' <C-W>
+ nmap <C-W>a :let didit = 1<CR>
+ call feedkeys("'a", 'xt')
+ call assert_equal(1, didit)
+
+ nunmap '
+ nunmap <C-W>a
+endfunc
+
+" Test GUI mouse events
+func Test_gui_mouse_event()
+ " Low level input isn't 100% reliable
+ let g:test_is_flaky = 1
+
+ set mousemodel=extend
+ call test_override('no_query_mouse', 1)
+ new
+ call setline(1, ['one two three', 'four five six'])
+ call cursor(1, 1)
+ redraw!
+
+ " place the cursor using left click and release in normal mode
+ let args = #{button: 0, row: 2, col: 4, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ eval 'mouse'->test_gui_event(args)
+ call feedkeys("\<Esc>", 'Lx!')
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+
+ " select and yank a word
+ let @" = ''
+ let args = #{button: 0, row: 1, col: 9, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.multiclick = 1
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ let args.multiclick = 0
+ call test_gui_event('mouse', args)
+ call feedkeys("y", 'Lx!')
+ call assert_equal('three', @")
+
+ " create visual selection using right click
+ let @" = ''
+ let args = #{button: 0, row: 2, col: 6, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ let args = #{button: 2, row: 2, col: 13, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ call feedkeys("y", 'Lx!')
+ call assert_equal('five six', @")
+
+ " paste using middle mouse button
+ let @* = 'abc '
+ call feedkeys('""', 'Lx!')
+ let args = #{button: 1, row: 1, col: 9, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ call feedkeys("\<Esc>", 'Lx!')
+ call assert_equal(['one two abc three', 'four five six'], getline(1, '$'))
+
+ " extend visual selection using right click in visual mode
+ let @" = ''
+ call cursor(1, 1)
+ call feedkeys('v', 'Lx!')
+ let args = #{button: 2, row: 1, col: 17, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ call feedkeys("y", 'Lx!')
+ call assert_equal('one two abc three', @")
+
+ " extend visual selection using mouse drag
+ let @" = ''
+ call cursor(1, 1)
+ let args = #{button: 0, row: 2, col: 1, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args = #{button: 0x43, row: 2, col: 9, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 0x3
+ call test_gui_event('mouse', args)
+ call feedkeys("y", 'Lx!')
+ call assert_equal('four five', @")
+
+ " select text by moving the mouse
+ let @" = ''
+ call cursor(1, 1)
+ redraw!
+ let args = #{button: 0, row: 1, col: 4, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 0x700
+ let args.col = 9
+ call test_gui_event('mouse', args)
+ let args.col = 13
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ call feedkeys("y", 'Lx!')
+ call assert_equal(' two abc t', @")
+
+ " Using mouse in insert mode
+ call cursor(1, 1)
+ call feedkeys('i', 't')
+ let args = #{button: 0, row: 2, col: 11, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ call feedkeys("po\<Esc>", 'Lx!')
+ call assert_equal(['one two abc three', 'four five posix'], getline(1, '$'))
+
+ %d _
+ set scrolloff=0
+ call setline(1, range(1, 100))
+ " scroll up
+ let args = #{button: 0x200, row: 2, col: 1, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ call test_gui_event('mouse', args)
+ call test_gui_event('mouse', args)
+ call feedkeys("H", 'Lx!')
+ call assert_equal(10, line('.'))
+
+ " scroll down
+ let args = #{button: 0x100, row: 2, col: 1, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ call test_gui_event('mouse', args)
+ call feedkeys("H", 'Lx!')
+ call assert_equal(4, line('.'))
+ set scrolloff&
+
+ %d _
+ set nowrap
+ call setline(1, range(10)->join('')->repeat(10))
+ " scroll left
+ let args = #{button: 0x500, row: 1, col: 5, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.col = 10
+ call test_gui_event('mouse', args)
+ let args.col = 15
+ call test_gui_event('mouse', args)
+ call feedkeys('g0', 'Lx!')
+ call assert_equal(19, col('.'))
+
+ " scroll right
+ let args = #{button: 0x600, row: 1, col: 15, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.col = 10
+ call test_gui_event('mouse', args)
+ call feedkeys('g0', 'Lx!')
+ call assert_equal(7, col('.'))
+ set wrap&
+
+ %d _
+ call setline(1, repeat([repeat('a', 60)], 10))
+
+ " record various mouse events
+ let mouseEventNames = [
+ \ 'LeftMouse', 'LeftRelease', '2-LeftMouse', '3-LeftMouse',
+ \ 'S-LeftMouse', 'A-LeftMouse', 'C-LeftMouse', 'MiddleMouse',
+ \ 'MiddleRelease', '2-MiddleMouse', '3-MiddleMouse',
+ \ 'S-MiddleMouse', 'A-MiddleMouse', 'C-MiddleMouse',
+ \ 'RightMouse', 'RightRelease', '2-RightMouse',
+ \ '3-RightMouse', 'S-RightMouse', 'A-RightMouse', 'C-RightMouse',
+ \ 'X1Mouse', 'S-X1Mouse', 'A-X1Mouse', 'C-X1Mouse', 'X2Mouse',
+ \ 'S-X2Mouse', 'A-X2Mouse', 'C-X2Mouse'
+ \ ]
+ let mouseEventCodes = map(copy(mouseEventNames), "'<' .. v:val .. '>'")
+ let g:events = []
+ for e in mouseEventCodes
+ exe 'nnoremap ' .. e .. ' <Cmd>call add(g:events, "' ..
+ \ substitute(e, '[<>]', '', 'g') .. '")<CR>'
+ endfor
+
+ " Test various mouse buttons (0 - Left, 1 - Middle, 2 - Right, 0x300 - X1,
+ " 0x300- X2)
+ for button in [0, 1, 2, 0x300, 0x400]
+ " Single click
+ let args = #{button: button, row: 2, col: 5, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+
+ " Double/Triple click is supported by only the Left/Middle/Right mouse
+ " buttons
+ if button <= 2
+ " Double Click
+ let args.button = button
+ call test_gui_event('mouse', args)
+ let args.multiclick = 1
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ let args.multiclick = 0
+ call test_gui_event('mouse', args)
+
+ " Triple Click
+ let args.button = button
+ call test_gui_event('mouse', args)
+ let args.multiclick = 1
+ call test_gui_event('mouse', args)
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ let args.multiclick = 0
+ call test_gui_event('mouse', args)
+ endif
+
+ " Shift click
+ let args = #{button: button, row: 3, col: 7, multiclick: 0, modifiers: 4}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+
+ " Alt click
+ let args.button = button
+ let args.modifiers = 8
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+
+ " Ctrl click
+ let args.button = button
+ let args.modifiers = 16
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+
+ call feedkeys("\<Esc>", 'Lx!')
+ endfor
+
+ call assert_equal(['LeftMouse', 'LeftRelease', 'LeftMouse', '2-LeftMouse',
+ \ 'LeftMouse', '2-LeftMouse', '3-LeftMouse', 'S-LeftMouse',
+ \ 'A-LeftMouse', 'C-LeftMouse', 'MiddleMouse', 'MiddleRelease',
+ \ 'MiddleMouse', '2-MiddleMouse', 'MiddleMouse', '2-MiddleMouse',
+ \ '3-MiddleMouse', 'S-MiddleMouse', 'A-MiddleMouse', 'C-MiddleMouse',
+ \ 'RightMouse', 'RightRelease', 'RightMouse', '2-RightMouse',
+ \ 'RightMouse', '2-RightMouse', '3-RightMouse', 'S-RightMouse',
+ \ 'A-RightMouse', 'C-RightMouse', 'X1Mouse', 'S-X1Mouse', 'A-X1Mouse',
+ \ 'C-X1Mouse', 'X2Mouse', 'S-X2Mouse', 'A-X2Mouse', 'C-X2Mouse'],
+ \ g:events)
+
+ for e in mouseEventCodes
+ exe 'nunmap ' .. e
+ endfor
+
+ " modeless selection
+ set mouse=
+ let save_guioptions = &guioptions
+ set guioptions+=A
+ %d _
+ call setline(1, ['one two three', 'four five sixteen'])
+ call cursor(1, 1)
+ redraw!
+ " Double click should select the word and copy it to clipboard
+ let @* = ''
+ let args = #{button: 0, row: 2, col: 11, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.multiclick = 1
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ let args.multiclick = 0
+ call test_gui_event('mouse', args)
+ call feedkeys("\<Esc>", 'Lx!')
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ call assert_equal('sixteen', @*)
+ " Right click should extend the selection from cursor
+ call cursor(1, 6)
+ redraw!
+ let @* = ''
+ let args = #{button: 2, row: 1, col: 11, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ call feedkeys("\<Esc>", 'Lx!')
+ call assert_equal([0, 1, 6, 0], getpos('.'))
+ call assert_equal('wo thr', @*)
+ " Middle click should paste the clipboard contents
+ call cursor(2, 1)
+ redraw!
+ let args = #{button: 1, row: 1, col: 11, multiclick: 0, modifiers: 0}
+ call test_gui_event('mouse', args)
+ let args.button = 3
+ call test_gui_event('mouse', args)
+ call feedkeys("\<Esc>", 'Lx!')
+ call assert_equal([0, 2, 7, 0], getpos('.'))
+ call assert_equal('wo thrfour five sixteen', getline(2))
+
+ set mouse&
+ let &guioptions = save_guioptions
+ bw!
+ call test_override('no_query_mouse', 0)
+ set mousemodel&
+endfunc
+
+" Test invalid parameters for test_gui_event()
+func Test_gui_event_mouse_fails()
+ call test_override('no_query_mouse', 1)
+ new
+ call setline(1, ['one two three', 'four five six'])
+ set mousemodel=extend
+
+ let args = #{row: 2, col: 4, multiclick: 0, modifiers: 0}
+ call assert_false(test_gui_event('mouse', args))
+ let args = #{button: 0, col: 4, multiclick: 0, modifiers: 0}
+ call assert_false(test_gui_event('mouse', args))
+ let args = #{button: 0, row: 2, multiclick: 0, modifiers: 0}
+ call assert_false(test_gui_event('mouse', args))
+ let args = #{button: 0, row: 2, col: 4, modifiers: 0}
+ call assert_false(test_gui_event('mouse', args))
+ let args = #{button: 0, row: 2, col: 4, multiclick: 0}
+ call assert_false(test_gui_event('mouse', args))
+
+ " Error cases for test_gui_event()
+ call assert_fails("call test_gui_event('a1b2c3', args)", 'E475:')
+ call assert_fails("call test_gui_event([], args)", 'E1174:')
+ call assert_fails("call test_gui_event('abc', [])", 'E1206:')
+ call assert_fails("call test_gui_event(test_null_string(), {})", 'E475:')
+ call assert_false(test_gui_event('mouse', test_null_dict()))
+
+ bw!
+ call test_override('no_query_mouse', 0)
+ set mousemodel&
+endfunc
+
+" Move the mouse to the top-left in preparation for mouse events
+func PrepareForMouseEvent(args)
+ call extend(a:args, #{row: 1, col: 1})
+ call test_gui_event('mouse', a:args)
+ let g:eventlist = []
+ call feedkeys('', 'Lx!')
+
+ " Wait a bit for the event. I may not come if the mouse didn't move, wait up
+ " to 100 msec.
+ for n in range(10)
+ if len(g:eventlist) > 0
+ break
+ endif
+ sleep 10m
+ endfor
+ let g:eventlist = []
+endfunc
+
+func MouseWasMoved()
+ let pos = getmousepos()
+ call add(g:eventlist, #{row: pos.screenrow, col: pos.screencol})
+endfunc
+
+func Test_gui_mouse_move_event()
+ let args = #{move: 1, button: 0, multiclick: 0, modifiers: 0}
+
+ " by default, no mouse move events are generated
+ set mousemev&
+ call assert_false(&mousemev)
+
+ let g:eventlist = []
+ nnoremap <special> <silent> <MouseMove> :call MouseWasMoved()<CR>
+
+ " start at mouse pos (1,1), clear counter
+ call PrepareForMouseEvent(args)
+
+ call extend(args, #{row: 3, col: 30, cell: v:true})
+ call test_gui_event('mouse', args)
+ call feedkeys('', 'Lx!')
+
+ call extend(args, #{row: 10, col: 30, cell: v:true})
+ call test_gui_event('mouse', args)
+ call feedkeys('', 'Lx!')
+
+ " no events since 'mousemev' is off
+ call assert_equal([], g:eventlist)
+
+ " turn on mouse events and try the same thing
+ set mousemev
+ call PrepareForMouseEvent(args)
+
+ call extend(args, #{row: 3, col: 30, cell: v:true})
+ call test_gui_event('mouse', args)
+ call feedkeys('', 'Lx!')
+
+ call extend(args, #{row: 10, col: 30, cell: v:true})
+ call test_gui_event('mouse', args)
+ call feedkeys('', 'Lx!')
+
+ " FIXME: on MS-Windows we get a stray event first
+ if has('win32') && len(g:eventlist) == 3
+ let g:eventlist = g:eventlist[1 : ]
+ endif
+
+ call assert_equal([#{row: 3, col: 30}, #{row: 10, col: 30}], g:eventlist)
+
+ " wiggle the mouse around within a screen cell, shouldn't trigger events
+ call extend(args, #{cell: v:false})
+ call PrepareForMouseEvent(args)
+
+ call extend(args, #{row: 1, col: 2, cell: v:false})
+ call test_gui_event('mouse', args)
+ call feedkeys('', 'Lx!')
+
+ call extend(args, #{row: 2, col: 2, cell: v:false})
+ call test_gui_event('mouse', args)
+ call feedkeys('', 'Lx!')
+
+ call extend(args, #{row: 2, col: 1, cell: v:false})
+ call test_gui_event('mouse', args)
+ call feedkeys('', 'Lx!')
+
+ call assert_equal([], g:eventlist)
+
+ unlet g:eventlist
+ unmap <MouseMove>
+ set mousemev&
+endfunc
+
+" Test for 'guitablabel' and 'guitabtooltip' options
+func TestGuiTabLabel()
+ call add(g:TabLabels, v:lnum + 100)
+ let bufnrlist = tabpagebuflist(v:lnum)
+ return bufname(bufnrlist[tabpagewinnr(v:lnum) - 1])
+endfunc
+
+func TestGuiTabToolTip()
+ call add(g:TabToolTips, v:lnum + 200)
+ let bufnrlist = tabpagebuflist(v:lnum)
+ return bufname(bufnrlist[tabpagewinnr(v:lnum) - 1])
+endfunc
+
+func Test_gui_tablabel_tooltip()
+ %bw!
+ " Removing the tabline at the end of this test, reduces the window height by
+ " one. Save and restore it after the test.
+ let save_lines = &lines
+ edit one
+ set modified
+ tabnew two
+ set modified
+ tabnew three
+ set modified
+ let g:TabLabels = []
+ set guitablabel=%{TestGuiTabLabel()}
+ call test_override('starting', 1)
+ redrawtabline
+ call test_override('starting', 0)
+ call assert_true(index(g:TabLabels, 101) != -1)
+ call assert_true(index(g:TabLabels, 102) != -1)
+ call assert_true(index(g:TabLabels, 103) != -1)
+ set guitablabel&
+ unlet g:TabLabels
+
+ if has('gui_gtk')
+ " Only on GTK+, the tooltip function is called even if the mouse is not
+ " on the tabline. on Win32 and Motif, the tooltip function is called only
+ " when the mouse pointer is over the tabline.
+ let g:TabToolTips = []
+ set guitabtooltip=%{TestGuiTabToolTip()}
+ call test_override('starting', 1)
+ redrawtabline
+ call test_override('starting', 0)
+ call assert_true(index(g:TabToolTips, 201) != -1)
+ call assert_true(index(g:TabToolTips, 202) != -1)
+ call assert_true(index(g:TabToolTips, 203) != -1)
+ set guitabtooltip&
+ unlet g:TabToolTips
+ endif
+ %bw!
+ let &lines = save_lines
+endfunc
+
+" Test for dropping files into a window in GUI
+func DropFilesInCmdLine()
+ call feedkeys(":\"", 'L')
+ let d = #{files: ['a.c', 'b.c'], row: &lines, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call feedkeys("\<CR>", 'L')
+endfunc
+
+func Test_gui_drop_files()
+ CheckFeature drop_file
+
+ %bw!
+ %argdelete
+ let d = #{files: [], row: 1, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call assert_equal([], argv())
+ let d = #{files: [1, 2], row: 1, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call assert_equal([], argv())
+
+ let d = #{files: ['a.c', 'b.c'], row: 1, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call assert_equal(['a.c', 'b.c'], argv())
+ %bw!
+ %argdelete
+ let d = #{files: [], row: 1, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call assert_equal([], argv())
+ %bw!
+ " if the buffer in the window is modified, then the file should be opened in
+ " a new window
+ set modified
+ let d = #{files: ['x.c', 'y.c'], row: 1, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call assert_equal(['x.c', 'y.c'], argv())
+ call assert_equal(2, winnr('$'))
+ call assert_equal('x.c', bufname(winbufnr(1)))
+ %bw!
+ %argdelete
+ " if Ctrl is pressed, then the file should be opened in a new window
+ let d = #{files: ['s.py', 't.py'], row: 1, col: 1, modifiers: 0x10}
+ eval 'dropfiles'->test_gui_event(d)
+ call assert_equal(['s.py', 't.py'], argv())
+ call assert_equal(2, winnr('$'))
+ call assert_equal('s.py', bufname(winbufnr(1)))
+ %bw!
+ %argdelete
+ " drop the files in a non-current window
+ belowright new
+ let d = #{files: ['a.py', 'b.py'], row: 1, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call assert_equal(['a.py', 'b.py'], argv())
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, winnr())
+ call assert_equal('a.py', bufname(winbufnr(1)))
+ %bw!
+ %argdelete
+ " pressing shift when dropping files should change directory
+ let save_cwd = getcwd()
+ call mkdir('Xdropdir1', 'R')
+ call writefile([], 'Xdropdir1/Xfile1')
+ call writefile([], 'Xdropdir1/Xfile2')
+ let d = #{files: ['Xdropdir1/Xfile1', 'Xdropdir1/Xfile2'], row: 1, col: 1,
+ \ modifiers: 0x4}
+ call test_gui_event('dropfiles', d)
+ call assert_equal('Xdropdir1', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xfile1', @%)
+ call chdir(save_cwd)
+ " pressing shift when dropping directory and files should change directory
+ let d = #{files: ['Xdropdir1', 'Xdropdir1/Xfile2'], row: 1, col: 1, modifiers: 0x4}
+ call test_gui_event('dropfiles', d)
+ call assert_equal('Xdropdir1', fnamemodify(getcwd(), ':t'))
+ call assert_equal('Xdropdir1', fnamemodify(@%, ':t'))
+ call chdir(save_cwd)
+ %bw!
+ %argdelete
+ " dropping a directory should edit it
+ let d = #{files: ['Xdropdir1'], row: 1, col: 1, modifiers: 0}
+ call test_gui_event('dropfiles', d)
+ call assert_equal('Xdropdir1', @%)
+ %bw!
+ %argdelete
+ " dropping only a directory name with Shift should ignore it
+ let d = #{files: ['Xdropdir1'], row: 1, col: 1, modifiers: 0x4}
+ call test_gui_event('dropfiles', d)
+ call assert_equal('', @%)
+ %bw!
+ %argdelete
+
+ " drop files in the command line. The GUI drop files adds the file names to
+ " the low level input buffer. So need to use a cmdline map and feedkeys()
+ " with 'Lx!' to process it in this function itself.
+ " This sometimes fails, e.g. when using valgrind.
+ let g:test_is_flaky = 1
+ cnoremap <expr> <buffer> <F4> DropFilesInCmdLine()
+ call feedkeys(":\"\<F4>\<CR>", 'xt')
+ call feedkeys('k', 'Lx!')
+ call assert_equal('"a.c b.c', @:)
+ cunmap <buffer> <F4>
+
+ " Invalid arguments
+ call assert_false(test_gui_event("dropfiles", {}))
+ let d = #{row: 1, col: 1, modifiers: 0}
+ call assert_false(test_gui_event("dropfiles", d))
+ let d = #{files: 1, row: 1, col: 1, modifiers: 0}
+ call assert_false(test_gui_event("dropfiles", d))
+ let d = #{files: test_null_list(), row: 1, col: 1, modifiers: 0}
+ call assert_false(test_gui_event("dropfiles", d))
+ let d = #{files: [test_null_string()], row: 1, col: 1, modifiers: 0}
+ call assert_true(test_gui_event("dropfiles", d))
+endfunc
+
+" Test for generating a GUI tabline event to select a tab page
+func Test_gui_tabline_event()
+ %bw!
+ edit Xfile1
+ tabedit Xfile2
+ tabedit Xfile3
+
+ tabfirst
+ call assert_equal(v:true, test_gui_event('tabline', #{tabnr: 2}))
+ call feedkeys("y", "Lx!")
+ call assert_equal(2, tabpagenr())
+ call assert_equal(v:true, test_gui_event('tabline', #{tabnr: 3}))
+ call feedkeys("y", "Lx!")
+ call assert_equal(3, tabpagenr())
+ call assert_equal(v:false, 'tabline'->test_gui_event(#{tabnr: 3}))
+
+ " From the cmdline window, tabline event should not be handled
+ call feedkeys("q::let t = test_gui_event('tabline', #{tabnr: 2})\<CR>:q\<CR>", 'x!')
+ call assert_equal(v:false, t)
+
+ " Invalid arguments
+ call assert_false(test_gui_event('tabline', {}))
+ call assert_false(test_gui_event('tabline', #{abc: 1}))
+
+ %bw!
+endfunc
+
+" Test for generating a GUI tabline menu event to execute an action
+func Test_gui_tabmenu_event()
+ %bw!
+
+ " Try to close the last tab page
+ call test_gui_event('tabmenu', #{tabnr: 1, item: 1})
+ call feedkeys("y", "Lx!")
+
+ edit Xfile1
+ tabedit Xfile2
+ call test_gui_event('tabmenu', #{tabnr: 1, item: 1})
+ call feedkeys("y", "Lx!")
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('Xfile2', bufname())
+
+ eval 'tabmenu'->test_gui_event(#{tabnr: 1, item: 2})
+ call feedkeys("y", "Lx!")
+ call assert_equal(2, tabpagenr('$'))
+
+ " If tabnr is 0, then the current tabpage should be used.
+ call test_gui_event('tabmenu', #{tabnr: 0, item: 2})
+ call feedkeys("y", "Lx!")
+ call assert_equal(3, tabpagenr('$'))
+ call test_gui_event('tabmenu', #{tabnr: 0, item: 1})
+ call feedkeys("y", "Lx!")
+ call assert_equal(2, tabpagenr('$'))
+
+ " Invalid arguments
+ call assert_false(test_gui_event('tabmenu', {}))
+ call assert_false(test_gui_event('tabmenu', #{tabnr: 1}))
+ call assert_false(test_gui_event('tabmenu', #{item: 1}))
+ call assert_false(test_gui_event('tabmenu', #{abc: 1}))
+
+ %bw!
+endfunc
+
+" Test for find/replace text dialog event
+func Test_gui_findrepl()
+ " Find/Replace dialog is supported only on GTK, Motif and MS-Windows.
+ if !has('gui_gtk') && !has('gui_motif') && !has('gui_win32')
+ return
+ endif
+
+ new
+ call setline(1, ['one two one', 'Twoo One two oneo'])
+
+ " Replace all instances of a string with another
+ let args = #{find_text: 'one', repl_text: 'ONE', flags: 0x4, forward: 1}
+ call test_gui_event('findrepl', args)
+ call assert_equal(['ONE two ONE', 'Twoo ONE two ONEo'], getline(1, '$'))
+
+ " Replace all instances of a whole string with another
+ call cursor(1, 1)
+ let args = #{find_text: 'two', repl_text: 'TWO', flags: 0xC, forward: 1}
+ call test_gui_event('findrepl', args)
+ call assert_equal(['ONE TWO ONE', 'Twoo ONE TWO ONEo'], getline(1, '$'))
+
+ " Find next occurrence of a string (in a find dialog)
+ call cursor(1, 11)
+ let args = #{find_text: 'TWO', repl_text: '', flags: 0x11, forward: 1}
+ call test_gui_event('findrepl', args)
+ call assert_equal([2, 10], [line('.'), col('.')])
+
+ " Find previous occurrences of a string (in a find dialog)
+ call cursor(1, 11)
+ let args = #{find_text: 'TWO', repl_text: '', flags: 0x11, forward: 0}
+ call test_gui_event('findrepl', args)
+ call assert_equal([1, 5], [line('.'), col('.')])
+
+ " Find next occurrence of a string (in a replace dialog)
+ call cursor(1, 1)
+ let args = #{find_text: 'Twoo', repl_text: '', flags: 0x2, forward: 1}
+ call test_gui_event('findrepl', args)
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ " Replace only the next occurrence of a string (once)
+ call cursor(1, 5)
+ let args = #{find_text: 'TWO', repl_text: 'two', flags: 0x3, forward: 1}
+ call test_gui_event('findrepl', args)
+ call assert_equal(['ONE two ONE', 'Twoo ONE TWO ONEo'], getline(1, '$'))
+
+ " Replace all instances of a whole string with another matching case
+ call cursor(1, 1)
+ let args = #{find_text: 'TWO', repl_text: 'two', flags: 0x1C, forward: 1}
+ call test_gui_event('findrepl', args)
+ call assert_equal(['ONE two ONE', 'Twoo ONE two ONEo'], getline(1, '$'))
+
+ " Replace all instances with sub-replace specials
+ call cursor(1, 1)
+ let args = #{find_text: 'ONE', repl_text: '&~&', flags: 0x4, forward: 1}
+ call test_gui_event('findrepl', args)
+ call assert_equal(['&~& two &~&', 'Twoo &~& two &~&o'], getline(1, '$'))
+
+ " Invalid arguments
+ call assert_false(test_gui_event('findrepl', {}))
+ let args = #{repl_text: 'a', flags: 1, forward: 1}
+ call assert_false(test_gui_event('findrepl', args))
+ let args = #{find_text: 'a', flags: 1, forward: 1}
+ call assert_false(test_gui_event('findrepl', args))
+ let args = #{find_text: 'a', repl_text: 'b', forward: 1}
+ call assert_false(test_gui_event('findrepl', args))
+ let args = #{find_text: 'a', repl_text: 'b', flags: 1}
+ call assert_false(test_gui_event('findrepl', args))
+
+ bw!
+endfunc
+
+func Test_gui_CTRL_SHIFT_V()
+ call feedkeys(":let g:str = '\<*C-S-V>\<*C-S-I>\<*C-S-V>\<*C-S-@>'\<CR>", 'tx')
+ call assert_equal('<C-S-I><C-S-@>', g:str)
+ unlet g:str
+endfunc
+
+func Test_gui_dialog_file()
+ " make sure the file does not exist, otherwise a dialog makes Vim hang
+ call delete('Xdialfile')
+
+ let lines =<< trim END
+ file Xdialfile
+ normal axxx
+ confirm qa
+ END
+ call writefile(lines, 'Xlines', 'D')
+ let prefix = '!'
+ if has('win32')
+ let prefix = '!start '
+ endif
+ execute prefix .. GetVimCommand() .. ' -g -f --clean --gui-dialog-file Xdialog -S Xlines'
+
+ call WaitForAssert({-> assert_true(filereadable('Xdialog'))})
+ call assert_match('Question: Save changes to "Xdialfile"?', readfile('Xdialog')->join('<NL>'))
+
+ call delete('Xdialog')
+ call delete('Xdialfile')
+endfunc
+
+" Test for sending low level key presses
+func SendKeys(keylist)
+ for k in a:keylist
+ call test_gui_event("key", #{event: "keydown", keycode: k})
+ endfor
+ for k in reverse(a:keylist)
+ call test_gui_event("key", #{event: "keyup", keycode: k})
+ endfor
+endfunc
+
+func Test_gui_lowlevel_keyevent()
+ CheckMSWindows
+ new
+
+ " Test for <Ctrl-A> to <Ctrl-Z> keys
+ for kc in range(65, 90)
+ call SendKeys([0x11, kc])
+ let ch = getcharstr()
+ call assert_equal(nr2char(kc - 64), ch)
+ endfor
+
+ " Testing more extensive windows keyboard handling
+ " is covered in test_mswin_event.vim
+
+ bw!
+endfunc
+
+func Test_gui_macro_csi()
+ " Test for issue #11270
+ nnoremap <C-L> <Cmd>let g:triggered = 1<CR>
+ let @q = "\x9b\xfc\x04L"
+ norm @q
+ call assert_equal(1, g:triggered)
+ unlet g:triggered
+ nunmap <C-L>
+
+ " Test for issue #11057
+ inoremap <C-D>t bbb
+ call setline(1, "\t")
+ let @q = "i\x9b\xfc\x04D"
+ " The end of :normal is like a mapping timing out
+ norm @q
+ call assert_equal('', getline(1))
+ iunmap <C-D>t
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_gui_init.vim b/src/testdir/test_gui_init.vim
new file mode 100644
index 0000000..6f22013
--- /dev/null
+++ b/src/testdir/test_gui_init.vim
@@ -0,0 +1,46 @@
+" Tests specifically for the GUI features/options that need to be set up at
+" startup to take effect at runtime.
+
+source shared.vim
+source check.vim
+CheckCanRunGui
+
+source setup_gui.vim
+
+func Setup()
+ call GUISetUpCommon()
+endfunc
+
+func TearDown()
+ call GUITearDownCommon()
+endfunc
+
+" Ignore the "failed to create input context" error.
+call test_ignore_error('E285:')
+
+" Start the GUI now, in the foreground.
+gui -f
+
+func Test_set_guiheadroom()
+ CheckX11BasedGui
+
+ " The 'expected' value must be consistent with the value specified with
+ " gui_init.vim.
+ call assert_equal(0, &guiheadroom)
+endfunc
+
+func Test_set_guioptions_for_M()
+ sleep 200ms
+ " Check if the 'M' option is included.
+ call assert_match('.*M.*', &guioptions)
+endfunc
+
+func Test_set_guioptions_for_p()
+ CheckX11BasedGui
+
+ sleep 200ms
+ " Check if the 'p' option is included.
+ call assert_match('.*p.*', &guioptions)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_hardcopy.vim b/src/testdir/test_hardcopy.vim
new file mode 100644
index 0000000..be83728
--- /dev/null
+++ b/src/testdir/test_hardcopy.vim
@@ -0,0 +1,212 @@
+" Test :hardcopy
+
+source check.vim
+
+func Test_printoptions()
+ edit test_hardcopy.vim
+ syn on
+
+ for opt in ['left:5in,right:10pt,top:8mm,bottom:2pc',
+ \ 'left:2in,top:30pt,right:16mm,bottom:3pc',
+ \ 'header:3,syntax:y,number:y,wrap:n',
+ \ 'header:3,syntax:n,number:y,wrap:y',
+ \ 'header:0,syntax:a,number:y,wrap:y',
+ \ 'duplex:short,collate:n,jobsplit:y,portrait:n',
+ \ 'duplex:long,collate:y,jobsplit:n,portrait:y',
+ \ 'duplex:off,collate:y,jobsplit:y,portrait:y',
+ \ 'paper:10x14',
+ \ 'paper:A3',
+ \ 'paper:A4',
+ \ 'paper:A5',
+ \ 'paper:B4',
+ \ 'paper:B5',
+ \ 'paper:executive',
+ \ 'paper:folio',
+ \ 'paper:ledger',
+ \ 'paper:legal',
+ \ 'paper:letter',
+ \ 'paper:quarto',
+ \ 'paper:statement',
+ \ 'paper:tabloid',
+ \ 'formfeed:y',
+ \ '']
+ exe 'set printoptions=' .. opt
+ if has('postscript')
+ 1,50hardcopy > Xhardcopy_printoptions
+ let lines = readfile('Xhardcopy_printoptions')
+ call assert_true(len(lines) > 20, opt)
+ call assert_true(lines[0] =~ 'PS-Adobe', opt)
+ call delete('Xhardcopy_printoptions')
+ endif
+ endfor
+
+ call assert_fails('set printoptions=paper', 'E550:')
+ call assert_fails('set printoptions=shredder:on', 'E551:')
+ call assert_fails('set printoptions=left:no', 'E552:')
+ set printoptions&
+ bwipe
+endfunc
+
+func Test_printmbfont()
+ " Print a help page which contains tabs, underlines (etc) to recover more code.
+ help syntax.txt
+ syn on
+
+ for opt in [':WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no',
+ \ '']
+ exe 'set printmbfont=' .. opt
+ if has('postscript')
+ hardcopy > Xhardcopy_printmbfont
+ let lines = readfile('Xhardcopy_printmbfont')
+ call assert_true(len(lines) > 20, opt)
+ call assert_true(lines[0] =~ 'PS-Adobe', opt)
+ call delete('Xhardcopy_printmbfont')
+ endif
+ endfor
+ set printmbfont&
+ bwipe
+endfunc
+
+func Test_printmbcharset()
+ CheckFeature postscript
+
+ " digraph.txt has plenty of non-latin1 characters.
+ help digraph.txt
+ set printmbcharset=ISO10646 printencoding=utf-8
+ for courier in ['yes', 'no']
+ for ascii in ['yes', 'no']
+ exe 'set printmbfont=r:WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-BoldItalic'
+ \ .. ',c:' .. courier .. ',a:' .. ascii
+ hardcopy > Xhardcopy_printmbcharset
+ let lines = readfile('Xhardcopy_printmbcharset')
+ call assert_true(len(lines) > 20)
+ call assert_true(lines[0] =~ 'PS-Adobe')
+ endfor
+ endfor
+
+ set printmbcharset=does-not-exist printencoding=utf-8 printmbfont=r:WadaMin-Regular
+ call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E456:')
+
+ set printmbcharset=GB_2312-80 printencoding=utf-8 printmbfont=r:WadaMin-Regular
+ call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E673:')
+
+ set printmbcharset=ISO10646 printencoding=utf-8 printmbfont=
+ call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E675:')
+
+ call delete('Xhardcopy_printmbcharset')
+ set printmbcharset& printencoding& printmbfont&
+ bwipe
+endfunc
+
+func Test_printexpr()
+ CheckFeature postscript
+
+ " Not a very useful printexpr value, but enough to test
+ " hardcopy with 'printexpr'.
+ function PrintFile(fname)
+ call writefile(['Test printexpr: ' .. v:cmdarg],
+ \ 'Xhardcopy_printexpr')
+ call delete(a:fname)
+ return 0
+ endfunc
+ set printexpr=PrintFile(v:fname_in)
+
+ help help
+ hardcopy dummy args
+ call assert_equal(['Test printexpr: dummy args'],
+ \ readfile('Xhardcopy_printexpr'))
+ call delete('Xhardcopy_printexpr')
+
+ " Function returns 1 to test print failure.
+ function PrintFails(fname)
+ call delete(a:fname)
+ return 1
+ endfunc
+ set printexpr=PrintFails(v:fname_in)
+ call assert_fails('hardcopy', 'E365:')
+
+ " Using a script-local function
+ func s:NewPrintExpr()
+ endfunc
+ set printexpr=s:NewPrintExpr()
+ call assert_equal(expand('<SID>') .. 'NewPrintExpr()', &printexpr)
+ set printexpr=<SID>NewPrintExpr()
+ call assert_equal(expand('<SID>') .. 'NewPrintExpr()', &printexpr)
+
+ set printexpr&
+ bwipe
+endfunc
+
+func Test_errors()
+ CheckFeature postscript
+
+ edit test_hardcopy.vim
+ call assert_fails('hardcopy >', 'E324:')
+ bwipe
+endfunc
+
+func Test_dark_background()
+ edit test_hardcopy.vim
+ syn on
+
+ for bg in ['dark', 'light']
+ exe 'set background=' .. bg
+
+ if has('postscript')
+ hardcopy > Xhardcopy_dark_background
+ let lines = readfile('Xhardcopy_dark_background')
+ call assert_true(len(lines) > 20)
+ call assert_true(lines[0] =~ 'PS-Adobe')
+ call delete('Xhardcopy_dark_background')
+ endif
+ endfor
+
+ set background&
+ bwipe
+endfun
+
+func Test_empty_buffer()
+ CheckFeature postscript
+
+ new
+ call assert_equal("\nNo text to be printed", execute('hardcopy'))
+ bwipe
+endfunc
+
+func Test_printheader_parsing()
+ " Only test that this doesn't throw an error.
+ set printheader=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
+ set printheader=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P
+ set printheader=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b'
+ set printheader=...%r%{VarExists('b:gzflag','\ [GZ]')}%h...
+ set printheader=
+ set printheader&
+endfunc
+
+func Test_fname_with_spaces()
+ CheckFeature postscript
+
+ split t\ e\ s\ t.txt
+ call setline(1, ['just', 'some', 'text'])
+ hardcopy > %.ps
+ call assert_true(filereadable('t e s t.txt.ps'))
+ call delete('t e s t.txt.ps')
+ bwipe!
+endfunc
+
+func Test_illegal_byte()
+ CheckFeature postscript
+ if &enc != 'utf-8'
+ return
+ endif
+
+ new
+ " conversion of 0xff will fail, this used to cause a crash
+ call setline(1, "\xff")
+ hardcopy >Xpstest
+
+ bwipe!
+ call delete('Xpstest')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_help.vim b/src/testdir/test_help.vim
new file mode 100644
index 0000000..6c8b3ab
--- /dev/null
+++ b/src/testdir/test_help.vim
@@ -0,0 +1,209 @@
+" Tests for :help
+
+source check.vim
+import './vim9.vim' as v9
+
+func Test_help_restore_snapshot()
+ help
+ set buftype=
+ help
+ edit x
+ help
+ helpclose
+endfunc
+
+func Test_help_restore_snapshot_split()
+ " Squeeze the unnamed buffer, Xfoo and the help one side-by-side and focus
+ " the first one before calling :help.
+ let bnr = bufnr()
+ botright vsp Xfoo
+ wincmd h
+ help
+ wincmd L
+ let g:did_bufenter = v:false
+ augroup T
+ au!
+ au BufEnter Xfoo let g:did_bufenter = v:true
+ augroup END
+ helpclose
+ augroup! T
+ " We're back to the unnamed buffer.
+ call assert_equal(bnr, bufnr())
+ " No BufEnter was triggered for Xfoo.
+ call assert_equal(v:false, g:did_bufenter)
+
+ close!
+ bwipe!
+endfunc
+
+func Test_help_errors()
+ call assert_fails('help doesnotexist', 'E149:')
+ call assert_fails('help!', 'E478:')
+ if has('multi_lang')
+ call assert_fails('help help@xy', 'E661:')
+ endif
+
+ let save_hf = &helpfile
+ set helpfile=help_missing
+ help
+ call assert_equal(1, winnr('$'))
+ call assert_notequal('help', &buftype)
+ let &helpfile = save_hf
+
+ call assert_fails('help ' . repeat('a', 1048), 'E149:')
+
+ new
+ set keywordprg=:help
+ call setline(1, " ")
+ call assert_fails('normal VK', 'E349:')
+ bwipe!
+endfunc
+
+func Test_help_expr()
+ help expr-!~?
+ call assert_equal('eval.txt', expand('%:t'))
+ close
+endfunc
+
+func Test_help_keyword()
+ new
+ set keywordprg=:help
+ call setline(1, " Visual ")
+ normal VK
+ call assert_match('^Visual mode', getline('.'))
+ call assert_equal('help', &ft)
+ close
+ bwipe!
+endfunc
+
+func Test_help_local_additions()
+ call mkdir('Xruntime/doc', 'pR')
+ call writefile(['*mydoc.txt* my awesome doc'], 'Xruntime/doc/mydoc.txt')
+ call writefile(['*mydoc-ext.txt* my extended awesome doc'], 'Xruntime/doc/mydoc-ext.txt')
+ let rtp_save = &rtp
+ set rtp+=./Xruntime
+ help local-additions
+ let lines = getline(line(".") + 1, search("^$") - 1)
+ call assert_equal([
+ \ '|mydoc-ext.txt| my extended awesome doc',
+ \ '|mydoc.txt| my awesome doc'
+ \ ], lines)
+ call delete('Xruntime/doc/mydoc-ext.txt')
+ close
+
+ call mkdir('Xruntime-ja/doc', 'pR')
+ call writefile(["local-additions\thelp.jax\t/*local-additions*"], 'Xruntime-ja/doc/tags-ja')
+ call writefile(['*help.txt* This is jax file', '',
+ \ 'LOCAL ADDITIONS: *local-additions*', ''], 'Xruntime-ja/doc/help.jax')
+ call writefile(['*work.txt* This is jax file'], 'Xruntime-ja/doc/work.jax')
+ call writefile(['*work2.txt* This is jax file'], 'Xruntime-ja/doc/work2.jax')
+ set rtp+=./Xruntime-ja
+
+ help local-additions@en
+ let lines = getline(line(".") + 1, search("^$") - 1)
+ call assert_equal([
+ \ '|mydoc.txt| my awesome doc'
+ \ ], lines)
+ close
+
+ help local-additions@ja
+ let lines = getline(line(".") + 1, search("^$") - 1)
+ call assert_equal([
+ \ '|mydoc.txt| my awesome doc',
+ \ '|help.txt| This is jax file',
+ \ '|work.txt| This is jax file',
+ \ '|work2.txt| This is jax file',
+ \ ], lines)
+ close
+
+ let &rtp = rtp_save
+endfunc
+
+func Test_help_completion()
+ call feedkeys(":help :undo\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"help :undo :undoj :undol :undojoin :undolist', @:)
+endfunc
+
+" Test for the :helptags command
+" NOTE: if you run tests as root this will fail. Don't run tests as root!
+func Test_helptag_cmd()
+ call mkdir('Xtagdir/a/doc', 'pR')
+
+ " No help file to process in the directory
+ call assert_fails('helptags Xtagdir', 'E151:')
+
+ call writefile([], 'Xtagdir/a/doc/sample.txt')
+
+ " Test for ++t argument
+ helptags ++t Xtagdir
+ call assert_equal(["help-tags\ttags\t1"], readfile('Xtagdir/tags'))
+ call delete('Xtagdir/tags')
+
+ " Test parsing tags
+ call writefile(['*tag1*', 'Example: >', ' *notag*', 'Example end: *tag2*'],
+ \ 'Xtagdir/a/doc/sample.txt')
+ helptags Xtagdir
+ call assert_equal(["tag1\ta/doc/sample.txt\t/*tag1*",
+ \ "tag2\ta/doc/sample.txt\t/*tag2*"], readfile('Xtagdir/tags'))
+
+ " Duplicate tags in the help file
+ call writefile(['*tag1*', '*tag1*', '*tag2*'], 'Xtagdir/a/doc/sample.txt')
+ call assert_fails('helptags Xtagdir', 'E154:')
+endfunc
+
+func Test_helptag_cmd_readonly()
+ CheckUnix
+ CheckNotRoot
+
+ " Read-only tags file
+ call mkdir('Xrodir/doc', 'pR')
+ call writefile([''], 'Xrodir/doc/tags')
+ call writefile([], 'Xrodir/doc/sample.txt')
+ call setfperm('Xrodir/doc/tags', 'r-xr--r--')
+ call assert_fails('helptags Xrodir/doc', 'E152:', getfperm('Xrodir/doc/tags'))
+
+ let rtp = &rtp
+ let &rtp = 'Xrodir'
+ helptags ALL
+ let &rtp = rtp
+
+ call delete('Xrodir/doc/tags')
+
+ " No permission to read the help file
+ call mkdir('Xrodir/b/doc', 'p')
+ call writefile([], 'Xrodir/b/doc/sample.txt')
+ call setfperm('Xrodir/b/doc/sample.txt', '-w-------')
+ call assert_fails('helptags Xrodir', 'E153:', getfperm('Xrodir/b/doc/sample.txt'))
+endfunc
+
+" Test for setting the 'helpheight' option in the help window
+func Test_help_window_height()
+ let &cmdheight = &lines - 23
+ set helpheight=10
+ help
+ set helpheight=14
+ call assert_equal(14, winheight(0))
+ set helpheight& cmdheight=1
+ close
+endfunc
+
+func Test_help_long_argument()
+ try
+ exe 'help \%' .. repeat('0', 1021)
+ catch
+ call assert_match("E149:", v:exception)
+ endtry
+endfunc
+
+func Test_help_using_visual_match()
+ let lines =<< trim END
+ call setline(1, ' ')
+ /^
+ exe "normal \<C-V>\<C-V>"
+ h5\%V€]
+ END
+ call v9.CheckScriptFailure(lines, 'E149:')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_help_tagjump.vim b/src/testdir/test_help_tagjump.vim
new file mode 100644
index 0000000..35b0c68
--- /dev/null
+++ b/src/testdir/test_help_tagjump.vim
@@ -0,0 +1,318 @@
+" Tests for :help! {subject}
+
+func Test_help_tagjump()
+ help
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*help.txt\*')
+ helpclose
+
+ help |
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*bar\*')
+ helpclose
+
+ help "
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*quote\*')
+ helpclose
+
+ help *
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*star\*')
+ helpclose
+
+ help "*
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*quotestar\*')
+ helpclose
+
+ help sm?le
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:smile\*')
+ helpclose
+
+ help ??
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*??\*')
+ helpclose
+
+ help :?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:?\*')
+ helpclose
+
+ help q?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*q?\*')
+ call assert_true(expand('<cword>') == 'q?')
+ helpclose
+
+ help -?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*-?\*')
+ helpclose
+
+ help v_g?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*v_g?\*')
+ helpclose
+
+ help expr-!=?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*expr-!=?\*')
+ call assert_true(expand('<cword>') == 'expr-!=?')
+ helpclose
+
+ help expr-isnot?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*expr-isnot?\*')
+ call assert_true(expand('<cword>') == 'expr-isnot?')
+ helpclose
+
+ help FileW*Post
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*FileWritePost\*')
+ helpclose
+
+ help `ls`
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:ls\*')
+ helpclose
+
+ help ^X
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*CTRL-X\*')
+ helpclose
+
+ help i_^_CTRL-D
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_^_CTRL-D\*')
+ helpclose
+
+ help i^x^y
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-X_CTRL-Y\*')
+ helpclose
+
+ exe "help i\<C-\>\<C-G>"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-\\_CTRL-G\*')
+ helpclose
+
+ exec "help \<C-V>"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*CTRL-V\*')
+ helpclose
+
+ help /\|
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*/\\bar\*')
+ helpclose
+
+ help \_$
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*/\\_$\*')
+ helpclose
+
+ help CTRL-\_CTRL-N
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*')
+ helpclose
+
+ help `:pwd`,
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:pwd\*')
+ helpclose
+
+ help `:ls`.
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:ls\*')
+ helpclose
+
+ exec "help! ('textwidth'"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ "\\*'textwidth'\\*")
+ helpclose
+
+ exec "help! ('buflisted'),"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ "\\*'buflisted'\\*")
+ helpclose
+
+ exec "help! abs({expr})"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*abs()\*')
+ helpclose
+
+ exec "help! arglistid([{winnr}"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*arglistid()\*')
+ helpclose
+
+ exec "help! 'autoindent'."
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ "\\*'autoindent'\\*")
+ helpclose
+
+ exec "help! {address}."
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*{address}\*')
+ helpclose
+
+ " Use special patterns in the help tag
+ for h in ['/\w', '/\%^', '/\%(', '/\zs', '/\@<=', '/\_$', '[++opt]', '/\{']
+ exec "help! " . h
+ call assert_equal("help", &filetype)
+ let pat = '\*' . escape(h, '\$[') . '\*'
+ call assert_true(getline('.') =~ pat, pat)
+ helpclose
+ endfor
+
+ exusage
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:index\*')
+ helpclose
+
+ viusage
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*normal-index\*')
+ helpclose
+endfunc
+
+let s:langs = ['en', 'ab', 'ja']
+
+func s:doc_config_setup()
+ let s:helpfile_save = &helpfile
+ let &helpfile="Xdocdir1/doc-en/doc/testdoc.txt"
+ let s:rtp_save = &rtp
+ let &rtp="Xdocdir1/doc-en"
+ if has('multi_lang')
+ let s:helplang_save=&helplang
+ endif
+
+ call delete('Xdocdir1', 'rf')
+
+ for lang in s:langs
+ if lang ==# 'en'
+ let tagfname = 'tags'
+ let docfname = 'testdoc.txt'
+ else
+ let tagfname = 'tags-' . lang
+ let docfname = 'testdoc.' . lang . 'x'
+ endif
+ let docdir = "Xdocdir1/doc-" . lang . "/doc"
+ call mkdir(docdir, "p")
+ call writefile(["\t*test-char*", "\t*test-col*"], docdir . '/' . docfname)
+ call writefile(["test-char\t" . docfname . "\t/*test-char*",
+ \ "test-col\t" . docfname . "\t/*test-col*"],
+ \ docdir . '/' . tagfname)
+ endfor
+endfunc
+
+func s:doc_config_teardown()
+ call delete('Xdocdir1', 'rf')
+
+ let &helpfile = s:helpfile_save
+ let &rtp = s:rtp_save
+ if has('multi_lang')
+ let &helplang = s:helplang_save
+ endif
+endfunc
+
+func s:get_help_compl_list(cmd)
+ return getcompletion(a:cmd, 'help')
+endfunc
+
+func Test_help_complete()
+ try
+ let list = []
+ call s:doc_config_setup()
+
+ " 'helplang=' and help file lang is 'en'
+ if has('multi_lang')
+ set helplang=
+ endif
+ let list = s:get_help_compl_list("test")
+ call assert_equal(['test-col', 'test-char'], list)
+
+ if has('multi_lang')
+ " 'helplang=ab' and help file lang is 'en'
+ set helplang=ab
+ let list = s:get_help_compl_list("test")
+ call assert_equal(['test-col', 'test-char'], list)
+
+ " 'helplang=' and help file lang is 'en' and 'ab'
+ set rtp+=Xdocdir1/doc-ab
+ set helplang=
+ let list = s:get_help_compl_list("test")
+ call assert_equal(sort(['test-col@en', 'test-col@ab',
+ \ 'test-char@en', 'test-char@ab']), sort(list))
+
+ " 'helplang=ab' and help file lang is 'en' and 'ab'
+ set helplang=ab
+ let list = s:get_help_compl_list("test")
+ call assert_equal(sort(['test-col', 'test-col@en',
+ \ 'test-char', 'test-char@en']), sort(list))
+
+ " 'helplang=' and help file lang is 'en', 'ab' and 'ja'
+ set rtp+=Xdocdir1/doc-ja
+ set helplang=
+ let list = s:get_help_compl_list("test")
+ call assert_equal(sort(['test-col@en', 'test-col@ab',
+ \ 'test-col@ja', 'test-char@en',
+ \ 'test-char@ab', 'test-char@ja']), sort(list))
+
+ " 'helplang=ab' and help file lang is 'en', 'ab' and 'ja'
+ set helplang=ab
+ let list = s:get_help_compl_list("test")
+ call assert_equal(sort(['test-col', 'test-col@en',
+ \ 'test-col@ja', 'test-char',
+ \ 'test-char@en', 'test-char@ja']), sort(list))
+
+ " 'helplang=ab,ja' and help file lang is 'en', 'ab' and 'ja'
+ set helplang=ab,ja
+ let list = s:get_help_compl_list("test")
+ call assert_equal(sort(['test-col', 'test-col@ja',
+ \ 'test-col@en', 'test-char',
+ \ 'test-char@ja', 'test-char@en']), sort(list))
+ endif
+ catch
+ call assert_exception('X')
+ finally
+ call s:doc_config_teardown()
+ endtry
+endfunc
+
+func Test_help_respect_current_file_lang()
+ try
+ let list = []
+ call s:doc_config_setup()
+
+ if has('multi_lang')
+ function s:check_help_file_ext(help_keyword, ext)
+ exec 'help ' . a:help_keyword
+ call assert_equal(a:ext, expand('%:e'))
+ call feedkeys("\<C-]>", 'tx')
+ call assert_equal(a:ext, expand('%:e'))
+ pop
+ helpclose
+ endfunc
+
+ set rtp+=Xdocdir1/doc-ab
+ set rtp+=Xdocdir1/doc-ja
+
+ set helplang=ab
+ call s:check_help_file_ext('test-char', 'abx')
+ call s:check_help_file_ext('test-char@ja', 'jax')
+ set helplang=ab,ja
+ call s:check_help_file_ext('test-char@ja', 'jax')
+ call s:check_help_file_ext('test-char@en', 'txt')
+ endif
+ catch
+ call assert_exception('X')
+ finally
+ call s:doc_config_teardown()
+ endtry
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_hide.vim b/src/testdir/test_hide.vim
new file mode 100644
index 0000000..b3ce395
--- /dev/null
+++ b/src/testdir/test_hide.vim
@@ -0,0 +1,97 @@
+" Tests for :hide command/modifier and 'hidden' option
+
+func SetUp()
+ let s:save_hidden = &hidden
+ let s:save_bufhidden = &bufhidden
+ let s:save_autowrite = &autowrite
+ set nohidden
+ set bufhidden=
+ set noautowrite
+endfunc
+
+function TearDown()
+ let &hidden = s:save_hidden
+ let &bufhidden = s:save_bufhidden
+ let &autowrite = s:save_autowrite
+endfunc
+
+function Test_hide()
+ let orig_bname = bufname('')
+ let orig_winnr = winnr('$')
+
+ new Xf1
+ set modified
+ call assert_fails('edit Xf2')
+ bwipeout! Xf1
+
+ new Xf1
+ set modified
+ edit! Xf2
+ call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
+ call assert_equal([1, 0], [buflisted('Xf1'), bufloaded('Xf1')])
+ bwipeout! Xf1
+ bwipeout! Xf2
+
+ new Xf1
+ set modified
+ " :hide as a command
+ hide
+ call assert_equal([orig_bname, orig_winnr], [bufname(''), winnr('$')])
+ call assert_equal([1, 1], ['Xf1'->buflisted(), 'Xf1'->bufloaded()])
+ bwipeout! Xf1
+
+ new Xf1
+ set modified
+ " :hide as a command with trailing comment
+ hide " comment
+ call assert_equal([orig_bname, orig_winnr], [bufname(''), winnr('$')])
+ call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
+ bwipeout! Xf1
+
+ new Xf1
+ set modified
+ " :hide as a command with bar
+ hide | new Xf2 " comment
+ call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
+ call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
+ bwipeout! Xf1
+ bwipeout! Xf2
+
+ new Xf1
+ set modified
+ " :hide as a modifier with trailing comment
+ hide edit Xf2 " comment
+ call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
+ call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
+ bwipeout! Xf1
+ bwipeout! Xf2
+
+ new Xf1
+ set modified
+ " To check that the bar is not recognized to separate commands
+ hide echo "one|two"
+ call assert_equal(['Xf1', 2], [bufname(''), winnr('$')])
+ call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
+ bwipeout! Xf1
+
+ " set hidden
+ new Xf1
+ set hidden
+ set modified
+ edit Xf2 " comment
+ call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
+ call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')])
+ bwipeout! Xf1
+ bwipeout! Xf2
+
+ " set hidden bufhidden=wipe
+ new Xf1
+ set bufhidden=wipe
+ set modified
+ hide edit! Xf2 " comment
+ call assert_equal(['Xf2', 2], [bufname(''), winnr('$')])
+ call assert_equal([0, 0], [buflisted('Xf1'), bufloaded('Xf1')])
+ bwipeout! Xf2
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_highlight.vim b/src/testdir/test_highlight.vim
new file mode 100644
index 0000000..feccf84
--- /dev/null
+++ b/src/testdir/test_highlight.vim
@@ -0,0 +1,1301 @@
+" Tests for ":highlight" and highlighting.
+
+source view_util.vim
+source screendump.vim
+source check.vim
+source script_util.vim
+import './vim9.vim' as v9
+
+func ClearDict(d)
+ for k in keys(a:d)
+ call remove(a:d, k)
+ endfor
+endfunc
+
+func Test_highlight()
+ " basic test if ":highlight" doesn't crash
+ highlight
+ hi Search
+
+ " test setting colors.
+ " test clearing one color and all doesn't generate error or warning
+ silent! hi NewGroup term=bold cterm=italic ctermfg=DarkBlue ctermbg=Grey gui= guifg=#00ff00 guibg=Cyan
+ silent! hi Group2 term= cterm=
+ hi Group3 term=underline cterm=bold
+
+ let res = split(execute("hi NewGroup"), "\n")[0]
+ " filter ctermfg and ctermbg, the numbers depend on the terminal
+ let res = substitute(res, 'ctermfg=\d*', 'ctermfg=2', '')
+ let res = substitute(res, 'ctermbg=\d*', 'ctermbg=3', '')
+ call assert_equal("NewGroup xxx term=bold cterm=italic ctermfg=2 ctermbg=3",
+ \ res)
+ call assert_equal("Group2 xxx cleared",
+ \ split(execute("hi Group2"), "\n")[0])
+ call assert_equal("Group3 xxx term=underline cterm=bold",
+ \ split(execute("hi Group3"), "\n")[0])
+
+ hi clear NewGroup
+ call assert_equal("NewGroup xxx cleared",
+ \ split(execute("hi NewGroup"), "\n")[0])
+ call assert_equal("Group2 xxx cleared",
+ \ split(execute("hi Group2"), "\n")[0])
+ hi Group2 NONE
+ call assert_equal("Group2 xxx cleared",
+ \ split(execute("hi Group2"), "\n")[0])
+ hi clear
+ call assert_equal("Group3 xxx cleared",
+ \ split(execute("hi Group3"), "\n")[0])
+ call assert_fails("hi Crash term='asdf", "E475:")
+
+ if has('gui_running')
+ call assert_fails('hi NotUsed guibg=none', 'E1361:')
+ endif
+endfunc
+
+func HighlightArgs(name)
+ return 'hi ' . substitute(split(execute('hi ' . a:name), '\n')[0], '\<xxx\>', '', '')
+endfunc
+
+func IsColorable()
+ return has('gui_running') || str2nr(&t_Co) >= 8
+endfunc
+
+func HiCursorLine()
+ let hiCursorLine = HighlightArgs('CursorLine')
+ if has('gui_running')
+ let guibg = matchstr(hiCursorLine, 'guibg=\w\+')
+ let hi_ul = 'hi CursorLine gui=underline guibg=NONE'
+ let hi_bg = 'hi CursorLine gui=NONE ' . guibg
+ else
+ let hi_ul = 'hi CursorLine cterm=underline ctermbg=NONE'
+ let hi_bg = 'hi CursorLine cterm=NONE ctermbg=Gray'
+ endif
+ return [hiCursorLine, hi_ul, hi_bg]
+endfunc
+
+func Check_lcs_eol_attrs(attrs, row, col)
+ let save_lcs = &lcs
+ set list
+
+ call assert_equal(a:attrs, ScreenAttrs(a:row, a:col)[0])
+
+ set nolist
+ let &lcs = save_lcs
+endfunc
+
+func Test_highlight_eol_with_cursorline()
+ let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
+
+ call NewWindow('topleft 5', 20)
+ call setline(1, 'abcd')
+ call matchadd('Search', '\n')
+
+ " expected:
+ " 'abcd '
+ " ^^^^ ^^^^^ no highlight
+ " ^ 'Search' highlight
+ let attrs0 = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs0[0]], 4), attrs0[0:3])
+ call assert_equal(repeat([attrs0[0]], 5), attrs0[5:9])
+ call assert_notequal(attrs0[0], attrs0[4])
+
+ setlocal cursorline
+
+ " underline
+ exe hi_ul
+
+ " expected:
+ " 'abcd '
+ " ^^^^ underline
+ " ^ 'Search' highlight with underline
+ " ^^^^^ underline
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
+ call assert_equal([attrs[4]] + repeat([attrs[5]], 5), attrs[4:9])
+ call assert_notequal(attrs[0], attrs[4])
+ call assert_notequal(attrs[4], attrs[5])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_notequal(attrs0[4], attrs[4])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+
+ if IsColorable()
+ " bg-color
+ exe hi_bg
+
+ " expected:
+ " 'abcd '
+ " ^^^^ bg-color of 'CursorLine'
+ " ^ 'Search' highlight
+ " ^^^^^ bg-color of 'CursorLine'
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
+ call assert_equal(repeat([attrs[5]], 5), attrs[5:9])
+ call assert_equal(attrs0[4], attrs[4])
+ call assert_notequal(attrs[0], attrs[4])
+ call assert_notequal(attrs[4], attrs[5])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_notequal(attrs0[5], attrs[5])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+ endif
+
+ call CloseWindow()
+ exe hiCursorLine
+endfunc
+
+func Test_highlight_eol_with_cursorline_vertsplit()
+ let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
+
+ call NewWindow('topleft 5', 5)
+ call setline(1, 'abcd')
+ call matchadd('Search', '\n')
+
+ let expected = "abcd |abcd "
+ let actual = ScreenLines(1, 15)[0]
+ call assert_equal(expected, actual)
+
+ " expected:
+ " 'abcd |abcd '
+ " ^^^^ ^^^^^^^^^ no highlight
+ " ^ 'Search' highlight
+ " ^ 'VertSplit' highlight
+ let attrs0 = ScreenAttrs(1, 15)[0]
+ call assert_equal(repeat([attrs0[0]], 4), attrs0[0:3])
+ call assert_equal(repeat([attrs0[0]], 9), attrs0[6:14])
+ call assert_notequal(attrs0[0], attrs0[4])
+ call assert_notequal(attrs0[0], attrs0[5])
+ call assert_notequal(attrs0[4], attrs0[5])
+
+ setlocal cursorline
+
+ " expected:
+ " 'abcd |abcd '
+ " ^^^^ underline
+ " ^ 'Search' highlight with underline
+ " ^ 'VertSplit' highlight
+ " ^^^^^^^^^ no highlight
+
+ " underline
+ exe hi_ul
+
+ let actual = ScreenLines(1, 15)[0]
+ call assert_equal(expected, actual)
+
+ let attrs = ScreenAttrs(1, 15)[0]
+ call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
+ call assert_equal(repeat([attrs[6]], 9), attrs[6:14])
+ call assert_equal(attrs0[5:14], attrs[5:14])
+ call assert_notequal(attrs[0], attrs[4])
+ call assert_notequal(attrs[0], attrs[5])
+ call assert_notequal(attrs[0], attrs[6])
+ call assert_notequal(attrs[4], attrs[5])
+ call assert_notequal(attrs[5], attrs[6])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_notequal(attrs0[4], attrs[4])
+ call Check_lcs_eol_attrs(attrs, 1, 15)
+
+ if IsColorable()
+ " bg-color
+ exe hi_bg
+
+ let actual = ScreenLines(1, 15)[0]
+ call assert_equal(expected, actual)
+
+ let attrs = ScreenAttrs(1, 15)[0]
+ call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
+ call assert_equal(repeat([attrs[6]], 9), attrs[6:14])
+ call assert_equal(attrs0[5:14], attrs[5:14])
+ call assert_notequal(attrs[0], attrs[4])
+ call assert_notequal(attrs[0], attrs[5])
+ call assert_notequal(attrs[0], attrs[6])
+ call assert_notequal(attrs[4], attrs[5])
+ call assert_notequal(attrs[5], attrs[6])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_equal(attrs0[4], attrs[4])
+ call Check_lcs_eol_attrs(attrs, 1, 15)
+ endif
+
+ call CloseWindow()
+ exe hiCursorLine
+endfunc
+
+func Test_highlight_eol_with_cursorline_rightleft()
+ CheckFeature rightleft
+
+ let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
+
+ call NewWindow('topleft 5', 10)
+ setlocal rightleft
+ call setline(1, 'abcd')
+ call matchadd('Search', '\n')
+ let attrs0 = ScreenAttrs(1, 10)[0]
+
+ setlocal cursorline
+
+ " underline
+ exe hi_ul
+
+ " expected:
+ " ' dcba'
+ " ^^^^ underline
+ " ^ 'Search' highlight with underline
+ " ^^^^^ underline
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[9]], 4), attrs[6:9])
+ call assert_equal(repeat([attrs[4]], 5) + [attrs[5]], attrs[0:5])
+ call assert_notequal(attrs[9], attrs[5])
+ call assert_notequal(attrs[4], attrs[5])
+ call assert_notequal(attrs0[9], attrs[9])
+ call assert_notequal(attrs0[5], attrs[5])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+
+ if IsColorable()
+ " bg-color
+ exe hi_bg
+
+ " expected:
+ " ' dcba'
+ " ^^^^ bg-color of 'CursorLine'
+ " ^ 'Search' highlight
+ " ^^^^^ bg-color of 'CursorLine'
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[9]], 4), attrs[6:9])
+ call assert_equal(repeat([attrs[4]], 5), attrs[0:4])
+ call assert_equal(attrs0[5], attrs[5])
+ call assert_notequal(attrs[9], attrs[5])
+ call assert_notequal(attrs[5], attrs[4])
+ call assert_notequal(attrs0[9], attrs[9])
+ call assert_notequal(attrs0[4], attrs[4])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+ endif
+
+ call CloseWindow()
+ exe hiCursorLine
+endfunc
+
+func Test_highlight_eol_with_cursorline_linewrap()
+ let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
+
+ call NewWindow('topleft 5', 10)
+ call setline(1, [repeat('a', 51) . 'bcd', ''])
+ call matchadd('Search', '\n')
+
+ setlocal wrap
+ normal! gg$
+ let attrs0 = ScreenAttrs(5, 10)[0]
+ setlocal cursorline
+
+ " underline
+ exe hi_ul
+
+ " expected:
+ " 'abcd '
+ " ^^^^ underline
+ " ^ 'Search' highlight with underline
+ " ^^^^^ underline
+ let attrs = ScreenAttrs(5, 10)[0]
+ call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
+ call assert_equal([attrs[4]] + repeat([attrs[5]], 5), attrs[4:9])
+ call assert_notequal(attrs[0], attrs[4])
+ call assert_notequal(attrs[4], attrs[5])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_notequal(attrs0[4], attrs[4])
+ call Check_lcs_eol_attrs(attrs, 5, 10)
+
+ if IsColorable()
+ " bg-color
+ exe hi_bg
+
+ " expected:
+ " 'abcd '
+ " ^^^^ bg-color of 'CursorLine'
+ " ^ 'Search' highlight
+ " ^^^^^ bg-color of 'CursorLine'
+ let attrs = ScreenAttrs(5, 10)[0]
+ call assert_equal(repeat([attrs[0]], 4), attrs[0:3])
+ call assert_equal(repeat([attrs[5]], 5), attrs[5:9])
+ call assert_equal(attrs0[4], attrs[4])
+ call assert_notequal(attrs[0], attrs[4])
+ call assert_notequal(attrs[4], attrs[5])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_notequal(attrs0[5], attrs[5])
+ call Check_lcs_eol_attrs(attrs, 5, 10)
+ endif
+
+ setlocal nocursorline nowrap
+ normal! gg$
+ let attrs0 = ScreenAttrs(1, 10)[0]
+ setlocal cursorline
+
+ " underline
+ exe hi_ul
+
+ " expected:
+ " 'aaabcd '
+ " ^^^^^^ underline
+ " ^ 'Search' highlight with underline
+ " ^^^ underline
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[0]], 6), attrs[0:5])
+ call assert_equal([attrs[6]] + repeat([attrs[7]], 3), attrs[6:9])
+ call assert_notequal(attrs[0], attrs[6])
+ call assert_notequal(attrs[6], attrs[7])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_notequal(attrs0[6], attrs[6])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+
+ if IsColorable()
+ " bg-color
+ exe hi_bg
+
+ " expected:
+ " 'aaabcd '
+ " ^^^^^^ bg-color of 'CursorLine'
+ " ^ 'Search' highlight
+ " ^^^ bg-color of 'CursorLine'
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[0]], 6), attrs[0:5])
+ call assert_equal(repeat([attrs[7]], 3), attrs[7:9])
+ call assert_equal(attrs0[6], attrs[6])
+ call assert_notequal(attrs[0], attrs[6])
+ call assert_notequal(attrs[6], attrs[7])
+ call assert_notequal(attrs0[0], attrs[0])
+ call assert_notequal(attrs0[7], attrs[7])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+ endif
+
+ call CloseWindow()
+ exe hiCursorLine
+endfunc
+
+func Test_highlight_eol_with_cursorline_sign()
+ CheckFeature signs
+
+ let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
+
+ call NewWindow('topleft 5', 10)
+ call setline(1, 'abcd')
+ call matchadd('Search', '\n')
+
+ sign define Sign text=>>
+ exe 'sign place 1 line=1 name=Sign buffer=' . bufnr('')
+ let attrs0 = ScreenAttrs(1, 10)[0]
+ setlocal cursorline
+
+ " underline
+ exe hi_ul
+
+ " expected:
+ " '>>abcd '
+ " ^^ sign
+ " ^^^^ underline
+ " ^ 'Search' highlight with underline
+ " ^^^ underline
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[2]], 4), attrs[2:5])
+ call assert_equal([attrs[6]] + repeat([attrs[7]], 3), attrs[6:9])
+ call assert_notequal(attrs[2], attrs[6])
+ call assert_notequal(attrs[6], attrs[7])
+ call assert_notequal(attrs0[2], attrs[2])
+ call assert_notequal(attrs0[6], attrs[6])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+
+ if IsColorable()
+ " bg-color
+ exe hi_bg
+
+ " expected:
+ " '>>abcd '
+ " ^^ sign
+ " ^^^^ bg-color of 'CursorLine'
+ " ^ 'Search' highlight
+ " ^^^ bg-color of 'CursorLine'
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[2]], 4), attrs[2:5])
+ call assert_equal(repeat([attrs[7]], 3), attrs[7:9])
+ call assert_equal(attrs0[6], attrs[6])
+ call assert_notequal(attrs[2], attrs[6])
+ call assert_notequal(attrs[6], attrs[7])
+ call assert_notequal(attrs0[2], attrs[2])
+ call assert_notequal(attrs0[7], attrs[7])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+ endif
+
+ sign unplace 1
+ call CloseWindow()
+ exe hiCursorLine
+endfunc
+
+func Test_highlight_eol_with_cursorline_breakindent()
+ CheckFeature linebreak
+
+ let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine()
+
+ call NewWindow('topleft 5', 10)
+ set showbreak=xxx
+ setlocal breakindent breakindentopt=min:0,shift:1 showbreak=>
+ call setline(1, ' ' . repeat('a', 9) . 'bcd')
+ call matchadd('Search', '\n')
+ let attrs0 = ScreenAttrs(2, 10)[0]
+ setlocal cursorline
+
+ " underline
+ exe hi_ul
+
+ " expected:
+ " ' >bcd '
+ " ^^^ breakindent and showbreak
+ " ^^^ underline
+ " ^ 'Search' highlight with underline
+ " ^^^ underline
+ let attrs = ScreenAttrs(2, 10)[0]
+ call assert_equal(repeat([attrs[0]], 2), attrs[0:1])
+ call assert_equal(repeat([attrs[3]], 3), attrs[3:5])
+ call assert_equal([attrs[6]] + repeat([attrs[7]], 3), attrs[6:9])
+ call assert_equal(attrs0[0], attrs[0])
+ call assert_notequal(attrs[0], attrs[2])
+ call assert_notequal(attrs[2], attrs[3])
+ call assert_notequal(attrs[3], attrs[6])
+ call assert_notequal(attrs[6], attrs[7])
+ call assert_notequal(attrs0[2], attrs[2])
+ call assert_notequal(attrs0[3], attrs[3])
+ call assert_notequal(attrs0[6], attrs[6])
+ call Check_lcs_eol_attrs(attrs, 2, 10)
+
+ if IsColorable()
+ " bg-color
+ exe hi_bg
+
+ " expected:
+ " ' >bcd '
+ " ^^^ breakindent and showbreak
+ " ^^^ bg-color of 'CursorLine'
+ " ^ 'Search' highlight
+ " ^^^ bg-color of 'CursorLine'
+ let attrs = ScreenAttrs(2, 10)[0]
+ call assert_equal(repeat([attrs[0]], 2), attrs[0:1])
+ call assert_equal(repeat([attrs[3]], 3), attrs[3:5])
+ call assert_equal(repeat([attrs[7]], 3), attrs[7:9])
+ call assert_equal(attrs0[0], attrs[0])
+ call assert_equal(attrs0[6], attrs[6])
+ call assert_notequal(attrs[0], attrs[2])
+ call assert_notequal(attrs[2], attrs[3])
+ call assert_notequal(attrs[3], attrs[6])
+ call assert_notequal(attrs[6], attrs[7])
+ call assert_notequal(attrs0[2], attrs[2])
+ call assert_notequal(attrs0[3], attrs[3])
+ call assert_notequal(attrs0[7], attrs[7])
+ call Check_lcs_eol_attrs(attrs, 2, 10)
+ endif
+
+ call CloseWindow()
+ set showbreak=
+ setlocal showbreak=
+ exe hiCursorLine
+endfunc
+
+func Test_highlight_eol_on_diff()
+ call setline(1, ['abcd', ''])
+ call matchadd('Search', '\n')
+ let attrs0 = ScreenAttrs(1, 10)[0]
+
+ diffthis
+ botright new
+ diffthis
+
+ " expected:
+ " ' abcd '
+ " ^^ sign
+ " ^^^^ ^^^ 'DiffAdd' highlight
+ " ^ 'Search' highlight
+ let attrs = ScreenAttrs(1, 10)[0]
+ call assert_equal(repeat([attrs[0]], 2), attrs[0:1])
+ call assert_equal(repeat([attrs[2]], 4), attrs[2:5])
+ call assert_equal(repeat([attrs[2]], 3), attrs[7:9])
+ call assert_equal(attrs0[4], attrs[6])
+ call assert_notequal(attrs[0], attrs[2])
+ call assert_notequal(attrs[0], attrs[6])
+ call assert_notequal(attrs[2], attrs[6])
+ call Check_lcs_eol_attrs(attrs, 1, 10)
+
+ bwipe!
+ diffoff
+endfunc
+
+func Test_termguicolors()
+ CheckOption termguicolors
+ if has('vtp') && !has('vcon') && !has('gui_running')
+ " Win32: 'guicolors' doesn't work without virtual console.
+ call assert_fails('set termguicolors', 'E954:')
+ return
+ endif
+
+ " Basic test that setting 'termguicolors' works with one color.
+ set termguicolors
+ redraw
+ set t_Co=1
+ redraw
+ set t_Co=0
+ redraw
+endfunc
+
+func Test_cursorline_after_yank()
+ CheckScreendump
+
+ call writefile([
+ \ 'set cul rnu',
+ \ 'call setline(1, ["","1","2","3",""])',
+ \ ], 'Xtest_cursorline_yank', 'D')
+ let buf = RunVimInTerminal('-S Xtest_cursorline_yank', {'rows': 8})
+ call TermWait(buf)
+ call term_sendkeys(buf, "Gy3k")
+ call TermWait(buf)
+ call term_sendkeys(buf, "jj")
+
+ call VerifyScreenDump(buf, 'Test_cursorline_yank_01', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" test for issue #4862
+func Test_put_before_cursorline()
+ new
+ only!
+ call setline(1, 'A')
+ redraw
+ let std_attr = screenattr(1, 1)
+ set cursorline
+ redraw
+ let cul_attr = screenattr(1, 1)
+ normal yyP
+ redraw
+ " Line 1 has cursor so it should be highlighted with CursorLine.
+ call assert_equal(cul_attr, screenattr(1, 1))
+ " And CursorLine highlighting from the second line should be gone.
+ call assert_equal(std_attr, screenattr(2, 1))
+ set nocursorline
+ bwipe!
+endfunc
+
+func Test_cursorline_with_visualmode()
+ CheckScreendump
+
+ call writefile([
+ \ 'set cul',
+ \ 'call setline(1, repeat(["abc"], 50))',
+ \ ], 'Xtest_cursorline_with_visualmode', 'D')
+ let buf = RunVimInTerminal('-S Xtest_cursorline_with_visualmode', {'rows': 12})
+ call TermWait(buf)
+ call term_sendkeys(buf, "V\<C-f>kkkjk")
+
+ call VerifyScreenDump(buf, 'Test_cursorline_with_visualmode_01', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cursorcolumn_insert_on_tab()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['123456789', "a\tb"])
+ set cursorcolumn
+ call cursor(2, 2)
+ END
+ call writefile(lines, 'Xcuc_insert_on_tab', 'D')
+
+ let buf = RunVimInTerminal('-S Xcuc_insert_on_tab', #{rows: 8})
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_1', {})
+
+ call term_sendkeys(buf, 'i')
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {})
+
+ call term_sendkeys(buf, "\<C-O>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_3', {})
+
+ call term_sendkeys(buf, 'i')
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cursorcolumn_callback()
+ CheckScreendump
+ CheckFeature timers
+
+ let lines =<< trim END
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set cursorcolumn
+ call cursor(4, 5)
+
+ func Func(timer)
+ call cursor(1, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ END
+ call writefile(lines, 'Xcuc_timer', 'D')
+
+ let buf = RunVimInTerminal('-S Xcuc_timer', #{rows: 8})
+ call TermWait(buf, 310)
+ call VerifyScreenDump(buf, 'Test_cursorcolumn_callback_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_wincolor()
+ CheckScreendump
+ " make sure the width is enough for the test
+ set columns=80
+
+ let lines =<< trim END
+ set cursorline cursorcolumn rnu
+ call setline(1, ["","1111111111","22222222222","3 here 3","","the cat is out of the bag"])
+ set wincolor=Pmenu
+ hi CatLine guifg=green ctermfg=green
+ hi Reverse gui=reverse cterm=reverse
+ syn match CatLine /^the.*/
+ call prop_type_add("foo", {"highlight": "Reverse", "combine": 1})
+ call prop_add(6, 12, {"type": "foo", "end_col": 15})
+ /here
+ END
+ call writefile(lines, 'Xtest_wincolor', 'D')
+ let buf = RunVimInTerminal('-S Xtest_wincolor', {'rows': 8})
+ call TermWait(buf)
+ call term_sendkeys(buf, "2G5lvj")
+ call TermWait(buf)
+
+ call VerifyScreenDump(buf, 'Test_wincolor_01', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_wincolor_listchars()
+ CheckScreendump
+ CheckFeature conceal
+
+ let lines =<< trim END
+ call setline(1, ["one","\t\tsome random text enough long to show 'extends' and 'precedes' includingnbsps, preceding tabs and trailing spaces ","three"])
+ set wincolor=Todo
+ set nowrap cole=1 cocu+=n
+ set list lcs=eol:$,tab:>-,space:.,trail:_,extends:>,precedes:<,conceal:*,nbsp:#
+ call matchadd('Conceal', 'text')
+ normal 2G5zl
+ END
+ call writefile(lines, 'Xtest_wincolorlcs', 'D')
+ let buf = RunVimInTerminal('-S Xtest_wincolorlcs', {'rows': 8})
+
+ call VerifyScreenDump(buf, 'Test_wincolor_lcs', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_colorcolumn()
+ CheckScreendump
+
+ " check that setting 'colorcolumn' when entering a buffer works
+ let lines =<< trim END
+ split
+ edit X
+ call setline(1, ["1111111111","22222222222","3333333333"])
+ set nomodified
+ set colorcolumn=3,9
+ set number cursorline cursorlineopt=number
+ wincmd w
+ buf X
+ END
+ call writefile(lines, 'Xtest_colorcolumn', 'D')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn', {'rows': 10})
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_colorcolumn_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_colorcolumn_bri()
+ CheckScreendump
+
+ " check 'colorcolumn' when 'breakindent' is set
+ let lines =<< trim END
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ END
+ call writefile(lines, 'Xtest_colorcolumn_bri', 'D')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn_bri', {'rows': 10,'columns': 40})
+ call term_sendkeys(buf, ":set co=40 linebreak bri briopt=shift:2 cc=40,41,43\<CR>")
+ call VerifyScreenDump(buf, 'Test_colorcolumn_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_colorcolumn_sbr()
+ CheckScreendump
+
+ " check 'colorcolumn' when 'showbreak' is set
+ let lines =<< trim END
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ END
+ call writefile(lines, 'Xtest_colorcolumn_srb', 'D')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn_srb', {'rows': 10,'columns': 40})
+ call term_sendkeys(buf, ":set co=40 showbreak=+++>\\ cc=40,41,43\<CR>")
+ call VerifyScreenDump(buf, 'Test_colorcolumn_3', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" This test must come before the Test_cursorline test, as it appears this
+" defines the Normal highlighting group anyway.
+func Test_1_highlight_Normalgroup_exists()
+ let hlNormal = HighlightArgs('Normal')
+ if !has('gui_running')
+ call assert_match('hi Normal\s*clear', hlNormal)
+ elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " expect is DEFAULT_FONT of gui_gtk_x11.c
+ call assert_match('hi Normal\s*font=Monospace 10', hlNormal)
+ elseif has('gui_motif')
+ " expect is DEFAULT_FONT of gui_x11.c
+ call assert_match('hi Normal\s*font=7x13', hlNormal)
+ elseif has('win32')
+ " expect any font
+ call assert_match('hi Normal\s*font=.*', hlNormal)
+ endif
+endfunc
+
+" Do this test last, sometimes restoring the columns doesn't work
+func Test_z_no_space_before_xxx()
+ let l:org_columns = &columns
+ set columns=17
+ let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC')))
+ call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC)
+ let &columns = l:org_columns
+endfunc
+
+" Test for :highlight command errors
+func Test_highlight_cmd_errors()
+ if has('gui_running')
+ " This test doesn't fail in the MS-Windows console version.
+ call assert_fails('hi Xcomment ctermbg=fg', 'E419:')
+ call assert_fails('hi Xcomment ctermfg=bg', 'E420:')
+ call assert_fails('hi Xcomment ctermfg=ul', 'E453:')
+ call assert_fails('hi ' .. repeat('a', 201) .. ' ctermfg=black', 'E1249:')
+ endif
+
+ " Try using a very long terminal code. Define a dummy terminal code for this
+ " test.
+ let &t_fo = "\<Esc>1;"
+ let c = repeat("t_fo,", 100) . "t_fo"
+ call assert_fails('exe "hi Xgroup1 start=" . c', 'E422:')
+ let &t_fo = ""
+endfunc
+
+" Test for 'highlight' option
+func Test_highlight_opt()
+ let save_hl = &highlight
+ call assert_fails('set highlight=j:b', 'E474:')
+ set highlight=f\ r
+ call assert_equal('f r', &highlight)
+ set highlight=fb
+ call assert_equal('fb', &highlight)
+ set highlight=fi
+ call assert_equal('fi', &highlight)
+ set highlight=f-
+ call assert_equal('f-', &highlight)
+ set highlight=fr
+ call assert_equal('fr', &highlight)
+ set highlight=fs
+ call assert_equal('fs', &highlight)
+ set highlight=fu
+ call assert_equal('fu', &highlight)
+ set highlight=fc
+ call assert_equal('fc', &highlight)
+ set highlight=ft
+ call assert_equal('ft', &highlight)
+ call assert_fails('set highlight=fr:Search', 'E474:')
+ set highlight=f:$#
+ call assert_match('W18:', v:statusmsg)
+ let &highlight = save_hl
+endfunc
+
+" Test for User group highlighting used in the statusline
+func Test_highlight_User()
+ CheckNotGui
+ hi User1 ctermfg=12
+ redraw!
+ call assert_equal('12', synIDattr(synIDtrans(hlID('User1')), 'fg'))
+ hi clear
+endfunc
+
+" Test for using RGB color values in a highlight group
+func Test_xxlast_highlight_RGB_color()
+ CheckCanRunGui
+ gui -f
+ hi MySearch guifg=#110000 guibg=#001100 guisp=#000011
+ call assert_equal('#110000', synIDattr(synIDtrans(hlID('MySearch')), 'fg#'))
+ call assert_equal('#001100', synIDattr(synIDtrans(hlID('MySearch')), 'bg#'))
+ call assert_equal('#000011', synIDattr(synIDtrans(hlID('MySearch')), 'sp#'))
+ hi clear
+endfunc
+
+" Test for using default highlighting group
+func Test_highlight_default()
+ highlight MySearch ctermfg=7
+ highlight default MySearch ctermfg=5
+ let hlSearch = HighlightArgs('MySearch')
+ call assert_match('ctermfg=7', hlSearch)
+
+ highlight default QFName ctermfg=3
+ call assert_match('ctermfg=3', HighlightArgs('QFName'))
+ hi clear
+endfunc
+
+" Test for 'ctermul in a highlight group
+func Test_highlight_ctermul()
+ CheckNotGui
+ call assert_notmatch('ctermul=', HighlightArgs('Normal'))
+ highlight Normal ctermul=3
+ call assert_match('ctermul=3', HighlightArgs('Normal'))
+ call assert_equal('3', synIDattr(synIDtrans(hlID('Normal')), 'ul'))
+ highlight Normal ctermul=NONE
+endfunc
+
+" Test for specifying 'start' and 'stop' in a highlight group
+func Test_highlight_start_stop()
+ hi HlGrp1 start=<Esc>[27h;<Esc>[<Space>r;
+ call assert_match("start=^[[27h;^[[ r;", HighlightArgs('HlGrp1'))
+ hi HlGrp1 start=NONE
+ call assert_notmatch("start=", HighlightArgs('HlGrp1'))
+ hi HlGrp2 stop=<Esc>[27h;<Esc>[<Space>r;
+ call assert_match("stop=^[[27h;^[[ r;", HighlightArgs('HlGrp2'))
+ hi HlGrp2 stop=NONE
+ call assert_notmatch("stop=", HighlightArgs('HlGrp2'))
+ hi clear
+endfunc
+
+" Test for setting various 'term' attributes
+func Test_highlight_term_attr()
+ hi HlGrp3 term=bold,underline,undercurl,underdouble,underdotted,underdashed,strikethrough,reverse,italic,standout
+ call assert_equal('hi HlGrp3 term=bold,standout,underline,undercurl,underdouble,underdotted,underdashed,italic,reverse,strikethrough', HighlightArgs('HlGrp3'))
+ hi HlGrp3 term=NONE
+ call assert_equal('hi HlGrp3 cleared', HighlightArgs('HlGrp3'))
+ hi clear
+endfunc
+
+func Test_highlight_clear_restores_links()
+ let aaa_id = hlID('aaa')
+ call assert_equal(aaa_id, 0)
+
+ " create default link aaa --> bbb
+ hi def link aaa bbb
+ let id_aaa = hlID('aaa')
+ let hl_aaa_bbb = HighlightArgs('aaa')
+
+ " try to redefine default link aaa --> ccc; check aaa --> bbb
+ hi def link aaa ccc
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
+
+ " clear aaa; check aaa --> bbb
+ hi clear aaa
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
+
+ " link aaa --> ccc; clear aaa; check aaa --> bbb
+ hi link aaa ccc
+ let id_ccc = hlID('ccc')
+ call assert_equal(synIDtrans(id_aaa), id_ccc)
+ hi clear aaa
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb)
+
+ " forcibly set default link aaa --> ddd
+ hi! def link aaa ddd
+ let id_ddd = hlID('ddd')
+ let hl_aaa_ddd = HighlightArgs('aaa')
+ call assert_equal(synIDtrans(id_aaa), id_ddd)
+
+ " link aaa --> eee; clear aaa; check aaa --> ddd
+ hi link aaa eee
+ let eee_id = hlID('eee')
+ call assert_equal(synIDtrans(id_aaa), eee_id)
+ hi clear aaa
+ call assert_equal(HighlightArgs('aaa'), hl_aaa_ddd)
+endfunc
+
+func Test_highlight_clear_restores_context()
+ func FuncContextDefault()
+ hi def link Context ContextDefault
+ endfun
+
+ func FuncContextRelink()
+ " Dummy line
+ hi link Context ContextRelink
+ endfunc
+
+ let scriptContextDefault = MakeScript("FuncContextDefault")
+ let scriptContextRelink = MakeScript("FuncContextRelink")
+ let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1'
+ let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2'
+
+ exec 'source ' .. scriptContextDefault
+ let hlContextDefault = execute("verbose hi Context")
+ call assert_match(patContextDefault, hlContextDefault)
+
+ exec 'source ' .. scriptContextRelink
+ let hlContextRelink = execute("verbose hi Context")
+ call assert_match(patContextRelink, hlContextRelink)
+
+ hi clear
+ let hlContextAfterClear = execute("verbose hi Context")
+ call assert_match(patContextDefault, hlContextAfterClear)
+
+ delfunc FuncContextDefault
+ delfunc FuncContextRelink
+ call delete(scriptContextDefault)
+ call delete(scriptContextRelink)
+endfunc
+
+func Test_highlight_default_colorscheme_restores_links()
+ hi link TestLink Identifier
+ hi TestHi ctermbg=red
+
+ let hlTestLinkPre = HighlightArgs('TestLink')
+ let hlTestHiPre = HighlightArgs('TestHi')
+
+ " Test colorscheme
+ call assert_equal("\ndefault", execute('colorscheme'))
+ hi clear
+ if exists('syntax_on')
+ syntax reset
+ endif
+ let g:colors_name = 'test'
+ call assert_equal("\ntest", execute('colorscheme'))
+ hi link TestLink ErrorMsg
+ hi TestHi ctermbg=green
+
+ " Restore default highlighting
+ colorscheme default
+ " 'default' should work no matter if highlight group was cleared
+ call assert_equal("\ndefault", execute('colorscheme'))
+ hi def link TestLink Identifier
+ hi def TestHi ctermbg=red
+ let hlTestLinkPost = HighlightArgs('TestLink')
+ let hlTestHiPost = HighlightArgs('TestHi')
+ call assert_equal(hlTestLinkPre, hlTestLinkPost)
+ call assert_equal(hlTestHiPre, hlTestHiPost)
+ hi clear
+endfunc
+
+func Test_colornames_assignment_and_lookup()
+ CheckAnyOf Feature:gui_running Feature:termguicolors
+
+ " Ensure highlight command can find custom color.
+ let v:colornames['a redish white'] = '#ffeedd'
+ highlight Normal guifg='a redish white'
+ highlight clear
+ call ClearDict(v:colornames)
+endfunc
+
+func Test_colornames_default_list()
+ CheckAnyOf Feature:gui_running Feature:termguicolors
+
+ " Ensure default lists are loaded automatically and can be used for all gui fields.
+ call assert_equal(0, len(v:colornames))
+ highlight Normal guifg='rebecca purple' guibg='rebecca purple' guisp='rebecca purple'
+ call assert_notequal(0, len(v:colornames))
+ echo v:colornames['rebecca purple']
+ highlight clear
+ call ClearDict(v:colornames)
+endfunc
+
+func Test_colornames_overwrite_default()
+ CheckAnyOf Feature:gui_running Feature:termguicolors
+
+ " Ensure entries in v:colornames can be overwritten.
+ " Load default color scheme to trigger default color list loading.
+ colorscheme default
+ let old_rebecca_purple = v:colornames['rebecca purple']
+ highlight Normal guifg='rebecca purple' guibg='rebecca purple'
+ let v:colornames['rebecca purple'] = '#550099'
+ highlight Normal guifg='rebecca purple' guibg='rebecca purple'
+ let v:colornames['rebecca purple'] = old_rebecca_purple
+ highlight clear
+endfunc
+
+func Test_colornames_assignment_and_unassignment()
+ " No feature check is needed for this test because the v:colornames dict
+ " always exists with +eval. The feature checks are only required for
+ " commands that do color lookup.
+
+ " Ensure we cannot overwrite the v:colornames dict.
+ call assert_fails("let v:colornames = {}", 'E46:')
+
+ " Ensure we can delete entries from the v:colornames dict.
+ let v:colornames['x1'] = '#111111'
+ call assert_equal(v:colornames['x1'], '#111111')
+ unlet v:colornames['x1']
+ call assert_fails("echo v:colornames['x1']")
+endfunc
+
+" Test for the hlget() function
+func Test_hlget()
+ let lines =<< trim END
+ call assert_notequal([], filter(hlget(), 'v:val.name == "Visual"'))
+ call assert_equal([], hlget('SomeHLGroup'))
+ highlight MyHLGroup term=standout cterm=reverse ctermfg=10 ctermbg=Black
+ call assert_equal([{'id': hlID('MyHLGroup'), 'ctermfg': '10', 'name': 'MyHLGroup', 'term': {'standout': v:true}, 'ctermbg': '0', 'cterm': {'reverse': v:true}}], hlget('MyHLGroup'))
+ highlight clear MyHLGroup
+ call assert_equal(v:true, hlget('MyHLGroup')[0].cleared)
+ highlight link MyHLGroup IncSearch
+ call assert_equal('IncSearch', hlget('MyHLGroup')[0].linksto)
+ highlight clear MyHLGroup
+ call assert_equal([], hlget(test_null_string()))
+ call assert_equal([], hlget(""))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for resolving highlight group links
+ let lines =<< trim END
+ highlight hlgA term=bold
+ VAR hlgAid = hlID('hlgA')
+ highlight link hlgB hlgA
+ VAR hlgBid = hlID('hlgB')
+ highlight link hlgC hlgB
+ VAR hlgCid = hlID('hlgC')
+ call assert_equal('hlgA', hlget('hlgB')[0].linksto)
+ call assert_equal('hlgB', hlget('hlgC')[0].linksto)
+ call assert_equal([{'id': hlgAid, 'name': 'hlgA',
+ \ 'term': {'bold': v:true}}], hlget('hlgA'))
+ call assert_equal([{'id': hlgBid, 'name': 'hlgB',
+ \ 'linksto': 'hlgA'}], hlget('hlgB'))
+ call assert_equal([{'id': hlgCid, 'name': 'hlgC',
+ \ 'linksto': 'hlgB'}], hlget('hlgC'))
+ call assert_equal([{'id': hlgAid, 'name': 'hlgA',
+ \ 'term': {'bold': v:true}}], hlget('hlgA', v:false))
+ call assert_equal([{'id': hlgBid, 'name': 'hlgB',
+ \ 'linksto': 'hlgA'}], hlget('hlgB', 0))
+ call assert_equal([{'id': hlgCid, 'name': 'hlgC',
+ \ 'linksto': 'hlgB'}], hlget('hlgC', v:false))
+ call assert_equal([{'id': hlgAid, 'name': 'hlgA',
+ \ 'term': {'bold': v:true}}], hlget('hlgA', v:true))
+ call assert_equal([{'id': hlgBid, 'name': 'hlgB',
+ \ 'term': {'bold': v:true}}], hlget('hlgB', 1))
+ call assert_equal([{'id': hlgCid, 'name': 'hlgC',
+ \ 'term': {'bold': v:true}}], hlget('hlgC', v:true))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call assert_fails('call hlget([])', 'E1174:')
+ call assert_fails('call hlget("abc", "xyz")', 'E1212:')
+endfunc
+
+" Test for the hlset() function
+func Test_hlset()
+ let lines =<< trim END
+ call assert_equal(0, hlset(test_null_list()))
+ call assert_equal(0, hlset([]))
+ call assert_fails('call hlset(["Search"])', 'E715:')
+ call hlset(hlget())
+ call hlset([{'name': 'NewHLGroup', 'cterm': {'reverse': v:true}, 'ctermfg': '10'}])
+ call assert_equal({'reverse': v:true}, hlget('NewHLGroup')[0].cterm)
+ call hlset([{'name': 'NewHLGroup', 'cterm': {'bold': v:true}}])
+ call assert_equal({'bold': v:true}, hlget('NewHLGroup')[0].cterm)
+ call hlset([{'name': 'NewHLGroup', 'cleared': v:true}])
+ call assert_equal(v:true, hlget('NewHLGroup')[0].cleared)
+ call hlset([{'name': 'NewHLGroup', 'linksto': 'Search'}])
+ call assert_false(has_key(hlget('NewHLGroup')[0], 'cleared'))
+ call assert_equal('Search', hlget('NewHLGroup')[0].linksto)
+ call assert_fails("call hlset([{'name': [], 'ctermfg': '10'}])", 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'cleared': []}])",
+ \ 'E745:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'cterm': 'Blue'}])",
+ \ 'E715:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'ctermbg': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'ctermfg': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'ctermul': []}])",
+ \ 'E928:')
+ if has('gui')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'font': []}])",
+ \ 'E928:')
+ endif
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'gui': 'Cyan'}])",
+ \ 'E715:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'guibg': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'guifg': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'guisp': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'linksto': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'start': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'stop': []}])",
+ \ 'E928:')
+ call assert_fails("call hlset([{'name': 'NewHLGroup', 'term': 'Cyan'}])",
+ \ 'E715:')
+ call assert_equal('Search', hlget('NewHLGroup')[0].linksto)
+ highlight clear NewHLGroup
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for clearing the 'term', 'cterm' and 'gui' attributes of a highlight
+ " group.
+ let lines =<< trim END
+ highlight myhlg1 term=bold cterm=italic gui=standout
+ VAR id = hlID('myhlg1')
+ call hlset([{'name': 'myhlg1', 'term': {}}])
+ call assert_equal([{'id': id, 'name': 'myhlg1',
+ \ 'cterm': {'italic': v:true}, 'gui': {'standout': v:true}}],
+ \ hlget('myhlg1'))
+ call hlset([{'name': 'myhlg1', 'cterm': {}}])
+ call assert_equal([{'id': id, 'name': 'myhlg1',
+ \ 'gui': {'standout': v:true}}], hlget('myhlg1'))
+ call hlset([{'name': 'myhlg1', 'gui': {}}])
+ call assert_equal([{'id': id, 'name': 'myhlg1', 'cleared': v:true}],
+ \ hlget('myhlg1'))
+ highlight clear myhlg1
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for setting all the 'term', 'cterm' and 'gui' attributes of a
+ " highlight group
+ let lines =<< trim END
+ VAR attr = {'bold': v:true, 'underline': v:true,
+ \ 'undercurl': v:true, 'underdouble': v:true,
+ \ 'underdotted': v:true, 'underdashed': v:true,
+ \ 'strikethrough': v:true, 'reverse': v:true, 'italic': v:true,
+ \ 'standout': v:true, 'nocombine': v:true}
+ call hlset([{'name': 'myhlg2', 'term': attr, 'cterm': attr, 'gui': attr}])
+ VAR id2 = hlID('myhlg2')
+ VAR expected = "myhlg2 xxx term=bold,standout,underline,undercurl,underdouble,underdotted,underdashed,italic,reverse,nocombine,strikethrough cterm=bold,standout,underline,undercurl,underdouble,underdotted,underdashed,italic,reverse,nocombine,strikethrough gui=bold,standout,underline,undercurl,underdouble,underdotted,underdashed,italic,reverse,nocombine,strikethrough"
+ VAR output = execute('highlight myhlg2')
+ LET output = output->split("\n")->join()->substitute('\s\+', ' ', 'g')
+ call assert_equal(expected, output)
+ call assert_equal([{'id': id2, 'name': 'myhlg2', 'gui': attr,
+ \ 'term': attr, 'cterm': attr}], hlget('myhlg2'))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for clearing some of the 'term', 'cterm' and 'gui' attributes of a
+ " highlight group
+ let lines =<< trim END
+ VAR attr = {'bold': v:false, 'underline': v:true, 'strikethrough': v:true}
+ call hlset([{'name': 'myhlg2', 'term': attr, 'cterm': attr, 'gui': attr}])
+ VAR id2 = hlID('myhlg2')
+ VAR expected = "myhlg2 xxx term=underline,strikethrough cterm=underline,strikethrough gui=underline,strikethrough"
+ VAR output = execute('highlight myhlg2')
+ LET output = output->split("\n")->join()->substitute('\s\+', ' ', 'g')
+ call assert_equal(expected, output)
+ LET attr = {'underline': v:true, 'strikethrough': v:true}
+ call assert_equal([{'id': id2, 'name': 'myhlg2', 'gui': attr,
+ \ 'term': attr, 'cterm': attr}], hlget('myhlg2'))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for clearing the attributes and link of a highlight group
+ let lines =<< trim END
+ highlight myhlg3 ctermbg=green guibg=green
+ highlight! default link myhlg3 ErrorMsg
+ VAR id3 = hlID('myhlg3')
+ call hlset([{'name': 'myhlg3', 'cleared': v:true, 'linksto': 'NONE'}])
+ call assert_equal([{'id': id3, 'name': 'myhlg3', 'cleared': v:true}],
+ \ hlget('myhlg3'))
+ highlight clear hlg3
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for setting default attributes for a highlight group
+ let lines =<< trim END
+ call hlset([{'name': 'hlg4', 'ctermfg': '8'}])
+ call hlset([{'name': 'hlg4', 'default': v:true, 'ctermfg': '9'}])
+ VAR id4 = hlID('hlg4')
+ call assert_equal([{'id': id4, 'name': 'hlg4', 'ctermfg': '8'}],
+ \ hlget('hlg4'))
+ highlight clear hlg4
+
+ call hlset([{'name': 'hlg5', 'default': v:true, 'ctermbg': '2'}])
+ call hlset([{'name': 'hlg5', 'ctermbg': '4'}])
+ VAR id5 = hlID('hlg5')
+ call assert_equal([{'id': id5, 'name': 'hlg5', 'ctermbg': '4'}],
+ \ hlget('hlg5'))
+ highlight clear hlg5
+
+ call hlset([{'name': 'hlg6', 'linksto': 'Error'}])
+ VAR id6 = hlID('hlg6')
+ call hlset([{'name': 'hlg6', 'default': v:true, 'ctermbg': '2'}])
+ call assert_equal([{'id': id6, 'name': 'hlg6', 'linksto': 'Error'}],
+ \ hlget('hlg6'))
+ highlight clear hlg6
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for setting default links for a highlight group
+ let lines =<< trim END
+ call hlset([{'name': 'hlg7', 'ctermfg': '5'}])
+ call hlset([{'name': 'hlg7', 'default': v:true, 'linksto': 'Search'}])
+ VAR id7 = hlID('hlg7')
+ call assert_equal([{'id': id7, 'name': 'hlg7', 'ctermfg': '5'}],
+ \ hlget('hlg7'))
+ highlight clear hlg7
+
+ call hlset([{'name': 'hlg8', 'default': v:true, 'linksto': 'Search'}])
+ VAR id8 = hlID('hlg8')
+ call assert_equal([{'id': id8, 'name': 'hlg8', 'default': v:true,
+ \ 'linksto': 'Search'}], hlget('hlg8'))
+ call hlset([{'name': 'hlg8', 'ctermbg': '2'}])
+ call assert_equal([{'id': id8, 'name': 'hlg8', 'ctermbg': '2'}],
+ \ hlget('hlg8'))
+ highlight clear hlg8
+
+ highlight default link hlg9 ErrorMsg
+ VAR hlg_save = hlget('hlg9')
+ LET hlg_save[0]['name'] = 'hlg9dup'
+ call hlset(hlg_save)
+ VAR id9 = hlID('hlg9dup')
+ highlight clear hlg9dup
+ call assert_equal([{'id': id9, 'name': 'hlg9dup', 'default': v:true,
+ \ 'linksto': 'ErrorMsg'}], hlget('hlg9dup'))
+ highlight clear hlg9
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for force creating a link to a highlight group
+ let lines =<< trim END
+ call hlset([{'name': 'hlg10', 'ctermfg': '8'}])
+ call hlset([{'name': 'hlg10', 'linksto': 'Search'}])
+ VAR id10 = hlID('hlg10')
+ call assert_equal([{'id': id10, 'name': 'hlg10', 'ctermfg': '8'}],
+ \ hlget('hlg10'))
+ call hlset([{'name': 'hlg10', 'linksto': 'Search', 'force': v:true}])
+ call assert_equal([{'id': id10, 'name': 'hlg10', 'ctermfg': '8',
+ \ 'linksto': 'Search'}], hlget('hlg10'))
+ highlight clear hlg10
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for empty values of attributes
+ call hlset([{'name': 'hlg11', 'cterm': {}}])
+ call hlset([{'name': 'hlg11', 'ctermfg': ''}])
+ call hlset([{'name': 'hlg11', 'ctermbg': ''}])
+ call hlset([{'name': 'hlg11', 'ctermul': ''}])
+ call hlset([{'name': 'hlg11', 'font': ''}])
+ call hlset([{'name': 'hlg11', 'gui': {}}])
+ call hlset([{'name': 'hlg11', 'guifg': ''}])
+ call hlset([{'name': 'hlg11', 'guibg': ''}])
+ call hlset([{'name': 'hlg11', 'guisp': ''}])
+ call hlset([{'name': 'hlg11', 'start': ''}])
+ call hlset([{'name': 'hlg11', 'stop': ''}])
+ call hlset([{'name': 'hlg11', 'term': {}}])
+ call assert_true(hlget('hlg11')[0].cleared)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_history.vim b/src/testdir/test_history.vim
new file mode 100644
index 0000000..f1c31de
--- /dev/null
+++ b/src/testdir/test_history.vim
@@ -0,0 +1,252 @@
+" Tests for the history functions
+
+source check.vim
+CheckFeature cmdline_hist
+
+set history=7
+
+function History_Tests(hist)
+ " First clear the history
+ call histadd(a:hist, 'dummy')
+ call assert_true(histdel(a:hist))
+ call assert_equal(-1, histnr(a:hist))
+ call assert_equal('', histget(a:hist))
+
+ call assert_true('ls'->histadd(a:hist))
+ call assert_true(histadd(a:hist, 'buffers'))
+ call assert_equal('buffers', histget(a:hist))
+ call assert_equal('ls', histget(a:hist, -2))
+ call assert_equal('ls', histget(a:hist, 1))
+ call assert_equal('', histget(a:hist, 5))
+ call assert_equal('', histget(a:hist, -5))
+ call assert_equal(2, histnr(a:hist))
+ call assert_true(histdel(a:hist, 2))
+ call assert_false(a:hist->histdel(7))
+ call assert_equal(1, histnr(a:hist))
+ call assert_equal('ls', histget(a:hist, -1))
+
+ call assert_true(histadd(a:hist, 'buffers'))
+ call assert_true(histadd(a:hist, 'ls'))
+ call assert_equal('ls', a:hist->histget(-1))
+ call assert_equal(4, a:hist->histnr())
+
+ let a=execute('history ' . a:hist)
+ call assert_match("^\n # \\S* history\n 3 buffers\n> 4 ls$", a)
+ let a=execute('history all')
+ call assert_match("^\n # .* history\n 3 buffers\n> 4 ls", a)
+
+ if len(a:hist) > 0
+ let a=execute('history ' . a:hist . ' 2')
+ call assert_match("^\n # \\S* history$", a)
+ let a=execute('history ' . a:hist . ' 3')
+ call assert_match("^\n # \\S* history\n 3 buffers$", a)
+ let a=execute('history ' . a:hist . ' 4')
+ call assert_match("^\n # \\S* history\n> 4 ls$", a)
+ let a=execute('history ' . a:hist . ' 3,4')
+ call assert_match("^\n # \\S* history\n 3 buffers\n> 4 ls$", a)
+ let a=execute('history ' . a:hist . ' -1')
+ call assert_match("^\n # \\S* history\n> 4 ls$", a)
+ let a=execute('history ' . a:hist . ' -2')
+ call assert_match("^\n # \\S* history\n 3 buffers$", a)
+ let a=execute('history ' . a:hist . ' -2,')
+ call assert_match("^\n # \\S* history\n 3 buffers\n> 4 ls$", a)
+ let a=execute('history ' . a:hist . ' -3')
+ call assert_match("^\n # \\S* history$", a)
+ endif
+
+ " Test for removing entries matching a pattern
+ for i in range(1, 3)
+ call histadd(a:hist, 'text_' . i)
+ endfor
+ call assert_true(histdel(a:hist, 'text_\d\+'))
+ call assert_equal('ls', histget(a:hist, -1))
+
+ " Test for freeing the entire history list
+ for i in range(1, 7)
+ call histadd(a:hist, 'text_' . i)
+ endfor
+ call histdel(a:hist)
+ for i in range(1, 7)
+ call assert_equal('', histget(a:hist, i))
+ call assert_equal('', histget(a:hist, i - 7 - 1))
+ endfor
+
+ " Test for freeing an entry at the beginning of the history list
+ for i in range(1, 4)
+ call histadd(a:hist, 'text_' . i)
+ endfor
+ call histdel(a:hist, 1)
+ call assert_equal('', histget(a:hist, 1))
+ call assert_equal('text_4', histget(a:hist, 4))
+endfunction
+
+function Test_History()
+ for h in ['cmd', ':', '', 'search', '/', '?', 'expr', '=', 'input', '@', 'debug', '>']
+ call History_Tests(h)
+ endfor
+
+ " Negative tests
+ call assert_false(histdel('abc'))
+ call assert_equal('', histget('abc'))
+ call assert_fails('call histdel([])', 'E730:')
+ call assert_equal('', histget(10))
+ call assert_fails('call histget([])', 'E730:')
+ call assert_equal(-1, histnr('abc'))
+ call assert_fails('call histnr([])', 'E730:')
+ call assert_fails('history xyz', 'E488:')
+ call assert_fails('history ,abc', 'E488:')
+ call assert_fails('call histdel(":", "\\%(")', 'E53:')
+endfunction
+
+function Test_history_truncates_long_entry()
+ " History entry short enough to fit on the screen should not be truncated.
+ call histadd(':', 'echo x' .. repeat('y', &columns - 17) .. 'z')
+ let a = execute('history : -1')
+
+ call assert_match("^\n # cmd history\n"
+ \ .. "> *\\d\\+ echo x" .. repeat('y', &columns - 17) .. 'z$', a)
+
+ " Long history entry should be truncated to fit on the screen, with, '...'
+ " inserted in the string to indicate the that there is truncation.
+ call histadd(':', 'echo x' .. repeat('y', &columns - 16) .. 'z')
+ let a = execute('history : -1')
+ call assert_match("^\n # cmd history\n"
+ \ .. "> *\\d\\+ echo xy\\+\.\.\.y\\+z$", a)
+endfunction
+
+function Test_Search_history_window()
+ new
+ call setline(1, ['a', 'b', 'a', 'b'])
+ 1
+ call feedkeys("/a\<CR>", 'xt')
+ call assert_equal('a', getline('.'))
+ 1
+ call feedkeys("/b\<CR>", 'xt')
+ call assert_equal('b', getline('.'))
+ 1
+ " select the previous /a command
+ call feedkeys("q/kk\<CR>", 'x!')
+ call assert_equal('a', getline('.'))
+ call assert_equal('a', @/)
+ bwipe!
+endfunc
+
+" Test for :history command option completion
+function Test_history_completion()
+ call feedkeys(":history \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"history / : = > ? @ all cmd debug expr input search', @:)
+endfunc
+
+" Test for increasing the 'history' option value
+func Test_history_size()
+ let save_histsz = &history
+ set history=10
+ call histadd(':', 'ls')
+ call histdel(':')
+ for i in range(1, 5)
+ call histadd(':', 'cmd' .. i)
+ endfor
+ call assert_equal(5, histnr(':'))
+ call assert_equal('cmd5', histget(':', -1))
+
+ set history=15
+ for i in range(6, 10)
+ call histadd(':', 'cmd' .. i)
+ endfor
+ call assert_equal(10, histnr(':'))
+ call assert_equal('cmd1', histget(':', 1))
+ call assert_equal('cmd10', histget(':', -1))
+
+ set history=5
+ call histadd(':', 'abc')
+ call assert_equal('', histget(':', 6))
+ call assert_equal('', histget(':', 12))
+ call assert_equal('cmd7', histget(':', 7))
+ call assert_equal('abc', histget(':', -1))
+
+ " This test works only when the language is English
+ if v:lang == "C" || v:lang =~ '^[Ee]n'
+ set history=0
+ redir => v
+ call feedkeys(":history\<CR>", 'xt')
+ redir END
+ call assert_equal(["'history' option is zero"], split(v, "\n"))
+ endif
+
+ let &history=save_histsz
+endfunc
+
+" Test for recalling old search patterns in /
+func Test_history_search()
+ call histdel('/')
+ let g:pat = []
+ func SavePat()
+ call add(g:pat, getcmdline())
+ return ''
+ endfunc
+ cnoremap <F2> <C-\>eSavePat()<CR>
+ call histadd('/', 'pat1')
+ call histadd('/', 'pat2')
+ let @/ = ''
+ call feedkeys("/\<Up>\<F2>\<Up>\<F2>\<Down>\<Down>\<F2>\<Esc>", 'xt')
+ call assert_equal(['pat2', 'pat1', ''], g:pat)
+ cunmap <F2>
+ delfunc SavePat
+
+ " Search for a pattern that is not present in the history
+ call assert_beeps('call feedkeys("/a1b2\<Up>\<CR>", "xt")')
+
+ " Recall patterns with 'history' set to 0
+ set history=0
+ let @/ = 'abc'
+ let cmd = 'call feedkeys("/\<Up>\<Down>\<S-Up>\<S-Down>\<CR>", "xt")'
+ call assert_fails(cmd, 'E486:')
+ set history&
+
+ " Recall patterns till the end of history
+ set history=4
+ call histadd('/', 'pat')
+ call histdel('/')
+ call histadd('/', 'pat1')
+ call histadd('/', 'pat2')
+ call assert_beeps('call feedkeys("/\<Up>\<Up>\<Up>\<C-U>\<cr>", "xt")')
+ call assert_beeps('call feedkeys("/\<Down><cr>", "xt")')
+
+ " Test for wrapping around the history list
+ for i in range(3, 7)
+ call histadd('/', 'pat' .. i)
+ endfor
+ let upcmd = "\<up>\<up>\<up>\<up>\<up>"
+ let downcmd = "\<down>\<down>\<down>\<down>\<down>"
+ try
+ call feedkeys("/" .. upcmd .. "\<cr>", 'xt')
+ catch /E486:/
+ endtry
+ call assert_equal('pat4', @/)
+ try
+ call feedkeys("/" .. upcmd .. downcmd .. "\<cr>", 'xt')
+ catch /E486:/
+ endtry
+ call assert_equal('pat4', @/)
+
+ " Test for changing the search command separator in the history
+ call assert_fails('call feedkeys("/def/\<cr>", "xt")', 'E486:')
+ call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:')
+ call assert_equal('def?', histget('/', -1))
+
+ call assert_fails('call feedkeys("/ghi?\<cr>", "xt")', 'E486:')
+ call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:')
+ call assert_equal('ghi\?', histget('/', -1))
+
+ set history&
+endfunc
+
+" Test for making sure the key value is not stored in history
+func Test_history_crypt_key()
+ CheckFeature cryptv
+ call feedkeys(":set bs=2 key=abc ts=8\<CR>", 'xt')
+ call assert_equal('set bs=2 key= ts=8', histget(':'))
+ set key& bs& ts&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_hlsearch.vim b/src/testdir/test_hlsearch.vim
new file mode 100644
index 0000000..476c075
--- /dev/null
+++ b/src/testdir/test_hlsearch.vim
@@ -0,0 +1,94 @@
+" Test for v:hlsearch
+
+source check.vim
+source screendump.vim
+
+func Test_hlsearch()
+ new
+ call setline(1, repeat(['aaa'], 10))
+ set hlsearch nolazyredraw
+ " redraw is needed to make hlsearch highlight the matches
+ exe "normal! /aaa\<CR>" | redraw
+ let r1 = screenattr(1, 1)
+ nohlsearch | redraw
+ call assert_notequal(r1, screenattr(1,1))
+ let v:hlsearch=1 | redraw
+ call assert_equal(r1, screenattr(1,1))
+ let v:hlsearch=0 | redraw
+ call assert_notequal(r1, screenattr(1,1))
+ set hlsearch | redraw
+ call assert_equal(r1, screenattr(1,1))
+ let v:hlsearch=0 | redraw
+ call assert_notequal(r1, screenattr(1,1))
+ exe "normal! n" | redraw
+ call assert_equal(r1, screenattr(1,1))
+ let v:hlsearch=0 | redraw
+ call assert_notequal(r1, screenattr(1,1))
+ exe "normal! /\<CR>" | redraw
+ call assert_equal(r1, screenattr(1,1))
+ set nohls
+ exe "normal! /\<CR>" | redraw
+ call assert_notequal(r1, screenattr(1,1))
+ call assert_fails('let v:hlsearch=[]', 'E745:')
+ call garbagecollect(1)
+ call getchar(1)
+ enew!
+endfunc
+
+func Test_hlsearch_hangs()
+ CheckFunction reltimefloat
+
+ " So, it turns out that Windows 7 implements TimerQueue timers differently
+ " and they can expire *before* the requested time has elapsed. So allow for
+ " the timeout occurring after 80 ms (5 * 16 (the typical clock tick)).
+ if has("win32")
+ let min_timeout = 0.08
+ else
+ let min_timeout = 0.1
+ endif
+
+ " This pattern takes a long time to match, it should timeout.
+ new
+ call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
+ let start = reltime()
+ set hlsearch nolazyredraw redrawtime=101
+ let @/ = '\%#=1a*.*X\@<=b*'
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_inrange(min_timeout, 1.0, elapsed)
+ set nohlsearch redrawtime&
+ bwipe!
+endfunc
+
+func Test_hlsearch_eol_highlight()
+ new
+ call append(1, repeat([''], 9))
+ set hlsearch nolazyredraw
+ exe "normal! /$\<CR>" | redraw
+ let attr = screenattr(1, 1)
+ for row in range(2, 10)
+ call assert_equal(attr, screenattr(row, 1), 'in line ' . row)
+ endfor
+ set nohlsearch
+ bwipe!
+endfunc
+
+func Test_hlsearch_Ctrl_R()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set incsearch hlsearch
+ let @" = "text"
+ put
+ END
+ call writefile(lines, 'XhlsearchCtrlR', 'D')
+ let buf = RunVimInTerminal('-S XhlsearchCtrlR', #{rows: 6, cols: 60})
+
+ call term_sendkeys(buf, "/\<C-R>\<C-R>\"")
+ call VerifyScreenDump(buf, 'Test_hlsearch_ctrlr_1', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_iminsert.vim b/src/testdir/test_iminsert.vim
new file mode 100644
index 0000000..11fe24e
--- /dev/null
+++ b/src/testdir/test_iminsert.vim
@@ -0,0 +1,326 @@
+" Test for 'iminsert'
+
+source view_util.vim
+source check.vim
+import './vim9.vim' as v9
+
+let s:imactivatefunc_called = 0
+let s:imstatusfunc_called = 0
+let s:imstatus_active = 0
+
+func IM_activatefunc(active)
+ let s:imactivatefunc_called = 1
+ let s:imstatus_active = a:active
+endfunc
+
+func IM_statusfunc()
+ let s:imstatusfunc_called = 1
+ return s:imstatus_active
+endfunc
+
+func Test_iminsert2()
+ let s:imactivatefunc_called = 0
+ let s:imstatusfunc_called = 0
+
+ set imactivatefunc=IM_activatefunc
+ set imstatusfunc=IM_statusfunc
+ set iminsert=2
+ normal! i
+ set iminsert=0
+ set imactivatefunc=
+ set imstatusfunc=
+
+ let expected = (has('win32') && has('gui_running')) ? 0 : 1
+ call assert_equal(expected, s:imactivatefunc_called)
+ call assert_equal(expected, s:imstatusfunc_called)
+endfunc
+
+func Test_getimstatus()
+ if has('win32')
+ CheckFeature multi_byte_ime
+ else
+ CheckFeature xim
+ endif
+ if has('win32') && has('gui_running')
+ set imactivatefunc=
+ set imstatusfunc=
+ else
+ set imactivatefunc=IM_activatefunc
+ set imstatusfunc=IM_statusfunc
+ let s:imstatus_active = 0
+ endif
+
+ new
+ set iminsert=2
+ call feedkeys("i\<C-R>=getimstatus()\<CR>\<ESC>", 'nx')
+ call assert_equal('1', getline(1))
+ set iminsert=0
+ call feedkeys("o\<C-R>=getimstatus()\<CR>\<ESC>", 'nx')
+ call assert_equal('0', getline(2))
+ bw!
+
+ set imactivatefunc=
+ set imstatusfunc=
+endfunc
+
+func Test_imactivatefunc_imstatusfunc_callback_no_breaks_foldopen()
+ CheckScreendump
+
+ let lines =<< trim END
+ func IM_activatefunc(active)
+ endfunc
+ func IM_statusfunc()
+ return 0
+ endfunc
+ set imactivatefunc=IM_activatefunc
+ set imstatusfunc=IM_statusfunc
+ set foldmethod=marker
+ set foldopen=search
+ call setline(1, ['{{{', 'abc', '}}}'])
+ %foldclose
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {})
+ call assert_notequal('abc', term_getline(buf, 2))
+ call term_sendkeys(buf, "/abc\n")
+ call WaitForAssert({-> assert_equal('abc', term_getline(buf, 2))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for using an lmap in insert mode
+func Test_lmap_in_insert_mode()
+ new
+ call setline(1, 'abc')
+ lmap { w
+ set iminsert=1
+ call feedkeys('r{', 'xt')
+ call assert_equal('wbc', getline(1))
+ set iminsert=2
+ call feedkeys('$r{', 'xt')
+ call assert_equal('wb{', getline(1))
+ call setline(1, 'vim web')
+ set iminsert=1
+ call feedkeys('0f{', 'xt')
+ call assert_equal(5, col('.'))
+ set iminsert&
+ lunmap {
+ close!
+endfunc
+
+" Test for using CTRL-^ to toggle iminsert in insert mode
+func Test_iminsert_toggle()
+ CheckGui
+ if has('win32')
+ CheckFeature multi_byte_ime
+ else
+ CheckFeature xim
+ endif
+ if has('gui_running') && !has('win32')
+ throw 'Skipped: works only in Win32 GUI version (for some reason)'
+ endif
+ new
+ let save_imdisable = &imdisable
+ let save_iminsert = &iminsert
+ set noimdisable
+ set iminsert=0
+ exe "normal i\<C-^>"
+ call assert_equal(2, &iminsert)
+ exe "normal i\<C-^>"
+ call assert_equal(0, &iminsert)
+ let &iminsert = save_iminsert
+ let &imdisable = save_imdisable
+ close!
+endfunc
+
+" Test for different ways of setting the 'imactivatefunc' and 'imstatusfunc'
+" options
+func Test_imactivatefunc_imstatusfunc_callback()
+ CheckNotMSWindows
+ func IMactivatefunc1(active)
+ let g:IMactivatefunc_called += 1
+ endfunc
+ func IMstatusfunc1()
+ let g:IMstatusfunc_called += 1
+ return 1
+ endfunc
+ set iminsert=2
+
+ let lines =<< trim END
+ LET g:IMactivatefunc_called = 0
+ LET g:IMstatusfunc_called = 0
+
+ #" Test for using a function name
+ LET &imactivatefunc = 'g:IMactivatefunc1'
+ LET &imstatusfunc = 'g:IMstatusfunc1'
+ normal! i
+
+ #" Test for using a function()
+ set imactivatefunc=function('g:IMactivatefunc1')
+ set imstatusfunc=function('g:IMstatusfunc1')
+ normal! i
+
+ #" Using a funcref variable to set 'completefunc'
+ VAR Fn1 = function('g:IMactivatefunc1')
+ LET &imactivatefunc = Fn1
+ VAR Fn2 = function('g:IMstatusfunc1')
+ LET &imstatusfunc = Fn2
+ normal! i
+
+ #" Using a string(funcref variable) to set 'completefunc'
+ LET &imactivatefunc = string(Fn1)
+ LET &imstatusfunc = string(Fn2)
+ normal! i
+
+ #" Test for using a funcref()
+ set imactivatefunc=funcref('g:IMactivatefunc1')
+ set imstatusfunc=funcref('g:IMstatusfunc1')
+ normal! i
+
+ #" Using a funcref variable to set 'imactivatefunc'
+ LET Fn1 = funcref('g:IMactivatefunc1')
+ LET &imactivatefunc = Fn1
+ LET Fn2 = funcref('g:IMstatusfunc1')
+ LET &imstatusfunc = Fn2
+ normal! i
+
+ #" Using a string(funcref variable) to set 'imactivatefunc'
+ LET &imactivatefunc = string(Fn1)
+ LET &imstatusfunc = string(Fn2)
+ normal! i
+
+ #" Test for using a lambda function
+ VAR optval = "LSTART a LMIDDLE g:IMactivatefunc1(a) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set imactivatefunc=" .. optval
+ LET optval = "LSTART LMIDDLE g:IMstatusfunc1() LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set imstatusfunc=" .. optval
+ normal! i
+
+ #" Set 'imactivatefunc' and 'imstatusfunc' to a lambda expression
+ LET &imactivatefunc = LSTART a LMIDDLE g:IMactivatefunc1(a) LEND
+ LET &imstatusfunc = LSTART LMIDDLE g:IMstatusfunc1() LEND
+ normal! i
+
+ #" Set 'imactivatefunc' and 'imstatusfunc' to a string(lambda expression)
+ LET &imactivatefunc = 'LSTART a LMIDDLE g:IMactivatefunc1(a) LEND'
+ LET &imstatusfunc = 'LSTART LMIDDLE g:IMstatusfunc1() LEND'
+ normal! i
+
+ #" Set 'imactivatefunc' 'imstatusfunc' to a variable with a lambda
+ #" expression
+ VAR Lambda1 = LSTART a LMIDDLE g:IMactivatefunc1(a) LEND
+ VAR Lambda2 = LSTART LMIDDLE g:IMstatusfunc1() LEND
+ LET &imactivatefunc = Lambda1
+ LET &imstatusfunc = Lambda2
+ normal! i
+
+ #" Set 'imactivatefunc' 'imstatusfunc' to a string(variable with a lambda
+ #" expression)
+ LET &imactivatefunc = string(Lambda1)
+ LET &imstatusfunc = string(Lambda2)
+ normal! i
+
+ #" Test for clearing the 'completefunc' option
+ set imactivatefunc='' imstatusfunc=''
+ set imactivatefunc& imstatusfunc&
+
+ set imactivatefunc=g:IMactivatefunc1
+ set imstatusfunc=g:IMstatusfunc1
+ call assert_fails("set imactivatefunc=function('abc')", "E700:")
+ call assert_fails("set imstatusfunc=function('abc')", "E700:")
+ call assert_fails("set imactivatefunc=funcref('abc')", "E700:")
+ call assert_fails("set imstatusfunc=funcref('abc')", "E700:")
+ call assert_fails("LET &imstatusfunc = function('abc')", "E700:")
+ call assert_fails("LET &imactivatefunc = function('abc')", "E700:")
+ normal! i
+
+ #" set 'imactivatefunc' and 'imstatusfunc' to a non-existing function
+ set imactivatefunc=IMactivatefunc1
+ set imstatusfunc=IMstatusfunc1
+ call assert_fails("set imactivatefunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("set imstatusfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &imactivatefunc = function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &imstatusfunc = function('NonExistingFunc')", 'E700:')
+ normal! i
+
+ call assert_equal(14, g:IMactivatefunc_called)
+ call assert_equal(28, g:IMstatusfunc_called)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Using Vim9 lambda expression in legacy context should fail
+ set imactivatefunc=(a)\ =>\ IMactivatefunc1(a)
+ set imstatusfunc=IMstatusfunc1
+ call assert_fails('normal! i', 'E117:')
+ set imactivatefunc=IMactivatefunc1
+ set imstatusfunc=()\ =>\ IMstatusfunc1(a)
+ call assert_fails('normal! i', 'E117:')
+
+ " set 'imactivatefunc' and 'imstatusfunc' to a partial with dict. This used
+ " to cause a crash.
+ func SetIMFunc()
+ let params1 = {'activate': function('g:DictActivateFunc')}
+ let params2 = {'status': function('g:DictStatusFunc')}
+ let &imactivatefunc = params1.activate
+ let &imstatusfunc = params2.status
+ endfunc
+ func g:DictActivateFunc(_) dict
+ endfunc
+ func g:DictStatusFunc(_) dict
+ endfunc
+ call SetIMFunc()
+ new
+ call SetIMFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set imactivatefunc=
+ set imstatusfunc=
+ wincmd w
+ set imactivatefunc=
+ set imstatusfunc=
+ :%bw!
+ delfunc g:DictActivateFunc
+ delfunc g:DictStatusFunc
+ delfunc SetIMFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ # Test for using function()
+ def IMactivatefunc1(active: number): any
+ g:IMactivatefunc_called += 1
+ return 1
+ enddef
+ def IMstatusfunc1(): number
+ g:IMstatusfunc_called += 1
+ return 1
+ enddef
+ g:IMactivatefunc_called = 0
+ g:IMstatusfunc_called = 0
+ set iminsert=2
+ set imactivatefunc=function('IMactivatefunc1')
+ set imstatusfunc=function('IMstatusfunc1')
+ normal! i
+
+ set iminsert=0
+ set imactivatefunc=
+ set imstatusfunc=
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ " cleanup
+ set iminsert=0
+ set imactivatefunc&
+ set imstatusfunc&
+ delfunc IMactivatefunc1
+ delfunc IMstatusfunc1
+ unlet g:IMactivatefunc_called g:IMstatusfunc_called
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_increment.vim b/src/testdir/test_increment.vim
new file mode 100644
index 0000000..c367f2b
--- /dev/null
+++ b/src/testdir/test_increment.vim
@@ -0,0 +1,896 @@
+" Tests for using Ctrl-A/Ctrl-X
+
+func SetUp()
+ new dummy
+ set nrformats&vim
+endfunc
+
+func TearDown()
+ bwipe!
+endfunc
+
+" 1) Ctrl-A on visually selected number
+" Text:
+" foobar-10
+" Expected:
+" 1) Ctrl-A on start of line:
+" foobar-9
+" 2) Ctrl-A on visually selected "-10":
+" foobar-9
+" 3) Ctrl-A on visually selected "10":
+" foobar-11
+" 4) Ctrl-X on visually selected "-10"
+" foobar-11
+" 5) Ctrl-X on visually selected "10"
+" foobar-9
+func Test_visual_increment_01()
+ call setline(1, repeat(["foobaar-10"], 5))
+
+ call cursor(1, 1)
+ exec "norm! \<C-A>"
+ call assert_equal("foobaar-9", getline('.'))
+ call assert_equal([0, 1, 9, 0], getpos('.'))
+
+ call cursor(2, 1)
+ exec "norm! f-v$\<C-A>"
+ call assert_equal("foobaar-9", getline('.'))
+ call assert_equal([0, 2, 8, 0], getpos('.'))
+
+ call cursor(3, 1)
+ exec "norm! f1v$\<C-A>"
+ call assert_equal("foobaar-11", getline('.'))
+ call assert_equal([0, 3, 9, 0], getpos('.'))
+
+ call cursor(4, 1)
+ exec "norm! f-v$\<C-X>"
+ call assert_equal("foobaar-11", getline('.'))
+ call assert_equal([0, 4, 8, 0], getpos('.'))
+
+ call cursor(5, 1)
+ exec "norm! f1v$\<C-X>"
+ call assert_equal("foobaar-9", getline('.'))
+ call assert_equal([0, 5, 9, 0], getpos('.'))
+endfunc
+
+" 2) Ctrl-A on visually selected lines
+" Text:
+" 10
+" 20
+" 30
+" 40
+"
+" Expected:
+" 1) Ctrl-A on visually selected lines:
+" 11
+" 21
+" 31
+" 41
+"
+" 2) Ctrl-X on visually selected lines:
+" 9
+" 19
+" 29
+" 39
+func Test_visual_increment_02()
+ call setline(1, ["10", "20", "30", "40"])
+ exec "norm! GV3k$\<C-A>"
+ call assert_equal(["11", "21", "31", "41"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ call setline(1, ["10", "20", "30", "40"])
+ exec "norm! GV3k$\<C-X>"
+ call assert_equal(["9", "19", "29", "39"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 3) g Ctrl-A on visually selected lines, with non-numbers in between
+" Text:
+" 10
+"
+" 20
+"
+" 30
+"
+" 40
+"
+" Expected:
+" 1) 2 g Ctrl-A on visually selected lines:
+" 12
+"
+" 24
+"
+" 36
+"
+" 48
+" 2) 2 g Ctrl-X on visually selected lines
+" 8
+"
+" 16
+"
+" 24
+"
+" 32
+func Test_visual_increment_03()
+ call setline(1, ["10", "", "20", "", "30", "", "40"])
+ exec "norm! GV6k2g\<C-A>"
+ call assert_equal(["12", "", "24", "", "36", "", "48"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ call setline(1, ["10", "", "20", "", "30", "", "40"])
+ exec "norm! GV6k2g\<C-X>"
+ call assert_equal(["8", "", "16", "", "24", "", "32"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 4) Ctrl-A on non-number
+" Text:
+" foobar-10
+" Expected:
+" 1) visually select foobar:
+" foobar-10
+func Test_visual_increment_04()
+ call setline(1, ["foobar-10"])
+ exec "norm! vf-\<C-A>"
+ call assert_equal(["foobar-10"], getline(1, '$'))
+ " NOTE: I think this is correct behavior...
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 5) g<Ctrl-A> on letter
+" Test:
+" a
+" a
+" a
+" a
+" Expected:
+" 1) g Ctrl-A on visually selected lines
+" b
+" c
+" d
+" e
+func Test_visual_increment_05()
+ set nrformats+=alpha
+ call setline(1, repeat(["a"], 4))
+ exec "norm! GV3kg\<C-A>"
+ call assert_equal(["b", "c", "d", "e"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 6) g<Ctrl-A> on letter
+" Test:
+" z
+" z
+" z
+" z
+" Expected:
+" 1) g Ctrl-X on visually selected lines
+" y
+" x
+" w
+" v
+func Test_visual_increment_06()
+ set nrformats+=alpha
+ call setline(1, repeat(["z"], 4))
+ exec "norm! GV3kg\<C-X>"
+ call assert_equal(["y", "x", "w", "v"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 7) <Ctrl-A> on letter
+" Test:
+" 2
+" 1
+" 0
+" -1
+" -2
+"
+" Expected:
+" 1) Ctrl-A on visually selected lines
+" 3
+" 2
+" 1
+" 0
+" -1
+"
+" 2) Ctrl-X on visually selected lines
+" 1
+" 0
+" -1
+" -2
+" -3
+func Test_visual_increment_07()
+ call setline(1, ["2", "1", "0", "-1", "-2"])
+ exec "norm! GV4k\<C-A>"
+ call assert_equal(["3", "2", "1", "0", "-1"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ call setline(1, ["2", "1", "0", "-1", "-2"])
+ exec "norm! GV4k\<C-X>"
+ call assert_equal(["1", "0", "-1", "-2", "-3"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 8) Block increment on 0x9
+" Text:
+" 0x9
+" 0x9
+" Expected:
+" 1) Ctrl-A on visually block selected region (cursor at beginning):
+" 0xa
+" 0xa
+" 2) Ctrl-A on visually block selected region (cursor at end)
+" 0xa
+" 0xa
+func Test_visual_increment_08()
+ call setline(1, repeat(["0x9"], 2))
+ exec "norm! \<C-V>j$\<C-A>"
+ call assert_equal(["0xa", "0xa"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ call setline(1, repeat(["0x9"], 2))
+ exec "norm! gg$\<C-V>+\<C-A>"
+ call assert_equal(["0xa", "0xa"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 9) Increment and redo
+" Text:
+" 2
+" 2
+"
+" 3
+" 3
+"
+" Expected:
+" 1) 2 Ctrl-A on first 2 visually selected lines
+" 4
+" 4
+" 2) redo (.) on 3
+" 5
+" 5
+func Test_visual_increment_09()
+ call setline(1, ["2", "2", "", "3", "3", ""])
+ exec "norm! ggVj2\<C-A>"
+ call assert_equal(["4", "4", "", "3", "3", ""], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ exec "norm! 3j."
+ call assert_equal(["4", "4", "", "5", "5", ""], getline(1, '$'))
+ call assert_equal([0, 4, 1, 0], getpos('.'))
+endfunc
+
+" 10) sequentially decrement 1
+" Text:
+" 1
+" 1
+" 1
+" 1
+" Expected:
+" 1) g Ctrl-X on visually selected lines
+" 0
+" -1
+" -2
+" -3
+func Test_visual_increment_10()
+ call setline(1, repeat(["1"], 4))
+ exec "norm! GV3kg\<C-X>"
+ call assert_equal(["0", "-1", "-2", "-3"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 11) visually block selected indented lines
+" Text:
+" 1
+" 1
+" 1
+" 1
+" Expected:
+" 1) g Ctrl-A on block selected indented lines
+" 2
+" 1
+" 3
+" 4
+func Test_visual_increment_11()
+ call setline(1, [" 1", "1", " 1", " 1"])
+ exec "norm! f1\<C-V>3jg\<C-A>"
+ call assert_equal([" 2", "1", " 3", " 4"], getline(1, '$'))
+ call assert_equal([0, 1, 5, 0], getpos('.'))
+endfunc
+
+" 12) visually selected several columns
+" Text:
+" 0 0
+" 0 0
+" 0 0
+" Expected:
+" 1) 'v' select last zero and first zeroes
+" 0 1
+" 1 0
+" 1 0
+func Test_visual_increment_12()
+ call setline(1, repeat(["0 0"], 3))
+ exec "norm! $v++\<C-A>"
+ call assert_equal(["0 1", "1 0", "1 0"], getline(1, '$'))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+endfunc
+
+" 13) visually selected part of columns
+" Text:
+" max: 100px
+" max: 200px
+" max: 300px
+" max: 400px
+" Expected:
+" 1) 'v' on first two numbers Ctrl-A
+" max: 110px
+" max: 220px
+" max: 330px
+" max: 400px
+" 2) 'v' on first two numbers Ctrl-X
+" max: 90px
+" max: 190px
+" max: 290px
+" max: 400px
+func Test_visual_increment_13()
+ call setline(1, ["max: 100px", "max: 200px", "max: 300px", "max: 400px"])
+ exec "norm! f1\<C-V>l2j\<C-A>"
+ call assert_equal(["max: 110px", "max: 210px", "max: 310px", "max: 400px"], getline(1, '$'))
+ call assert_equal([0, 1, 6, 0], getpos('.'))
+
+ call setline(1, ["max: 100px", "max: 200px", "max: 300px", "max: 400px"])
+ exec "norm! ggf1\<C-V>l2j\<C-X>"
+ call assert_equal(["max: 90px", "max: 190px", "max: 290px", "max: 400px"], getline(1, '$'))
+ call assert_equal([0, 1, 6, 0], getpos('.'))
+endfunc
+
+" 14) redo in block mode
+" Text:
+" 1 1
+" 1 1
+" Expected:
+" 1) Ctrl-a on first column, redo on second column
+" 2 2
+" 2 2
+func Test_visual_increment_14()
+ call setline(1, repeat(["1 1"], 2))
+ exec "norm! G\<C-V>k\<C-A>w."
+ call assert_equal(["2 2", "2 2"], getline(1, '$'))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+endfunc
+
+" 15) block select single numbers
+" Text:
+" 101
+" Expected:
+" 1) Ctrl-a on visually selected zero
+" 111
+"
+" Also: 019 with "01" selected increments to "029".
+func Test_visual_increment_15()
+ call setline(1, ["101"])
+ exec "norm! lv\<C-A>"
+ call assert_equal(["111"], getline(1, '$'))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["019"])
+ exec "norm! 0vl\<C-A>"
+ call assert_equal("029", getline(1))
+
+ call setline(1, ["01239"])
+ exec "norm! 0vlll\<C-A>"
+ call assert_equal("01249", getline(1))
+
+ call setline(1, ["01299"])
+ exec "norm! 0vlll\<C-A>"
+ call assert_equal("1309", getline(1))
+endfunc
+
+" 16) increment right aligned numbers
+" Text:
+" 1
+" 19
+" 119
+" Expected:
+" 1) Ctrl-a on line selected region
+" 2
+" 20
+" 120
+func Test_visual_increment_16()
+ call setline(1, [" 1", " 19", " 119"])
+ exec "norm! VG\<C-A>"
+ call assert_equal([" 2", " 20", " 120"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 17) block-wise increment and redo
+" Text:
+" 100
+" 1
+"
+" 100
+" 1
+"
+" Expected:
+" 1) Ctrl-V j $ on first block, afterwards '.' on second
+" 101
+" 2
+"
+" 101
+" 2
+func Test_visual_increment_17()
+ call setline(1, [" 100", " 1", "", " 100", " 1"])
+ exec "norm! \<C-V>j$\<C-A>2j."
+ call assert_equal([" 101", " 2", "", " 101", " 1"], getline(1, '$'))
+ call assert_equal([0, 3, 1, 0], getpos('.'))
+endfunc
+
+" 18) repeat of g<Ctrl-a>
+" Text:
+" 0
+" 0
+" 0
+" 0
+"
+" Expected:
+" 1) V 4j g<ctrl-a>, repeat twice afterwards with .
+" 3
+" 6
+" 9
+" 12
+func Test_visual_increment_18()
+ call setline(1, repeat(["0"], 4))
+ exec "norm! GV3kg\<C-A>"
+ exec "norm! .."
+ call assert_equal(["3", "6", "9", "12"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 19) increment on number with nrformat including alpha
+" Text:
+" 1
+" 1a
+"
+" Expected:
+" 1) <Ctrl-V>j$ <ctrl-a>
+" 2
+" 2a
+func Test_visual_increment_19()
+ set nrformats+=alpha
+ call setline(1, ["1", "1a"])
+ exec "norm! \<C-V>G$\<C-A>"
+ call assert_equal(["2", "2a"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 20) increment a single letter
+" Text:
+" a
+"
+" Expected:
+" 1) <Ctrl-a> and cursor is on a
+" b
+func Test_visual_increment_20()
+ set nrformats+=alpha
+ call setline(1, ["a"])
+ exec "norm! \<C-A>"
+ call assert_equal(["b"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ " decrement a and A and increment z and Z
+ call setline(1, ['a', 'A', 'z', 'Z'])
+ exe "normal 1G\<C-X>2G\<C-X>3G\<C-A>4G\<C-A>"
+ call assert_equal(['a', 'A', 'z', 'Z'], getline(1, '$'))
+endfunc
+
+" 21) block-wise increment on part of hexadecimal
+" Text:
+" 0x123456
+"
+" Expected:
+" 1) Ctrl-V f3 <ctrl-a>
+" 0x124456
+func Test_visual_increment_21()
+ call setline(1, ["0x123456"])
+ exec "norm! \<C-V>f3\<C-A>"
+ call assert_equal(["0x124456"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 22) Block increment on 0b0
+" Text:
+" 0b1
+" 0b1
+" Expected:
+" 1) Ctrl-A on visually block selected region (cursor at beginning):
+" 0b10
+" 0b10
+" 2) Ctrl-A on visually block selected region (cursor at end)
+" 0b10
+" 0b10
+func Test_visual_increment_22()
+ call setline(1, repeat(["0b1"], 2))
+ exec "norm! \<C-V>j$\<C-A>"
+ call assert_equal(repeat(["0b10"], 2), getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ call setline(1, repeat(["0b1"], 2))
+ exec "norm! $\<C-V>+\<C-A>"
+ call assert_equal(repeat(["0b10"], 2), getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 23) block-wise increment on part of binary
+" Text:
+" 0b1001
+"
+" Expected:
+" 1) Ctrl-V 5l <ctrl-a>
+" 0b1011
+func Test_visual_increment_23()
+ call setline(1, ["0b1001"])
+ exec "norm! \<C-V>4l\<C-A>"
+ call assert_equal(["0b1011"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 24) increment hexadecimal
+" Text:
+" 0x0b1001
+"
+" Expected:
+" 1) <ctrl-a>
+" 0x0b1002
+func Test_visual_increment_24()
+ call setline(1, ["0x0b1001"])
+ exec "norm! \<C-V>$\<C-A>"
+ call assert_equal(["0x0b1002"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 25) increment binary with nrformats including alpha
+" Text:
+" 0b1001a
+"
+" Expected:
+" 1) <ctrl-a>
+" 0b1010a
+func Test_visual_increment_25()
+ set nrformats+=alpha
+ call setline(1, ["0b1001a"])
+ exec "norm! \<C-V>$\<C-A>"
+ call assert_equal(["0b1010a"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" 26) increment binary with 32 bits
+" Text:
+" 0b11111111111111111111111111111110
+"
+" Expected:
+" 1) <ctrl-a>
+" 0b11111111111111111111111111111111
+func Test_visual_increment_26()
+ set nrformats+=bin
+ call setline(1, ["0b11111111111111111111111111111110"])
+ exec "norm! \<C-V>$\<C-A>"
+ call assert_equal(["0b11111111111111111111111111111111"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ exec "norm! \<C-V>$\<C-X>"
+ call assert_equal(["0b11111111111111111111111111111110"], getline(1, '$'))
+ set nrformats-=bin
+endfunc
+
+" 27) increment with 'rightreft', if supported
+func Test_visual_increment_27()
+ if exists('+rightleft')
+ set rightleft
+ call setline(1, ["1234 56"])
+
+ exec "norm! $\<C-A>"
+ call assert_equal(["1234 57"], getline(1, '$'))
+ call assert_equal([0, 1, 7, 0], getpos('.'))
+
+ exec "norm! \<C-A>"
+ call assert_equal(["1234 58"], getline(1, '$'))
+ call assert_equal([0, 1, 7, 0], getpos('.'))
+ set norightleft
+ endif
+endfunc
+
+" Tab code and linewise-visual inc/dec
+func Test_visual_increment_28()
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! Vj\<C-A>"
+ call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! ggVj\<C-X>"
+ call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code and linewise-visual inc/dec with 'nrformats'+=alpha
+func Test_visual_increment_29()
+ set nrformats+=alpha
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! Vj\<C-A>"
+ call assert_equal(["y\<TAB>10", "\<TAB>0"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! ggVj\<C-X>"
+ call assert_equal(["w\<TAB>10", "\<TAB>-2"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code and character-visual inc/dec
+func Test_visual_increment_30()
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! f1vjf1\<C-A>"
+ call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! ggf1vjf1\<C-X>"
+ call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+endfunc
+
+" Tab code and blockwise-visual inc/dec
+func Test_visual_increment_31()
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! f1\<C-V>jl\<C-A>"
+ call assert_equal(["x\<TAB>11", "\<TAB>0"], getline(1, '$'))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ call setline(1, ["x\<TAB>10", "\<TAB>-1"])
+ exec "norm! ggf1\<C-V>jl\<C-X>"
+ call assert_equal(["x\<TAB>9", "\<TAB>-2"], getline(1, '$'))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+endfunc
+
+" Tab code and blockwise-visual decrement with 'linebreak' and 'showbreak'
+func Test_visual_increment_32()
+ 28vnew dummy_31
+ set linebreak showbreak=+
+ call setline(1, ["x\<TAB>\<TAB>\<TAB>10", "\<TAB>\<TAB>\<TAB>\<TAB>-1"])
+ exec "norm! ggf0\<C-V>jg_\<C-X>"
+ call assert_equal(["x\<TAB>\<TAB>\<TAB>1-1", "\<TAB>\<TAB>\<TAB>\<TAB>-2"], getline(1, '$'))
+ call assert_equal([0, 1, 6, 0], getpos('.'))
+ bwipe!
+endfunc
+
+" Tab code and blockwise-visual increment with $
+func Test_visual_increment_33()
+ call setline(1, ["\<TAB>123", "456"])
+ exec "norm! gg0\<C-V>j$\<C-A>"
+ call assert_equal(["\<TAB>124", "457"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code and blockwise-visual increment and redo
+func Test_visual_increment_34()
+ call setline(1, ["\<TAB>123", " 456789"])
+ exec "norm! gg0\<C-V>j\<C-A>"
+ call assert_equal(["\<TAB>123", " 457789"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ exec "norm! .."
+ call assert_equal(["\<TAB>123", " 459789"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" Tab code, spaces and character-visual increment and redo
+func Test_visual_increment_35()
+ call setline(1, ["\<TAB>123", " 123", "\<TAB>123", "\<TAB>123"])
+ exec "norm! ggvjf3\<C-A>..."
+ call assert_equal(["\<TAB>127", " 127", "\<TAB>123", "\<TAB>123"], getline(1, '$'))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+endfunc
+
+" Tab code, spaces and blockwise-visual increment and redo
+func Test_visual_increment_36()
+ call setline(1, [" 123", "\<TAB>456789"])
+ exec "norm! G0\<C-V>kl\<C-A>"
+ call assert_equal([" 123", "\<TAB>556789"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+
+ exec "norm! ..."
+ call assert_equal([" 123", "\<TAB>856789"], getline(1, '$'))
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+endfunc
+
+" block-wise increment and dot-repeat
+" Text:
+" 1 23
+" 4 56
+"
+" Expected:
+" 1) f2 Ctrl-V jl <ctrl-a>, repeat twice afterwards with .
+" 1 26
+" 4 59
+"
+" Try with and without indent.
+func Test_visual_increment_37()
+ call setline(1, [" 1 23", " 4 56"])
+ exec "norm! ggf2\<C-V>jl\<C-A>.."
+ call assert_equal([" 1 26", " 4 59"], getline(1, 2))
+
+ call setline(1, ["1 23", "4 56"])
+ exec "norm! ggf2\<C-V>jl\<C-A>.."
+ call assert_equal(["1 26", "4 59"], getline(1, 2))
+endfunc
+
+" Check redo after the normal mode increment
+func Test_visual_increment_38()
+ exec "norm! i10\<ESC>5\<C-A>."
+ call assert_equal(["20"], getline(1, '$'))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+endfunc
+
+" Test what patch 7.3.414 fixed. Ctrl-A on "000" drops the leading zeros.
+func Test_normal_increment_01()
+ call setline(1, "000")
+ exec "norm! gg0\<C-A>"
+ call assert_equal("001", getline(1))
+
+ call setline(1, "000")
+ exec "norm! gg$\<C-A>"
+ call assert_equal("001", getline(1))
+
+ call setline(1, "001")
+ exec "norm! gg0\<C-A>"
+ call assert_equal("002", getline(1))
+
+ call setline(1, "001")
+ exec "norm! gg$\<C-A>"
+ call assert_equal("002", getline(1))
+endfunc
+
+" Test a regression of patch 7.4.1087 fixed.
+func Test_normal_increment_02()
+ call setline(1, ["hello 10", "world"])
+ exec "norm! ggl\<C-A>jx"
+ call assert_equal(["hello 11", "worl"], getline(1, '$'))
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+endfunc
+
+" The test35 unified to this file.
+func Test_normal_increment_03()
+ call setline(1, ["100 0x100 077 0",
+ \ "100 0x100 077 ",
+ \ "100 0x100 077 0xfF 0xFf",
+ \ "100 0x100 077 "])
+ set nrformats=octal,hex
+ exec "norm! gg\<C-A>102\<C-X>\<C-A>l\<C-X>l\<C-A>64\<C-A>128\<C-X>$\<C-X>"
+ set nrformats=octal
+ exec "norm! j0\<C-A>102\<C-X>\<C-A>l\<C-X>2\<C-A>w65\<C-A>129\<C-X>blx6lD"
+ set nrformats=hex
+ exec "norm! j0101\<C-X>l257\<C-X>\<C-A>Txldt \<C-A> \<C-X> \<C-X>"
+ set nrformats=
+ exec "norm! j0200\<C-X>l100\<C-X>w78\<C-X>\<C-A>k"
+ call assert_equal(["0 0x0ff 0000 -1",
+ \ "0 1x100 0777777",
+ \ "-1 0x0 078 0xFE 0xfe",
+ \ "-100 -100x100 000 "], getline(1, '$'))
+ call assert_equal([0, 3, 25, 0], getpos('.'))
+endfunc
+
+func Test_increment_empty_line()
+ call setline(1, ['0', '0', '0', '0', '0', '0', ''])
+ exe "normal Gvgg\<C-A>"
+ call assert_equal(['1', '1', '1', '1', '1', '1', ''], getline(1, 7))
+
+ " Ctrl-A/Ctrl-X should do nothing in operator pending mode
+ %d
+ call setline(1, 'one two')
+ exe "normal! c\<C-A>l"
+ exe "normal! c\<C-X>l"
+ call assert_equal('one two', getline(1))
+endfunc
+
+" Try incrementing/decrementing a non-digit/alpha character
+func Test_increment_special_char()
+ call setline(1, '!')
+ call assert_beeps("normal \<C-A>")
+ call assert_beeps("normal \<C-X>")
+endfunc
+
+" Try incrementing/decrementing a number when nrformats contains unsigned
+func Test_increment_unsigned()
+ set nrformats+=unsigned
+
+ call setline(1, '0')
+ exec "norm! gg0\<C-X>"
+ call assert_equal('0', getline(1))
+
+ call setline(1, '3')
+ exec "norm! gg010\<C-X>"
+ call assert_equal('0', getline(1))
+
+ call setline(1, '-0')
+ exec "norm! gg0\<C-X>"
+ call assert_equal("-0", getline(1))
+
+ call setline(1, '-11')
+ exec "norm! gg08\<C-X>"
+ call assert_equal('-3', getline(1))
+
+ " NOTE: 18446744073709551615 == 2^64 - 1
+ call setline(1, '18446744073709551615')
+ exec "norm! gg0\<C-A>"
+ call assert_equal('18446744073709551615', getline(1))
+
+ call setline(1, '-18446744073709551615')
+ exec "norm! gg0\<C-A>"
+ call assert_equal('-18446744073709551615', getline(1))
+
+ call setline(1, '-18446744073709551614')
+ exec "norm! gg08\<C-A>"
+ call assert_equal('-18446744073709551615', getline(1))
+
+ call setline(1, '-1')
+ exec "norm! gg0\<C-A>"
+ call assert_equal('-2', getline(1))
+
+ call setline(1, '-3')
+ exec "norm! gg08\<C-A>"
+ call assert_equal('-11', getline(1))
+
+ set nrformats-=unsigned
+endfunc
+
+func Test_normal_increment_with_virtualedit()
+ set virtualedit=all
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0l\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 07l\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0w\<C-A>"
+ call assert_equal("\<TAB>2", getline(1))
+ call assert_equal([0, 1, 2, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0wl\<C-A>"
+ call assert_equal("\<TAB>1", getline(1))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ call setline(1, ["\<TAB>1"])
+ exec "norm! 0w30l\<C-A>"
+ call assert_equal("\<TAB>1", getline(1))
+ call assert_equal([0, 1, 3, 29], getpos('.'))
+
+ set virtualedit&
+endfunc
+
+" Test for incrementing a signed hexadecimal and octal number
+func Test_normal_increment_signed_hexoct_nr()
+ new
+ " negative sign before a hex number should be ignored
+ call setline(1, ["-0x9"])
+ exe "norm \<C-A>"
+ call assert_equal(["-0xa"], getline(1, '$'))
+ exe "norm \<C-X>"
+ call assert_equal(["-0x9"], getline(1, '$'))
+ call setline(1, ["-007"])
+ exe "norm \<C-A>"
+ call assert_equal(["-010"], getline(1, '$'))
+ exe "norm \<C-X>"
+ call assert_equal(["-007"], getline(1, '$'))
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_increment_dbcs.vim b/src/testdir/test_increment_dbcs.vim
new file mode 100644
index 0000000..b090d15
--- /dev/null
+++ b/src/testdir/test_increment_dbcs.vim
@@ -0,0 +1,28 @@
+" Tests for using Ctrl-A/Ctrl-X using DBCS.
+
+set encoding=cp932
+scriptencoding cp932
+
+func SetUp()
+ new
+ set nrformats&
+endfunc
+
+func TearDown()
+ bwipe!
+endfunc
+
+func Test_increment_dbcs_1()
+ set nrformats+=alpha
+ call setline(1, ["ŽR1"])
+ exec "norm! 0\<C-A>"
+ call assert_equal(["ŽR2"], getline(1, '$'))
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ call setline(1, ["‚`‚a‚b0xDE‚e"])
+ exec "norm! 0\<C-X>"
+ call assert_equal(["‚`‚a‚b0xDD‚e"], getline(1, '$'))
+ call assert_equal([0, 1, 10, 0], getpos('.'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_indent.vim b/src/testdir/test_indent.vim
new file mode 100644
index 0000000..96e9d23
--- /dev/null
+++ b/src/testdir/test_indent.vim
@@ -0,0 +1,278 @@
+" Test for various indent options
+
+func Test_preserveindent()
+ new
+ " Test for autoindent copying indent from the previous line
+ setlocal autoindent
+ call setline(1, [repeat(' ', 16) .. 'line1'])
+ call feedkeys("A\nline2", 'xt')
+ call assert_equal("\t\tline2", getline(2))
+ setlocal autoindent&
+
+ " Test for using CTRL-T with and without 'preserveindent'
+ set shiftwidth=4
+ call cursor(1, 1)
+ call setline(1, " \t ")
+ call feedkeys("Al\<C-T>", 'xt')
+ call assert_equal("\t\tl", getline(1))
+ set preserveindent
+ call setline(1, " \t ")
+ call feedkeys("Al\<C-T>", 'xt')
+ call assert_equal(" \t \tl", getline(1))
+ set pi& sw&
+
+ " Test for using CTRL-T with 'expandtab' and 'preserveindent'
+ call cursor(1, 1)
+ call setline(1, "\t \t")
+ set shiftwidth=4 expandtab preserveindent
+ call feedkeys("Al\<C-T>", 'xt')
+ call assert_equal("\t \t l", getline(1))
+ set sw& et& pi&
+
+ close!
+endfunc
+
+" Test for indent()
+func Test_indent_func()
+ call assert_equal(-1, indent(-1))
+ new
+ call setline(1, "\tabc")
+ call assert_equal(8, indent(1))
+ call setline(1, " abc")
+ call assert_equal(4, indent(1))
+ call setline(1, " \t abc")
+ call assert_equal(12, indent(1))
+ close!
+endfunc
+
+" Test for reindenting a line using the '=' operator
+func Test_reindent()
+ new
+ call setline(1, 'abc')
+ set nomodifiable
+ call assert_fails('normal ==', 'E21:')
+ set modifiable
+
+ call setline(1, ['foo', 'bar'])
+ call feedkeys('ggVG=', 'xt')
+ call assert_equal(['foo', 'bar'], getline(1, 2))
+ close!
+endfunc
+
+" Test indent operator creating one undo entry
+func Test_indent_operator_undo()
+ enew
+ call setline(1, range(12)->map('"\t" .. v:val'))
+ func FoldExpr()
+ let g:foldcount += 1
+ return '='
+ endfunc
+ set foldmethod=expr foldexpr=FoldExpr()
+ let g:foldcount = 0
+ redraw
+ call assert_equal(12, g:foldcount)
+ normal gg=G
+ call assert_equal(24, g:foldcount)
+ undo
+ call assert_equal(38, g:foldcount)
+
+ bwipe!
+ set foldmethod& foldexpr=
+ delfunc FoldExpr
+ unlet g:foldcount
+endfunc
+
+" Test for shifting a line with a preprocessor directive ('#')
+func Test_preproc_indent()
+ new
+ set sw=4
+ call setline(1, '#define FOO 1')
+ normal >>
+ call assert_equal(' #define FOO 1', getline(1))
+
+ " with 'smartindent'
+ call setline(1, '#define FOO 1')
+ set smartindent
+ normal >>
+ call assert_equal('#define FOO 1', getline(1))
+ set smartindent&
+
+ " with 'cindent'
+ set cindent
+ normal >>
+ call assert_equal('#define FOO 1', getline(1))
+ set cindent&
+
+ close!
+endfunc
+
+" Test for 'copyindent'
+func Test_copyindent()
+ new
+ set shiftwidth=4 autoindent expandtab copyindent
+ call setline(1, " \t abc")
+ call feedkeys("ol", 'xt')
+ call assert_equal(" \t l", getline(2))
+ set noexpandtab
+ call setline(1, " \t abc")
+ call feedkeys("ol", 'xt')
+ call assert_equal(" \t l", getline(2))
+ set sw& ai& et& ci&
+ close!
+endfunc
+
+" Test for changing multiple lines with lisp indent
+func Test_lisp_indent_change_multiline()
+ new
+ setlocal lisp autoindent
+ call setline(1, ['(if a', ' (if b', ' (return 5)))'])
+ normal! jc2j(return 4))
+ call assert_equal(' (return 4))', getline(2))
+ close!
+endfunc
+
+func Test_lisp_indent()
+ new
+ setlocal lisp autoindent
+ call setline(1, ['(if a', ' ;; comment', ' \ abc', '', ' " str1\', ' " st\b', ' (return 5)'])
+ normal! jo;; comment
+ normal! jo\ abc
+ normal! jo;; ret
+ normal! jostr1"
+ normal! jostr2"
+ call assert_equal([' ;; comment', ' ;; comment', ' \ abc', ' \ abc', '', ' ;; ret', ' " str1\', ' str1"', ' " st\b', ' str2"'], getline(2, 11))
+ close!
+endfunc
+
+func Test_lisp_indent_quoted()
+ " This was going past the end of the line
+ new
+ setlocal lisp autoindent
+ call setline(1, ['"[', '='])
+ normal Gvk=
+
+ bwipe!
+endfunc
+
+" Test for setting the 'indentexpr' from a modeline
+func Test_modeline_indent_expr()
+ let modeline = &modeline
+ set modeline
+ func GetIndent()
+ return line('.') * 2
+ endfunc
+ call writefile(['# vim: indentexpr=GetIndent()'], 'Xmlfile.txt', 'D')
+ set modelineexpr
+ new Xmlfile.txt
+ call assert_equal('GetIndent()', &indentexpr)
+ exe "normal Oa\nb\n"
+ call assert_equal([' a', ' b'], getline(1, 2))
+
+ set modelineexpr&
+ delfunc GetIndent
+ let &modeline = modeline
+ close!
+endfunc
+
+func Test_indent_func_with_gq()
+
+ function GetTeXIndent()
+ " Sample indent expression for TeX files
+ let lnum = prevnonblank(v:lnum - 1)
+ " At the start of the file use zero indent.
+ if lnum == 0
+ return 0
+ endif
+ let line = getline(lnum)
+ let ind = indent(lnum)
+ " Add a 'shiftwidth' after beginning of environments.
+ if line =~ '\\begin{center}'
+ let ind = ind + shiftwidth()
+ endif
+ return ind
+ endfunction
+
+ new
+ setl et sw=2 sts=2 ts=2 tw=50 indentexpr=GetTeXIndent()
+ put =[ '\documentclass{article}', '', '\begin{document}', '',
+ \ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ut enim non',
+ \ 'libero efficitur aliquet. Maecenas metus justo, facilisis convallis blandit',
+ \ 'non, semper eu urna. Suspendisse diam diam, iaculis faucibus lorem eu,',
+ \ 'fringilla condimentum lectus. Quisque euismod diam at convallis vulputate.',
+ \ 'Pellentesque laoreet tortor sit amet mauris euismod ornare. Sed varius',
+ \ 'bibendum orci vel vehicula. Pellentesque tempor, ipsum et auctor accumsan,',
+ \ 'metus lectus ultrices odio, sed elementum mi ante at arcu.', '', '\begin{center}', '',
+ \ 'Proin nec risus consequat nunc dapibus consectetur. Mauris lacinia est a augue',
+ \ 'tristique accumsan. Morbi pretium, felis molestie eleifend condimentum, arcu',
+ \ 'ipsum congue nisl, quis euismod purus libero in ante.', '',
+ \ 'Donec id semper purus.',
+ \ 'Suspendisse eget aliquam nunc. Maecenas fringilla mauris vitae maximus',
+ \ 'condimentum. Cras a quam in mi dictum eleifend at a lorem. Sed convallis',
+ \ 'ante a commodo facilisis. Nam suscipit vulputate odio, vel dapibus nisl',
+ \ 'dignissim facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et',
+ \ 'ultrices posuere cubilia curae;', '', '']
+ 1d_
+ call cursor(5, 1)
+ ka
+ call cursor(14, 1)
+ kb
+ norm! 'agqap
+ norm! 'bgqG
+ let expected = [ '\documentclass{article}', '', '\begin{document}', '',
+ \ 'Lorem ipsum dolor sit amet, consectetur adipiscing',
+ \ 'elit. Fusce ut enim non libero efficitur aliquet.',
+ \ 'Maecenas metus justo, facilisis convallis blandit',
+ \ 'non, semper eu urna. Suspendisse diam diam,',
+ \ 'iaculis faucibus lorem eu, fringilla condimentum',
+ \ 'lectus. Quisque euismod diam at convallis',
+ \ 'vulputate. Pellentesque laoreet tortor sit amet',
+ \ 'mauris euismod ornare. Sed varius bibendum orci',
+ \ 'vel vehicula. Pellentesque tempor, ipsum et auctor',
+ \ 'accumsan, metus lectus ultrices odio, sed',
+ \ 'elementum mi ante at arcu.', '', '\begin{center}', '',
+ \ ' Proin nec risus consequat nunc dapibus',
+ \ ' consectetur. Mauris lacinia est a augue',
+ \ ' tristique accumsan. Morbi pretium, felis',
+ \ ' molestie eleifend condimentum, arcu ipsum congue',
+ \ ' nisl, quis euismod purus libero in ante.',
+ \ '',
+ \ ' Donec id semper purus. Suspendisse eget aliquam',
+ \ ' nunc. Maecenas fringilla mauris vitae maximus',
+ \ ' condimentum. Cras a quam in mi dictum eleifend',
+ \ ' at a lorem. Sed convallis ante a commodo',
+ \ ' facilisis. Nam suscipit vulputate odio, vel',
+ \ ' dapibus nisl dignissim facilisis. Vestibulum',
+ \ ' ante ipsum primis in faucibus orci luctus et',
+ \ ' ultrices posuere cubilia curae;', '', '']
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+ delmark ab
+ delfunction GetTeXIndent
+endfu
+
+func Test_formatting_keeps_first_line_indent()
+ let lines =<< trim END
+ foo()
+ {
+ int x; // manually positioned
+ // more text that will be formatted
+ // but not reindented
+ END
+ new
+ call setline(1, lines)
+ setlocal sw=4 cindent tw=45 et
+ normal! 4Ggqj
+ let expected =<< trim END
+ foo()
+ {
+ int x; // manually positioned
+ // more text that will be
+ // formatted but not
+ // reindented
+ END
+ call assert_equal(expected, getline(1, '$'))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_input.vim b/src/testdir/test_input.vim
new file mode 100644
index 0000000..3b1e2eb
--- /dev/null
+++ b/src/testdir/test_input.vim
@@ -0,0 +1,61 @@
+" Tests for character input and feedkeys() function.
+
+func Test_feedkeys_x_with_empty_string()
+ new
+ call feedkeys("ifoo\<Esc>")
+ call assert_equal('', getline('.'))
+ call feedkeys('', 'x')
+ call assert_equal('foo', getline('.'))
+
+ " check it goes back to normal mode immediately.
+ call feedkeys('i', 'x')
+ call assert_equal('foo', getline('.'))
+ quit!
+endfunc
+
+func Test_feedkeys_with_abbreviation()
+ new
+ inoreabbrev trigger value
+ call feedkeys("atrigger ", 'x')
+ call feedkeys("atrigger ", 'x')
+ call assert_equal('value value ', getline(1))
+ bwipe!
+ iunabbrev trigger
+endfunc
+
+func Test_feedkeys_escape_special()
+ nnoremap … <Cmd>let g:got_ellipsis += 1<CR>
+ call feedkeys('…', 't')
+ call assert_equal('…', getcharstr())
+ let g:got_ellipsis = 0
+ call feedkeys('…', 'xt')
+ call assert_equal(1, g:got_ellipsis)
+ unlet g:got_ellipsis
+ nunmap …
+endfunc
+
+func Test_input_simplify_ctrl_at()
+ new
+ " feeding unsimplified CTRL-@ should still trigger i_CTRL-@
+ call feedkeys("ifoo\<Esc>A\<*C-@>x", 'xt')
+ call assert_equal('foofo', getline(1))
+ bw!
+endfunc
+
+func Test_input_simplify_noremap()
+ call feedkeys("i\<*C-M>", 'nx')
+ call assert_equal('', getline(1))
+ call assert_equal([0, 2, 1, 0, 1], getcurpos())
+ bw!
+endfunc
+
+func Test_input_simplify_timedout()
+ inoremap <C-M>a b
+ call feedkeys("i\<*C-M>", 'xt')
+ call assert_equal('', getline(1))
+ call assert_equal([0, 2, 1, 0, 1], getcurpos())
+ iunmap <C-M>a
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim
new file mode 100644
index 0000000..20dd556
--- /dev/null
+++ b/src/testdir/test_ins_complete.vim
@@ -0,0 +1,2224 @@
+" Test for insert completion
+
+source screendump.vim
+source check.vim
+import './vim9.vim' as v9
+
+" Test for insert expansion
+func Test_ins_complete()
+ edit test_ins_complete.vim
+ " The files in the current directory interferes with the files
+ " used by this test. So use a separate directory for the test.
+ call mkdir('Xcpldir')
+ cd Xcpldir
+
+ set ff=unix
+ call writefile(["test11\t36Gepeto\t/Tag/",
+ \ "asd\ttest11file\t36G",
+ \ "Makefile\tto\trun"], 'Xtestfile', 'D')
+ call writefile(['', 'start of testfile',
+ \ 'ru',
+ \ 'run1',
+ \ 'run2',
+ \ 'STARTTEST',
+ \ 'ENDTEST',
+ \ 'end of testfile'], 'Xtestdata', 'D')
+ set ff&
+
+ enew!
+ edit Xtestdata
+ new
+ call append(0, ['#include "Xtestfile"', ''])
+ call cursor(2, 1)
+
+ set cot=
+ set cpt=.,w
+ " add-expands (word from next line) from other window
+ exe "normal iru\<C-N>\<C-N>\<C-X>\<C-N>\<Esc>\<C-A>"
+ call assert_equal('run1 run3', getline('.'))
+ " add-expands (current buffer first)
+ exe "normal o\<C-P>\<C-X>\<C-N>"
+ call assert_equal('run3 run3', getline('.'))
+ " Local expansion, ends in an empty line (unless it becomes a global
+ " expansion)
+ exe "normal o\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>"
+ call assert_equal('', getline('.'))
+ " starts Local and switches to global add-expansion
+ exe "normal o\<C-X>\<C-P>\<C-P>\<C-X>\<C-X>\<C-N>\<C-X>\<C-N>\<C-N>"
+ call assert_equal('run1 run2', getline('.'))
+
+ set cpt=.,\ ,w,i
+ " i-add-expands and switches to local
+ exe "normal OM\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-X>\<C-X>\<C-P>"
+ call assert_equal("Makefile\tto\trun3", getline('.'))
+ " add-expands lines (it would end in an empty line if it didn't ignore
+ " itself)
+ exe "normal o\<C-X>\<C-L>\<C-X>\<C-L>\<C-P>\<C-P>"
+ call assert_equal("Makefile\tto\trun3", getline('.'))
+ call assert_equal("Makefile\tto\trun3", getline(line('.') - 1))
+
+ set cpt=kXtestfile
+ " checks k-expansion, and file expansion (use Xtest11 instead of test11,
+ " because TEST11.OUT may match first on DOS)
+ write Xtest11.one
+ write Xtest11.two
+ exe "normal o\<C-N>\<Esc>IX\<Esc>A\<C-X>\<C-F>\<C-N>"
+ call assert_equal('Xtest11.two', getline('.'))
+
+ " use CTRL-X CTRL-F to complete Xtest11.one, remove it and then use CTRL-X
+ " CTRL-F again to verify this doesn't cause trouble.
+ exe "normal oXt\<C-X>\<C-F>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<C-X>\<C-F>"
+ call assert_equal('Xtest11.one', getline('.'))
+ normal ddk
+
+ " Test for expanding a non-existing filename
+ exe "normal oa1b2X3Y4\<C-X>\<C-F>"
+ call assert_equal('a1b2X3Y4', getline('.'))
+ normal ddk
+
+ set cpt=w
+ " checks make_cyclic in other window
+ exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>"
+ call assert_equal('STARTTEST', getline('.'))
+
+ set cpt=u nohid
+ " checks unloaded buffer expansion
+ only
+ exe "normal oEN\<C-N>"
+ call assert_equal('ENDTEST', getline('.'))
+ " checks adding mode abortion
+ exe "normal ounl\<C-N>\<C-X>\<C-X>\<C-P>"
+ call assert_equal('unless', getline('.'))
+
+ set cpt=t,d def=^\\k* tags=Xtestfile notagbsearch
+ " tag expansion, define add-expansion interrupted
+ exe "normal o\<C-X>\<C-]>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>"
+ call assert_equal('test11file 36Gepeto /Tag/ asd', getline('.'))
+ " t-expansion
+ exe "normal oa\<C-N>\<Esc>"
+ call assert_equal('asd', getline('.'))
+
+ %bw!
+ call delete('Xtest11.one')
+ call delete('Xtest11.two')
+ set cpt& cot& def& tags& tagbsearch& hidden&
+ cd ..
+ call delete('Xcpldir', 'rf')
+endfunc
+
+func Test_ins_complete_invalid_byte()
+ if has('unix') && executable('base64')
+ " this weird command was causing an illegal memory access
+ call writefile(['bm9ybTlvMDCAMM4Dbw4OGA4ODg=='], 'Xinvalid64', 'D')
+ call system('base64 -d Xinvalid64 > Xinvalid')
+ call writefile(['qa!'], 'Xexit', 'D')
+ call RunVim([], [], " -i NONE -n -X -Z -e -m -s -S Xinvalid -S Xexit")
+ call delete('Xinvalid')
+ endif
+endfunc
+
+func Test_omni_dash()
+ func Omni(findstart, base)
+ if a:findstart
+ return 5
+ else
+ echom a:base
+ return ['-help', '-v']
+ endif
+ endfunc
+ set omnifunc=Omni
+ new
+ exe "normal Gofind -\<C-x>\<C-o>"
+ call assert_equal("find -help", getline('$'))
+
+ bwipe!
+ delfunc Omni
+ set omnifunc=
+endfunc
+
+func Test_omni_throw()
+ let g:CallCount = 0
+ func Omni(findstart, base)
+ let g:CallCount += 1
+ if a:findstart
+ throw "he he he"
+ endif
+ endfunc
+ set omnifunc=Omni
+ new
+ try
+ exe "normal ifoo\<C-x>\<C-o>"
+ call assert_false(v:true, 'command should have failed')
+ catch
+ call assert_exception('he he he')
+ call assert_equal(1, g:CallCount)
+ endtry
+
+ bwipe!
+ delfunc Omni
+ unlet g:CallCount
+ set omnifunc=
+endfunc
+
+func Test_omni_autoload()
+ let save_rtp = &rtp
+ set rtp=Xruntime/some
+ let dir = 'Xruntime/some/autoload'
+ call mkdir(dir, 'pR')
+
+ let lines =<< trim END
+ vim9script
+ export def Func(findstart: bool, base: string): any
+ if findstart
+ return 1
+ else
+ return ['match']
+ endif
+ enddef
+ {
+ eval 1 + 2
+ }
+ END
+ call writefile(lines, dir .. '/omni.vim')
+
+ new
+ setlocal omnifunc=omni#Func
+ call feedkeys("i\<C-X>\<C-O>\<Esc>", 'xt')
+
+ bwipe!
+ set omnifunc=
+ let &rtp = save_rtp
+endfunc
+
+func Test_completefunc_args()
+ let s:args = []
+ func! CompleteFunc(findstart, base)
+ let s:args += [[a:findstart, empty(a:base)]]
+ endfunc
+ new
+
+ set completefunc=CompleteFunc
+ call feedkeys("i\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([1, 1], s:args[0])
+ call assert_equal(0, s:args[1][0])
+ set completefunc=
+
+ let s:args = []
+ set omnifunc=CompleteFunc
+ call feedkeys("i\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([1, 1], s:args[0])
+ call assert_equal(0, s:args[1][0])
+ set omnifunc=
+
+ bwipe!
+ unlet s:args
+ delfunc CompleteFunc
+endfunc
+
+func s:CompleteDone_CompleteFuncNone( findstart, base )
+ if a:findstart
+ return 0
+ endif
+
+ return v:none
+endfunc
+
+func s:CompleteDone_CompleteFuncDict( findstart, base )
+ if a:findstart
+ return 0
+ endif
+
+ return {
+ \ 'words': [
+ \ {
+ \ 'word': 'aword',
+ \ 'abbr': 'wrd',
+ \ 'menu': 'extra text',
+ \ 'info': 'words are cool',
+ \ 'kind': 'W',
+ \ 'user_data': ['one', 'two']
+ \ }
+ \ ]
+ \ }
+endfunc
+
+func s:CompleteDone_CheckCompletedItemNone()
+ let s:called_completedone = 1
+endfunc
+
+func s:CompleteDone_CheckCompletedItemDict(pre)
+ call assert_equal( 'aword', v:completed_item[ 'word' ] )
+ call assert_equal( 'wrd', v:completed_item[ 'abbr' ] )
+ call assert_equal( 'extra text', v:completed_item[ 'menu' ] )
+ call assert_equal( 'words are cool', v:completed_item[ 'info' ] )
+ call assert_equal( 'W', v:completed_item[ 'kind' ] )
+ call assert_equal( ['one', 'two'], v:completed_item[ 'user_data' ] )
+
+ if a:pre
+ call assert_equal('function', complete_info().mode)
+ endif
+
+ let s:called_completedone = 1
+endfunc
+
+func Test_CompleteDoneNone()
+ au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemNone()
+ let oldline = join(map(range(&columns), 'nr2char(screenchar(&lines-1, v:val+1))'), '')
+
+ set completefunc=<SID>CompleteDone_CompleteFuncNone
+ execute "normal a\<C-X>\<C-U>\<C-Y>"
+ set completefunc&
+ let newline = join(map(range(&columns), 'nr2char(screenchar(&lines-1, v:val+1))'), '')
+
+ call assert_true(s:called_completedone)
+ call assert_equal(oldline, newline)
+
+ let s:called_completedone = 0
+ au! CompleteDone
+endfunc
+
+func Test_CompleteDoneDict()
+ au CompleteDonePre * :call <SID>CompleteDone_CheckCompletedItemDict(1)
+ au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict(0)
+
+ set completefunc=<SID>CompleteDone_CompleteFuncDict
+ execute "normal a\<C-X>\<C-U>\<C-Y>"
+ set completefunc&
+
+ call assert_equal(['one', 'two'], v:completed_item[ 'user_data' ])
+ call assert_true(s:called_completedone)
+
+ let s:called_completedone = 0
+ au! CompleteDone
+endfunc
+
+func s:CompleteDone_CompleteFuncDictNoUserData(findstart, base)
+ if a:findstart
+ return 0
+ endif
+
+ return {
+ \ 'words': [
+ \ {
+ \ 'word': 'aword',
+ \ 'abbr': 'wrd',
+ \ 'menu': 'extra text',
+ \ 'info': 'words are cool',
+ \ 'kind': 'W',
+ \ }
+ \ ]
+ \ }
+endfunc
+
+func s:CompleteDone_CheckCompletedItemDictNoUserData()
+ call assert_equal( 'aword', v:completed_item[ 'word' ] )
+ call assert_equal( 'wrd', v:completed_item[ 'abbr' ] )
+ call assert_equal( 'extra text', v:completed_item[ 'menu' ] )
+ call assert_equal( 'words are cool', v:completed_item[ 'info' ] )
+ call assert_equal( 'W', v:completed_item[ 'kind' ] )
+ call assert_equal( '', v:completed_item[ 'user_data' ] )
+
+ let s:called_completedone = 1
+endfunc
+
+func Test_CompleteDoneDictNoUserData()
+ au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDictNoUserData()
+
+ set completefunc=<SID>CompleteDone_CompleteFuncDictNoUserData
+ execute "normal a\<C-X>\<C-U>\<C-Y>"
+ set completefunc&
+
+ call assert_equal('', v:completed_item[ 'user_data' ])
+ call assert_true(s:called_completedone)
+
+ let s:called_completedone = 0
+ au! CompleteDone
+endfunc
+
+func s:CompleteDone_CompleteFuncList(findstart, base)
+ if a:findstart
+ return 0
+ endif
+
+ return [ 'aword' ]
+endfunc
+
+func s:CompleteDone_CheckCompletedItemList()
+ call assert_equal( 'aword', v:completed_item[ 'word' ] )
+ call assert_equal( '', v:completed_item[ 'abbr' ] )
+ call assert_equal( '', v:completed_item[ 'menu' ] )
+ call assert_equal( '', v:completed_item[ 'info' ] )
+ call assert_equal( '', v:completed_item[ 'kind' ] )
+ call assert_equal( '', v:completed_item[ 'user_data' ] )
+
+ let s:called_completedone = 1
+endfunc
+
+func Test_CompleteDoneList()
+ au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemList()
+
+ set completefunc=<SID>CompleteDone_CompleteFuncList
+ execute "normal a\<C-X>\<C-U>\<C-Y>"
+ set completefunc&
+
+ call assert_equal('', v:completed_item[ 'user_data' ])
+ call assert_true(s:called_completedone)
+
+ let s:called_completedone = 0
+ au! CompleteDone
+endfunc
+
+func Test_CompleteDone_undo()
+ au CompleteDone * call append(0, "prepend1")
+ new
+ call setline(1, ["line1", "line2"])
+ call feedkeys("Go\<C-X>\<C-N>\<CR>\<ESC>", "tx")
+ call assert_equal(["prepend1", "line1", "line2", "line1", ""],
+ \ getline(1, '$'))
+ undo
+ call assert_equal(["line1", "line2"], getline(1, '$'))
+ bwipe!
+ au! CompleteDone
+endfunc
+
+func Test_CompleteDone_modify()
+ let value = {
+ \ 'word': '',
+ \ 'abbr': '',
+ \ 'menu': '',
+ \ 'info': '',
+ \ 'kind': '',
+ \ 'user_data': '',
+ \ }
+ let v:completed_item = value
+ call assert_equal(value, v:completed_item)
+endfunc
+
+func CompleteTest(findstart, query)
+ if a:findstart
+ return col('.')
+ endif
+ return ['matched']
+endfunc
+
+func Test_completefunc_info()
+ new
+ set completeopt=menuone
+ set completefunc=CompleteTest
+ call feedkeys("i\<C-X>\<C-U>\<C-R>\<C-R>=string(complete_info())\<CR>\<ESC>", "tx")
+ call assert_equal("matched{'pum_visible': 1, 'mode': 'function', 'selected': 0, 'items': [{'word': 'matched', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}]}", getline(1))
+ bwipe!
+ set completeopt&
+ set completefunc&
+endfunc
+
+" Test that mouse scrolling/movement should not interrupt completion.
+func Test_mouse_scroll_move_during_completion()
+ new
+ com! -buffer TestCommand1 echo 'TestCommand1'
+ com! -buffer TestCommand2 echo 'TestCommand2'
+ call setline(1, ['', '', '', '', ''])
+ call cursor(5, 1)
+
+ " Without completion menu scrolling can move text.
+ set completeopt-=menu wrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelDown>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_notequal(1, winsaveview().topline)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelUp>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(1, winsaveview().topline)
+ set nowrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelRight>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_notequal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelLeft>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<MouseMove>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+
+ " With completion menu scrolling cannot move text.
+ set completeopt+=menu wrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelDown>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(1, winsaveview().topline)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelUp>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(1, winsaveview().topline)
+ set nowrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelRight>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelLeft>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<MouseMove>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+
+ bwipe!
+ set completeopt& wrap&
+endfunc
+
+" Check that when using feedkeys() typeahead does not interrupt searching for
+" completions.
+func Test_compl_feedkeys()
+ new
+ set completeopt=menuone,noselect
+ call feedkeys("ajump ju\<C-X>\<C-N>\<C-P>\<ESC>", "tx")
+ call assert_equal("jump jump", getline(1))
+ bwipe!
+ set completeopt&
+endfunc
+
+" Test for insert path completion with completeslash option
+func Test_ins_completeslash()
+ CheckMSWindows
+
+ call mkdir('Xcpldir', 'R')
+ let orig_shellslash = &shellslash
+ set cpt&
+ new
+
+ set noshellslash
+
+ set completeslash=
+ exe "normal oXcp\<C-X>\<C-F>"
+ call assert_equal('Xcpldir\', getline('.'))
+
+ set completeslash=backslash
+ exe "normal oXcp\<C-X>\<C-F>"
+ call assert_equal('Xcpldir\', getline('.'))
+
+ set completeslash=slash
+ exe "normal oXcp\<C-X>\<C-F>"
+ call assert_equal('Xcpldir/', getline('.'))
+
+ set shellslash
+
+ set completeslash=
+ exe "normal oXcp\<C-X>\<C-F>"
+ call assert_equal('Xcpldir/', getline('.'))
+
+ set completeslash=backslash
+ exe "normal oXcp\<C-X>\<C-F>"
+ call assert_equal('Xcpldir\', getline('.'))
+
+ set completeslash=slash
+ exe "normal oXcp\<C-X>\<C-F>"
+ call assert_equal('Xcpldir/', getline('.'))
+ %bw!
+
+ set noshellslash
+ set completeslash=slash
+ call assert_true(stridx(globpath(&rtp, 'syntax/*.vim', 1, 1)[0], '\') != -1)
+
+ let &shellslash = orig_shellslash
+ set completeslash=
+endfunc
+
+func Test_pum_stopped_by_timer()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['hello', 'hullo', 'heeee', ''])
+ func StartCompl()
+ call timer_start(100, { -> execute('stopinsert') })
+ call feedkeys("Gah\<C-N>")
+ endfunc
+ END
+
+ call writefile(lines, 'Xpumscript', 'D')
+ let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12})
+ call term_sendkeys(buf, ":call StartCompl()\<CR>")
+ call TermWait(buf, 200)
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_complete_stopinsert_startinsert()
+ nnoremap <F2> <Cmd>startinsert<CR>
+ inoremap <F2> <Cmd>stopinsert<CR>
+ " This just checks if this causes an error
+ call feedkeys("i\<C-X>\<C-N>\<F2>\<F2>", 'x')
+ nunmap <F2>
+ iunmap <F2>
+endfunc
+
+func Test_pum_with_folds_two_tabs()
+ CheckScreendump
+
+ let lines =<< trim END
+ set fdm=marker
+ call setline(1, ['" x {{{1', '" a some text'])
+ call setline(3, range(&lines)->map({_, val -> '" a' .. val}))
+ norm! zm
+ tab sp
+ call feedkeys('2Gzv', 'xt')
+ call feedkeys("0fa", 'xt')
+ END
+
+ call writefile(lines, 'Xpumscript', 'D')
+ let buf = RunVimInTerminal('-S Xpumscript', #{rows: 10})
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "a\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_with_folds_two_tabs', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_pum_with_preview_win()
+ CheckScreendump
+
+ let lines =<< trim END
+ funct Omni_test(findstart, base)
+ if a:findstart
+ return col(".") - 1
+ endif
+ return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}]
+ endfunc
+ set omnifunc=Omni_test
+ set completeopt+=longest
+ END
+
+ call writefile(lines, 'Xpreviewscript', 'D')
+ let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12})
+ call term_sendkeys(buf, "Gi\<C-X>\<C-O>")
+ call TermWait(buf, 200)
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_scrollbar_on_wide_char()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['a', ' å•Šå•Šå•Š',
+ \ ' 哦哦哦',
+ \ ' 呃呃呃'])
+ call setline(5, range(10)->map({i, v -> 'aa' .. v .. 'bb'}))
+ END
+ call writefile(lines, 'Xwidescript', 'D')
+ let buf = RunVimInTerminal('-S Xwidescript', #{rows: 10})
+ call term_sendkeys(buf, "A\<C-N>")
+ call VerifyScreenDump(buf, 'Test_scrollbar_on_wide_char', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for inserting the tag search pattern in insert mode
+func Test_ins_compl_tag_sft()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t/^int first() {}$/",
+ \ "second\tXfoo\t/^int second() {}$/",
+ \ "third\tXfoo\t/^int third() {}$/"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ enew
+ set showfulltag
+ exe "normal isec\<C-X>\<C-]>\<C-N>\<CR>"
+ call assert_equal('int second() {}', getline(1))
+ set noshowfulltag
+
+ set tags&
+ %bwipe!
+endfunc
+
+" Test for 'completefunc' deleting text
+func Test_completefunc_error()
+ new
+ " delete text when called for the first time
+ func CompleteFunc(findstart, base)
+ if a:findstart == 1
+ normal dd
+ return col('.') - 1
+ endif
+ return ['a', 'b']
+ endfunc
+ set completefunc=CompleteFunc
+ call setline(1, ['', 'abcd', ''])
+ call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:')
+
+ " delete text when called for the second time
+ func CompleteFunc2(findstart, base)
+ if a:findstart == 1
+ return col('.') - 1
+ endif
+ normal dd
+ return ['a', 'b']
+ endfunc
+ set completefunc=CompleteFunc2
+ call setline(1, ['', 'abcd', ''])
+ call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:')
+
+ " Jump to a different window from the complete function
+ func CompleteFunc3(findstart, base)
+ if a:findstart == 1
+ return col('.') - 1
+ endif
+ wincmd p
+ return ['a', 'b']
+ endfunc
+ set completefunc=CompleteFunc3
+ new
+ call assert_fails('exe "normal a\<C-X>\<C-U>"', 'E565:')
+ close!
+
+ set completefunc&
+ delfunc CompleteFunc
+ delfunc CompleteFunc2
+ delfunc CompleteFunc3
+ close!
+endfunc
+
+" Test for returning non-string values from 'completefunc'
+func Test_completefunc_invalid_data()
+ new
+ func! CompleteFunc(findstart, base)
+ if a:findstart == 1
+ return col('.') - 1
+ endif
+ return [{}, '', 'moon']
+ endfunc
+ set completefunc=CompleteFunc
+ exe "normal i\<C-X>\<C-U>"
+ call assert_equal('moon', getline(1))
+ set completefunc&
+ close!
+endfunc
+
+" Test for errors in using complete() function
+func Test_complete_func_error()
+ call assert_fails('call complete(1, ["a"])', 'E785:')
+ func ListColors()
+ call complete(col('.'), "blue")
+ endfunc
+ call assert_fails('exe "normal i\<C-R>=ListColors()\<CR>"', 'E1211:')
+ func ListMonths()
+ call complete(col('.'), test_null_list())
+ endfunc
+ call assert_fails('exe "normal i\<C-R>=ListMonths()\<CR>"', 'E1298:')
+ delfunc ListColors
+ delfunc ListMonths
+ call assert_fails('call complete_info({})', 'E1211:')
+ call assert_equal([], complete_info(['items']).items)
+endfunc
+
+" Test for recursively starting completion mode using complete()
+func Test_recursive_complete_func()
+ func ListColors()
+ call complete(5, ["red", "blue"])
+ return ''
+ endfunc
+ new
+ call setline(1, ['a1', 'a2'])
+ set complete=.
+ exe "normal Goa\<C-X>\<C-L>\<C-R>=ListColors()\<CR>\<C-N>"
+ call assert_equal('a2blue', getline(3))
+ delfunc ListColors
+ bw!
+endfunc
+
+" Test for using complete() with completeopt+=longest
+func Test_complete_with_longest()
+ new
+ inoremap <buffer> <f3> <cmd>call complete(1, ["iaax", "iaay", "iaaz"])<cr>
+
+ " default: insert first match
+ set completeopt&
+ call setline(1, ['i'])
+ exe "normal Aa\<f3>\<esc>"
+ call assert_equal('iaax', getline(1))
+
+ " with longest: insert longest prefix
+ set completeopt+=longest
+ call setline(1, ['i'])
+ exe "normal Aa\<f3>\<esc>"
+ call assert_equal('iaa', getline(1))
+ set completeopt&
+ bwipe!
+endfunc
+
+
+" Test for completing words following a completed word in a line
+func Test_complete_wrapscan()
+ " complete words from another buffer
+ new
+ call setline(1, ['one two', 'three four'])
+ new
+ setlocal complete=w
+ call feedkeys("itw\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>", 'xt')
+ call assert_equal('two three four', getline(1))
+ close!
+ " complete words from the current buffer
+ setlocal complete=.
+ %d
+ call setline(1, ['one two', ''])
+ call cursor(2, 1)
+ call feedkeys("ion\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>", 'xt')
+ call assert_equal('one two one two', getline(2))
+ close!
+endfunc
+
+" Test for completing special characters
+func Test_complete_special_chars()
+ new
+ call setline(1, 'int .*[-\^$ func float')
+ call feedkeys("oin\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>", 'xt')
+ call assert_equal('int .*[-\^$ func float', getline(2))
+ close!
+endfunc
+
+" Test for completion when text is wrapped across lines.
+func Test_complete_across_line()
+ new
+ call setline(1, ['red green blue', 'one two three'])
+ setlocal textwidth=20
+ exe "normal 2G$a re\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
+ call assert_equal(['one two three red', 'green blue one'], getline(2, '$'))
+ close!
+endfunc
+
+" Test for completing words with a '.' at the end of a word.
+func Test_complete_joinspaces()
+ new
+ call setline(1, ['one two.', 'three. four'])
+ set joinspaces
+ exe "normal Goon\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
+ call assert_equal("one two. three. four", getline(3))
+ set joinspaces&
+ bw!
+endfunc
+
+" Test for using CTRL-L to add one character when completing matching
+func Test_complete_add_onechar()
+ new
+ call setline(1, ['wool', 'woodwork'])
+ call feedkeys("Gowoo\<C-P>\<C-P>\<C-P>\<C-L>f", 'xt')
+ call assert_equal('woof', getline(3))
+
+ " use 'ignorecase' and backspace to erase characters from the prefix string
+ " and then add letters using CTRL-L
+ %d
+ set ignorecase backspace=2
+ setlocal complete=.
+ call setline(1, ['workhorse', 'workload'])
+ normal Go
+ exe "normal aWOR\<C-P>\<bs>\<bs>\<bs>\<bs>\<bs>\<bs>\<C-L>r\<C-L>\<C-L>"
+ call assert_equal('workh', getline(3))
+ set ignorecase& backspace&
+ close!
+endfunc
+
+" Test for using CTRL-X CTRL-L to complete whole lines lines
+func Test_complete_wholeline()
+ new
+ " complete one-line
+ call setline(1, ['a1', 'a2'])
+ exe "normal ggoa\<C-X>\<C-L>"
+ call assert_equal(['a1', 'a1', 'a2'], getline(1, '$'))
+ " go to the next match (wrapping around the buffer)
+ exe "normal 2GCa\<C-X>\<C-L>\<C-N>"
+ call assert_equal(['a1', 'a', 'a2'], getline(1, '$'))
+ " go to the next match
+ exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>"
+ call assert_equal(['a1', 'a2', 'a2'], getline(1, '$'))
+ exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>\<C-N>"
+ call assert_equal(['a1', 'a1', 'a2'], getline(1, '$'))
+ " repeat the test using CTRL-L
+ " go to the next match (wrapping around the buffer)
+ exe "normal 2GCa\<C-X>\<C-L>\<C-L>"
+ call assert_equal(['a1', 'a2', 'a2'], getline(1, '$'))
+ " go to the next match
+ exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>"
+ call assert_equal(['a1', 'a', 'a2'], getline(1, '$'))
+ exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>\<C-L>"
+ call assert_equal(['a1', 'a1', 'a2'], getline(1, '$'))
+ %d
+ " use CTRL-X CTRL-L to add one more line
+ call setline(1, ['a1', 'b1'])
+ setlocal complete=.
+ exe "normal ggOa\<C-X>\<C-L>\<C-X>\<C-L>\<C-X>\<C-L>"
+ call assert_equal(['a1', 'b1', '', 'a1', 'b1'], getline(1, '$'))
+ bw!
+endfunc
+
+" Test insert completion with 'cindent' (adjust the indent)
+func Test_complete_with_cindent()
+ new
+ setlocal cindent
+ call setline(1, ['if (i == 1)', " j = 2;"])
+ exe "normal Go{\<CR>i\<C-X>\<C-L>\<C-X>\<C-L>\<CR>}"
+ call assert_equal(['{', "\tif (i == 1)", "\t\tj = 2;", '}'], getline(3, '$'))
+
+ %d
+ call setline(1, ['when while', '{', ''])
+ setlocal cinkeys+==while
+ exe "normal Giwh\<C-P> "
+ call assert_equal("\twhile ", getline('$'))
+ close!
+endfunc
+
+" Test for <CTRL-X> <CTRL-V> completion. Complete commands and functions
+func Test_complete_cmdline()
+ new
+ exe "normal icaddb\<C-X>\<C-V>"
+ call assert_equal('caddbuffer', getline(1))
+ exe "normal ocall getqf\<C-X>\<C-V>"
+ call assert_equal('call getqflist(', getline(2))
+ exe "normal oabcxyz(\<C-X>\<C-V>"
+ call assert_equal('abcxyz(', getline(3))
+ com! -buffer TestCommand1 echo 'TestCommand1'
+ com! -buffer TestCommand2 echo 'TestCommand2'
+ write TestCommand1Test
+ write TestCommand2Test
+ " Test repeating <CTRL-X> <CTRL-V> and switching to another CTRL-X mode
+ exe "normal oT\<C-X>\<C-V>\<C-X>\<C-V>\<C-X>\<C-F>\<Esc>"
+ call assert_equal('TestCommand2Test', getline(4))
+ call delete('TestCommand1Test')
+ call delete('TestCommand2Test')
+ delcom TestCommand1
+ delcom TestCommand2
+ close!
+endfunc
+
+" Test for <CTRL-X> <CTRL-Z> stopping completion without changing the match
+func Test_complete_stop()
+ new
+ func Save_mode1()
+ let g:mode1 = mode(1)
+ return ''
+ endfunc
+ func Save_mode2()
+ let g:mode2 = mode(1)
+ return ''
+ endfunc
+ inoremap <F1> <C-R>=Save_mode1()<CR>
+ inoremap <F2> <C-R>=Save_mode2()<CR>
+ call setline(1, ['aaa bbb ccc '])
+ exe "normal A\<C-N>\<C-P>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
+ call assert_equal('ic', g:mode1)
+ call assert_equal('i', g:mode2)
+ call assert_equal('aaa bbb ccc ', getline(1))
+ exe "normal A\<C-N>\<Down>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
+ call assert_equal('ic', g:mode1)
+ call assert_equal('i', g:mode2)
+ call assert_equal('aaa bbb ccc aaa', getline(1))
+ set completeopt+=noselect
+ exe "normal A \<C-N>\<Down>\<Down>\<C-L>\<C-L>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
+ call assert_equal('ic', g:mode1)
+ call assert_equal('i', g:mode2)
+ call assert_equal('aaa bbb ccc aaa bb', getline(1))
+ set completeopt&
+ exe "normal A d\<C-N>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
+ call assert_equal('ic', g:mode1)
+ call assert_equal('i', g:mode2)
+ call assert_equal('aaa bbb ccc aaa bb d', getline(1))
+ com! -buffer TestCommand1 echo 'TestCommand1'
+ com! -buffer TestCommand2 echo 'TestCommand2'
+ exe "normal oT\<C-X>\<C-V>\<C-X>\<C-V>\<F1>\<C-X>\<C-Z>\<F2>\<Esc>"
+ call assert_equal('ic', g:mode1)
+ call assert_equal('i', g:mode2)
+ call assert_equal('TestCommand2', getline(2))
+ delcom TestCommand1
+ delcom TestCommand2
+ unlet g:mode1
+ unlet g:mode2
+ iunmap <F1>
+ iunmap <F2>
+ delfunc Save_mode1
+ delfunc Save_mode2
+ close!
+endfunc
+
+" Test for typing CTRL-R in insert completion mode to insert a register
+" content.
+func Test_complete_reginsert()
+ new
+ call setline(1, ['a1', 'a12', 'a123', 'a1234'])
+
+ " if a valid CTRL-X mode key is returned from <C-R>=, then it should be
+ " processed. Otherwise, CTRL-X mode should be stopped and the key should be
+ " inserted.
+ exe "normal Goa\<C-P>\<C-R>=\"\\<C-P>\"\<CR>"
+ call assert_equal('a123', getline(5))
+ let @r = "\<C-P>\<C-P>"
+ exe "normal GCa\<C-P>\<C-R>r"
+ call assert_equal('a12', getline(5))
+ exe "normal GCa\<C-P>\<C-R>=\"x\"\<CR>"
+ call assert_equal('a1234x', getline(5))
+ bw!
+endfunc
+
+func Test_issue_7021()
+ CheckMSWindows
+
+ let orig_shellslash = &shellslash
+ set noshellslash
+
+ set completeslash=slash
+ call assert_false(expand('~') =~ '/')
+
+ let &shellslash = orig_shellslash
+ set completeslash=
+endfunc
+
+" Test for 'longest' setting in 'completeopt' with latin1 and utf-8 encodings
+func Test_complete_longest_match()
+ for e in ['latin1', 'utf-8']
+ exe 'set encoding=' .. e
+ new
+ set complete=.
+ set completeopt=menu,longest
+ call setline(1, ['pfx_a1', 'pfx_a12', 'pfx_a123', 'pfx_b1'])
+ exe "normal Gopfx\<C-P>"
+ call assert_equal('pfx_', getline(5))
+ bw!
+ endfor
+
+ " Test for completing additional words with longest match set
+ new
+ call setline(1, ['abc1', 'abd2'])
+ exe "normal Goab\<C-P>\<C-X>\<C-P>"
+ call assert_equal('ab', getline(3))
+ bw!
+ set complete& completeopt&
+endfunc
+
+" Test for removing the first displayed completion match and selecting the
+" match just before that.
+func Test_complete_erase_firstmatch()
+ new
+ call setline(1, ['a12', 'a34', 'a56'])
+ set complete=.
+ exe "normal Goa\<C-P>\<BS>\<BS>3\<CR>"
+ call assert_equal('a34', getline('$'))
+ set complete&
+ bw!
+endfunc
+
+" Test for completing words from unloaded buffers
+func Test_complete_from_unloadedbuf()
+ call writefile(['abc'], "Xfile1", 'D')
+ call writefile(['def'], "Xfile2", 'D')
+ edit Xfile1
+ edit Xfile2
+ new | close
+ enew
+ bunload Xfile1 Xfile2
+ set complete=u
+ " complete from an unloaded buffer
+ exe "normal! ia\<C-P>"
+ call assert_equal('abc', getline(1))
+ exe "normal! od\<C-P>"
+ call assert_equal('def', getline(2))
+
+ set complete&
+ %bw!
+endfunc
+
+" Test for completing whole lines from unloaded buffers
+func Test_complete_wholeline_unloadedbuf()
+ call writefile(['a line1', 'a line2', 'a line3'], "Xfile1", 'D')
+ edit Xfile1
+ enew
+ set complete=u
+ exe "normal! ia\<C-X>\<C-L>\<C-P>"
+ call assert_equal('a line2', getline(1))
+ %d
+ " completing from an unlisted buffer should fail
+ bdel Xfile1
+ exe "normal! ia\<C-X>\<C-L>\<C-P>"
+ call assert_equal('a', getline(1))
+
+ set complete&
+ %bw!
+endfunc
+
+" Test for completing words from unlisted buffers
+func Test_complete_from_unlistedbuf()
+ call writefile(['abc'], "Xfile1", 'D')
+ call writefile(['def'], "Xfile2", 'D')
+ edit Xfile1
+ edit Xfile2
+ new | close
+ bdel Xfile1 Xfile2
+ set complete=U
+ " complete from an unlisted buffer
+ exe "normal! ia\<C-P>"
+ call assert_equal('abc', getline(1))
+ exe "normal! od\<C-P>"
+ call assert_equal('def', getline(2))
+
+ set complete&
+ %bw!
+endfunc
+
+" Test for completing whole lines from unlisted buffers
+func Test_complete_wholeline_unlistedbuf()
+ call writefile(['a line1', 'a line2', 'a line3'], "Xfile1", 'D')
+ edit Xfile1
+ enew
+ set complete=U
+ " completing from a unloaded buffer should fail
+ exe "normal! ia\<C-X>\<C-L>\<C-P>"
+ call assert_equal('a', getline(1))
+ %d
+ bdel Xfile1
+ exe "normal! ia\<C-X>\<C-L>\<C-P>"
+ call assert_equal('a line2', getline(1))
+
+ set complete&
+ %bw!
+endfunc
+
+" Test for adding a multibyte character using CTRL-L in completion mode
+func Test_complete_mbyte_char_add()
+ new
+ set complete=.
+ call setline(1, 'abÄ—')
+ exe "normal! oa\<C-P>\<BS>\<BS>\<C-L>\<C-L>"
+ call assert_equal('abÄ—', getline(2))
+ " Test for a leader with multibyte character
+ %d
+ call setline(1, 'abÄ—Ä•')
+ exe "normal! oabÄ—\<C-P>"
+ call assert_equal('abÄ—Ä•', getline(2))
+ bw!
+endfunc
+
+" Test for using <C-X><C-P> for local expansion even if 'complete' is set to
+" not to complete matches from the local buffer. Also test using multiple
+" <C-X> to cancel the current completion mode.
+func Test_complete_local_expansion()
+ new
+ set complete=t
+ call setline(1, ['abc', 'def'])
+ exe "normal! Go\<C-X>\<C-P>"
+ call assert_equal("def", getline(3))
+ exe "normal! Go\<C-P>"
+ call assert_equal("", getline(4))
+ exe "normal! Go\<C-X>\<C-N>"
+ call assert_equal("abc", getline(5))
+ exe "normal! Go\<C-N>"
+ call assert_equal("", getline(6))
+
+ " use multiple <C-X> to cancel the previous completion mode
+ exe "normal! Go\<C-P>\<C-X>\<C-P>"
+ call assert_equal("", getline(7))
+ exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-P>"
+ call assert_equal("", getline(8))
+ exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-X>\<C-P>"
+ call assert_equal("abc", getline(9))
+
+ " interrupt the current completion mode
+ set completeopt=menu,noinsert
+ exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-X>\<C-P>\<C-Y>"
+ call assert_equal("abc", getline(10))
+
+ " when only one <C-X> is used to interrupt, do normal expansion
+ exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-P>"
+ call assert_equal("", getline(11))
+ set completeopt&
+
+ " using two <C-X> in non-completion mode and restarting the same mode
+ exe "normal! God\<C-X>\<C-X>\<C-P>\<C-X>\<C-X>\<C-P>\<C-Y>"
+ call assert_equal("def", getline(12))
+
+ " test for adding a match from the original empty text
+ %d
+ call setline(1, 'abc def g')
+ exe "normal! o\<C-X>\<C-P>\<C-N>\<C-X>\<C-P>"
+ call assert_equal('def', getline(2))
+ exe "normal! 0C\<C-X>\<C-N>\<C-P>\<C-X>\<C-N>"
+ call assert_equal('abc', getline(2))
+
+ bw!
+endfunc
+
+" Test for undoing changes after a insert-mode completion
+func Test_complete_undo()
+ new
+ set complete=.
+ " undo with 'ignorecase'
+ call setline(1, ['ABOVE', 'BELOW'])
+ set ignorecase
+ exe "normal! Goab\<C-G>u\<C-P>"
+ call assert_equal("ABOVE", getline(3))
+ undo
+ call assert_equal("ab", getline(3))
+ set ignorecase&
+ %d
+ " undo with longest match
+ set completeopt=menu,longest
+ call setline(1, ['above', 'about'])
+ exe "normal! Goa\<C-G>u\<C-P>"
+ call assert_equal("abo", getline(3))
+ undo
+ call assert_equal("a", getline(3))
+ set completeopt&
+ %d
+ " undo for line completion
+ call setline(1, ['above that change', 'below that change'])
+ exe "normal! Goabove\<C-G>u\<C-X>\<C-L>"
+ call assert_equal("above that change", getline(3))
+ undo
+ call assert_equal("above", getline(3))
+
+ bw!
+endfunc
+
+" Test for completing a very long word
+func Test_complete_long_word()
+ set complete&
+ new
+ call setline(1, repeat('x', 950) .. ' one two three')
+ exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
+ call assert_equal(repeat('x', 950) .. ' one two three', getline(2))
+ %d
+ " should fail when more than 950 characters are in a word
+ call setline(1, repeat('x', 951) .. ' one two three')
+ exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
+ call assert_equal(repeat('x', 951), getline(2))
+
+ " Test for adding a very long word to an existing completion
+ %d
+ call setline(1, ['abc', repeat('x', 1016) .. '012345'])
+ exe "normal! Goab\<C-P>\<C-X>\<C-P>"
+ call assert_equal('abc ' .. repeat('x', 1016) .. '0123', getline(3))
+ bw!
+endfunc
+
+" Test for some fields in the complete items used by complete()
+func Test_complete_items()
+ func CompleteItems(idx)
+ let items = [[#{word: "one", dup: 1, user_data: 'u1'}, #{word: "one", dup: 1, user_data: 'u2'}],
+ \ [#{word: "one", dup: 0, user_data: 'u3'}, #{word: "one", dup: 0, user_data: 'u4'}],
+ \ [#{word: "one", icase: 1, user_data: 'u7'}, #{word: "oNE", icase: 1, user_data: 'u8'}],
+ \ [#{user_data: 'u9'}],
+ \ [#{word: "", user_data: 'u10'}],
+ \ [#{word: "", empty: 1, user_data: 'u11'}]]
+ call complete(col('.'), items[a:idx])
+ return ''
+ endfunc
+ new
+ exe "normal! i\<C-R>=CompleteItems(0)\<CR>\<C-N>\<C-Y>"
+ call assert_equal('u2', v:completed_item.user_data)
+ call assert_equal('one', getline(1))
+ exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-Y>"
+ call assert_equal('u3', v:completed_item.user_data)
+ call assert_equal('one', getline(2))
+ exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-N>"
+ call assert_equal('', getline(3))
+ set completeopt=menu,noinsert
+ exe "normal! o\<C-R>=CompleteItems(2)\<CR>one\<C-N>\<C-Y>"
+ call assert_equal('oNE', getline(4))
+ call assert_equal('u8', v:completed_item.user_data)
+ set completeopt&
+ exe "normal! o\<C-R>=CompleteItems(3)\<CR>"
+ call assert_equal('', getline(5))
+ exe "normal! o\<C-R>=CompleteItems(4)\<CR>"
+ call assert_equal('', getline(6))
+ exe "normal! o\<C-R>=CompleteItems(5)\<CR>"
+ call assert_equal('', getline(7))
+ call assert_equal('u11', v:completed_item.user_data)
+ " pass invalid argument to complete()
+ let cmd = "normal! o\<C-R>=complete(1, [[]])\<CR>"
+ call assert_fails('exe cmd', 'E730:')
+ bw!
+ delfunc CompleteItems
+endfunc
+
+" Test for the "refresh" item in the dict returned by an insert completion
+" function
+func Test_complete_item_refresh_always()
+ let g:CallCount = 0
+ func! Tcomplete(findstart, base)
+ if a:findstart
+ " locate the start of the word
+ let line = getline('.')
+ let start = col('.') - 1
+ while start > 0 && line[start - 1] =~ '\a'
+ let start -= 1
+ endwhile
+ return start
+ else
+ let g:CallCount += 1
+ let res = ["update1", "update12", "update123"]
+ return #{words: res, refresh: 'always'}
+ endif
+ endfunc
+ new
+ set completeopt=menu,longest
+ set completefunc=Tcomplete
+ exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>"
+ call assert_equal('up', getline(1))
+ call assert_equal(2, g:CallCount)
+ set completeopt&
+ set completefunc&
+ bw!
+ delfunc Tcomplete
+endfunc
+
+" Test for completing from a thesaurus file without read permission
+func Test_complete_unreadable_thesaurus_file()
+ CheckUnix
+ CheckNotRoot
+
+ call writefile(['about', 'above'], 'Xunrfile', 'D')
+ call setfperm('Xunrfile', '---r--r--')
+ new
+ set complete=sXfile
+ exe "normal! ia\<C-P>"
+ call assert_equal('a', getline(1))
+
+ bw!
+ set complete&
+endfunc
+
+" Test to ensure 'Scanning...' messages are not recorded in messages history
+func Test_z1_complete_no_history()
+ new
+ messages clear
+ let currmess = execute('messages')
+ setlocal dictionary=README.txt
+ exe "normal owh\<C-X>\<C-K>"
+ exe "normal owh\<C-N>"
+ call assert_equal(currmess, execute('messages'))
+ bwipe!
+endfunc
+
+" A mapping is not used for the key after CTRL-X.
+func Test_no_mapping_for_ctrl_x_key()
+ new
+ inoremap <buffer> <C-K> <Cmd>let was_mapped = 'yes'<CR>
+ setlocal dictionary=README.txt
+ call feedkeys("aexam\<C-X>\<C-K> ", 'xt')
+ call assert_equal('example ', getline(1))
+ call assert_false(exists('was_mapped'))
+ bwipe!
+endfunc
+
+" Test for different ways of setting the 'completefunc' option
+func Test_completefunc_callback()
+ func CompleteFunc1(callnr, findstart, base)
+ call add(g:CompleteFunc1Args, [a:callnr, a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ func CompleteFunc2(findstart, base)
+ call add(g:CompleteFunc2Args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a global function name
+ LET &completefunc = 'g:CompleteFunc2'
+ new
+ call setline(1, 'global')
+ LET g:CompleteFunc2Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'global']], g:CompleteFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set completefunc=function('g:CompleteFunc1',\ [10])
+ new
+ call setline(1, 'one')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[10, 1, ''], [10, 0, 'one']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'completefunc'
+ VAR Fn = function('g:CompleteFunc1', [11])
+ LET &completefunc = Fn
+ new
+ call setline(1, 'two')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[11, 1, ''], [11, 0, 'two']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using string(funcref_variable) to set 'completefunc'
+ LET Fn = function('g:CompleteFunc1', [12])
+ LET &completefunc = string(Fn)
+ new
+ call setline(1, 'two')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[12, 1, ''], [12, 0, 'two']], g:CompleteFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set completefunc=funcref('g:CompleteFunc1',\ [13])
+ new
+ call setline(1, 'three')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[13, 1, ''], [13, 0, 'three']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'completefunc'
+ LET Fn = funcref('g:CompleteFunc1', [14])
+ LET &completefunc = Fn
+ new
+ call setline(1, 'four')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[14, 1, ''], [14, 0, 'four']], g:CompleteFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'completefunc'
+ LET Fn = funcref('g:CompleteFunc1', [15])
+ LET &completefunc = string(Fn)
+ new
+ call setline(1, 'four')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[15, 1, ''], [15, 0, 'four']], g:CompleteFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with set
+ VAR optval = "LSTART a, b LMIDDLE g:CompleteFunc1(16, a, b) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set completefunc=" .. optval
+ new
+ call setline(1, 'five')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[16, 1, ''], [16, 0, 'five']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to a lambda expression
+ LET &completefunc = LSTART a, b LMIDDLE g:CompleteFunc1(17, a, b) LEND
+ new
+ call setline(1, 'six')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[17, 1, ''], [17, 0, 'six']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to string(lambda_expression)
+ LET &completefunc = 'LSTART a, b LMIDDLE g:CompleteFunc1(18, a, b) LEND'
+ new
+ call setline(1, 'six')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[18, 1, ''], [18, 0, 'six']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b LMIDDLE g:CompleteFunc1(19, a, b) LEND
+ LET &completefunc = Lambda
+ new
+ call setline(1, 'seven')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:CompleteFunc1Args)
+ bw!
+
+ #" Set 'completefunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b LMIDDLE g:CompleteFunc1(20, a, b) LEND
+ LET &completefunc = string(Lambda)
+ new
+ call setline(1, 'seven')
+ LET g:CompleteFunc1Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:CompleteFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
+ LET &completefunc = Lambda
+ new
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ bw!
+
+ #" Test for clearing the 'completefunc' option
+ set completefunc=''
+ set completefunc&
+ call assert_fails("set completefunc=function('abc')", "E700:")
+ call assert_fails("set completefunc=funcref('abc')", "E700:")
+
+ #" set 'completefunc' to a non-existing function
+ set completefunc=g:CompleteFunc2
+ call setline(1, 'five')
+ call assert_fails("set completefunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &completefunc = function('NonExistingFunc')", 'E700:')
+ LET g:CompleteFunc2Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'five']], g:CompleteFunc2Args)
+ bw!
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for using a script-local function name
+ func s:CompleteFunc3(findstart, base)
+ call add(g:CompleteFunc3Args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set completefunc=s:CompleteFunc3
+ new
+ call setline(1, 'script1')
+ let g:CompleteFunc3Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'script1']], g:CompleteFunc3Args)
+ bw!
+
+ let &completefunc = 's:CompleteFunc3'
+ new
+ call setline(1, 'script2')
+ let g:CompleteFunc3Args = []
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'script2']], g:CompleteFunc3Args)
+ bw!
+ delfunc s:CompleteFunc3
+
+ " In Vim9 script s: can be omitted
+ let lines =<< trim END
+ vim9script
+ var CompleteFunc4Args = []
+ def CompleteFunc4(findstart: bool, base: string): any
+ add(CompleteFunc4Args, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ set completefunc=CompleteFunc4
+ new
+ setline(1, 'script1')
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'script1']], CompleteFunc4Args)
+ bw!
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ " invalid return value
+ let &completefunc = {a -> 'abc'}
+ call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+
+ " Using Vim9 lambda expression in legacy context should fail
+ set completefunc=(a,\ b)\ =>\ g:CompleteFunc1(21,\ a,\ b)
+ new | only
+ let g:CompleteFunc1Args = []
+ call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:')
+ call assert_equal([], g:CompleteFunc1Args)
+
+ " set 'completefunc' to a partial with dict. This used to cause a crash.
+ func SetCompleteFunc()
+ let params = {'complete': function('g:DictCompleteFunc')}
+ let &completefunc = params.complete
+ endfunc
+ func g:DictCompleteFunc(_) dict
+ endfunc
+ call SetCompleteFunc()
+ new
+ call SetCompleteFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set completefunc=
+ wincmd w
+ set completefunc=
+ %bw!
+ delfunc g:DictCompleteFunc
+ delfunc SetCompleteFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ def Vim9CompleteFunc(callnr: number, findstart: number, base: string): any
+ add(g:Vim9completeFuncArgs, [callnr, findstart, base])
+ return findstart ? 0 : []
+ enddef
+
+ # Test for using a def function with completefunc
+ set completefunc=function('Vim9CompleteFunc',\ [60])
+ new | only
+ setline(1, 'one')
+ g:Vim9completeFuncArgs = []
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9completeFuncArgs)
+ bw!
+
+ # Test for using a global function name
+ &completefunc = g:CompleteFunc2
+ new | only
+ setline(1, 'two')
+ g:CompleteFunc2Args = []
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'two']], g:CompleteFunc2Args)
+ bw!
+
+ # Test for using a script-local function name
+ def LocalCompleteFunc(findstart: number, base: string): any
+ add(g:LocalCompleteFuncArgs, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ &completefunc = LocalCompleteFunc
+ new | only
+ setline(1, 'three')
+ g:LocalCompleteFuncArgs = []
+ feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'three']], g:LocalCompleteFuncArgs)
+ bw!
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ " cleanup
+ set completefunc&
+ delfunc CompleteFunc1
+ delfunc CompleteFunc2
+ unlet g:CompleteFunc1Args g:CompleteFunc2Args
+ %bw!
+endfunc
+
+" Test for different ways of setting the 'omnifunc' option
+func Test_omnifunc_callback()
+ func OmniFunc1(callnr, findstart, base)
+ call add(g:OmniFunc1Args, [a:callnr, a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ func OmniFunc2(findstart, base)
+ call add(g:OmniFunc2Args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &omnifunc = 'g:OmniFunc2'
+ new
+ call setline(1, 'zero')
+ LET g:OmniFunc2Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'zero']], g:OmniFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set omnifunc=function('g:OmniFunc1',\ [10])
+ new
+ call setline(1, 'one')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[10, 1, ''], [10, 0, 'one']], g:OmniFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'omnifunc'
+ VAR Fn = function('g:OmniFunc1', [11])
+ LET &omnifunc = Fn
+ new
+ call setline(1, 'two')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[11, 1, ''], [11, 0, 'two']], g:OmniFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'omnifunc'
+ LET Fn = function('g:OmniFunc1', [12])
+ LET &omnifunc = string(Fn)
+ new
+ call setline(1, 'two')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[12, 1, ''], [12, 0, 'two']], g:OmniFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set omnifunc=funcref('g:OmniFunc1',\ [13])
+ new
+ call setline(1, 'three')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[13, 1, ''], [13, 0, 'three']], g:OmniFunc1Args)
+ bw!
+
+ #" Use let to set 'omnifunc' to a funcref
+ LET Fn = funcref('g:OmniFunc1', [14])
+ LET &omnifunc = Fn
+ new
+ call setline(1, 'four')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[14, 1, ''], [14, 0, 'four']], g:OmniFunc1Args)
+ bw!
+
+ #" Using a string(funcref) to set 'omnifunc'
+ LET Fn = funcref("g:OmniFunc1", [15])
+ LET &omnifunc = string(Fn)
+ new
+ call setline(1, 'four')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[15, 1, ''], [15, 0, 'four']], g:OmniFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with set
+ VAR optval = "LSTART a, b LMIDDLE g:OmniFunc1(16, a, b) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set omnifunc=" .. optval
+ new
+ call setline(1, 'five')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[16, 1, ''], [16, 0, 'five']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a lambda expression
+ LET &omnifunc = LSTART a, b LMIDDLE g:OmniFunc1(17, a, b) LEND
+ new
+ call setline(1, 'six')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[17, 1, ''], [17, 0, 'six']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a string(lambda_expression)
+ LET &omnifunc = 'LSTART a, b LMIDDLE g:OmniFunc1(18, a, b) LEND'
+ new
+ call setline(1, 'six')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[18, 1, ''], [18, 0, 'six']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b LMIDDLE g:OmniFunc1(19, a, b) LEND
+ LET &omnifunc = Lambda
+ new
+ call setline(1, 'seven')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:OmniFunc1Args)
+ bw!
+
+ #" Set 'omnifunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b LMIDDLE g:OmniFunc1(20, a, b) LEND
+ LET &omnifunc = string(Lambda)
+ new
+ call setline(1, 'seven')
+ LET g:OmniFunc1Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:OmniFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
+ LET &omnifunc = Lambda
+ new
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ bw!
+
+ #" Test for clearing the 'omnifunc' option
+ set omnifunc=''
+ set omnifunc&
+ call assert_fails("set omnifunc=function('abc')", "E700:")
+ call assert_fails("set omnifunc=funcref('abc')", "E700:")
+
+ #" set 'omnifunc' to a non-existing function
+ set omnifunc=g:OmniFunc2
+ call setline(1, 'nine')
+ call assert_fails("set omnifunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &omnifunc = function('NonExistingFunc')", 'E700:')
+ LET g:OmniFunc2Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'nine']], g:OmniFunc2Args)
+ bw!
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for using a script-local function name
+ func s:OmniFunc3(findstart, base)
+ call add(g:OmniFunc3Args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set omnifunc=s:OmniFunc3
+ new
+ call setline(1, 'script1')
+ let g:OmniFunc3Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'script1']], g:OmniFunc3Args)
+ bw!
+
+ let &omnifunc = 's:OmniFunc3'
+ new
+ call setline(1, 'script2')
+ let g:OmniFunc3Args = []
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'script2']], g:OmniFunc3Args)
+ bw!
+ delfunc s:OmniFunc3
+
+ " invalid return value
+ let &omnifunc = {a -> 'abc'}
+ call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+
+ " Using Vim9 lambda expression in legacy context should fail
+ set omnifunc=(a,\ b)\ =>\ OmniFunc1(21,\ a,\ b)
+ new | only
+ let g:OmniFunc1Args = []
+ call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:')
+ call assert_equal([], g:OmniFunc1Args)
+
+ " set 'omnifunc' to a partial with dict. This used to cause a crash.
+ func SetOmniFunc()
+ let params = {'omni': function('g:DictOmniFunc')}
+ let &omnifunc = params.omni
+ endfunc
+ func g:DictOmniFunc(_) dict
+ endfunc
+ call SetOmniFunc()
+ new
+ call SetOmniFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set omnifunc=
+ wincmd w
+ set omnifunc=
+ %bw!
+ delfunc g:DictOmniFunc
+ delfunc SetOmniFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ def Vim9omniFunc(callnr: number, findstart: number, base: string): any
+ add(g:Vim9omniFunc_Args, [callnr, findstart, base])
+ return findstart ? 0 : []
+ enddef
+
+ # Test for using a def function with omnifunc
+ set omnifunc=function('Vim9omniFunc',\ [60])
+ new | only
+ setline(1, 'one')
+ g:Vim9omniFunc_Args = []
+ feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9omniFunc_Args)
+ bw!
+
+ # Test for using a global function name
+ &omnifunc = g:OmniFunc2
+ new | only
+ setline(1, 'two')
+ g:OmniFunc2Args = []
+ feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'two']], g:OmniFunc2Args)
+ bw!
+
+ # Test for using a script-local function name
+ def LocalOmniFunc(findstart: number, base: string): any
+ add(g:LocalOmniFuncArgs, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ &omnifunc = LocalOmniFunc
+ new | only
+ setline(1, 'three')
+ g:LocalOmniFuncArgs = []
+ feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'three']], g:LocalOmniFuncArgs)
+ bw!
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ " cleanup
+ set omnifunc&
+ delfunc OmniFunc1
+ delfunc OmniFunc2
+ unlet g:OmniFunc1Args g:OmniFunc2Args
+ %bw!
+endfunc
+
+" Test for different ways of setting the 'thesaurusfunc' option
+func Test_thesaurusfunc_callback()
+ func TsrFunc1(callnr, findstart, base)
+ call add(g:TsrFunc1Args, [a:callnr, a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ func TsrFunc2(findstart, base)
+ call add(g:TsrFunc2Args, [a:findstart, a:base])
+ return a:findstart ? 0 : ['sunday']
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &thesaurusfunc = 'g:TsrFunc2'
+ new
+ call setline(1, 'zero')
+ LET g:TsrFunc2Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'zero']], g:TsrFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set thesaurusfunc=function('g:TsrFunc1',\ [10])
+ new
+ call setline(1, 'one')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[10, 1, ''], [10, 0, 'one']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'thesaurusfunc'
+ VAR Fn = function('g:TsrFunc1', [11])
+ LET &thesaurusfunc = Fn
+ new
+ call setline(1, 'two')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[11, 1, ''], [11, 0, 'two']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'thesaurusfunc'
+ LET Fn = function('g:TsrFunc1', [12])
+ LET &thesaurusfunc = string(Fn)
+ new
+ call setline(1, 'two')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[12, 1, ''], [12, 0, 'two']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set thesaurusfunc=funcref('g:TsrFunc1',\ [13])
+ new
+ call setline(1, 'three')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[13, 1, ''], [13, 0, 'three']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'thesaurusfunc'
+ LET Fn = funcref('g:TsrFunc1', [14])
+ LET &thesaurusfunc = Fn
+ new
+ call setline(1, 'four')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[14, 1, ''], [14, 0, 'four']], g:TsrFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'thesaurusfunc'
+ LET Fn = funcref('g:TsrFunc1', [15])
+ LET &thesaurusfunc = string(Fn)
+ new
+ call setline(1, 'four')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[15, 1, ''], [15, 0, 'four']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a lambda function
+ VAR optval = "LSTART a, b LMIDDLE g:TsrFunc1(16, a, b) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set thesaurusfunc=" .. optval
+ new
+ call setline(1, 'five')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[16, 1, ''], [16, 0, 'five']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with set
+ LET &thesaurusfunc = LSTART a, b LMIDDLE g:TsrFunc1(17, a, b) LEND
+ new
+ call setline(1, 'six')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[17, 1, ''], [17, 0, 'six']], g:TsrFunc1Args)
+ bw!
+
+ #" Set 'thesaurusfunc' to a string(lambda expression)
+ LET &thesaurusfunc = 'LSTART a, b LMIDDLE g:TsrFunc1(18, a, b) LEND'
+ new
+ call setline(1, 'six')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[18, 1, ''], [18, 0, 'six']], g:TsrFunc1Args)
+ bw!
+
+ #" Set 'thesaurusfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b LMIDDLE g:TsrFunc1(19, a, b) LEND
+ LET &thesaurusfunc = Lambda
+ new
+ call setline(1, 'seven')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:TsrFunc1Args)
+ bw!
+
+ #" Set 'thesaurusfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b LMIDDLE g:TsrFunc1(20, a, b) LEND
+ LET &thesaurusfunc = string(Lambda)
+ new
+ call setline(1, 'seven')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:TsrFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND
+ LET &thesaurusfunc = Lambda
+ new
+ call setline(1, 'eight')
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ bw!
+
+ #" Test for clearing the 'thesaurusfunc' option
+ set thesaurusfunc=''
+ set thesaurusfunc&
+ call assert_fails("set thesaurusfunc=function('abc')", "E700:")
+ call assert_fails("set thesaurusfunc=funcref('abc')", "E700:")
+
+ #" set 'thesaurusfunc' to a non-existing function
+ set thesaurusfunc=g:TsrFunc2
+ call setline(1, 'ten')
+ call assert_fails("set thesaurusfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &thesaurusfunc = function('NonExistingFunc')", 'E700:')
+ LET g:TsrFunc2Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'ten']], g:TsrFunc2Args)
+ bw!
+
+ #" Use a buffer-local value and a global value
+ set thesaurusfunc&
+ setlocal thesaurusfunc=function('g:TsrFunc1',\ [22])
+ call setline(1, 'sun')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sun', getline(1))
+ call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args)
+ new
+ call setline(1, 'sun')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sun', getline(1))
+ call assert_equal([], g:TsrFunc1Args)
+ set thesaurusfunc=function('g:TsrFunc1',\ [23])
+ wincmd w
+ call setline(1, 'sun')
+ LET g:TsrFunc1Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
+ call assert_equal('sun', getline(1))
+ call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args)
+ :%bw!
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for using a script-local function name
+ func s:TsrFunc3(findstart, base)
+ call add(g:TsrFunc3Args, [a:findstart, a:base])
+ return a:findstart ? 0 : []
+ endfunc
+ set tsrfu=s:TsrFunc3
+ new
+ call setline(1, 'script1')
+ let g:TsrFunc3Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'script1']], g:TsrFunc3Args)
+ bw!
+
+ let &tsrfu = 's:TsrFunc3'
+ new
+ call setline(1, 'script2')
+ let g:TsrFunc3Args = []
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ call assert_equal([[1, ''], [0, 'script2']], g:TsrFunc3Args)
+ bw!
+ delfunc s:TsrFunc3
+
+ " invalid return value
+ let &thesaurusfunc = {a -> 'abc'}
+ call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+
+ " Using Vim9 lambda expression in legacy context should fail
+ set thesaurusfunc=(a,\ b)\ =>\ TsrFunc1(21,\ a,\ b)
+ new | only
+ let g:TsrFunc1Args = []
+ call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:')
+ call assert_equal([], g:TsrFunc1Args)
+ bw!
+
+ " set 'thesaurusfunc' to a partial with dict. This used to cause a crash.
+ func SetTsrFunc()
+ let params = {'thesaurus': function('g:DictTsrFunc')}
+ let &thesaurusfunc = params.thesaurus
+ endfunc
+ func g:DictTsrFunc(_) dict
+ endfunc
+ call SetTsrFunc()
+ new
+ call SetTsrFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set thesaurusfunc=
+ wincmd w
+ %bw!
+ delfunc SetTsrFunc
+
+ " set buffer-local 'thesaurusfunc' to a partial with dict. This used to
+ " cause a crash.
+ func SetLocalTsrFunc()
+ let params = {'thesaurus': function('g:DictTsrFunc')}
+ let &l:thesaurusfunc = params.thesaurus
+ endfunc
+ call SetLocalTsrFunc()
+ call test_garbagecollect_now()
+ call SetLocalTsrFunc()
+ set thesaurusfunc=
+ bw!
+ delfunc g:DictTsrFunc
+ delfunc SetLocalTsrFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ def Vim9tsrFunc(callnr: number, findstart: number, base: string): any
+ add(g:Vim9tsrFunc_Args, [callnr, findstart, base])
+ return findstart ? 0 : []
+ enddef
+
+ # Test for using a def function with thesaurusfunc
+ set thesaurusfunc=function('Vim9tsrFunc',\ [60])
+ new | only
+ setline(1, 'one')
+ g:Vim9tsrFunc_Args = []
+ feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9tsrFunc_Args)
+ bw!
+
+ # Test for using a global function name
+ &thesaurusfunc = g:TsrFunc2
+ new | only
+ setline(1, 'two')
+ g:TsrFunc2Args = []
+ feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'two']], g:TsrFunc2Args)
+ bw!
+
+ # Test for using a script-local function name
+ def LocalTsrFunc(findstart: number, base: string): any
+ add(g:LocalTsrFuncArgs, [findstart, base])
+ return findstart ? 0 : []
+ enddef
+ &thesaurusfunc = LocalTsrFunc
+ new | only
+ setline(1, 'three')
+ g:LocalTsrFuncArgs = []
+ feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+ assert_equal([[1, ''], [0, 'three']], g:LocalTsrFuncArgs)
+ bw!
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ " cleanup
+ set thesaurusfunc&
+ delfunc TsrFunc1
+ delfunc TsrFunc2
+ unlet g:TsrFunc1Args g:TsrFunc2Args
+ %bw!
+endfunc
+
+func FooBarComplete(findstart, base)
+ if a:findstart
+ return col('.') - 1
+ else
+ return ["Foo", "Bar", "}"]
+ endif
+endfunc
+
+func Test_complete_smartindent()
+ new
+ setlocal smartindent completefunc=FooBarComplete
+
+ exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>"
+ let result = getline(1,'$')
+ call assert_equal(['', '{','}',''], result)
+ bw!
+ delfunction! FooBarComplete
+endfunc
+
+func Test_complete_overrun()
+ " this was going past the end of the copied text
+ new
+ sil norm si”0s0 
+ bwipe!
+endfunc
+
+func Test_infercase_very_long_line()
+ " this was truncating the line when inferring case
+ new
+ let longLine = "blah "->repeat(300)
+ let verylongLine = "blah "->repeat(400)
+ call setline(1, verylongLine)
+ call setline(2, longLine)
+ set ic infercase
+ exe "normal 2Go\<C-X>\<C-L>\<Esc>"
+ call assert_equal(longLine, getline(3))
+
+ " check that the too long text is NUL terminated
+ %del
+ norm o
+ norm 1987ax
+ exec "norm ox\<C-X>\<C-L>"
+ call assert_equal(repeat('x', 1987), getline(3))
+
+ bwipe!
+ set noic noinfercase
+endfunc
+
+func Test_ins_complete_add()
+ " this was reading past the end of allocated memory
+ new
+ norm o
+ norm 7o€€
+ sil! norm o
+
+ bwipe!
+endfunc
+
+func Test_ins_complete_end_of_line()
+ " this was reading past the end of the line
+ new
+ norm 8o€ý 
+ sil! norm o
+
+ bwipe!
+endfunc
+
+func s:Tagfunc(t,f,o)
+ bwipe!
+ return []
+endfunc
+
+" This was using freed memory, since 'complete' was in a wiped out buffer.
+" Also using a window that was closed.
+func Test_tagfunc_wipes_out_buffer()
+ new
+ set complete=.,t,w,b,u,i
+ se tagfunc=s:Tagfunc
+ sil norm i
+
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ins_complete_no_halt.vim b/src/testdir/test_ins_complete_no_halt.vim
new file mode 100644
index 0000000..e12925d
--- /dev/null
+++ b/src/testdir/test_ins_complete_no_halt.vim
@@ -0,0 +1,51 @@
+" Test insert mode completion does not get stuck when looping around.
+" In a separate file to avoid the settings to leak to other test cases.
+
+set complete+=kspell
+set completeopt+=menu
+set completeopt+=menuone
+set completeopt+=noselect
+set completeopt+=noinsert
+let g:autocompletion = v:true
+
+func Test_ins_complete_no_halt()
+ function! OpenCompletion()
+ if pumvisible() && (g:autocompletion == v:true)
+ call feedkeys("\<C-e>\<C-n>", "i")
+ return
+ endif
+ if ((v:char >= 'a' && v:char <= 'z') || (v:char >= 'A' && v:char <= 'Z')) && (g:autocompletion == v:true)
+ call feedkeys("\<C-n>", "i")
+ redraw
+ endif
+ endfunction
+
+ autocmd InsertCharPre * noautocmd call OpenCompletion()
+
+ setlocal spell! spelllang=en_us
+
+ call feedkeys("iauto-complete-halt-test test test test test test test test test test test test test test test test test test test\<C-c>", "tx!")
+ call assert_equal(["auto-complete-halt-test test test test test test test test test test test test test test test test test test test"], getline(1, "$"))
+endfunc
+
+func Test_auto_complete_backwards_no_halt()
+ function! OpenCompletion()
+ if pumvisible() && (g:autocompletion == v:true)
+ call feedkeys("\<C-e>\<C-p>", "i")
+ return
+ endif
+ if ((v:char >= 'a' && v:char <= 'z') || (v:char >= 'A' && v:char <= 'Z')) && (g:autocompletion == v:true)
+ call feedkeys("\<C-p>", "i")
+ redraw
+ endif
+ endfunction
+
+ autocmd InsertCharPre * noautocmd call OpenCompletion()
+
+ setlocal spell! spelllang=en_us
+
+ call feedkeys("iauto-complete-halt-test test test test test test test test test test test test test test test test test test test\<C-c>", "tx!")
+ call assert_equal(["auto-complete-halt-test test test test test test test test test test test test test test test test test test test"], getline(1, "$"))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_interrupt.vim b/src/testdir/test_interrupt.vim
new file mode 100644
index 0000000..aa7f634
--- /dev/null
+++ b/src/testdir/test_interrupt.vim
@@ -0,0 +1,32 @@
+" Test behavior of interrupt()
+
+let s:bufwritepre_called = 0
+let s:bufwritepost_called = 0
+
+func s:bufwritepre()
+ let s:bufwritepre_called = 1
+ call interrupt()
+endfunction
+
+func s:bufwritepost()
+ let s:bufwritepost_called = 1
+endfunction
+
+func Test_interrupt()
+ new Xinterrupt
+ let n = 0
+ try
+ au BufWritePre Xinterrupt call s:bufwritepre()
+ au BufWritePost Xinterrupt call s:bufwritepost()
+ w!
+ catch /^Vim:Interrupt$/
+ endtry
+ call assert_equal(1, s:bufwritepre_called)
+ call assert_equal(0, s:bufwritepost_called)
+ call assert_equal(0, filereadable('Xinterrupt'))
+
+ au! BufWritePre
+ au! BufWritePost
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_job_fails.vim b/src/testdir/test_job_fails.vim
new file mode 100644
index 0000000..1751d1d
--- /dev/null
+++ b/src/testdir/test_job_fails.vim
@@ -0,0 +1,17 @@
+" This test is in a separate file, because it usually causes reports for memory
+" leaks under valgrind. That is because when fork/exec fails memory is not
+" freed. Since the process exits right away it's not a real leak.
+
+source check.vim
+
+func Test_job_start_fails()
+ CheckFeature job
+ let job = job_start('axdfxsdf')
+ if has('unix')
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ else
+ call WaitForAssert({-> assert_equal("fail", job_status(job))})
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_join.vim b/src/testdir/test_join.vim
new file mode 100644
index 0000000..8f7ccb6
--- /dev/null
+++ b/src/testdir/test_join.vim
@@ -0,0 +1,448 @@
+" Test for joining lines.
+
+func Test_join_with_count()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ normal J
+ call assert_equal('one two', getline(1))
+ %del
+ call setline(1, ['one', 'two', 'three', 'four'])
+ normal 10J
+ call assert_equal('one two three four', getline(1))
+
+ call setline(1, ['one', '', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', ' ', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal 2J
+ call assert_equal('one', getline(1))
+
+ quit!
+endfunc
+
+" Tests for setting the '[,'] marks when joining lines.
+func Test_join_marks()
+ enew
+ call append(0, [
+ \ "\t\tO sodales, ludite, vos qui",
+ \ "attamen consulite per voster honur. Tua pulchra " .
+ \ "facies me fay planszer milies",
+ \ "",
+ \ "This line.",
+ \ "Should be joined with the next line",
+ \ "and with this line"])
+
+ normal gg0gqj
+ call assert_equal([0, 1, 1, 0], getpos("'["))
+ call assert_equal([0, 2, 1, 0], getpos("']"))
+
+ /^This line/;'}-join
+ call assert_equal([0, 4, 11, 0], getpos("'["))
+ call assert_equal([0, 4, 67, 0], getpos("']"))
+ enew!
+endfunc
+
+" Test for joining lines and marks in them
+" in compatible and nocompatible modes
+" and with 'joinspaces' set or not
+" and with 'cpoptions' flag 'j' set or not
+func Test_join_spaces_marks()
+ new
+ " Text used for the test
+ insert
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+zx cvn.
+as dfg?
+hjkl iop!
+ert
+zx cvn.
+as dfg?
+hjkl iop!
+ert
+.
+ let text = getline(1, '$')
+ normal gg
+
+ set nojoinspaces
+ set cpoptions-=j
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ set cpoptions+=j
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert ernop
+ zx cvn. as dfg? hjkl iop! ert ernop
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ enew!
+ call append(0, text)
+ normal gg
+
+ set cpoptions-=j
+ set joinspaces
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ set cpoptions+=j
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert enop
+ zx cvn. as dfg? hjkl iop! ert ernop
+
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ enew!
+ call append(0, text)
+ normal gg
+
+ set cpoptions-=j
+ set nojoinspaces
+ set compatible
+
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j4Jy3l$pjdG
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert a
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ set nocompatible
+ set cpoptions&vim
+ set joinspaces&vim
+ close!
+endfunc
+
+" Test for joining lines with comments
+func Test_join_lines_with_comments()
+ new
+
+ " Text used by the test
+ insert
+{
+
+/*
+* Make sure the previous comment leader is not removed.
+*/
+
+/*
+* Make sure the previous comment leader is not removed.
+*/
+
+// Should the next comment leader be left alone?
+// Yes.
+
+// Should the next comment leader be left alone?
+// Yes.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+if (condition) // Remove the next comment leader!
+// OK, I will.
+action();
+
+if (condition) // Remove the next comment leader!
+// OK, I will.
+action();
+}
+.
+
+ call cursor(2, 1)
+ set comments=s1:/*,mb:*,ex:*/,://
+ set nojoinspaces fo=j
+ set backspace=eol,start
+
+ .,+3join
+ exe "normal j4J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal jj3J\<CR>"
+
+ " Expected output
+ let expected =<< trim [CODE]
+ {
+ /* Make sure the previous comment leader is not removed. */
+ /* Make sure the previous comment leader is not removed. */
+ // Should the next comment leader be left alone? Yes.
+ // Should the next comment leader be left alone? Yes.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ }
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ set comments&vim
+ set joinspaces&vim
+ set fo&vim
+ set backspace&vim
+ close!
+endfunc
+
+" Test for joining lines with different comment leaders
+func Test_join_comments_2()
+ new
+
+ insert
+{
+
+/*
+ * Make sure the previous comment leader is not removed.
+ */
+
+/*
+ * Make sure the previous comment leader is not removed.
+ */
+
+/* List:
+ * - item1
+ * foo bar baz
+ * foo bar baz
+ * - item2
+ * foo bar baz
+ * foo bar baz
+ */
+
+/* List:
+ * - item1
+ * foo bar baz
+ * foo bar baz
+ * - item2
+ * foo bar baz
+ * foo bar baz
+ */
+
+// Should the next comment leader be left alone?
+// Yes.
+
+// Should the next comment leader be left alone?
+// Yes.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+if (condition) // Remove the next comment leader!
+ // OK, I will.
+ action();
+
+if (condition) // Remove the next comment leader!
+ // OK, I will.
+ action();
+
+int i = 7 /* foo *// 3
+ // comment
+ ;
+
+int i = 7 /* foo *// 3
+ // comment
+ ;
+
+># Note that the last character of the ending comment leader (left angle
+ # bracket) is a comment leader itself. Make sure that this comment leader is
+ # not removed from the next line #<
+< On this line a new comment is opened which spans 2 lines. This comment should
+< retain its comment leader.
+
+># Note that the last character of the ending comment leader (left angle
+ # bracket) is a comment leader itself. Make sure that this comment leader is
+ # not removed from the next line #<
+< On this line a new comment is opened which spans 2 lines. This comment should
+< retain its comment leader.
+
+}
+.
+
+ call cursor(2, 1)
+ set comments=sO:*\ -,mO:*\ \ ,exO:*/
+ set comments+=s1:/*,mb:*,ex:*/,://
+ set comments+=s1:>#,mb:#,ex:#<,:<
+ set cpoptions-=j joinspaces fo=j
+ set backspace=eol,start
+
+ .,+3join
+ exe "normal j4J\<CR>"
+ .,+8join
+ exe "normal j9J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal jj3J\<CR>j"
+ .,+2join
+ exe "normal jj3J\<CR>j"
+ .,+5join
+ exe "normal j6J\<CR>"
+ exe "normal oSome code!\<CR>// Make sure backspacing does not remove this comment leader.\<Esc>0i\<C-H>\<Esc>"
+
+ " Expected output
+ let expected =<< trim [CODE]
+ {
+ /* Make sure the previous comment leader is not removed. */
+ /* Make sure the previous comment leader is not removed. */
+ /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */
+ /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */
+ // Should the next comment leader be left alone? Yes.
+ // Should the next comment leader be left alone? Yes.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ int i = 7 /* foo *// 3 // comment
+ ;
+ int i = 7 /* foo *// 3 // comment
+ ;
+ ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader.
+ ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader.
+
+ Some code!// Make sure backspacing does not remove this comment leader.
+ }
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+func Test_join_lines()
+ new
+ call setline(1, ['a', 'b', '', 'c', 'd'])
+ %join
+ call assert_equal('a b c d', getline(1))
+ call setline(1, ['a', 'b', '', 'c', 'd'])
+ normal 5J
+ call assert_equal('a b c d', getline(1))
+ call setline(1, ['a', 'b', 'c'])
+ 2,2join
+ call assert_equal(['a', 'b', 'c'], getline(1, '$'))
+ call assert_equal(2, line('.'))
+ 2join
+ call assert_equal(['a', 'b c'], getline(1, '$'))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_json.vim b/src/testdir/test_json.vim
new file mode 100644
index 0000000..96eddc2
--- /dev/null
+++ b/src/testdir/test_json.vim
@@ -0,0 +1,329 @@
+" Test for JSON functions.
+
+let s:json1 = '"str\"in\\g"'
+let s:var1 = "str\"in\\g"
+let s:json2 = '"\u0001\u0002\u0003\u0004\u0005\u0006\u0007"'
+let s:var2 = "\x01\x02\x03\x04\x05\x06\x07"
+let s:json3 = '"\b\t\n\u000b\f\r\u000e\u000f"'
+let s:var3 = "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+let s:json4 = '"\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017"'
+let s:var4 = "\x10\x11\x12\x13\x14\x15\x16\x17"
+let s:json5 = '"\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"'
+let s:var5 = "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+
+" surrogate pair
+let s:jsonsp1 = '"\ud83c\udf63"'
+let s:varsp1 = "\xf0\x9f\x8d\xa3"
+let s:jsonsp2 = '"\ud83c\u00a0"'
+let s:varsp2 = "\ud83c\u00a0"
+
+let s:jsonmb = '"s¢cĴgё"'
+let s:varmb = "s¢cĴgё"
+let s:jsonnr = '1234'
+let s:varnr = 1234
+let s:jsonfl = '12.34'
+let s:varfl = 12.34
+let s:jsonneginf = '-Infinity'
+let s:jsonposinf = 'Infinity'
+let s:varneginf = -1.0 / 0.0
+let s:varposinf = 1.0 / 0.0
+let s:jsonnan = 'NaN'
+let s:varnan = 0.0 / 0.0
+
+let s:jsonl1 = '[1,"a",3]'
+let s:varl1 = [1, "a", 3]
+let s:jsonl2 = '[1,["a",[],"c"],3]'
+let s:jsonl2s = " [\r1 , [ \"a\" , [ ] , \"c\" ] , 3\<Tab>]\r\n"
+let s:varl2 = [1, 2, 3]
+let l2 = ['a', s:varl2, 'c']
+let s:varl2[1] = l2
+let s:varl2x = [1, ["a", [], "c"], 3]
+let s:jsonl3 = '[[1,2],[1,2]]'
+let l3 = [1, 2]
+let s:varl3 = [l3, l3]
+
+let s:jsond1 = '{"a":1,"b":"bee","c":[1,2]}'
+let s:jsd1 = '{a:1,b:"bee",c:[1,2]}'
+let s:vard1 = {"a": 1, "b": "bee","c": [1,2]}
+let s:jsond2 = '{"1":1,"2":{"a":"aa","b":{},"c":"cc"},"3":3}'
+let s:jsd2 = '{"1":1,"2":{a:"aa",b:{},c:"cc"},"3":3}'
+let s:jsond2s = " { \"1\" : 1 , \"2\" :\n{ \"a\"\r: \"aa\" , \"b\" : {\<Tab>} , \"c\" : \"cc\" } , \"3\" : 3 }\r\n"
+let s:jsd2s = " { \"1\" : 1 , \"2\" :\n{ a\r: \"aa\" , b : {\<Tab>} , c : \"cc\" } , \"3\" : 3 }\r\n"
+let s:vard2 = {"1": 1, "2": 2, "3": 3}
+let d2 = {"a": "aa", "b": s:vard2, "c": "cc"}
+let s:vard2["2"] = d2
+let s:vard2x = {"1": 1, "2": {"a": "aa", "b": {}, "c": "cc"}, "3": 3}
+let d3 = {"a": 1, "b": 2}
+let s:vard3 = {"x": d3, "y": d3}
+let s:jsond3 = '{"x":{"a":1,"b":2},"y":{"a":1,"b":2}}'
+let s:jsd3 = '{x:{a:1,b:2},y:{a:1,b:2}}'
+let s:vard4 = {"key": v:none}
+let s:vard4x = {"key": v:null}
+let s:jsond4 = '{"key":null}'
+let s:jsd4 = '{key:null}' " js_encode puts no quotes around simple key.
+let s:vard5 = {"key!": v:none}
+let s:vard5x = {"key!": v:null}
+let s:jsond5 = '{"key!":null}'
+let s:jsd5 = '{"key!":null}' " js_encode puts quotes around non-simple key.
+
+let s:jsonvals = '[true,false,null,null]'
+let s:varvals = [v:true, v:false, v:null, v:null]
+
+func Test_json_encode()
+ call assert_equal(s:json1, json_encode(s:var1))
+ call assert_equal(s:json2, json_encode(s:var2))
+ call assert_equal(s:json3, s:var3->json_encode())
+ call assert_equal(s:json4, json_encode(s:var4))
+ call assert_equal(s:json5, json_encode(s:var5))
+
+ call assert_equal(s:jsonmb, json_encode(s:varmb))
+ " no test for surrogate pair, json_encode() doesn't create them.
+
+ call assert_equal(s:jsonnr, json_encode(s:varnr))
+ call assert_equal(s:jsonfl, json_encode(s:varfl))
+ call assert_equal(s:jsonneginf, json_encode(s:varneginf))
+ call assert_equal(s:jsonposinf, json_encode(s:varposinf))
+ call assert_equal(s:jsonnan, json_encode(s:varnan))
+
+ call assert_equal(s:jsonl1, json_encode(s:varl1))
+ call assert_equal(s:jsonl2, json_encode(s:varl2))
+ call assert_equal(s:jsonl3, json_encode(s:varl3))
+
+ call assert_equal(s:jsond1, json_encode(s:vard1))
+ call assert_equal(s:jsond2, json_encode(s:vard2))
+ call assert_equal(s:jsond3, json_encode(s:vard3))
+ call assert_equal(s:jsond4, json_encode(s:vard4))
+ call assert_equal(s:jsond5, json_encode(s:vard5))
+
+ call assert_equal(s:jsonvals, json_encode(s:varvals))
+
+ " JSON is always encoded in utf-8 regardless of 'encoding' value.
+ let save_encoding = &encoding
+ set encoding=latin1
+ call assert_equal('"café"', json_encode("caf\xe9"))
+ let &encoding = save_encoding
+
+ " Invalid utf-8 sequences are replaced with U+FFFD (replacement character)
+ call assert_equal('"foo' . "\ufffd" . '"', json_encode("foo\xAB"))
+
+ call assert_fails('echo json_encode(function("tr"))', 'E1161: Cannot json encode a func')
+ call assert_fails('echo json_encode([function("tr")])', 'E1161: Cannot json encode a func')
+
+ call assert_equal('{"a":""}', json_encode({'a': test_null_string()}))
+ call assert_equal('{"a":[]}', json_encode({"a": test_null_list()}))
+ call assert_equal('{"a":{}}', json_encode({"a": test_null_dict()}))
+
+ silent! let res = json_encode(function("tr"))
+ call assert_equal("", res)
+endfunc
+
+func Test_json_decode()
+ call assert_equal(s:var1, json_decode(s:json1))
+ call assert_equal(s:var2, json_decode(s:json2))
+ call assert_equal(s:var3, s:json3->json_decode())
+ call assert_equal(s:var4, json_decode(s:json4))
+ call assert_equal(s:var5, json_decode(s:json5))
+
+ call assert_equal(s:varmb, json_decode(s:jsonmb))
+ call assert_equal(s:varsp1, json_decode(s:jsonsp1))
+ call assert_equal(s:varsp2, json_decode(s:jsonsp2))
+
+ call assert_equal(s:varnr, json_decode(s:jsonnr))
+ call assert_equal(s:varfl, json_decode(s:jsonfl))
+
+ call assert_equal(s:varl1, json_decode(s:jsonl1))
+ call assert_equal(s:varl2x, json_decode(s:jsonl2))
+ call assert_equal(s:varl2x, json_decode(s:jsonl2s))
+ call assert_equal(s:varl3, json_decode(s:jsonl3))
+
+ call assert_equal(s:vard1, json_decode(s:jsond1))
+ call assert_equal(s:vard2x, json_decode(s:jsond2))
+ call assert_equal(s:vard2x, json_decode(s:jsond2s))
+ call assert_equal(s:vard3, json_decode(s:jsond3))
+ call assert_equal(s:vard4x, json_decode(s:jsond4))
+ call assert_equal(s:vard5x, json_decode(s:jsond5))
+
+ call assert_equal(s:varvals, json_decode(s:jsonvals))
+
+ call assert_equal(v:true, json_decode('true'))
+ call assert_equal(type(v:true), type(json_decode('true')))
+ call assert_equal(v:none, json_decode(''))
+ call assert_equal(type(v:none), type(json_decode('')))
+ call assert_equal("", json_decode('""'))
+
+ " Character in string after \ is ignored if not special.
+ call assert_equal("x", json_decode('"\x"'))
+
+ " JSON is always encoded in utf-8 regardless of 'encoding' value.
+ let save_encoding = &encoding
+ set encoding=latin1
+ call assert_equal("caf\xe9", json_decode('"café"'))
+ let &encoding = save_encoding
+
+ " empty key is OK
+ call assert_equal({'': 'ok'}, json_decode('{"": "ok"}'))
+ " but not twice
+ call assert_fails("call json_decode('{\"\": \"ok\", \"\": \"bad\"}')", 'E938:')
+
+ call assert_equal({'n': 1}, json_decode('{"n":1,}'))
+ call assert_fails("call json_decode(\"{'n':'1',}\")", 'E491:')
+ call assert_fails("call json_decode(\"'n'\")", 'E491:')
+
+ call assert_fails('call json_decode("\"")', "E491:")
+ call assert_fails('call json_decode("blah")', "E491:")
+ call assert_fails('call json_decode("true blah")', "E488:")
+ call assert_fails('call json_decode("<foobar>")', "E491:")
+ call assert_fails('call json_decode("{\"a\":1,\"a\":2}")', "E938:")
+
+ call assert_fails('call json_decode("{")', "E491:")
+ call assert_fails('call json_decode("{foobar}")', "E491:")
+ call assert_fails('call json_decode("{\"n\",")', "E491:")
+ call assert_fails('call json_decode("{\"n\":")', "E491:")
+ call assert_fails('call json_decode("{\"n\":1")', "E491:")
+ call assert_fails('call json_decode("{\"n\":1,")', "E491:")
+ call assert_fails('call json_decode("{\"n\",1}")', "E491:")
+ call assert_fails('call json_decode("{-}")', "E491:")
+ call assert_fails('call json_decode("{3.14:1}")', "E806:")
+
+ call assert_fails('call json_decode("[foobar]")', "E491:")
+ call assert_fails('call json_decode("[")', "E491:")
+ call assert_fails('call json_decode("[1")', "E491:")
+ call assert_fails('call json_decode("[1,")', "E491:")
+ call assert_fails('call json_decode("[1 2]")', "E491:")
+
+ call assert_fails('call json_decode("[1,,2]")', "E491:")
+
+ call assert_fails('call json_decode("{{}:42}")', "E491:")
+ call assert_fails('call json_decode("{[]:42}")', "E491:")
+ call assert_fails('call json_decode("{1:1{")', "E491:")
+
+ call assert_fails('call json_decode("-")', "E491:")
+ call assert_fails('call json_decode("-1x")', "E491:")
+ call assert_fails('call json_decode("infinit")', "E491:")
+
+ call assert_fails('call json_decode("\"\\u111Z\"")', 'E491:')
+ call assert_equal('[😂]', json_decode('"[\uD83D\uDE02]"'))
+ call assert_equal('a😂b', json_decode('"a\uD83D\uDE02b"'))
+
+ call assert_fails('call json_decode("{\"\":{\"\":{")', 'E491:')
+endfunc
+
+let s:jsl5 = '[7,,,]'
+let s:varl5 = [7, v:none, v:none]
+
+func Test_js_encode()
+ call assert_equal(s:json1, js_encode(s:var1))
+ call assert_equal(s:json2, js_encode(s:var2))
+ call assert_equal(s:json3, s:var3->js_encode())
+ call assert_equal(s:json4, js_encode(s:var4))
+ call assert_equal(s:json5, js_encode(s:var5))
+
+ call assert_equal(s:jsonmb, js_encode(s:varmb))
+ " no test for surrogate pair, js_encode() doesn't create them.
+
+ call assert_equal(s:jsonnr, js_encode(s:varnr))
+ call assert_equal(s:jsonfl, js_encode(s:varfl))
+ call assert_equal(s:jsonneginf, js_encode(s:varneginf))
+ call assert_equal(s:jsonposinf, js_encode(s:varposinf))
+ call assert_equal(s:jsonnan, js_encode(s:varnan))
+
+ call assert_equal(s:jsonl1, js_encode(s:varl1))
+ call assert_equal(s:jsonl2, js_encode(s:varl2))
+ call assert_equal(s:jsonl3, js_encode(s:varl3))
+
+ call assert_equal(s:jsd1, js_encode(s:vard1))
+ call assert_equal(s:jsd2, js_encode(s:vard2))
+ call assert_equal(s:jsd3, js_encode(s:vard3))
+ call assert_equal(s:jsd4, js_encode(s:vard4))
+ call assert_equal(s:jsd5, js_encode(s:vard5))
+
+ call assert_equal(s:jsonvals, js_encode(s:varvals))
+
+ call assert_fails('echo js_encode(function("tr"))', 'E1161: Cannot json encode a func')
+ call assert_fails('echo js_encode([function("tr")])', 'E1161: Cannot json encode a func')
+
+ silent! let res = js_encode(function("tr"))
+ call assert_equal("", res)
+
+ call assert_equal(s:jsl5, js_encode(s:varl5))
+endfunc
+
+func Test_js_decode()
+ call assert_equal(s:var1, js_decode(s:json1))
+ call assert_equal(s:var2, js_decode(s:json2))
+ call assert_equal(s:var3, s:json3->js_decode())
+ call assert_equal(s:var4, js_decode(s:json4))
+ call assert_equal(s:var5, js_decode(s:json5))
+
+ call assert_equal(s:varmb, js_decode(s:jsonmb))
+ call assert_equal(s:varsp1, js_decode(s:jsonsp1))
+ call assert_equal(s:varsp2, js_decode(s:jsonsp2))
+
+ call assert_equal(s:varnr, js_decode(s:jsonnr))
+ call assert_equal(s:varfl, js_decode(s:jsonfl))
+ call assert_equal(s:varneginf, js_decode(s:jsonneginf))
+ call assert_equal(s:varposinf, js_decode(s:jsonposinf))
+ call assert_true(isnan(js_decode(s:jsonnan)))
+
+ call assert_equal(s:varl1, js_decode(s:jsonl1))
+ call assert_equal(s:varl2x, js_decode(s:jsonl2))
+ call assert_equal(s:varl2x, js_decode(s:jsonl2s))
+ call assert_equal(s:varl3, js_decode(s:jsonl3))
+
+ call assert_equal(s:vard1, js_decode(s:jsond1))
+ call assert_equal(s:vard1, js_decode(s:jsd1))
+ call assert_equal(s:vard2x, js_decode(s:jsond2))
+ call assert_equal(s:vard2x, js_decode(s:jsd2))
+ call assert_equal(s:vard2x, js_decode(s:jsond2s))
+ call assert_equal(s:vard2x, js_decode(s:jsd2s))
+ call assert_equal(s:vard3, js_decode(s:jsond3))
+ call assert_equal(s:vard3, js_decode(s:jsd3))
+ call assert_equal(s:vard4x, js_decode(s:jsond4))
+ call assert_equal(s:vard4x, js_decode(s:jsd4))
+ call assert_equal(s:vard5x, js_decode(s:jsond5))
+ call assert_equal(s:vard5x, js_decode(s:jsd5))
+
+ call assert_equal(s:varvals, js_decode(s:jsonvals))
+
+ call assert_equal(v:true, js_decode('true'))
+ call assert_equal(type(v:true), type(js_decode('true')))
+ call assert_equal(v:none, js_decode(''))
+ call assert_equal(type(v:none), type(js_decode('')))
+ call assert_equal("", js_decode('""'))
+ call assert_equal("", js_decode("''"))
+
+ call assert_equal('n', js_decode("'n'"))
+ call assert_equal({'n': 1}, js_decode('{"n":1,}'))
+ call assert_equal({'n': '1'}, js_decode("{'n':'1',}"))
+
+ call assert_fails('call js_decode("\"")', "E491:")
+ call assert_fails('call js_decode("blah")', "E491:")
+ call assert_fails('call js_decode("true blah")', "E488:")
+ call assert_fails('call js_decode("<foobar>")', "E491:")
+
+ call assert_fails('call js_decode("{")', "E491:")
+ call assert_fails('call js_decode("{foobar}")', "E491:")
+ call assert_fails('call js_decode("{\"n\",")', "E491:")
+ call assert_fails('call js_decode("{\"n\":")', "E491:")
+ call assert_fails('call js_decode("{\"n\":1")', "E491:")
+ call assert_fails('call js_decode("{\"n\":1,")', "E491:")
+ call assert_fails('call js_decode("{\"n\",1}")', "E491:")
+ call assert_fails('call js_decode("{-}")', "E491:")
+
+ call assert_fails('call js_decode("[foobar]")', "E491:")
+ call assert_fails('call js_decode("[")', "E491:")
+ call assert_fails('call js_decode("[1")', "E491:")
+ call assert_fails('call js_decode("[1,")', "E491:")
+ call assert_fails('call js_decode("[1 2]")', "E491:")
+
+ call assert_equal(s:varl5, js_decode(s:jsl5))
+endfunc
+
+func Test_json_encode_long()
+ " The growarray uses a grow size of 4000, check that a result that is exactly
+ " 4000 bytes long is not missing the final NUL.
+ let json = json_encode([repeat('a', 3996)])
+ call assert_equal(4000, len(json))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_jumplist.vim b/src/testdir/test_jumplist.vim
new file mode 100644
index 0000000..4b5e321
--- /dev/null
+++ b/src/testdir/test_jumplist.vim
@@ -0,0 +1,101 @@
+" Tests for the jumplist functionality
+
+" Tests for the getjumplist() function
+func Test_getjumplist()
+ %bwipe
+ clearjumps
+ call assert_equal([[], 0], getjumplist())
+ call assert_equal([[], 0], getjumplist(1))
+ call assert_equal([[], 0], getjumplist(1, 1))
+
+ call assert_equal([], getjumplist(100))
+ call assert_equal([], getjumplist(1, 100))
+
+ let lines = []
+ for i in range(1, 100)
+ call add(lines, "Line " . i)
+ endfor
+ call writefile(lines, "Xtest", 'D')
+
+ " Jump around and create a jump list
+ edit Xtest
+ let bnr = bufnr('%')
+ normal 50%
+ normal G
+ normal gg
+
+ let expected = [[
+ \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 100, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 3]
+ call assert_equal(expected, getjumplist())
+ " jumplist doesn't change in between calls
+ call assert_equal(expected, getjumplist())
+
+ " Traverse the jump list and verify the results
+ 5
+ exe "normal \<C-O>"
+ call assert_equal(2, 1->getjumplist()[1])
+ exe "normal 2\<C-O>"
+ call assert_equal(0, getjumplist(1, 1)[1])
+ exe "normal 3\<C-I>"
+ call assert_equal(3, getjumplist()[1])
+ exe "normal \<C-O>"
+ normal 20%
+ let expected = [[
+ \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 5, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 100, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 4]
+ call assert_equal(expected, getjumplist())
+ " jumplist doesn't change in between calls
+ call assert_equal(expected, getjumplist())
+
+ let l = getjumplist()
+ call test_garbagecollect_now()
+ call assert_equal(4, l[1])
+ clearjumps
+ call test_garbagecollect_now()
+ call assert_equal(4, l[1])
+endfunc
+
+func Test_jumplist_invalid()
+ new
+ clearjumps
+ " put some randome text
+ put ='a'
+ let prev = bufnr('%')
+ setl nomodified bufhidden=wipe
+ e XXJumpListBuffer
+ let bnr = bufnr('%')
+ " 1) empty jumplist
+ let expected = [[
+ \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}], 1]
+ call assert_equal(expected, getjumplist())
+ let jumps = execute(':jumps')
+ call assert_equal('>', jumps[-1:])
+ " now jump back
+ exe ":norm! \<c-o>"
+ let expected = [[
+ \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0},
+ \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 0]
+ call assert_equal(expected, getjumplist())
+ let jumps = execute(':jumps')
+ call assert_match('> 0 2 0 -invalid-', jumps)
+endfunc
+
+" Test for '' mark in an empty buffer
+
+func Test_empty_buffer()
+ new
+ insert
+a
+b
+c
+d
+.
+ call assert_equal(1, line("''"))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim
new file mode 100644
index 0000000..250ce5c
--- /dev/null
+++ b/src/testdir/test_lambda.vim
@@ -0,0 +1,384 @@
+" Test for lambda and closure
+
+source check.vim
+import './vim9.vim' as v9
+
+func Test_lambda_feature()
+ call assert_equal(1, has('lambda'))
+endfunc
+
+func Test_lambda_with_filter()
+ let s:x = 2
+ call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
+endfunc
+
+func Test_lambda_with_map()
+ let s:x = 1
+ call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
+endfunc
+
+func Test_lambda_with_sort()
+ call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
+endfunc
+
+func Test_lambda_with_timer()
+ CheckFeature timers
+
+ let s:n = 0
+ let s:timer_id = 0
+ func! s:Foo()
+ let s:timer_id = timer_start(10, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
+ endfunc
+
+ call s:Foo()
+ " check timer works
+ for i in range(0, 10)
+ if s:n > 0
+ break
+ endif
+ sleep 10m
+ endfor
+
+ " do not collect lambda
+ call test_garbagecollect_now()
+
+ " check timer still works
+ let m = s:n
+ for i in range(0, 10)
+ if s:n > m
+ break
+ endif
+ sleep 10m
+ endfor
+
+ call timer_stop(s:timer_id)
+ call assert_true(s:n > m)
+endfunc
+
+func Test_lambda_vim9cmd_linebreak()
+ CheckFeature timers
+
+ let g:test_is_flaky = 1
+ let lines =<< trim END
+ vim9cmd call timer_start(10, (x) => {
+ # comment
+ g:result = 'done'
+ })
+ END
+ call v9.CheckScriptSuccess(lines)
+ " sleep longer on a retry
+ exe 'sleep ' .. [20, 100, 500, 500, 500][g:run_nr] .. 'm'
+ call assert_equal('done', g:result)
+ unlet g:result
+
+ let lines =<< trim END
+ g:result = [0]->map((_, v) =>
+ 1 # inline comment
+ +
+ 2
+ )
+ assert_equal([3], g:result)
+ END
+ call v9.CheckDefAndScriptSuccess(lines)
+endfunc
+
+def Test_lamba_compiled_linebreak()
+ var lines =<< trim END
+ vim9script
+
+ def Echo(what: any)
+ assert_equal('hello world', what)
+ enddef
+ def That()
+ printf("hello ")
+ ->((x) => x .. "world")()
+ ->Echo()
+ enddef
+ That()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+func Test_lambda_with_partial()
+ let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
+ call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
+endfunc
+
+function Test_lambda_fails()
+ call assert_equal(3, {a, b -> a + b}(1, 2))
+ call assert_fails('echo {a, a -> a + a}(1, 2)', 'E853:')
+ call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E451:')
+ echo assert_fails('echo 10->{a -> a + 2}', 'E107:')
+
+ call assert_fails('eval 0->(', "E110: Missing ')'")
+ call assert_fails('eval 0->(3)()', "E1275:")
+ call assert_fails('eval 0->([3])()', "E1275:")
+ call assert_fails('eval 0->({"a": 3})()', "E1275:")
+ call assert_fails('eval 0->(xxx)()', "E121:")
+endfunc
+
+func Test_not_lamda()
+ let x = {'>' : 'foo'}
+ call assert_equal('foo', x['>'])
+endfunc
+
+func Test_lambda_capture_by_reference()
+ let v = 1
+ let l:F = {x -> x + v}
+ let v = 2
+ call assert_equal(12, l:F(10))
+endfunc
+
+func Test_lambda_side_effect()
+ func! s:update_and_return(arr)
+ let a:arr[1] = 5
+ return a:arr
+ endfunc
+
+ func! s:foo(arr)
+ return {-> s:update_and_return(a:arr)}
+ endfunc
+
+ let arr = [3,2,1]
+ call assert_equal([3, 5, 1], s:foo(arr)())
+endfunc
+
+func Test_lambda_refer_local_variable_from_other_scope()
+ func! s:foo(X)
+ return a:X() " refer l:x in s:bar()
+ endfunc
+
+ func! s:bar()
+ let x = 123
+ return s:foo({-> x})
+ endfunc
+
+ call assert_equal(123, s:bar())
+endfunc
+
+func Test_lambda_do_not_share_local_variable()
+ func! s:define_funcs()
+ let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
+ let l:Two = {-> exists("a") ? a : "no"}
+ return [l:One, l:Two]
+ endfunc
+
+ let l:F = s:define_funcs()
+
+ call assert_equal('no', l:F[1]())
+ call assert_equal('abc', l:F[0]())
+ call assert_equal('no', l:F[1]())
+endfunc
+
+func Test_lambda_closure_counter()
+ func! s:foo()
+ let x = 0
+ return {-> [execute("let x += 1"), x][-1]}
+ endfunc
+
+ let l:F = s:foo()
+ call test_garbagecollect_now()
+ call assert_equal(1, l:F())
+ call assert_equal(2, l:F())
+ call assert_equal(3, l:F())
+ call assert_equal(4, l:F())
+endfunc
+
+func Test_lambda_with_a_var()
+ func! s:foo()
+ let x = 2
+ return {... -> a:000 + [x]}
+ endfunc
+ func! s:bar()
+ return s:foo()(1)
+ endfunc
+
+ call assert_equal([1, 2], s:bar())
+endfunc
+
+func Test_lambda_call_lambda_from_lambda()
+ func! s:foo(x)
+ let l:F1 = {-> {-> a:x}}
+ return {-> l:F1()}
+ endfunc
+
+ let l:F = s:foo(1)
+ call assert_equal(1, l:F()())
+endfunc
+
+func Test_lambda_delfunc()
+ func! s:gen()
+ let pl = l:
+ let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
+ let l:Bar = l:Foo
+ delfunction l:Foo
+ return l:Bar
+ endfunc
+
+ let l:F = s:gen()
+ call assert_fails(':call l:F()', 'E933:')
+endfunc
+
+func Test_lambda_scope()
+ func! s:NewCounter()
+ let c = 0
+ return {-> [execute('let c += 1'), c][-1]}
+ endfunc
+
+ func! s:NewCounter2()
+ return {-> [execute('let c += 100'), c][-1]}
+ endfunc
+
+ let l:C = s:NewCounter()
+ let l:D = s:NewCounter2()
+
+ call assert_equal(1, l:C())
+ call assert_fails(':call l:D()', 'E121:')
+ call assert_equal(2, l:C())
+endfunc
+
+func Test_lambda_share_scope()
+ func! s:New()
+ let c = 0
+ let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
+ let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
+ return [l:Inc0, l:Dec0]
+ endfunc
+
+ let [l:Inc, l:Dec] = s:New()
+
+ call assert_equal(1, l:Inc())
+ call assert_equal(2, l:Inc())
+ call assert_equal(1, l:Dec())
+endfunc
+
+func Test_lambda_circular_reference()
+ func! s:Foo()
+ let d = {}
+ let d.f = {-> d}
+ return d.f
+ endfunc
+
+ call s:Foo()
+ call test_garbagecollect_now()
+ let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
+ call test_garbagecollect_now()
+endfunc
+
+func Test_lambda_combination()
+ call assert_equal(2, {x -> {x -> x}}(1)(2))
+ call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
+ call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
+ call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
+
+ call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
+ call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
+
+ " Z combinator
+ let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
+ let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
+ call assert_equal(120, Z(Fact)(5))
+endfunc
+
+func Test_closure_counter()
+ func! s:foo()
+ let x = 0
+ func! s:bar() closure
+ let x += 1
+ return x
+ endfunc
+ return function('s:bar')
+ endfunc
+
+ let l:F = s:foo()
+ call test_garbagecollect_now()
+ call assert_equal(1, l:F())
+ call assert_equal(2, l:F())
+ call assert_equal(3, l:F())
+ call assert_equal(4, l:F())
+
+ call assert_match("^\n function <SNR>\\d\\+_bar() closure"
+ \ .. "\n1 let x += 1"
+ \ .. "\n2 return x"
+ \ .. "\n endfunction$", execute('func s:bar'))
+endfunc
+
+func Test_closure_unlet()
+ func! s:foo()
+ let x = 1
+ func! s:bar() closure
+ unlet x
+ endfunc
+ call s:bar()
+ return l:
+ endfunc
+
+ call assert_false(has_key(s:foo(), 'x'))
+ call test_garbagecollect_now()
+endfunc
+
+func LambdaFoo()
+ let x = 0
+ func! LambdaBar() closure
+ let x += 1
+ return x
+ endfunc
+ return function('LambdaBar')
+endfunc
+
+func Test_closure_refcount()
+ let g:Count = LambdaFoo()
+ call test_garbagecollect_now()
+ call assert_equal(1, g:Count())
+ let g:Count2 = LambdaFoo()
+ call test_garbagecollect_now()
+ call assert_equal(1, g:Count2())
+ call assert_equal(2, g:Count())
+ call assert_equal(3, g:Count2())
+
+ delfunc LambdaFoo
+ delfunc LambdaBar
+endfunc
+
+func Test_named_function_closure()
+ func! Afoo()
+ let x = 14
+ func! s:Abar() closure
+ return x
+ endfunc
+ call assert_equal(14, s:Abar())
+ endfunc
+ call Afoo()
+ call assert_equal(14, s:Abar())
+ call test_garbagecollect_now()
+ call assert_equal(14, s:Abar())
+endfunc
+
+func Test_lambda_with_index()
+ let List = {x -> [x]}
+ let Extract = {-> function(List, ['foobar'])()[0]}
+ call assert_equal('foobar', Extract())
+endfunc
+
+func Test_lambda_error()
+ " This was causing a crash
+ call assert_fails('ec{@{->{d->()()', 'E15:')
+endfunc
+
+func Test_closure_error()
+ let l =<< trim END
+ func F1() closure
+ return 1
+ endfunc
+ END
+ call writefile(l, 'Xscript', 'D')
+ let caught_932 = 0
+ try
+ source Xscript
+ catch /E932:/
+ let caught_932 = 1
+ endtry
+ call assert_equal(1, caught_932)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_langmap.vim b/src/testdir/test_langmap.vim
new file mode 100644
index 0000000..aaed77e
--- /dev/null
+++ b/src/testdir/test_langmap.vim
@@ -0,0 +1,89 @@
+" tests for 'langmap'
+
+source check.vim
+CheckFeature langmap
+
+func Test_langmap()
+ new
+ set langmap=}l,^x,%v
+
+ call setline(1, ['abc'])
+ call feedkeys('gg0}^', 'tx')
+ call assert_equal('ac', getline(1))
+
+ " in Replace mode
+ " need silent! to avoid a delay when entering Insert mode
+ call setline(1, ['abcde'])
+ silent! call feedkeys("gg0lR%{z\<Esc>00", 'tx')
+ call assert_equal('a%{ze', getline(1))
+
+ " in Select mode
+ " need silent! to avoid a delay when entering Insert mode
+ call setline(1, ['abcde'])
+ silent! call feedkeys("gg0}%}\<C-G>}^\<Esc>00", 'tx')
+ call assert_equal('a}^de', getline(1))
+
+ " Error cases
+ call assert_fails('set langmap=aA,b', 'E357:')
+ call assert_fails('set langmap=z;y;y;z', 'E358:')
+
+ " Map character > 256
+ enew!
+ set langmap=Äx,ăl,Äx
+ call setline(1, ['abcde'])
+ call feedkeys('gg2lÄ', 'tx')
+ call assert_equal('abde', getline(1))
+
+ " special characters in langmap
+ enew!
+ call setline(1, ['Hello World'])
+ set langmap=\\;\\,,\\,\\;
+ call feedkeys('ggfo,', 'tx')
+ call assert_equal(8, col('.'))
+ call feedkeys(';', 'tx')
+ call assert_equal(5, col('.'))
+ set langmap&
+ set langmap=\\;\\,;\\,\\;
+ call feedkeys('ggfo,', 'tx')
+ call assert_equal(8, col('.'))
+ call feedkeys(';', 'tx')
+ call assert_equal(5, col('.'))
+
+ set langmap=RL
+ let g:counter = 0
+ nnoremap L;L <Cmd>let g:counter += 1<CR>
+ nnoremap <C-L> <Cmd>throw 'This mapping should not be triggered'<CR>
+
+ " 'langmap' is applied to keys without modifiers when matching a mapping
+ call feedkeys('R;R', 'tx')
+ call assert_equal(1, g:counter)
+ nunmap L;L
+ unlet g:counter
+
+ delete
+ call assert_equal('', getline(1))
+ undo
+ call assert_equal('Hello World', getline(1))
+ " 'langmap' does not change Ctrl-R to Ctrl-L for consistency
+ call feedkeys("\<*C-R>", 'tx')
+ call assert_equal('', getline(1))
+
+ set langmap=6L
+ undo
+ setlocal bufhidden=hide
+ let oldbuf = bufnr()
+ enew
+ call assert_notequal(oldbuf, bufnr())
+ " 'langmap' does not change Ctrl-6 to Ctrl-L for consistency
+ " Ctrl-6 becomes Ctrl-^ after merging the Ctrl modifier
+ call feedkeys("\<*C-6>", 'tx')
+ call assert_equal(oldbuf, bufnr())
+ setlocal bufhidden&
+
+ nunmap <C-L>
+
+ set langmap&
+ quit!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_largefile.vim b/src/testdir/test_largefile.vim
new file mode 100644
index 0000000..4d0e2f0
--- /dev/null
+++ b/src/testdir/test_largefile.vim
@@ -0,0 +1,31 @@
+" Tests for large files
+" This is only executed manually: "make test_largefile".
+" This is not run as part of "make test".
+
+func Test_largefile()
+ let fname = 'Xlarge.txt'
+
+ call delete(fname)
+ exe "e" fname
+ " Make sure that a line break is 1 byte (LF).
+ set ff=unix
+ set undolevels=-1
+ " Input 99 'A's. The line becomes 100 bytes including a line break.
+ exe "normal 99iA\<Esc>"
+ yank
+ " Put 39,999,999 times. The file becomes 4,000,000,000 bytes.
+ normal 39999999p
+ " Moving around in the file randomly.
+ normal G
+ normal 10%
+ normal 90%
+ normal 50%
+ normal gg
+ w
+ " Check if the file size is 4,000,000,000 bytes.
+ let fsize=getfsize(fname)
+ call assert_true(fsize == 4000000000)
+ call delete(fname)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_let.vim b/src/testdir/test_let.vim
new file mode 100644
index 0000000..4e4a386
--- /dev/null
+++ b/src/testdir/test_let.vim
@@ -0,0 +1,616 @@
+" Tests for the :let command.
+
+import './vim9.vim' as v9
+
+func Test_let()
+ " Test to not autoload when assigning. It causes internal error.
+ set runtimepath+=./sautest
+ let Test104#numvar = function('tr')
+ call assert_equal("function('tr')", string(Test104#numvar))
+
+ let foo#tr = function('tr')
+ call assert_equal("function('tr')", string(foo#tr))
+ unlet foo#tr
+
+ let a = 1
+ let b = 2
+
+ let out = execute('let a b')
+ let s = "\na #1\nb #2"
+ call assert_equal(s, out)
+
+ let out = execute('let {0 == 1 ? "a" : "b"}')
+ let s = "\nb #2"
+ call assert_equal(s, out)
+
+ let out = execute('let {0 == 1 ? "a" : "b"} a')
+ let s = "\nb #2\na #1"
+ call assert_equal(s, out)
+
+ let out = execute('let a {0 == 1 ? "a" : "b"}')
+ let s = "\na #1\nb #2"
+ call assert_equal(s, out)
+
+ " Test for displaying a string variable
+ let s = 'vim'
+ let out = execute('let s')
+ let s = "\ns vim"
+ call assert_equal(s, out)
+
+ " Test for displaying a list variable
+ let l = [1, 2]
+ let out = execute('let l')
+ let s = "\nl [1, 2]"
+ call assert_equal(s, out)
+
+ " Test for displaying a dict variable
+ let d = {'k' : 'v'}
+ let out = execute('let d')
+ let s = "\nd {'k': 'v'}"
+ call assert_equal(s, out)
+
+ " Test for displaying a function reference variable
+ let F = function('min')
+ let out = execute('let F')
+ let s = "\nF *min()"
+ call assert_equal(s, out)
+
+ let x = 0
+ if 0 | let x = 1 | endif
+ call assert_equal(0, x)
+
+ " Display a list item using an out of range index
+ let l = [10]
+ call assert_fails('let l[1]', 'E684:')
+
+ " List special variable dictionaries
+ let g:Test_Global_Var = 5
+ call assert_match("\nTest_Global_Var #5", execute('let g:'))
+ unlet g:Test_Global_Var
+
+ let b:Test_Buf_Var = 8
+ call assert_match("\nb:Test_Buf_Var #8", execute('let b:'))
+ unlet b:Test_Buf_Var
+
+ let w:Test_Win_Var = 'foo'
+ call assert_equal("\nw:Test_Win_Var foo", execute('let w:'))
+ unlet w:Test_Win_Var
+
+ let t:Test_Tab_Var = 'bar'
+ call assert_equal("\nt:Test_Tab_Var bar", execute('let t:'))
+ unlet t:Test_Tab_Var
+
+ let s:Test_Script_Var = [7]
+ call assert_match("\ns:Test_Script_Var \\[7]", execute('let s:'))
+ unlet s:Test_Script_Var
+
+ let l:Test_Local_Var = {'k' : 5}
+ call assert_match("\nl:Test_Local_Var {'k': 5}", execute('let l:'))
+ call assert_match("v:errors []", execute('let v:'))
+
+ " Test for assigning multiple list items
+ let l = [1, 2, 3]
+ let [l[0], l[1]] = [10, 20]
+ call assert_equal([10, 20, 3], l)
+
+ " Test for errors in conditional expression
+ call assert_fails('let val = [] ? 1 : 2', 'E745:')
+ call assert_fails('let val = 1 ? 5+ : 6', 'E121:')
+ call assert_fails('let val = 1 ? 0 : 5+', 'E15:')
+ call assert_false(exists('val'))
+
+ " Test for errors in logical operators
+ let @a = 'if [] || 0 | let val = 2 | endif'
+ call assert_fails('exe @a', 'E745:')
+ call assert_fails('call feedkeys(":let val = 0 || []\<cr>", "xt")', 'E745:')
+ call assert_fails('exe "let val = [] && 5"', 'E745:')
+ call assert_fails('exe "let val = 6 && []"', 'E745:')
+endfunc
+
+func s:set_arg1(a) abort
+ let a:a = 1
+endfunction
+
+func s:set_arg2(a) abort
+ let a:b = 1
+endfunction
+
+func s:set_arg3(a) abort
+ let b = a:
+ let b['a'] = 1
+endfunction
+
+func s:set_arg4(a) abort
+ let b = a:
+ let b['a'] = 1
+endfunction
+
+func s:set_arg5(a) abort
+ let b = a:
+ let b['a'][0] = 1
+endfunction
+
+func s:set_arg6(a) abort
+ let a:a[0] = 1
+endfunction
+
+func s:set_arg7(a) abort
+ call extend(a:, {'a': 1})
+endfunction
+
+func s:set_arg8(a) abort
+ call extend(a:, {'b': 1})
+endfunction
+
+func s:set_arg9(a) abort
+ let a:['b'] = 1
+endfunction
+
+func s:set_arg10(a) abort
+ let b = a:
+ call extend(b, {'a': 1})
+endfunction
+
+func s:set_arg11(a) abort
+ let b = a:
+ call extend(b, {'b': 1})
+endfunction
+
+func s:set_arg12(a) abort
+ let b = a:
+ let b['b'] = 1
+endfunction
+
+func Test_let_arg_fail()
+ call assert_fails('call s:set_arg1(1)', 'E46:')
+ call assert_fails('call s:set_arg2(1)', 'E461:')
+ call assert_fails('call s:set_arg3(1)', 'E46:')
+ call assert_fails('call s:set_arg4(1)', 'E46:')
+ call assert_fails('call s:set_arg5(1)', 'E46:')
+ call s:set_arg6([0])
+ call assert_fails('call s:set_arg7(1)', 'E742:')
+ call assert_fails('call s:set_arg8(1)', 'E742:')
+ call assert_fails('call s:set_arg9(1)', 'E461:')
+ call assert_fails('call s:set_arg10(1)', 'E742:')
+ call assert_fails('call s:set_arg11(1)', 'E742:')
+ call assert_fails('call s:set_arg12(1)', 'E461:')
+endfunction
+
+func s:set_varg1(...) abort
+ let a:000 = []
+endfunction
+
+func s:set_varg2(...) abort
+ let a:000[0] = 1
+endfunction
+
+func s:set_varg3(...) abort
+ let a:000 += [1]
+endfunction
+
+func s:set_varg4(...) abort
+ call add(a:000, 1)
+endfunction
+
+func s:set_varg5(...) abort
+ let a:000[0][0] = 1
+endfunction
+
+func s:set_varg6(...) abort
+ let b = a:000
+ let b[0] = 1
+endfunction
+
+func s:set_varg7(...) abort
+ let b = a:000
+ let b += [1]
+endfunction
+
+func s:set_varg8(...) abort
+ let b = a:000
+ call add(b, 1)
+endfunction
+
+func s:set_varg9(...) abort
+ let b = a:000
+ let b[0][0] = 1
+endfunction
+
+func Test_let_varg_fail()
+ call assert_fails('call s:set_varg1(1)', 'E46:')
+ call assert_fails('call s:set_varg2(1)', 'E742:')
+ call assert_fails('call s:set_varg3(1)', 'E46:')
+ call assert_fails('call s:set_varg4(1)', 'E742:')
+ call s:set_varg5([0])
+ call assert_fails('call s:set_varg6(1)', 'E742:')
+ call assert_fails('call s:set_varg7(1)', 'E742:')
+ call assert_fails('call s:set_varg8(1)', 'E742:')
+ call s:set_varg9([0])
+endfunction
+
+func Test_let_utf8_environment()
+ let $a = 'ĀĒĪŌŪã‚ã„ã†ãˆãŠ'
+ call assert_equal('ĀĒĪŌŪã‚ã„ã†ãˆãŠ', $a)
+endfunc
+
+func Test_let_no_type_checking()
+ let v = 1
+ let v = [1,2,3]
+ let v = {'a': 1, 'b': 2}
+ let v = 3.4
+ let v = 'hello'
+endfunc
+
+func Test_let_termcap()
+ " Terminal code
+ let old_t_te = &t_te
+ let &t_te = "\<Esc>[yes;"
+ call assert_match('t_te.*^[[yes;', execute("set termcap"))
+ let &t_te = old_t_te
+
+ if exists("+t_k1")
+ " Key code
+ let old_t_k1 = &t_k1
+ let &t_k1 = "that"
+ call assert_match('t_k1.*that', execute("set termcap"))
+ let &t_k1 = old_t_k1
+ endif
+
+ call assert_fails('let x = &t_xx', 'E113:')
+ let &t_xx = "yes"
+ call assert_equal("yes", &t_xx)
+ let &t_xx = ""
+ call assert_fails('let x = &t_xx', 'E113:')
+endfunc
+
+func Test_let_option_error()
+ let _w = &tw
+ let &tw = 80
+ call assert_fails('let &tw .= 1', 'E734:')
+ call assert_equal(80, &tw)
+ let &tw = _w
+
+ let _w = &fillchars
+ let &fillchars = "vert:|"
+ call assert_fails('let &fillchars += "diff:-"', 'E734:')
+ call assert_equal("vert:|", &fillchars)
+ let &fillchars = _w
+endfunc
+
+" Errors with the :let statement
+func Test_let_errors()
+ let s = 'abcd'
+ call assert_fails('let s[1] = 5', 'E689:')
+
+ let l = [1, 2, 3]
+ call assert_fails('let l[:] = 5', 'E709:')
+
+ call assert_fails('let x:lnum=5', ['E121:', 'E121:'])
+ call assert_fails('let v:=5', 'E461:')
+ call assert_fails('let [a]', 'E474:')
+ call assert_fails('let [a, b] = [', 'E697:')
+ call assert_fails('let [a, b] = [10, 20', 'E696:')
+ call assert_fails('let [a, b] = 10', 'E714:')
+ call assert_fails('let [a, , b] = [10, 20]', 'E475:')
+ call assert_fails('let [a, b&] = [10, 20]', 'E475:')
+ call assert_fails('let $ = 10', 'E475:')
+ call assert_fails('let $FOO[1] = "abc"', 'E18:')
+ call assert_fails('let &buftype[1] = "nofile"', 'E18:')
+ let s = "var"
+ let var = 1
+ call assert_fails('let var += [1,2]', 'E734:')
+ call assert_fails('let {s}.1 = 2', 'E1203:')
+ call assert_fails('let a[1] = 5', 'E121:')
+ let l = [[1,2]]
+ call assert_fails('let l[:][0] = [5]', 'E708:')
+ let d = {'k' : 4}
+ call assert_fails('let d.# = 5', 'E488:')
+ call assert_fails('let d.m += 5', 'E716:')
+ call assert_fails('let m = d[{]', 'E15:')
+ let l = [1, 2]
+ call assert_fails('let l[2] = 0', 'E684:')
+ call assert_fails('let l[0:1] = [1, 2, 3]', 'E710:')
+ call assert_fails('let l[-2:-3] = [3, 4]', 'E684:')
+ call assert_fails('let l[0:4] = [5, 6]', 'E711:')
+ call assert_fails('let l -= 2', 'E734:')
+ call assert_fails('let l += 2', 'E734:')
+ call assert_fails('let g:["a;b"] = 10', 'E461:')
+ call assert_fails('let g:.min = function("max")', 'E704:')
+ call assert_fails('let g:cos = "" | let g:.cos = {-> 42}', 'E704:')
+ if has('channel')
+ let ch = test_null_channel()
+ call assert_fails('let ch += 1', 'E734:')
+ endif
+ call assert_fails('let name = "a" .. "b",', 'E488: Trailing characters: ,')
+
+ " This test works only when the language is English
+ if v:lang == "C" || v:lang =~ '^[Ee]n'
+ call assert_fails('let [a ; b;] = [10, 20]',
+ \ 'Double ; in list of variables')
+ endif
+endfunc
+
+func Test_let_heredoc_fails()
+ call assert_fails('let v =<< marker', 'E991:')
+ try
+ exe "let v =<< TEXT | abc | TEXT"
+ call assert_report('No exception thrown')
+ catch /E488:/
+ catch
+ call assert_report("Caught exception: " .. v:exception)
+ endtry
+
+ let text =<< trim END
+ func WrongSyntax()
+ let v =<< that there
+ endfunc
+ END
+ call writefile(text, 'XheredocFail', 'D')
+ call assert_fails('source XheredocFail', 'E1145:')
+
+ let text =<< trim CodeEnd
+ func MissingEnd()
+ let v =<< END
+ endfunc
+ CodeEnd
+ call writefile(text, 'XheredocWrong', 'D')
+ call assert_fails('source XheredocWrong', 'E1145:')
+
+ let text =<< trim TEXTend
+ let v =<< " comment
+ TEXTend
+ call writefile(text, 'XheredocNoMarker', 'D')
+ call assert_fails('source XheredocNoMarker', 'E172:')
+
+ let text =<< trim TEXTend
+ let v =<< text
+ TEXTend
+ call writefile(text, 'XheredocBadMarker', 'D')
+ call assert_fails('source XheredocBadMarker', 'E221:')
+
+ call writefile(['let v =<< TEXT', 'abc'], 'XheredocMissingMarker', 'D')
+ call assert_fails('source XheredocMissingMarker', 'E990:')
+endfunc
+
+func Test_let_heredoc_trim_no_indent_marker()
+ let text =<< trim END
+ Text
+ with
+ indent
+END
+ call assert_equal(['Text', 'with', 'indent'], text)
+endfunc
+
+func Test_let_interpolated()
+ call assert_equal('{text}', $'{{text}}')
+ call assert_equal('{{text}}', $'{{{{text}}}}')
+ let text = 'text'
+ call assert_equal('text{{', $'{text .. "{{"}')
+ call assert_equal('text{{', $"{text .. '{{'}")
+ call assert_equal('text{{', $'{text .. '{{'}')
+ call assert_equal('text{{', $"{text .. "{{"}")
+endfunc
+
+" Test for the setting a variable using the heredoc syntax.
+" Keep near the end, this messes up highlighting.
+func Test_let_heredoc()
+ let var1 =<< END
+Some sample text
+ Text with indent
+ !@#$%^&*()-+_={}|[]\~`:";'<>?,./
+END
+
+ call assert_equal(["Some sample text", "\tText with indent", " !@#$%^&*()-+_={}|[]\\~`:\";'<>?,./"], var1)
+
+ let var2 =<< XXX
+Editor
+XXX
+ call assert_equal(['Editor'], var2)
+
+ let var3 =<<END
+END
+ call assert_equal([], var3)
+
+ let var3 =<<END
+vim
+
+end
+ END
+END
+END
+ call assert_equal(['vim', '', 'end', ' END', 'END '], var3)
+
+ let var1 =<< trim END
+ Line1
+ Line2
+ Line3
+ END
+ END
+ call assert_equal(['Line1', ' Line2', "\tLine3", ' END'], var1)
+
+ let var1 =<< trim !!!
+ Line1
+ line2
+ Line3
+ !!!
+ !!!
+ call assert_equal(['Line1', ' line2', "\tLine3", '!!!',], var1)
+
+ let var1 =<< trim XX
+ Line1
+ XX
+ call assert_equal(['Line1'], var1)
+
+ " ignore "endfunc"
+ let var1 =<< END
+something
+endfunc
+END
+ call assert_equal(['something', 'endfunc'], var1)
+
+ " ignore "endfunc" with trim
+ let var1 =<< trim END
+ something
+ endfunc
+ END
+ call assert_equal(['something', 'endfunc'], var1)
+
+ " not concatenate lines
+ let var1 =<< END
+some
+ \thing
+ \ else
+END
+ call assert_equal(['some', ' \thing', ' \ else'], var1)
+
+ " ignore "python << xx"
+ let var1 =<<END
+something
+python << xx
+END
+ call assert_equal(['something', 'python << xx'], var1)
+
+ " ignore "python << xx" with trim
+ let var1 =<< trim END
+ something
+ python << xx
+ END
+ call assert_equal(['something', 'python << xx'], var1)
+
+ " ignore "append"
+ let var1 =<< E
+something
+app
+E
+ call assert_equal(['something', 'app'], var1)
+
+ " ignore "append" with trim
+ let var1 =<< trim END
+ something
+ app
+ END
+ call assert_equal(['something', 'app'], var1)
+
+ let check = []
+ if 0
+ let check =<< trim END
+ from heredoc
+ END
+ endif
+ call assert_equal([], check)
+
+ " unpack assignment
+ let [a, b, c] =<< END
+ x
+ \y
+ z
+END
+ call assert_equal([' x', ' \y', ' z'], [a, b, c])
+endfunc
+
+" Test for evaluating Vim expressions in a heredoc using {expr}
+" Keep near the end, this messes up highlighting.
+func Test_let_heredoc_eval()
+ let str = ''
+ let code =<< trim eval END
+ let a = {5 + 10}
+ let b = {min([10, 6])} + {max([4, 6])}
+ {str}
+ let c = "abc{str}d"
+ END
+ call assert_equal(['let a = 15', 'let b = 6 + 6', '', 'let c = "abcd"'], code)
+
+ let $TESTVAR = "Hello"
+ let code =<< eval trim END
+ let s = "{$TESTVAR}"
+ END
+ call assert_equal(['let s = "Hello"'], code)
+
+ let code =<< eval END
+ let s = "{$TESTVAR}"
+END
+ call assert_equal([' let s = "Hello"'], code)
+
+ let a = 10
+ let data =<< eval END
+{a}
+END
+ call assert_equal(['10'], data)
+
+ let x = 'X'
+ let code =<< eval trim END
+ let a = {{abc}}
+ let b = {x}
+ let c = {{
+ END
+ call assert_equal(['let a = {abc}', 'let b = X', 'let c = {'], code)
+
+ let code = 'xxx'
+ let code =<< eval trim END
+ let n = {5 +
+ 6}
+ END
+ call assert_equal('xxx', code)
+
+ let code =<< eval trim END
+ let n = {min([1, 2]} + {max([3, 4])}
+ END
+ call assert_equal('xxx', code)
+
+ let lines =<< trim LINES
+ let text =<< eval trim END
+ let b = {
+ END
+ LINES
+ call v9.CheckScriptFailure(lines, 'E1279:')
+
+ let lines =<< trim LINES
+ let text =<< eval trim END
+ let b = {abc
+ END
+ LINES
+ call v9.CheckScriptFailure(lines, 'E1279:')
+
+ let lines =<< trim LINES
+ let text =<< eval trim END
+ let b = {}
+ END
+ LINES
+ call v9.CheckScriptFailure(lines, 'E15:')
+
+ " skipped heredoc
+ if 0
+ let msg =<< trim eval END
+ n is: {n}
+ END
+ endif
+
+ " Test for sourcing a script containing a heredoc with invalid expression.
+ " Variable assignment should fail, if expression evaluation fails
+ new
+ let g:Xvar = 'test'
+ let g:b = 10
+ let lines =<< trim END
+ let Xvar =<< eval CODE
+ let a = 1
+ let b = {5+}
+ let c = 2
+ CODE
+ let g:Count += 1
+ END
+ call setline(1, lines)
+ let g:Count = 0
+ call assert_fails('source', 'E15:')
+ call assert_equal(1, g:Count)
+ call setline(3, 'let b = {abc}')
+ call assert_fails('source', 'E121:')
+ call assert_equal(2, g:Count)
+ call setline(3, 'let b = {abc} + {min([9, 4])} + 2')
+ call assert_fails('source', 'E121:')
+ call assert_equal(3, g:Count)
+ call assert_equal('test', g:Xvar)
+ call assert_equal(10, g:b)
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_lineending.vim b/src/testdir/test_lineending.vim
new file mode 100644
index 0000000..3f214e4
--- /dev/null
+++ b/src/testdir/test_lineending.vim
@@ -0,0 +1,22 @@
+" Tests for saving/loading a file with some lines ending in
+" CTRL-M, some not
+
+func Test_lineending()
+ let l = ["this line ends in a\<CR>",
+ \ "this one doesn't",
+ \ "this one does\<CR>",
+ \ "and the last one doesn't"]
+ set ta tx
+ enew!
+ call append(0, l)
+ $delete
+ write Xfile1
+ bwipe Xfile1
+ edit Xfile1
+ let t = getline(1, '$')
+ call assert_equal(l, t)
+ new | only
+ call delete('Xfile1')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_lispindent.vim b/src/testdir/test_lispindent.vim
new file mode 100644
index 0000000..770c501
--- /dev/null
+++ b/src/testdir/test_lispindent.vim
@@ -0,0 +1,129 @@
+" Tests for 'lispwords' settings being global-local.
+" And other lisp indent stuff.
+
+set nocompatible viminfo+=nviminfo
+
+func Test_global_local_lispwords()
+ setglobal lispwords=foo,bar,baz
+ setlocal lispwords-=foo | setlocal lispwords+=quux
+ call assert_equal('foo,bar,baz', &g:lispwords)
+ call assert_equal('bar,baz,quux', &l:lispwords)
+ call assert_equal('bar,baz,quux', &lispwords)
+
+ setlocal lispwords<
+ call assert_equal('foo,bar,baz', &g:lispwords)
+ call assert_equal('foo,bar,baz', &l:lispwords)
+ call assert_equal('foo,bar,baz', &lispwords)
+endfunc
+
+def Test_lisp_indent()
+ enew!
+
+ append(0, [
+ '(defun html-file (base)',
+ '(format nil "~(~A~).html" base))',
+ '',
+ '(defmacro page (name title &rest body)',
+ '(let ((ti (gensym)))',
+ '`(with-open-file (*standard-output*',
+ '(html-file ,name)',
+ ':direction :output',
+ ':if-exists :supersede)',
+ '(let ((,ti ,title))',
+ '(as title ,ti)',
+ '(with center ',
+ '(as h2 (string-upcase ,ti)))',
+ '(brs 3)',
+ ',@body))))',
+ '',
+ ';;; Utilities for generating links',
+ '',
+ '(defmacro with-link (dest &rest body)',
+ '`(progn',
+ '(format t "<a href=\"~A\">" (html-file ,dest))',
+ ',@body',
+ '(princ "</a>")))'
+ ])
+ assert_equal(7, lispindent(2))
+ assert_equal(5, 6->lispindent())
+ assert_fails('lispindent(-1)', 'E966: Invalid line number: -1')
+
+ set lisp
+ set lispwords&
+ var save_copt = &cpoptions
+ set cpoptions+=p
+ normal 1G=G
+
+ assert_equal([
+ '(defun html-file (base)',
+ ' (format nil "~(~A~).html" base))',
+ '',
+ '(defmacro page (name title &rest body)',
+ ' (let ((ti (gensym)))',
+ ' `(with-open-file (*standard-output*',
+ ' (html-file ,name)',
+ ' :direction :output',
+ ' :if-exists :supersede)',
+ ' (let ((,ti ,title))',
+ ' (as title ,ti)',
+ ' (with center ',
+ ' (as h2 (string-upcase ,ti)))',
+ ' (brs 3)',
+ ' ,@body))))',
+ '',
+ ';;; Utilities for generating links',
+ '',
+ '(defmacro with-link (dest &rest body)',
+ ' `(progn',
+ ' (format t "<a href=\"~A\">" (html-file ,dest))',
+ ' ,@body',
+ ' (princ "</a>")))',
+ ''
+ ], getline(1, "$"))
+
+ enew!
+ &cpoptions = save_copt
+ set nolisp
+enddef
+
+func Test_lispindent_negative()
+ " in legacy script there is no error
+ call assert_equal(-1, lispindent(-1))
+endfunc
+
+func Test_lispindent_with_indentexpr()
+ enew
+ setl ai lisp nocin indentexpr=11
+ exe "normal a(x\<CR>1\<CR>2)\<Esc>"
+ let expected = ['(x', ' 1', ' 2)']
+ call assert_equal(expected, getline(1, 3))
+ " with Lisp indenting the first line is not indented
+ normal 1G=G
+ call assert_equal(expected, getline(1, 3))
+
+ %del
+ setl lispoptions=expr:1 indentexpr=5
+ exe "normal a(x\<CR>1\<CR>2)\<Esc>"
+ let expected_expr = ['(x', ' 1', ' 2)']
+ call assert_equal(expected_expr, getline(1, 3))
+ normal 2G2<<=G
+ call assert_equal(expected_expr, getline(1, 3))
+
+ setl lispoptions=expr:0
+ " with Lisp indenting the first line is not indented
+ normal 1G3<<=G
+ call assert_equal(expected, getline(1, 3))
+
+ bwipe!
+endfunc
+
+func Test_lisp_indent_works()
+ " This was reading beyond the end of the line
+ new
+ exe "norm a\tü(\<CR>="
+ set lisp
+ norm ==
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_listchars.vim b/src/testdir/test_listchars.vim
new file mode 100644
index 0000000..2f495c3
--- /dev/null
+++ b/src/testdir/test_listchars.vim
@@ -0,0 +1,693 @@
+" Tests for 'listchars' display with 'list' and :list
+
+source check.vim
+source view_util.vim
+source screendump.vim
+
+func Test_listchars()
+ enew!
+ set ff=unix
+ set list
+
+ set listchars+=tab:>-,space:.,trail:<
+ call append(0, [
+ \ ' aa ',
+ \ ' bb ',
+ \ ' cccc ',
+ \ 'dd ee ',
+ \ ' '
+ \ ])
+ let expected = [
+ \ '>-------aa>-----$',
+ \ '..bb>---<<$',
+ \ '...cccc><$',
+ \ 'dd........ee<<>-$',
+ \ '<$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, '$'->virtcol()))
+ endfor
+
+ set listchars-=trail:<
+ let expected = [
+ \ '>-------aa>-----$',
+ \ '..bb>---..$',
+ \ '...cccc>.$',
+ \ 'dd........ee..>-$',
+ \ '.$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ " tab with 3rd character.
+ set listchars-=tab:>-
+ set listchars+=tab:<=>,trail:-
+ let expected = [
+ \ '<======>aa<====>$',
+ \ '..bb<==>--$',
+ \ '...cccc>-$',
+ \ 'dd........ee--<>$',
+ \ '-$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ " tab with 3rd character and linebreak set
+ set listchars-=tab:<=>
+ set listchars+=tab:<·>
+ set linebreak
+ let expected = [
+ \ '<······>aa<····>$',
+ \ '..bb<··>--$',
+ \ '...cccc>-$',
+ \ 'dd........ee--<>$',
+ \ '-$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+ set nolinebreak
+ set listchars-=tab:<·>
+ set listchars+=tab:<=>
+
+ set listchars-=trail:-
+ let expected = [
+ \ '<======>aa<====>$',
+ \ '..bb<==>..$',
+ \ '...cccc>.$',
+ \ 'dd........ee..<>$',
+ \ '.$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ set listchars-=tab:<=>
+ set listchars+=tab:>-
+ set listchars+=trail:<
+ set nolist
+ normal ggdG
+ call append(0, [
+ \ ' fff ',
+ \ ' gg ',
+ \ ' h ',
+ \ 'iii ',
+ \ ])
+ let l = split(execute("%list"), "\n")
+ call assert_equal([
+ \ '..fff>--<<$',
+ \ '>-------gg>-----$',
+ \ '.....h>-$',
+ \ 'iii<<<<><<$', '$'], l)
+
+ " Test lead and trail
+ normal ggdG
+ set listchars&
+ set listchars+=lead:>,trail:<,space:x
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' gg',
+ \ 'h ',
+ \ ' ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '>>>>ffff<<<<$',
+ \ '>>>>>>>>>>gg$',
+ \ 'h<<<<<<<<<<<$',
+ \ '<<<<<<<<<<<<$',
+ \ '>>>>0xx0<<<<$',
+ \ '$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test multispace
+ normal ggdG
+ set listchars&
+ set listchars+=multispace:yYzZ
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ 'yYzZffffyYzZ$',
+ \ 'yYi iyYzZygg$',
+ \ ' hyYzZyYzZyY$',
+ \ 'yYzZyYzZyYj $',
+ \ 'yYzZ0yY0yYzZ$',
+ \ '$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test leadmultispace + multispace
+ normal ggdG
+ set listchars=eol:$,multispace:yYzZ,nbsp:S
+ set listchars+=leadmultispace:.-+*
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i  gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffffyYzZ$',
+ \ '.-i iSyYzZgg$',
+ \ ' hyYzZyYzZyY$',
+ \ '.-+*.-+*.-j $',
+ \ '.-+*0yY0yYzZ$',
+ \ '$'
+ \ ]
+ redraw!
+ call assert_equal('eol:$,multispace:yYzZ,nbsp:S,leadmultispace:.-+*', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test leadmultispace without multispace
+ normal ggdG
+ set listchars-=multispace:yYzZ
+ set listchars+=space:+,trail:>,eol:$
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffff>>>>$',
+ \ '.-i+i+++++gg$',
+ \ '+h>>>>>>>>>>$',
+ \ '.-+*.-+*.-j>$',
+ \ '.-+*0++0>>>>$',
+ \ '$',
+ \ ]
+
+ redraw!
+ call assert_equal('eol:$,nbsp:S,leadmultispace:.-+*,space:+,trail:>,eol:$', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test leadmultispace only
+ normal ggdG
+ set listchars&
+ set listchars=leadmultispace:.-+*
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffff ',
+ \ '.-i i gg',
+ \ ' h ',
+ \ '.-+*.-+*.-j ',
+ \ '.-+*0 0 ',
+ \ ' ',
+ \ ]
+ redraw!
+ call assert_equal('leadmultispace:.-+*', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, 12))
+ endfor
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " Test leadmultispace and lead and space
+ normal ggdG
+ set listchars&
+ set listchars+=lead:<,space:-
+ set listchars+=leadmultispace:.-+*
+ set list
+
+ call append(0, [
+ \ ' ffff ',
+ \ ' i i gg',
+ \ ' h ',
+ \ ' j ',
+ \ ' 0 0 ',
+ \ ])
+
+ let expected = [
+ \ '.-+*ffff----$',
+ \ '.-i-i-----gg$',
+ \ '<h----------$',
+ \ '.-+*.-+*.-j-$',
+ \ '.-+*0--0----$',
+ \ '$',
+ \ ]
+ redraw!
+ call assert_equal('eol:$,lead:<,space:-,leadmultispace:.-+*', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " the last occurrence of 'multispace:' is used
+ set listchars&
+ set listchars+=multispace:yYzZ
+ set listchars+=space:x,multispace:XyY
+
+ let expected = [
+ \ 'XyYXffffXyYX$',
+ \ 'XyixiXyYXygg$',
+ \ 'xhXyYXyYXyYX$',
+ \ 'XyYXyYXyYXjx$',
+ \ 'XyYX0Xy0XyYX$',
+ \ '$'
+ \ ]
+ redraw!
+ call assert_equal('eol:$,multispace:yYzZ,space:x,multispace:XyY', &listchars)
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ set listchars+=lead:>,trail:<
+
+ let expected = [
+ \ '>>>>ffff<<<<$',
+ \ '>>ixiXyYXygg$',
+ \ '>h<<<<<<<<<<$',
+ \ '>>>>>>>>>>j<$',
+ \ '>>>>0Xy0<<<<$',
+ \ '$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " removing 'multispace:'
+ set listchars-=multispace:XyY
+ set listchars-=multispace:yYzZ
+
+ let expected = [
+ \ '>>>>ffff<<<<$',
+ \ '>>ixixxxxxgg$',
+ \ '>h<<<<<<<<<<$',
+ \ '>>>>>>>>>>j<$',
+ \ '>>>>0xx0<<<<$',
+ \ '$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+
+ call assert_equal(expected, split(execute("%list"), "\n"))
+
+ " test nbsp
+ normal ggdG
+ set listchars=nbsp:X,trail:Y
+ set list
+ " Non-breaking space
+ let nbsp = nr2char(0xa0)
+ call append(0, [ ">" .. nbsp .. "<" ])
+
+ let expected = '>X< '
+
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, virtcol('$')))
+
+ set listchars=nbsp:X
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, virtcol('$')))
+
+ " test extends
+ normal ggdG
+ set listchars=extends:Z
+ set nowrap
+ set nolist
+ call append(0, [ repeat('A', &columns + 1) ])
+
+ let expected = repeat('A', &columns)
+
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, &columns))
+
+ set list
+ let expected = expected[:-2] . 'Z'
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, &columns))
+
+ enew!
+ set listchars& ff&
+endfunc
+
+" Test that unicode listchars characters get properly inserted
+func Test_listchars_unicode()
+ enew!
+ let oldencoding=&encoding
+ set encoding=utf-8
+ set ff=unix
+
+ set listchars=eol:⇔,space:â£,multispace:≡≢≣,nbsp:≠,tab:â†â†”→
+ set list
+
+ let nbsp = nr2char(0xa0)
+ call append(0, [" a\tb c" .. nbsp .. "d "])
+ let expected = ['≡≢≣≡≢≣≡≢aâ†â†”↔↔↔↔→bâ£c≠d≡≢⇔']
+ redraw!
+ call cursor(1, 1)
+ call assert_equal(expected, ScreenLines(1, virtcol('$')))
+
+ set listchars=eol:\\u21d4,space:\\u2423,multispace:≡\\u2262\\U00002263,nbsp:\\U00002260,tab:â†â†”\\u2192
+ redraw!
+ call assert_equal(expected, ScreenLines(1, virtcol('$')))
+
+ set listchars+=lead:⇨,trail:⇦
+ let expected = ['⇨⇨⇨⇨⇨⇨⇨⇨aâ†â†”↔↔↔↔→bâ£c≠d⇦⇦⇔']
+ redraw!
+ call cursor(1, 1)
+ call assert_equal(expected, ScreenLines(1, virtcol('$')))
+
+ let &encoding=oldencoding
+ enew!
+ set listchars& ff&
+endfunction
+
+func Test_listchars_invalid()
+ enew!
+ set ff=unix
+
+ set listchars&
+ set list
+ set ambiwidth=double
+
+ " No colon
+ call assert_fails('set listchars=x', 'E474:')
+ call assert_fails('set listchars=x', 'E474:')
+ call assert_fails('set listchars=multispace', 'E474:')
+ call assert_fails('set listchars=leadmultispace', 'E474:')
+
+ " Too short
+ call assert_fails('set listchars=space:', 'E474:')
+ call assert_fails('set listchars=tab:x', 'E474:')
+ call assert_fails('set listchars=multispace:', 'E474:')
+ call assert_fails('set listchars=leadmultispace:', 'E474:')
+
+ " One occurrence too short
+ call assert_fails('set listchars=space:,space:x', 'E474:')
+ call assert_fails('set listchars=space:x,space:', 'E474:')
+ call assert_fails('set listchars=tab:x,tab:xx', 'E474:')
+ call assert_fails('set listchars=tab:xx,tab:x', 'E474:')
+ call assert_fails('set listchars=multispace:,multispace:x', 'E474:')
+ call assert_fails('set listchars=multispace:x,multispace:', 'E474:')
+ call assert_fails('set listchars=leadmultispace:,leadmultispace:x', 'E474:')
+ call assert_fails('set listchars=leadmultispace:x,leadmultispace:', 'E474:')
+
+ " Too long
+ call assert_fails('set listchars=space:xx', 'E474:')
+ call assert_fails('set listchars=tab:xxxx', 'E474:')
+
+ " Has double-width character
+ call assert_fails('set listchars=space:·', 'E474:')
+ call assert_fails('set listchars=tab:·x', 'E474:')
+ call assert_fails('set listchars=tab:x·', 'E474:')
+ call assert_fails('set listchars=tab:xx·', 'E474:')
+ call assert_fails('set listchars=multispace:·', 'E474:')
+ call assert_fails('set listchars=multispace:xxx·', 'E474:')
+ call assert_fails('set listchars=leadmultispace:·', 'E474:')
+ call assert_fails('set listchars=leadmultispace:xxx·', 'E474:')
+
+ " Has control character
+ call assert_fails("set listchars=space:\x01", 'E474:')
+ call assert_fails("set listchars=tab:\x01x", 'E474:')
+ call assert_fails("set listchars=tab:x\x01", 'E474:')
+ call assert_fails("set listchars=tab:xx\x01", 'E474:')
+ call assert_fails("set listchars=multispace:\x01", 'E474:')
+ call assert_fails("set listchars=multispace:xxx\x01", 'E474:')
+ call assert_fails('set listchars=space:\\x01', 'E474:')
+ call assert_fails('set listchars=tab:\\x01x', 'E474:')
+ call assert_fails('set listchars=tab:x\\x01', 'E474:')
+ call assert_fails('set listchars=tab:xx\\x01', 'E474:')
+ call assert_fails('set listchars=multispace:\\x01', 'E474:')
+ call assert_fails('set listchars=multispace:xxx\\x01', 'E474:')
+ call assert_fails("set listchars=leadmultispace:\x01", 'E474:')
+ call assert_fails('set listchars=leadmultispace:\\x01', 'E474:')
+ call assert_fails("set listchars=leadmultispace:xxx\x01", 'E474:')
+ call assert_fails('set listchars=leadmultispace:xxx\\x01', 'E474:')
+
+ enew!
+ set ambiwidth& listchars& ff&
+endfunction
+
+" Tests that space characters following composing character won't get replaced
+" by listchars.
+func Test_listchars_composing()
+ enew!
+ let oldencoding=&encoding
+ set encoding=utf-8
+ set ff=unix
+ set list
+
+ set listchars=eol:$,space:_,nbsp:=
+
+ let nbsp1 = nr2char(0xa0)
+ let nbsp2 = nr2char(0x202f)
+ call append(0, [
+ \ " \u3099\t \u309A" .. nbsp1 .. nbsp1 .. "\u0302" .. nbsp2 .. nbsp2 .. "\u0302",
+ \ ])
+ let expected = [
+ \ "_ \u3099^I \u309A=" .. nbsp1 .. "\u0302=" .. nbsp2 .. "\u0302$"
+ \ ]
+ redraw!
+ call cursor(1, 1)
+ call assert_equal(expected, ScreenLines(1, virtcol('$')))
+ let &encoding=oldencoding
+ enew!
+ set listchars& ff&
+endfunction
+
+" Check for the value of the 'listchars' option
+func s:CheckListCharsValue(expected)
+ call assert_equal(a:expected, &listchars)
+ call assert_equal(a:expected, getwinvar(0, '&listchars'))
+endfunc
+
+" Test for using a window local value for 'listchars'
+func Test_listchars_window_local()
+ %bw!
+ set list listchars&
+ new
+ " set a local value for 'listchars'
+ setlocal listchars=tab:+-,eol:#
+ call s:CheckListCharsValue('tab:+-,eol:#')
+ " When local value is reset, global value should be used
+ setlocal listchars=
+ call s:CheckListCharsValue('eol:$')
+ " Use 'setlocal <' to copy global value
+ setlocal listchars=space:.,extends:>
+ setlocal listchars<
+ call s:CheckListCharsValue('eol:$')
+ " Use 'set <' to copy global value
+ setlocal listchars=space:.,extends:>
+ set listchars<
+ call s:CheckListCharsValue('eol:$')
+ " Changing global setting should not change the local setting
+ setlocal listchars=space:.,extends:>
+ setglobal listchars=tab:+-,eol:#
+ call s:CheckListCharsValue('space:.,extends:>')
+ " when split opening a new window, local value should be copied
+ split
+ call s:CheckListCharsValue('space:.,extends:>')
+ " clearing local value in one window should not change the other window
+ set listchars&
+ call s:CheckListCharsValue('eol:$')
+ close
+ call s:CheckListCharsValue('space:.,extends:>')
+
+ " use different values for 'listchars' items in two different windows
+ call setline(1, ["\t one two "])
+ setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
+ split
+ setlocal listchars=tab:[.],lead:#,space:_,trail:.,eol:&
+ split
+ set listchars=tab:+-+,lead:^,space:>,trail:<,eol:%
+ call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ " changing the global setting should not change the local value
+ setglobal listchars=tab:[.],lead:#,space:_,trail:.,eol:&
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ set listchars<
+ call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$')))
+
+ " Using setglobal in a window with local setting should not affect the
+ " window. But should impact other windows using the global setting.
+ enew! | only
+ call setline(1, ["\t one two "])
+ set listchars=tab:[.],lead:#,space:_,trail:.,eol:&
+ split
+ setlocal listchars=tab:+-+,lead:^,space:>,trail:<,eol:%
+ split
+ setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
+ setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$')))
+
+ " Setting the global setting to the default value should not impact a window
+ " using a local setting.
+ split
+ setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
+ setglobal listchars&vim
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$')))
+
+ " Setting the local setting to the default value should not impact a window
+ " using a global setting.
+ set listchars=tab:{.},lead:-,space:=,trail:#,eol:$
+ split
+ setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ setlocal listchars&vim
+ call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$')))
+
+ " Using set in a window with a local setting should change it to use the
+ " global setting and also impact other windows using the global setting.
+ split
+ setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ set listchars=tab:+-+,lead:^,space:>,trail:<,eol:%
+ call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
+
+ " Setting invalid value for a local setting should not impact the local and
+ " global settings.
+ split
+ setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
+ let cmd = 'setlocal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x'
+ call assert_fails(cmd, 'E474:')
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
+
+ " Setting invalid value for a global setting should not impact the local and
+ " global settings.
+ split
+ setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:#
+ let cmd = 'setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x'
+ call assert_fails(cmd, 'E474:')
+ call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$')))
+ close
+ call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$')))
+
+ " Closing window with local lcs-multispace should not cause a memory leak.
+ setlocal listchars=multispace:---+
+ split
+ call s:CheckListCharsValue('multispace:---+')
+ close
+
+ %bw!
+ set list& listchars&
+endfunc
+
+func Test_listchars_foldcolumn()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['aaa', '', 'a', 'aaaaaa'])
+ vsplit
+ vsplit
+ windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:<
+ END
+ call writefile(lines, 'XTest_listchars', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60})
+
+ call term_sendkeys(buf, "13\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_01', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_02', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_03', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_04', {})
+ call term_sendkeys(buf, "\<C-W>>")
+ call VerifyScreenDump(buf, 'Test_listchars_05', {})
+ call term_sendkeys(buf, "\<C-W>h")
+ call term_sendkeys(buf, ":set nowrap foldcolumn=4\<CR>")
+ call term_sendkeys(buf, "15\<C-W><")
+ call VerifyScreenDump(buf, 'Test_listchars_06', {})
+ call term_sendkeys(buf, "4\<C-W><")
+ call VerifyScreenDump(buf, 'Test_listchars_07', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim
new file mode 100644
index 0000000..49f278c
--- /dev/null
+++ b/src/testdir/test_listdict.vim
@@ -0,0 +1,1529 @@
+" Tests for the List and Dict types
+scriptencoding utf-8
+
+import './vim9.vim' as v9
+
+func TearDown()
+ " Run garbage collection after every test
+ call test_garbagecollect_now()
+endfunc
+
+" Tests for List type
+
+" List creation
+func Test_list_create()
+ " Creating List directly with different types
+ let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ call assert_equal("[1, 'as''d', [1, 2, function('strlen')], {'a': 1}]", string(l))
+ call assert_equal({'a' : 1}, l[-1])
+ call assert_equal(1, l[-4])
+ let x = 10
+ try
+ let x = l[-5]
+ catch
+ call assert_match('E684:', v:exception)
+ endtry
+ call assert_equal(10, x)
+endfunc
+
+" This was allowed in legacy Vim script
+let s:list_with_spaces = [1 , 2 , 3]
+
+" List slices
+func Test_list_slice()
+ let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[:])
+ call assert_equal(['as''d', [1, 2, function('strlen')], {'a': 1}], l[1:])
+ call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2])
+ call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8])
+ call assert_equal([], l[8:-1])
+ call assert_equal([], l[0:-10])
+ " perform an operation on a list slice
+ let l = [1, 2, 3]
+ let l[:1] += [1, 2]
+ let l[2:] -= [1]
+ call assert_equal([2, 4, 2], l)
+
+ let lines =<< trim END
+ VAR l = [1, 2]
+ call assert_equal([1, 2], l[:])
+ call assert_equal([2], l[-1 : -1])
+ call assert_equal([1, 2], l[-2 : -1])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let l = [1, 2]
+ call assert_equal([], l[-3 : -1])
+
+ let lines =<< trim END
+ var l = [1, 2]
+ assert_equal([1, 2], l[-3 : -1])
+ END
+ call v9.CheckDefAndScriptSuccess(lines)
+endfunc
+
+" List identity
+func Test_list_identity()
+ let lines =<< trim END
+ VAR l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ VAR ll = l
+ VAR lx = copy(l)
+ call assert_true(l == ll)
+ call assert_false(l isnot ll)
+ call assert_true(l is ll)
+ call assert_true(l == lx)
+ call assert_false(l is lx)
+ call assert_true(l isnot lx)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" removing items with :unlet
+func Test_list_unlet()
+ let lines =<< trim END
+ VAR l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ unlet l[2]
+ call assert_equal([1, 'as''d', {'a': 1}], l)
+ LET l = range(8)
+ unlet l[: 3]
+ unlet l[1 :]
+ call assert_equal([4], l)
+
+ #" removing items out of range: silently skip items that don't exist
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 2]
+ call assert_equal([0, 1, 3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 3]
+ call assert_equal([0, 1], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 4]
+ call assert_equal([0, 1], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 5]
+ call assert_equal([0, 1], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-2 : 2]
+ call assert_equal([0, 1, 3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-3 : 2]
+ call assert_equal([0, 3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-4 : 2]
+ call assert_equal([3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-5 : 2]
+ call assert_equal([3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-6 : 2]
+ call assert_equal([3], l)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let l = [0, 1, 2, 3]
+ unlet l[2:2]
+ call assert_equal([0, 1, 3], l)
+ let l = [0, 1, 2, 3]
+ unlet l[2:3]
+ call assert_equal([0, 1], l)
+
+ let lines =<< trim END
+ VAR l = [0, 1, 2, 3]
+ unlet l[2 : 1]
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E684:')
+
+ let lines =<< trim END
+ VAR l = [0, 1, 2, 3]
+ unlet l[-1 : 2]
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E684:')
+endfunc
+
+" assignment to a list
+func Test_list_assign()
+ let lines =<< trim END
+ VAR l = [0, 1, 2, 3]
+ VAR va = 0
+ VAR vb = 0
+ LET [va, vb] = l[2 : 3]
+ call assert_equal([2, 3], [va, vb])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ let l = [0, 1, 2, 3]
+ let [va, vb] = l
+ END
+ call v9.CheckScriptFailure(lines, 'E687:')
+ let lines =<< trim END
+ var l = [0, 1, 2, 3]
+ var va = 0
+ var vb = 0
+ [va, vb] = l
+ END
+ call v9.CheckScriptFailure(['vim9script'] + lines, 'E687:')
+ call v9.CheckDefExecFailure(lines, 'E1093: Expected 2 items but got 4')
+
+ let lines =<< trim END
+ let l = [0, 1, 2, 3]
+ let [va, vb] = l[1:1]
+ END
+ call v9.CheckScriptFailure(lines, 'E688:')
+ let lines =<< trim END
+ var l = [0, 1, 2, 3]
+ var va = 0
+ var vb = 0
+ [va, vb] = l[1 : 1]
+ END
+ call v9.CheckScriptFailure(['vim9script'] + lines, 'E688:')
+ call v9.CheckDefExecFailure(lines, 'E1093: Expected 2 items but got 1')
+endfunc
+
+" test for range assign
+func Test_list_range_assign()
+ let lines =<< trim END
+ VAR l = [0]
+ LET l[:] = [1, 2]
+ call assert_equal([1, 2], l)
+ LET l[-4 : -1] = [5, 6]
+ call assert_equal([5, 6], l)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ var l = [7]
+ l[:] = ['text']
+ END
+ call v9.CheckDefAndScriptFailure(lines, 'E1012:', 2)
+endfunc
+
+func Test_list_items()
+ let r = []
+ let l = ['a', 'b', 'c']
+ for [idx, val] in items(l)
+ call extend(r, [[idx, val]])
+ endfor
+ call assert_equal([[0, 'a'], [1, 'b'], [2, 'c']], r)
+
+ call assert_fails('call items(3)', 'E1225:')
+endfunc
+
+func Test_string_items()
+ let r = []
+ let s = 'aÌbツ'
+ for [idx, val] in items(s)
+ call extend(r, [[idx, val]])
+ endfor
+ call assert_equal([[0, 'aÌ'], [1, 'b'], [2, 'ツ']], r)
+endfunc
+
+" Test removing items in list
+func Test_list_func_remove()
+ let lines =<< trim END
+ #" Test removing 1 element
+ VAR l = [1, 2, 3, 4]
+ call assert_equal(1, remove(l, 0))
+ call assert_equal([2, 3, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal(2, remove(l, 1))
+ call assert_equal([1, 3, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal(4, remove(l, -1))
+ call assert_equal([1, 2, 3], l)
+
+ #" Test removing range of element(s)
+ LET l = [1, 2, 3, 4]
+ call assert_equal([3], remove(l, 2, 2))
+ call assert_equal([1, 2, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal([2, 3], remove(l, 1, 2))
+ call assert_equal([1, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal([2, 3], remove(l, -3, -2))
+ call assert_equal([1, 4], l)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test invalid cases
+ let l = [1, 2, 3, 4]
+ call assert_fails("call remove(l, 5)", 'E684:')
+ call assert_fails("call remove(l, 1, 5)", 'E684:')
+ call assert_fails("call remove(l, 3, 2)", 'E16:')
+ call assert_fails("call remove(1, 0)", 'E896:')
+ call assert_fails("call remove(l, l)", 'E745:')
+endfunc
+
+" List add() function
+func Test_list_add()
+ let lines =<< trim END
+ VAR l = []
+ call add(l, 1)
+ call add(l, [2, 3])
+ call add(l, [])
+ call add(l, test_null_list())
+ call add(l, {'k': 3})
+ call add(l, {})
+ call add(l, test_null_dict())
+ call assert_equal([1, [2, 3], [], [], {'k': 3}, {}, {}], l)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " weird legacy behavior
+ call assert_equal(1, add(test_null_list(), 4))
+endfunc
+
+" Tests for Dictionary type
+
+func Test_dict()
+ " Creating Dictionary directly with different types
+ let lines =<< trim END
+ VAR d = {'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}, }
+ call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d))
+ call assert_equal('asd', d.1)
+ call assert_equal(['-1', '1', 'b'], sort(keys(d)))
+ call assert_equal(['asd', [1, 2, function('strlen')], {'a': 1}], values(d))
+ call extend(d, {3: 33, 1: 99})
+ call extend(d, {'b': 'bbb', 'c': 'ccc'}, "keep")
+ call assert_equal({'c': 'ccc', '1': 99, 'b': [1, 2, function('strlen')], '3': 33, '-1': {'a': 1}}, d)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},}
+ call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d))
+
+ let v = []
+ for [key, val] in items(d)
+ call extend(v, [key, val])
+ unlet key val
+ endfor
+ call assert_equal(['1','asd','b',[1, 2, function('strlen')],'-1',{'a': 1}], v)
+
+ call extend(d, {3: 33, 1: 99})
+ call assert_fails("call extend(d, {3:333,4:444}, 'error')", 'E737:')
+
+ " duplicate key
+ call assert_fails("let d = {'k' : 10, 'k' : 20}", 'E721:')
+ " missing comma
+ call assert_fails("let d = {'k' : 10 'k' : 20}", 'E722:')
+ " missing curly brace
+ call assert_fails("let d = {'k' : 10,", 'E723:')
+ " invalid key
+ call assert_fails('let d = #{++ : 10}', 'E15:')
+ " wrong type for key
+ call assert_fails('let d={[] : 10}', 'E730:')
+ " undefined variable as value
+ call assert_fails("let d={'k' : i}", 'E121:')
+
+ " allow key starting with number at the start, not a curly expression
+ call assert_equal({'1foo': 77}, #{1foo: 77})
+
+ " #{expr} is not a curly expression
+ let x = 'x'
+ call assert_equal(#{g: x}, #{g:x})
+endfunc
+
+" This was allowed in legacy Vim script
+let s:dict_with_spaces = {'one' : 1 , 'two' : 2 , 'three' : 3}
+let s:dict_with_spaces_lit = #{one : 1 , two : 2 , three : 3}
+
+" Dictionary identity
+func Test_dict_identity()
+ let lines =<< trim END
+ VAR d = {'1': 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1}, }
+ VAR dd = d
+ VAR dx = copy(d)
+ call assert_true(d == dd)
+ call assert_false(d isnot dd)
+ call assert_true(d is dd)
+ call assert_true(d == dx)
+ call assert_false(d is dx)
+ call assert_true(d isnot dx)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" removing items with :unlet
+func Test_dict_unlet()
+ let lines =<< trim END
+ VAR d = {'b': 'bbb', '1': 99, '3': 33, '-1': {'a': 1}}
+ unlet d.b
+ unlet d[-1]
+ call assert_equal({'1': 99, '3': 33}, d)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" manipulating a big Dictionary (hashtable.c has a border of 1000 entries)
+func Test_dict_big()
+ let d = {}
+ for i in range(1500)
+ let d[i] = 3000 - i
+ endfor
+ call assert_equal([3000, 2900, 2001, 1600, 1501], [d[0], d[100], d[999], d[1400], d[1499]])
+ let str = ''
+ try
+ let n = d[1500]
+ catch
+ let str = substitute(v:exception, '\v(.{14}).*( "\d{4}").*', '\1\2', '')
+ endtry
+ call assert_equal('Vim(let):E716: "1500"', str)
+
+ " lookup each items
+ for i in range(1500)
+ call assert_equal(3000 - i, d[i])
+ endfor
+ let i += 1
+
+ " delete even items
+ while i >= 2
+ let i -= 2
+ unlet d[i]
+ endwhile
+ call assert_equal('NONE', get(d, 1500 - 100, 'NONE'))
+ call assert_equal(2999, d[1])
+
+ " delete odd items, checking value, one intentionally wrong
+ let d[33] = 999
+ let i = 1
+ while i < 1500
+ if i != 33
+ call assert_equal(3000 - i, d[i])
+ else
+ call assert_equal(999, d[i])
+ endif
+ unlet d[i]
+ let i += 2
+ endwhile
+ call assert_equal({}, d)
+ unlet d
+endfunc
+
+" Dictionary function
+func Test_dict_func()
+ let d = {}
+ func d.func(a) dict
+ return a:a . len(self.data)
+ endfunc
+ let d.data = [1,2,3]
+ call assert_equal('len: 3', d.func('len: '))
+ let x = d.func('again: ')
+ call assert_equal('again: 3', x)
+ let Fn = d.func
+ call assert_equal('xxx3', Fn('xxx'))
+endfunc
+
+func Test_dict_assign()
+ let d = {}
+ let d.1 = 1
+ let d._ = 2
+ call assert_equal({'1': 1, '_': 2}, d)
+
+ let lines =<< trim END
+ VAR d = {}
+ LET d.a = 1
+ LET d._ = 2
+ call assert_equal({'a': 1, '_': 2}, d)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ let n = 0
+ let n.key = 3
+ END
+ call v9.CheckScriptFailure(lines, 'E1203: Dot can only be used on a dictionary: n.key = 3')
+ let lines =<< trim END
+ vim9script
+ var n = 0
+ n.key = 3
+ END
+ call v9.CheckScriptFailure(lines, 'E1203: Dot can only be used on a dictionary: n.key = 3')
+ let lines =<< trim END
+ var n = 0
+ n.key = 3
+ END
+ call v9.CheckDefFailure(lines, 'E1141:')
+endfunc
+
+" Function in script-local List or Dict
+func Test_script_local_dict_func()
+ let g:dict = {}
+ function g:dict.func() dict
+ return 'g:dict.func' . self.foo[1] . self.foo[0]('asdf')
+ endfunc
+ let g:dict.foo = ['-', 2, 3]
+ call insert(g:dict.foo, function('strlen'))
+ call assert_equal('g:dict.func-4', g:dict.func())
+ unlet g:dict
+endfunc
+
+" Test removing items in a dictionary
+func Test_dict_func_remove()
+ let lines =<< trim END
+ VAR d = {1: 'a', 2: 'b', 3: 'c'}
+ call assert_equal('b', remove(d, 2))
+ call assert_equal({1: 'a', 3: 'c'}, d)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR d = {1: 'a', 3: 'c'}
+ call remove(d, 1, 2)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E118:')
+
+ let lines =<< trim END
+ VAR d = {1: 'a', 3: 'c'}
+ call remove(d, 'a')
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E716:')
+
+ let lines =<< trim END
+ let d = {'a-b': 55}
+ echo d.a-b
+ END
+ call v9.CheckScriptFailure(lines, 'E716: Key not present in Dictionary: "a"')
+
+ let lines =<< trim END
+ vim9script
+ var d = {'a-b': 55}
+ echo d.a-b
+ END
+ call v9.CheckScriptFailure(lines, 'E716: Key not present in Dictionary: "a"')
+
+ let lines =<< trim END
+ var d = {'a-b': 55}
+ echo d.a-b
+ END
+ call v9.CheckDefFailure(lines, 'E1004: White space required before and after ''-''')
+
+ let lines =<< trim END
+ let d = {1: 'a', 3: 'c'}
+ call remove(d, [])
+ END
+ call v9.CheckScriptFailure(lines, 'E730:')
+ let lines =<< trim END
+ vim9script
+ var d = {1: 'a', 3: 'c'}
+ call remove(d, [])
+ END
+ call v9.CheckScriptFailure(lines, 'E1220: String or Number required for argument 2')
+ let lines =<< trim END
+ var d = {1: 'a', 3: 'c'}
+ call remove(d, [])
+ END
+ call v9.CheckDefExecFailure(lines, 'E1013: Argument 2: type mismatch, expected string but got list<unknown>')
+endfunc
+
+" Nasty: remove func from Dict that's being called (works)
+func Test_dict_func_remove_in_use()
+ let d = {1:1}
+ func d.func(a)
+ return "a:" . a:a
+ endfunc
+ let expected = 'a:' . string(get(d, 'func'))
+ call assert_equal(expected, d.func(string(remove(d, 'func'))))
+
+ " similar, in a way it also works in Vim9
+ let lines =<< trim END
+ VAR d = {1: 1, 2: 'x'}
+ func GetArg(a)
+ return "a:" .. a:a
+ endfunc
+ LET d.func = function('GetArg')
+ VAR expected = 'a:' .. string(get(d, 'func'))
+ call assert_equal(expected, d.func(string(remove(d, 'func'))))
+ END
+ call v9.CheckTransLegacySuccess(lines)
+ call v9.CheckTransVim9Success(lines)
+endfunc
+
+func Test_dict_literal_keys()
+ call assert_equal({'one': 1, 'two2': 2, '3three': 3, '44': 4}, #{one: 1, two2: 2, 3three: 3, 44: 4},)
+
+ " why *{} cannot be used for a literal dictionary
+ let blue = 'blue'
+ call assert_equal('6', trim(execute('echo 2 *{blue: 3}.blue')))
+endfunc
+
+" Nasty: deepcopy() dict that refers to itself (fails when noref used)
+func Test_dict_deepcopy()
+ let lines =<< trim END
+ VAR d = {1: 1, 2: '2'}
+ VAR l = [4, d, 6]
+ LET d[3] = l
+ VAR dc = deepcopy(d)
+ call deepcopy(d, 1)
+ END
+ call v9.CheckLegacyAndVim9Failure(lines, 'E698:')
+
+ let lines =<< trim END
+ VAR d = {1: 1, 2: '2'}
+ VAR l = [4, d, 6]
+ LET d[3] = l
+ VAR l2 = [0, l, l, 3]
+ LET l[1] = l2
+ VAR l3 = deepcopy(l2)
+ call assert_true(l3[1] is l3[2])
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call assert_fails("call deepcopy([1, 2], 2)", 'E1212:')
+endfunc
+
+" Locked variables
+func Test_list_locked_var()
+ " Not tested with :def function, local vars cannot be locked.
+ let lines =<< trim END
+ VAR expected = [
+ \ [['1000-000', 'ppppppF'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1000-000', 'ppppppF'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1100-100', 'ppFppFF'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1110-110', 'pFFpFFF'],
+ \ ['0010-010', 'pFppFpp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1111-111', 'FFFFFFF'],
+ \ ['0011-011', 'FFpFFpp'],
+ \ ['0000-000', 'ppppppp']]
+ \ ]
+ for depth in range(5)
+ for u in range(3)
+ VAR l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
+ exe "lockvar " .. depth .. " l"
+ if u == 1
+ exe "unlockvar l"
+ elseif u == 2
+ exe "unlockvar " .. depth .. " l"
+ endif
+ VAR ps = islocked("l") .. islocked("l[1]") .. islocked("l[1][1]") .. islocked("l[1][1][0]") .. '-' .. islocked("l[2]") .. islocked("l[2]['6']") .. islocked("l[2]['6'][7]")
+ call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
+ LET ps = ''
+ try
+ LET l[1][1][0] = 99
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[1][1] = [99]
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[1] = [99]
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[2]['6'][7] = 99
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[2][6] = {99: 99}
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[2] = {99: 99}
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l = [99]
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ call assert_equal(expected[depth][u][1], ps, 'depth: ' .. depth)
+ unlock! l
+ endfor
+ endfor
+ END
+ call v9.CheckTransLegacySuccess(lines)
+ call v9.CheckTransVim9Success(lines)
+
+ call assert_fails("let x=islocked('a b')", 'E488:')
+ let mylist = [1, 2, 3]
+ call assert_fails("let x = islocked('mylist[1:2]')", 'E786:')
+ let mydict = {'k' : 'v'}
+ call assert_fails("let x = islocked('mydict.a')", 'E716:')
+endfunc
+
+" Unletting locked variables
+func Test_list_locked_var_unlet()
+ " Not tested with Vim9: script and local variables cannot be unlocked
+ let expected = [
+ \ [['1000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1000-000', 'ppFppFp'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1100-100', 'pFFpFFp'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1110-110', 'FFFFFFp'],
+ \ ['0010-010', 'FppFppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1111-111', 'FFFFFFp'],
+ \ ['0011-011', 'FppFppp'],
+ \ ['0000-000', 'ppppppp']]
+ \ ]
+
+ for depth in range(5)
+ for u in range(3)
+ unlet! l
+ let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
+ exe "lockvar " . depth . " l"
+ if u == 1
+ exe "unlockvar l"
+ elseif u == 2
+ exe "unlockvar " . depth . " l"
+ endif
+ let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]")
+ call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
+ let ps = ''
+ try
+ unlet l[2]['6'][7]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[2][6]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[2]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[1][1][0]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[1][1]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[1]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ call assert_equal(expected[depth][u][1], ps)
+ endfor
+ endfor
+
+ " Deleting a list range with locked items works, but changing the items
+ " fails.
+ let l = [1, 2, 3, 4]
+ lockvar l[1:2]
+ call assert_fails('let l[1:2] = [8, 9]', 'E741:')
+ unlet l[1:2]
+ call assert_equal([1, 4], l)
+ unlet l
+endfunc
+
+" Locked variables and :unlet or list / dict functions
+
+" No :unlet after lock on dict:
+func Test_dict_lock_unlet()
+ let d = {'a': 99, 'b': 100}
+ lockvar 1 d
+ call assert_fails('unlet d.a', 'E741:')
+endfunc
+
+" unlet after lock on dict item
+func Test_dict_item_lock_unlet()
+ let lines =<< trim END
+ VAR d = {'a': 99, 'b': 100}
+ lockvar d.a
+ unlet d.a
+ call assert_equal({'b': 100}, d)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" filter() after lock on dict item
+func Test_dict_lock_filter()
+ let lines =<< trim END
+ VAR d = {'a': 99, 'b': 100}
+ lockvar d.a
+ call filter(d, 'v:key != "a"')
+ call assert_equal({'b': 100}, d)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" map() after lock on dict
+func Test_dict_lock_map()
+ let lines =<< trim END
+ VAR d = {'a': 99, 'b': 100}
+ lockvar 1 d
+ call map(d, 'v:val + 200')
+ call assert_equal({'a': 299, 'b': 300}, d)
+ END
+ " This won't work in a :def function
+ call v9.CheckTransLegacySuccess(lines)
+ call v9.CheckTransVim9Success(lines)
+
+ " For a :def function use a global dict.
+ let lines =<< trim END
+ let g:thedict = {'a': 77, 'b': 88}
+ lockvar 1 g:thedict
+ def Delkey()
+ unlet g:thedict.a
+ enddef
+ call Delkey()
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+endfunc
+
+" Lock one item in a list
+func Test_list_item_lock_map()
+ let lines =<< trim END
+ VAR l = [99, 100, 101]
+ lockvar l[1]
+ call assert_fails("echo map(l, 'v:val + 200')", 'E741:')
+ call assert_equal([299, 100, 101], l)
+ END
+ " This won't work in a :def function
+ call v9.CheckTransLegacySuccess(lines)
+ call v9.CheckTransVim9Success(lines)
+endfunc
+
+" Lock one item in a dict
+func Test_dict_item_lock_map()
+ let lines =<< trim END
+ VAR d = {'a': 99, 'b': 100, 'c': 101}
+ lockvar d.b
+ call assert_fails("echo map(d, 'v:val + 200')", 'E741:')
+ call assert_equal({'a': 299, 'b': 100, 'c': 101}, d)
+ END
+ " This won't work in a :def function
+ call v9.CheckTransLegacySuccess(lines)
+ call v9.CheckTransVim9Success(lines)
+endfunc
+
+" No extend() after lock on dict item
+func Test_dict_lock_extend()
+ let d = {'a': 99, 'b': 100}
+ lockvar d.a
+ call assert_fails("call extend(d, {'a' : 123})", 'E741:')
+ call assert_equal({'a': 99, 'b': 100}, d)
+endfunc
+
+" Cannot use += with a locked dict
+func Test_dict_lock_operator()
+ let d = {}
+ lockvar d
+ call assert_fails("let d += {'k' : 10}", 'E741:')
+ unlockvar d
+endfunc
+
+" No remove() of write-protected scope-level variable
+func Tfunc1(this_is_a_long_parameter_name)
+ call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742:')
+endfunc
+func Test_dict_scope_var_remove()
+ call Tfunc1('testval')
+endfunc
+
+" No extend() of write-protected scope-level variable
+func Test_dict_scope_var_extend()
+ call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742:')
+endfunc
+
+func Tfunc2(this_is_a_long_parameter_name)
+ call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742:')
+endfunc
+func Test_dict_scope_var_extend_overwrite()
+ call Tfunc2('testval')
+endfunc
+
+" No :unlet of variable in locked scope
+func Test_lock_var_unlet()
+ let b:testvar = 123
+ lockvar 1 b:
+ call assert_fails('unlet b:testvar', 'E741:')
+ unlockvar 1 b:
+ unlet! b:testvar
+endfunc
+
+" No :let += of locked list variable
+func Test_let_lock_list()
+ let l = ['a', 'b', 3]
+ lockvar 1 l
+ call assert_fails("let l += ['x']", 'E741:')
+ call assert_equal(['a', 'b', 3], l)
+
+ unlet l
+ let l = [1, 2, 3, 4]
+ lockvar! l
+ call assert_equal([1, 2, 3, 4], l)
+ unlockvar l[1]
+ call assert_fails('unlet l[0:1]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ call assert_fails('unlet l[1:2]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ unlockvar l[1]
+ call assert_fails('let l[0:1] = [0, 1]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ call assert_fails('let l[1:2] = [0, 1]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ unlet l
+
+ let lines =<< trim END
+ def TryUnletListItem(l: list<any>)
+ unlet l[0]
+ enddef
+ let l = [1, 2, 3, 4]
+ lockvar! l
+ call TryUnletListItem(l)
+ END
+ call v9.CheckScriptFailure(lines, 'E741:')
+ unlet g:l
+endfunc
+
+" Locking part of the list
+func Test_let_lock_list_items()
+ let l = [1, 2, 3, 4]
+ lockvar l[2:]
+ call assert_equal(0, islocked('l[0]'))
+ call assert_equal(1, islocked('l[2]'))
+ call assert_equal(1, islocked('l[3]'))
+ call assert_fails('let l[2] = 10', 'E741:')
+ call assert_fails('let l[3] = 20', 'E741:')
+ unlet l
+endfunc
+
+" lockvar/islocked() triggering script autoloading
+func Test_lockvar_script_autoload()
+ let old_rtp = &rtp
+ set rtp+=./sautest
+ lockvar g:footest#x
+ unlockvar g:footest#x
+ call assert_equal(-1, 'g:footest#x'->islocked())
+ call assert_equal(0, exists('g:footest#x'))
+ call assert_equal(1, g:footest#x)
+ let &rtp = old_rtp
+endfunc
+
+" a:000 function argument test
+func s:arg_list_test(...)
+ call assert_fails('let a:000 = [1, 2]', 'E46:')
+ call assert_fails('let a:000[0] = 9', 'E742:')
+ call assert_fails('let a:000[2] = [9, 10]', 'E742:')
+ call assert_fails('let a:000[3] = {9 : 10}', 'E742:')
+
+ " now the tests that should pass
+ let a:000[2][1] = 9
+ call extend(a:000[2], [5, 6])
+ let a:000[3][5] = 8
+ let a:000[3]['a'] = 12
+ call assert_equal([1, 2, [3, 9, 5, 6], {'a': 12, '5': 8}], a:000)
+endfunc
+
+func Test_func_arg_list()
+ call s:arg_list_test(1, 2, [3, 4], {5: 6})
+endfunc
+
+" Tests for reverse(), sort(), uniq()
+func Test_reverse_sort_uniq()
+ let lines =<< trim END
+ VAR l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5]
+ call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l)))
+ call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l))
+ call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l)))
+ call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l))
+ call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l)))
+ call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l))))
+ call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l)))
+
+ LET l = [7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four']
+ call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n'))
+
+ LET l = [7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []]
+ call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
+ call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
+ call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l)))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call assert_fails('call reverse("")', 'E899:')
+ call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:')
+ call assert_fails("call sort([1, 2], function('min'), 1)", "E1206:")
+ call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:")
+ call assert_fails("call sort([1, 2], function('min'))", "E118:")
+
+ let lines =<< trim END
+ call sort(['a', 'b'], 0)
+ END
+ call v9.CheckDefAndScriptFailure(lines, 'E1256: String or function required for argument 2')
+
+ let lines =<< trim END
+ call sort(['a', 'b'], 1)
+ END
+ call v9.CheckDefAndScriptFailure(lines, 'E1256: String or function required for argument 2')
+endfunc
+
+" reduce a list, blob or string
+func Test_reduce()
+ let lines =<< trim END
+ call assert_equal(1, reduce([], LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(10, reduce([1, 3, 5], LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], LSTART acc, val LMIDDLE 2 * acc + val LEND, 1))
+ call assert_equal('a x y z', ['x', 'y', 'z']->reduce(LSTART acc, val LMIDDLE acc .. ' ' .. val LEND, 'a'))
+ call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
+
+ VAR l = ['x', 'y', 'z']
+ call assert_equal(42, reduce(l, function('get'), {'x': {'y': {'z': 42 } } }))
+ call assert_equal(['x', 'y', 'z'], l)
+
+ call assert_equal(1, reduce([1], LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal('x y z', reduce(['x', 'y', 'z'], LSTART acc, val LMIDDLE acc .. ' ' .. val LEND))
+ call assert_equal(120, range(1, 5)->reduce(LSTART acc, val LMIDDLE acc * val LEND))
+
+ call assert_equal(0, range(1)->reduce(LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal(1, range(2)->reduce(LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal(3, range(3)->reduce(LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal(6, range(4)->reduce(LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal(10, range(5)->reduce(LSTART acc, val LMIDDLE acc + val LEND))
+
+ call assert_equal(1, reduce(0z, LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce(LSTART acc, val LMIDDLE 2 * acc + val LEND, 1))
+
+ call assert_equal(0xff, reduce(0zff, LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, LSTART acc, val LMIDDLE 2 * acc + val LEND))
+
+ call assert_equal('x,y,z', 'xyz'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('', ''->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND, ''))
+ call assert_equal('ã‚,ã„,ã†,ãˆ,ãŠ,😊,💕', 'ã‚ã„ã†ãˆãŠðŸ˜ŠðŸ’•'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('😊,ã‚,ã„,ã†,ãˆ,ãŠ,💕', 'ã‚ã„ã†ãˆãŠðŸ’•'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND, '😊'))
+ call assert_equal('ऊ,ॠ,ॡ', reduce('ऊॠॡ', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('c,à,t', reduce('càt', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('Å,s,t,r,ö,m', reduce('Åström', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('Å,s,t,r,ö,m', reduce('Åström', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal(',a,b,c', reduce('abc', LSTART acc, val LMIDDLE acc .. ',' .. val LEND, test_null_string()))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call assert_equal({'x': 1, 'y': 1, 'z': 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+ vim9 assert_equal({'x': 1, 'y': 1, 'z': 1 }, ['x', 'y', 'z']->reduce((acc, val) => extend(acc, {[val]: 1 }), {}))
+
+ call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+ call assert_fails("call reduce(range(0), { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+ call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+ call assert_fails("call reduce(test_null_blob(), { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+ call assert_fails("call reduce('', { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value')
+ call assert_fails("call reduce(test_null_string(), { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value')
+
+ call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E1098:')
+ call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E1098:')
+ call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E121:')
+ call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E1210:')
+
+ call assert_fails("vim9 reduce(0, (acc, val) => (acc .. val), '')", 'E1252:')
+ call assert_fails("vim9 reduce({}, (acc, val) => (acc .. val), '')", 'E1252:')
+ call assert_fails("vim9 reduce(0.1, (acc, val) => (acc .. val), '')", 'E1252:')
+ call assert_fails("vim9 reduce(function('tr'), (acc, val) => (acc .. val), '')", 'E1252:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E1174:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, {})", 'E1174:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 0.1)", 'E1174:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, function('tr'))", 'E1174:')
+ call assert_fails("call reduce('abc', { a, v -> a10}, '')", 'E121:')
+ call assert_fails("call reduce(0z0102, { a, v -> a10}, 1)", 'E121:')
+ call assert_fails("call reduce([1, 2], { a, v -> a10}, '')", 'E121:')
+
+ let g:lut = [1, 2, 3, 4]
+ func EvilRemove()
+ call remove(g:lut, 1)
+ return 1
+ endfunc
+ call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
+ unlet g:lut
+ delfunc EvilRemove
+
+ call assert_equal(42, reduce(test_null_list(), function('add'), 42))
+ call assert_equal(42, reduce(test_null_blob(), function('add'), 42))
+
+ " should not crash
+ call assert_fails('echo reduce([1], test_null_function())', 'E1132:')
+ call assert_fails('echo reduce([1], test_null_partial())', 'E1132:')
+endfunc
+
+" splitting a string to a List using split()
+func Test_str_split()
+ let lines =<< trim END
+ call assert_equal(['aa', 'bb'], split(' aa bb '))
+ call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0))
+ call assert_equal(['', 'aa', 'bb', ''], split(' aa bb ', '\W\+', 1))
+ call assert_equal(['', '', 'aa', '', 'bb', '', ''], split(' aa bb ', '\W', 1))
+ call assert_equal(['aa', '', 'bb'], split(':aa::bb:', ':', 0))
+ call assert_equal(['', 'aa', '', 'bb', ''], split(':aa::bb:', ':', 1))
+ call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1))
+ call assert_equal(['a', 'b', 'c'], split('abc', '\zs'))
+ call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1))
+ call assert_equal(['abc'], split('abc', '\\%('))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ call assert_fails("call split('abc', [])", 'E730:')
+ call assert_fails("call split('abc', 'b', [])", 'E745:')
+endfunc
+
+" compare recursively linked list and dict
+func Test_listdict_compare()
+ let lines =<< trim END
+ VAR l = [1, 2, 3, '4']
+ VAR d = {'1': 1, '2': l, '3': 3}
+ LET l[1] = d
+ call assert_true(l == l)
+ call assert_true(d == d)
+ call assert_false(l != deepcopy(l))
+ call assert_false(d != deepcopy(d))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " comparison errors
+ call assert_fails('echo [1, 2] =~ {}', 'E691:')
+ call assert_fails('echo [1, 2] =~ [1, 2]', 'E692:')
+ call assert_fails('echo {} =~ 5', 'E735:')
+ call assert_fails('echo {} =~ {}', 'E736:')
+endfunc
+
+ " compare complex recursively linked list and dict
+func Test_listdict_compare_complex()
+ let lines =<< trim END
+ VAR l = []
+ call add(l, l)
+ VAR dict4 = {"l": l}
+ call add(dict4.l, dict4)
+ VAR lcopy = deepcopy(l)
+ VAR dict4copy = deepcopy(dict4)
+ call assert_true(l == lcopy)
+ call assert_true(dict4 == dict4copy)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" Test for extending lists and dictionaries
+func Test_listdict_extend()
+ " Test extend() with lists
+
+ " Pass the same List to extend()
+ let lines =<< trim END
+ VAR l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 1, 2, 3], extend(l, l))
+ call assert_equal([1, 2, 3, 1, 2, 3], l)
+
+ LET l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 4, 5, 6], extend(l, [4, 5, 6]))
+ call assert_equal([1, 2, 3, 4, 5, 6], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], 0)
+ call assert_equal([4, 5, 6, 1, 2, 3], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], 1)
+ call assert_equal([1, 4, 5, 6, 2, 3], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], 3)
+ call assert_equal([1, 2, 3, 4, 5, 6], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], -1)
+ call assert_equal([1, 2, 4, 5, 6, 3], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], -3)
+ call assert_equal([4, 5, 6, 1, 2, 3], l)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let l = [1, 2, 3]
+ call assert_fails("call extend(l, [4, 5, 6], 4)", 'E684:')
+ call assert_fails("call extend(l, [4, 5, 6], -4)", 'E684:')
+ call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
+
+ " Test extend() with dictionaries.
+
+ " Pass the same Dict to extend()
+ let lines =<< trim END
+ VAR d = {'a': {'b': 'B'}, 'x': 9}
+ call extend(d, d)
+ call assert_equal({'a': {'b': 'B'}, 'x': 9}, d)
+
+ LET d = {'a': 'A', 'b': 9}
+ call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, extend(d, {'b': 0, 'c': 'C'}))
+ call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
+
+ LET d = {'a': 'A', 'b': 9}
+ call extend(d, {'a': 'A', 'b': 0, 'c': 'C'}, "force")
+ call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
+
+ LET d = {'a': 'A', 'b': 9}
+ call extend(d, {'b': 0, 'c': 'C'}, "keep")
+ call assert_equal({'a': 'A', 'b': 9, 'c': 'C'}, d)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let d = {'a': 'A', 'b': 'B'}
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'error')", 'E737:')
+ call assert_fails("call extend(d, {'b': 0}, [])", 'E730:')
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'xxx')", 'E475:')
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E475:')
+ call assert_equal({'a': 'A', 'b': 'B'}, d)
+
+ call assert_fails("call extend([1, 2], 1)", 'E712:')
+ call assert_fails("call extend([1, 2], {})", 'E712:')
+
+ " Extend g: dictionary with an invalid variable name
+ call assert_fails("call extend(g:, {'-!' : 10})", 'E461:')
+
+ " Extend a list with itself.
+ let lines =<< trim END
+ VAR l = [1, 5, 7]
+ call extend(l, l, 0)
+ call assert_equal([1, 5, 7, 1, 5, 7], l)
+ LET l = [1, 5, 7]
+ call extend(l, l, 1)
+ call assert_equal([1, 1, 5, 7, 5, 7], l)
+ LET l = [1, 5, 7]
+ call extend(l, l, 2)
+ call assert_equal([1, 5, 1, 5, 7, 7], l)
+ LET l = [1, 5, 7]
+ call extend(l, l, 3)
+ call assert_equal([1, 5, 7, 1, 5, 7], l)
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_listdict_extendnew()
+ " Test extendnew() with lists
+ let l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 4, 5], extendnew(l, [4, 5]))
+ call assert_equal([1, 2, 3], l)
+ lockvar l
+ call assert_equal([1, 2, 3, 4, 5], extendnew(l, [4, 5]))
+
+ " Test extendnew() with dictionaries.
+ let d = {'a': {'b': 'B'}}
+ call assert_equal({'a': {'b': 'B'}, 'c': 'cc'}, extendnew(d, {'c': 'cc'}))
+ call assert_equal({'a': {'b': 'B'}}, d)
+ lockvar d
+ call assert_equal({'a': {'b': 'B'}, 'c': 'cc'}, extendnew(d, {'c': 'cc'}))
+endfunc
+
+func s:check_scope_dict(x, fixed)
+ func s:gen_cmd(cmd, x)
+ return substitute(a:cmd, '\<x\ze:', a:x, 'g')
+ endfunc
+
+ let cmd = s:gen_cmd('let x:foo = 1', a:x)
+ if a:fixed
+ call assert_fails(cmd, 'E461:')
+ else
+ exe cmd
+ exe s:gen_cmd('call assert_equal(1, x:foo)', a:x)
+ endif
+
+ let cmd = s:gen_cmd('let x:["bar"] = 2', a:x)
+ if a:fixed
+ call assert_fails(cmd, 'E461:')
+ else
+ exe cmd
+ exe s:gen_cmd('call assert_equal(2, x:bar)', a:x)
+ endif
+
+ let cmd = s:gen_cmd('call extend(x:, {"baz": 3})', a:x)
+ if a:fixed
+ call assert_fails(cmd, 'E742:')
+ else
+ exe cmd
+ exe s:gen_cmd('call assert_equal(3, x:baz)', a:x)
+ endif
+
+ if a:fixed
+ if a:x ==# 'a'
+ call assert_fails('unlet a:x', 'E795:')
+ call assert_fails('call remove(a:, "x")', 'E742:')
+ elseif a:x ==# 'v'
+ call assert_fails('unlet v:count', 'E795:')
+ call assert_fails('call remove(v:, "count")', 'E742:')
+ endif
+ else
+ exe s:gen_cmd('unlet x:foo', a:x)
+ exe s:gen_cmd('unlet x:bar', a:x)
+ exe s:gen_cmd('call remove(x:, "baz")', a:x)
+ endif
+
+ delfunc s:gen_cmd
+endfunc
+
+func Test_scope_dict()
+ " Test for g:
+ call s:check_scope_dict('g', v:false)
+
+ " Test for s:
+ call s:check_scope_dict('s', v:false)
+
+ " Test for l:
+ call s:check_scope_dict('l', v:false)
+
+ " Test for a:
+ call s:check_scope_dict('a', v:true)
+
+ " Test for b:
+ call s:check_scope_dict('b', v:false)
+
+ " Test for w:
+ call s:check_scope_dict('w', v:false)
+
+ " Test for t:
+ call s:check_scope_dict('t', v:false)
+
+ " Test for v:
+ call s:check_scope_dict('v', v:true)
+endfunc
+
+" Test for deep nesting of lists (> 100)
+func Test_deep_nested_list()
+ let deep_list = []
+ let l = deep_list
+ for i in range(102)
+ let newlist = []
+ call add(l, newlist)
+ let l = newlist
+ endfor
+ call add(l, 102)
+
+ call assert_fails('let m = deepcopy(deep_list)', 'E698:')
+ call assert_fails('lockvar 110 deep_list', 'E743:')
+ call assert_fails('unlockvar 110 deep_list', 'E743:')
+ call assert_fails('let x = execute("echo deep_list")', 'E724:')
+ call test_garbagecollect_now()
+ unlet deep_list
+endfunc
+
+" Test for deep nesting of dicts (> 100)
+func Test_deep_nested_dict()
+ let deep_dict = {}
+ let d = deep_dict
+ for i in range(102)
+ let newdict = {}
+ let d.k = newdict
+ let d = newdict
+ endfor
+ let d.k = 'v'
+
+ call assert_fails('let m = deepcopy(deep_dict)', 'E698:')
+ call assert_fails('lockvar 110 deep_dict', 'E743:')
+ call assert_fails('unlockvar 110 deep_dict', 'E743:')
+ call assert_fails('let x = execute("echo deep_dict")', 'E724:')
+ call test_garbagecollect_now()
+ unlet deep_dict
+endfunc
+
+" List and dict indexing tests
+func Test_listdict_index()
+ call v9.CheckLegacyAndVim9Failure(['echo function("min")[0]'], 'E695:')
+ call v9.CheckLegacyAndVim9Failure(['echo v:true[0]'], 'E909:')
+ call v9.CheckLegacyAndVim9Failure(['echo v:null[0]'], 'E909:')
+ call v9.CheckLegacyAndVim9Failure(['VAR d = {"k": 10}', 'echo d.'], ['E15:', 'E1127:', 'E15:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR d = {"k": 10}', 'echo d[1 : 2]'], 'E719:')
+
+ call assert_fails("let v = [4, 6][{-> 1}]", 'E729:')
+ call v9.CheckDefAndScriptFailure(['var v = [4, 6][() => 1]'], ['E1012', 'E703:'])
+
+ call v9.CheckLegacyAndVim9Failure(['VAR v = range(5)[2 : []]'], ['E730:', 'E1012:', 'E730:'])
+
+ call assert_fails("let v = range(5)[2:{-> 2}(]", ['E15:', 'E116:'])
+ call v9.CheckDefAndScriptFailure(['var v = range(5)[2 : () => 2(]'], 'E15:')
+
+ call v9.CheckLegacyAndVim9Failure(['VAR v = range(5)[2 : 3'], ['E111:', 'E1097:', 'E111:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR l = insert([1, 2, 3], 4, 10)'], 'E684:')
+ call v9.CheckLegacyAndVim9Failure(['VAR l = insert([1, 2, 3], 4, -10)'], 'E684:')
+ call v9.CheckLegacyAndVim9Failure(['VAR l = insert([1, 2, 3], 4, [])'], ['E745:', 'E1013:', 'E1210:'])
+
+ call v9.CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[i] = 3'], ['E121:', 'E1001:', 'E121:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[1.1] = 4'], ['E805:', 'E1012:', 'E805:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[: i] = [4, 5]'], ['E121:', 'E1001:', 'E121:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[: 3.2] = [4, 5]'], ['E805:', 'E1012:', 'E805:'])
+ call v9.CheckLegacyAndVim9Failure(['VAR t = test_unknown()', 'echo t[0]'], 'E685:')
+endfunc
+
+" Test for a null list
+func Test_null_list()
+ let lines =<< trim END
+ VAR l = test_null_list()
+ call assert_equal('', join(test_null_list()))
+ call assert_equal('', join(l))
+ call assert_equal(0, len(l))
+ call assert_equal(1, empty(l))
+ call assert_equal([], split(test_null_string()))
+ call assert_equal([], l[ : 2])
+ call assert_true([] == l)
+ call assert_equal('[]', string(l))
+ call assert_equal([], sort(test_null_list()))
+ call assert_equal([], sort(l))
+ call assert_equal([], uniq(test_null_list()))
+ call assert_equal([], uniq(l))
+ VAR k = [] + l
+ call assert_equal([], k)
+ LET k = l + []
+ call assert_equal([], k)
+ call assert_equal(0, len(copy(l)))
+ call assert_equal(0, count(l, 5))
+ call assert_equal([], deepcopy(l))
+ call assert_equal(5, get(l, 2, 5))
+ call assert_equal(-1, index(l, 2, 5))
+ call assert_equal(0, min(l))
+ call assert_equal(0, max(l))
+ call assert_equal(0, remove(test_null_list(), 0, 2))
+ call assert_equal([], repeat(l, 2))
+ call assert_equal([], reverse(test_null_list()))
+ call assert_equal([], reverse(l))
+ call assert_equal([], sort(test_null_list()))
+ call assert_equal([], sort(l))
+ call assert_equal('[]', string(l))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let l = test_null_list()
+ call assert_equal([], extend(l, l, 0))
+ call assert_equal(0, insert(test_null_list(), 2, -1))
+ call assert_fails('let s = join([1, 2], [])', 'E730:')
+ call assert_fails('call remove(l, 0, 2)', 'E684:')
+ call assert_fails('call insert(l, 2, -1)', 'E684:')
+ call assert_fails('call extend(test_null_list(), test_null_list())', 'E1134:')
+
+ lockvar l
+ call assert_equal(1, islocked('l'))
+ unlockvar l
+endfunc
+
+" Test for a null dict
+func Test_null_dict()
+ let lines =<< trim END
+ call assert_equal(test_null_dict(), test_null_dict())
+ VAR d = test_null_dict()
+ call assert_equal({}, d)
+ call assert_equal(0, len(d))
+ call assert_equal(1, empty(d))
+ call assert_equal([], items(test_null_dict()))
+ call assert_equal([], items(d))
+ call assert_equal([], keys(test_null_dict()))
+ call assert_equal([], keys(d))
+ call assert_equal([], values(test_null_dict()))
+ call assert_equal([], values(d))
+ call assert_false(has_key(d, 'k'))
+ call assert_equal('{}', string(d))
+ call assert_equal({}, {})
+ call assert_equal(0, len(copy(d)))
+ call assert_equal(0, count(d, 'k'))
+ call assert_equal({}, deepcopy(d))
+ call assert_equal(20, get(d, 'k', 20))
+ call assert_equal(0, min(d))
+ call assert_equal(0, max(d))
+ call assert_equal(0, remove(test_null_dict(), 'k'))
+ call assert_equal('{}', string(d))
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ let d = test_null_dict()
+ call assert_equal({}, extend(d, d, 'keep'))
+ call assert_fails("call remove(d, 'k')", 'E716:')
+ call assert_fails('let x = d[10]', 'E716:')
+ call assert_fails('call extend(test_null_dict(), test_null_dict())', 'E1133:')
+ lockvar d
+ call assert_equal(1, islocked('d'))
+ unlockvar d
+endfunc
+
+" Test for the indexof() function
+func Test_indexof()
+ let l = [#{color: 'red'}, #{color: 'blue'}, #{color: 'green'}]
+ call assert_equal(0, indexof(l, {i, v -> v.color == 'red'}))
+ call assert_equal(2, indexof(l, {i, v -> v.color == 'green'}))
+ call assert_equal(-1, indexof(l, {i, v -> v.color == 'grey'}))
+ call assert_equal(1, indexof(l, "v:val.color == 'blue'"))
+ call assert_equal(-1, indexof(l, "v:val.color == 'cyan'"))
+
+ let l = [#{n: 10}, #{n: 10}, #{n: 20}]
+ call assert_equal(0, indexof(l, "v:val.n == 10", #{startidx: 0}))
+ call assert_equal(1, indexof(l, "v:val.n == 10", #{startidx: -2}))
+ call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: 4}))
+ call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: -4}))
+ call assert_equal(0, indexof(l, "v:val.n == 10", test_null_dict()))
+
+ let s = ["a", "b", "c"]
+ call assert_equal(2, indexof(s, {_, v -> v == 'c'}))
+ call assert_equal(-1, indexof(s, {_, v -> v == 'd'}))
+ call assert_equal(-1, indexof(s, {_, v -> "v == 'd'"}))
+
+ call assert_equal(-1, indexof([], {i, v -> v == 'a'}))
+ call assert_equal(-1, indexof([1, 2, 3], {_, v -> "v == 2"}))
+ call assert_equal(-1, indexof(test_null_list(), {i, v -> v == 'a'}))
+ call assert_equal(-1, indexof(l, test_null_string()))
+ call assert_equal(-1, indexof(l, test_null_function()))
+
+ " failure cases
+ call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:')
+ call assert_fails('let i = indexof(l, "color == ''cyan''")', 'E121:')
+ call assert_fails('let i = indexof(l, {})', 'E1256:')
+ call assert_fails('let i = indexof({}, "v:val == 2")', 'E1226:')
+ call assert_fails('let i = indexof([], "v:val == 2", [])', 'E1206:')
+
+ func TestIdx(k, v)
+ return a:v.n == 20
+ endfunc
+ call assert_equal(2, indexof(l, function("TestIdx")))
+ delfunc TestIdx
+ func TestIdx(k, v)
+ return {}
+ endfunc
+ call assert_fails('let i = indexof(l, function("TestIdx"))', 'E728:')
+ delfunc TestIdx
+ func TestIdx(k, v)
+ throw "IdxError"
+ endfunc
+ call assert_fails('let i = indexof(l, function("TestIdx"))', 'E605:')
+ delfunc TestIdx
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_listener.vim b/src/testdir/test_listener.vim
new file mode 100644
index 0000000..413275d
--- /dev/null
+++ b/src/testdir/test_listener.vim
@@ -0,0 +1,453 @@
+" tests for listener_add() and listener_remove()
+
+func s:StoreList(s, e, a, l)
+ let s:start = a:s
+ let s:end = a:e
+ let s:added = a:a
+ let s:text = getline(a:s)
+ let s:list = a:l
+endfunc
+
+func s:AnotherStoreList(l)
+ let s:list2 = a:l
+endfunc
+
+func s:EvilStoreList(l)
+ let s:list3 = a:l
+ call assert_fails("call add(a:l, 'myitem')", "E742:")
+endfunc
+
+func Test_listening()
+ new
+ call setline(1, ['one', 'two'])
+ let s:list = []
+ let id = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
+ call setline(1, 'one one')
+ call listener_flush()
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+
+ " Undo is also a change
+ set undolevels& " start new undo block
+ call append(2, 'two two')
+ undo
+ call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
+ redraw
+ " the two changes are not merged
+ call assert_equal([{'lnum': 3, 'end': 4, 'col': 1, 'added': -1}], s:list)
+ 1
+
+ " Two listeners, both get called. Also check column.
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ let id2 = listener_add({b, s, e, a, l -> s:AnotherStoreList(l)})
+ let s:list = []
+ let s:list2 = []
+ exe "normal $asome\<Esc>"
+ redraw
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list)
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list2)
+
+ " removing listener works
+ call listener_remove(id2)
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ let s:list = []
+ let s:list2 = []
+ call setline(3, 'three')
+ redraw
+ call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
+ call assert_equal([], s:list2)
+
+ " a change above a previous change without a line number change is reported
+ " together
+ call setline(1, ['one one', 'two'])
+ call listener_flush(bufnr())
+ call append(2, 'two two')
+ call setline(1, 'something')
+ call bufnr()->listener_flush()
+ call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
+ \ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+ call assert_equal(1, s:start)
+ call assert_equal(3, s:end)
+ call assert_equal(1, s:added)
+
+ " an insert just above a previous change that was the last one does not get
+ " merged
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ let s:list = []
+ call setline(2, 'something')
+ call append(1, 'two two')
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+ call listener_flush()
+ call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)
+
+ " an insert above a previous change causes a flush
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ call setline(2, 'something')
+ call append(0, 'two two')
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+ call assert_equal('something', s:text)
+ call listener_flush()
+ call assert_equal([{'lnum': 1, 'end': 1, 'col': 1, 'added': 1}], s:list)
+ call assert_equal('two two', s:text)
+
+ " a delete at a previous change that was the last one does not get merged
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ let s:list = []
+ call setline(2, 'something')
+ 2del
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+ call listener_flush()
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': -1}], s:list)
+
+ " a delete above a previous change causes a flush
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ call setline(2, 'another')
+ 1del
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+ call assert_equal(2, s:start)
+ call assert_equal('another', s:text)
+ call listener_flush()
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': -1}], s:list)
+ call assert_equal('another', s:text)
+
+ " the "o" command first adds an empty line and then changes it
+ %del
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ let s:list = []
+ exe "normal Gofour\<Esc>"
+ redraw
+ call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
+ \ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
+
+ " Remove last listener
+ let s:list = []
+ call listener_remove(id)
+ call setline(1, 'asdfasdf')
+ redraw
+ call assert_equal([], s:list)
+
+ " Trying to change the list fails
+ let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)})
+ let s:list3 = []
+ call setline(1, 'asdfasdf')
+ redraw
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list3)
+
+ eval id->listener_remove()
+ bwipe!
+endfunc
+
+func s:StoreListArgs(buf, start, end, added, list)
+ let s:buf = a:buf
+ let s:start = a:start
+ let s:end = a:end
+ let s:added = a:added
+ let s:list = a:list
+endfunc
+
+func Test_listener_args()
+ new
+ call setline(1, ['one', 'two'])
+ let s:list = []
+ let id = listener_add('s:StoreListArgs')
+
+ " just one change
+ call setline(1, 'one one')
+ call listener_flush()
+ call assert_equal(bufnr(''), s:buf)
+ call assert_equal(1, s:start)
+ call assert_equal(2, s:end)
+ call assert_equal(0, s:added)
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+
+ " two disconnected changes
+ call setline(1, ['one', 'two', 'three', 'four'])
+ call listener_flush()
+ call setline(1, 'one one')
+ call setline(3, 'three three')
+ call listener_flush()
+ call assert_equal(bufnr(''), s:buf)
+ call assert_equal(1, s:start)
+ call assert_equal(4, s:end)
+ call assert_equal(0, s:added)
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0},
+ \ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
+
+ " add and remove lines
+ call setline(1, ['one', 'two', 'three', 'four', 'five', 'six'])
+ call listener_flush()
+ call append(2, 'two two')
+ 4del
+ call append(5, 'five five')
+ call listener_flush()
+ call assert_equal(bufnr(''), s:buf)
+ call assert_equal(3, s:start)
+ call assert_equal(6, s:end)
+ call assert_equal(1, s:added)
+ call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
+ \ {'lnum': 4, 'end': 5, 'col': 1, 'added': -1},
+ \ {'lnum': 6, 'end': 6, 'col': 1, 'added': 1}], s:list)
+
+ " split a line then insert one, should get two disconnected change lists
+ call setline(1, 'split here')
+ call listener_flush()
+ let s:list = []
+ exe "normal 1ggwi\<CR>\<Esc>"
+ 1
+ normal o
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 7, 'added': 1}], s:list)
+ call listener_flush()
+ call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)
+
+ call listener_remove(id)
+ bwipe!
+
+ " Invalid arguments
+ call assert_fails('call listener_add([])', 'E921:')
+ call assert_fails('call listener_add("s:StoreListArgs", [])', 'E730:')
+ call assert_fails('call listener_flush([])', 'E730:')
+endfunc
+
+func s:StoreBufList(buf, start, end, added, list)
+ let s:bufnr = a:buf
+ let s:list = a:list
+endfunc
+
+func Test_listening_other_buf()
+ new
+ call setline(1, ['one', 'two'])
+ let bufnr = bufnr('')
+ normal ww
+ let id = bufnr->listener_add(function('s:StoreBufList'))
+ let s:list = []
+ call setbufline(bufnr, 1, 'hello')
+ redraw
+ call assert_equal(bufnr, s:bufnr)
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+
+ call listener_remove(id)
+ exe "buf " .. bufnr
+ bwipe!
+endfunc
+
+func Test_listener_garbage_collect()
+ func MyListener(x, bufnr, start, end, added, changes)
+ " NOP
+ endfunc
+
+ new
+ let id = listener_add(function('MyListener', [{}]), bufnr(''))
+ call test_garbagecollect_now()
+ " must not crash caused by invalid memory access
+ normal ia
+ call assert_true(v:true)
+
+ call listener_remove(id)
+ delfunc MyListener
+ bwipe!
+endfunc
+
+" This verifies the fix for issue #4455
+func Test_listener_caches_buffer_line()
+ new
+ inoremap <silent> <CR> <CR><Esc>O
+
+ function EchoChanges(bufnr, start, end, added, changes)
+ for l:change in a:changes
+ let text = getbufline(a:bufnr, l:change.lnum, l:change.end-1+l:change.added)
+ endfor
+ endfunction
+ let lid = listener_add("EchoChanges")
+ set autoindent
+ set cindent
+
+ call setline(1, ["{", "\tif true {}", "}"])
+ exe "normal /{}\nl"
+ call feedkeys("i\r\e", 'xt')
+ call assert_equal(["{", "\tif true {", "", "\t}", "}"], getline(1, 5))
+
+ bwipe!
+ delfunc EchoChanges
+ call listener_remove(lid)
+ iunmap <CR>
+ set nocindent
+endfunc
+
+" Verify the fix for issue #4908
+func Test_listener_undo_line_number()
+ function DoIt()
+ " NOP
+ endfunction
+ function EchoChanges(bufnr, start, end, added, changes)
+ call DoIt()
+ endfunction
+
+ new
+ let lid = listener_add("EchoChanges")
+ call setline(1, ['a', 'b', 'c'])
+ set undolevels& " start new undo block
+ call feedkeys("ggcG\<Esc>", 'xt')
+ undo
+
+ bwipe!
+ delfunc DoIt
+ delfunc EchoChanges
+ call listener_remove(lid)
+endfunc
+
+func Test_listener_undo_delete_all()
+ new
+ call setline(1, [1, 2, 3, 4])
+ let s:changes = []
+ func s:ExtendList(bufnr, start, end, added, changes)
+ call extend(s:changes, a:changes)
+ endfunc
+ let id = listener_add('s:ExtendList')
+
+ set undolevels& " start new undo block
+ normal! ggdG
+ undo
+ call listener_flush()
+ call assert_equal(2, s:changes->len())
+ " delete removes four lines, empty line remains
+ call assert_equal({'lnum': 1, 'end': 5, 'col': 1, 'added': -4}, s:changes[0])
+ " undo replaces empty line and adds 3 lines
+ call assert_equal({'lnum': 1, 'end': 2, 'col': 1, 'added': 3}, s:changes[1])
+
+ call listener_remove(id)
+ delfunc s:ExtendList
+ unlet s:changes
+ bwipe!
+endfunc
+
+func Test_listener_cleared_newbuf()
+ func Listener(bufnr, start, end, added, changes)
+ let g:gotCalled += 1
+ endfunc
+ new
+ " check that listening works
+ let g:gotCalled = 0
+ let lid = listener_add("Listener")
+ call feedkeys("axxx\<Esc>", 'xt')
+ call listener_flush(bufnr())
+ call assert_equal(1, g:gotCalled)
+ %bwipe!
+ let bufnr = bufnr()
+ let b:testing = 123
+ let lid = listener_add("Listener")
+ enew!
+ " check buffer is reused
+ call assert_equal(bufnr, bufnr())
+ call assert_false(exists('b:testing'))
+
+ " check that listening stops when reusing the buffer
+ let g:gotCalled = 0
+ call feedkeys("axxx\<Esc>", 'xt')
+ call listener_flush(bufnr())
+ call assert_equal(0, g:gotCalled)
+ unlet g:gotCalled
+
+ bwipe!
+ delfunc Listener
+endfunc
+
+func Test_col_after_deletion_moved_cur()
+ func Listener(bufnr, start, end, added, changes)
+ call assert_equal([#{lnum: 1, end: 2, added: 0, col: 2}], a:changes)
+ endfunc
+ new
+ call setline(1, ['foo'])
+ let lid = listener_add('Listener')
+ call feedkeys("lD", 'xt')
+ call listener_flush()
+ bwipe!
+ delfunc Listener
+endfunc
+
+func Test_remove_listener_in_callback()
+ new
+ let s:ID = listener_add('Listener')
+ func Listener(...)
+ call listener_remove(s:ID)
+ let g:listener_called = 'yes'
+ endfunc
+ call setline(1, ['foo'])
+ call feedkeys("lD", 'xt')
+ call listener_flush()
+ call assert_equal('yes', g:listener_called)
+
+ bwipe!
+ delfunc Listener
+ unlet g:listener_called
+endfunc
+
+" When multiple listeners are registered, remove one listener and verify the
+" other listener is still called
+func Test_remove_one_listener_in_callback()
+ new
+ let g:listener1_called = 0
+ let g:listener2_called = 0
+ let s:ID1 = listener_add('Listener1')
+ let s:ID2 = listener_add('Listener2')
+ func Listener1(...)
+ call listener_remove(s:ID1)
+ let g:listener1_called += 1
+ endfunc
+ func Listener2(...)
+ let g:listener2_called += 1
+ endfunc
+ call setline(1, ['foo'])
+ call feedkeys("~", 'xt')
+ call listener_flush()
+ call feedkeys("~", 'xt')
+ call listener_flush()
+ call assert_equal(1, g:listener1_called)
+ call assert_equal(2, g:listener2_called)
+
+ call listener_remove(s:ID2)
+ bwipe!
+ delfunc Listener1
+ delfunc Listener2
+ unlet g:listener1_called
+ unlet g:listener2_called
+endfunc
+
+func Test_no_change_for_empty_undo()
+ new
+ let text = ['some word here', 'second line']
+ call setline(1, text)
+ let g:entries = []
+ func Listener(bufnr, start, end, added, changes)
+ for change in a:changes
+ call add(g:entries, [change.lnum, change.end, change.added])
+ endfor
+ endfunc
+ let s:ID = listener_add('Listener')
+ let @a = "one line\ntwo line\nthree line"
+ set undolevels& " start new undo block
+ call feedkeys('fwviw"ap', 'xt')
+ call listener_flush(bufnr())
+ " first change deletes "word", second change inserts the register
+ call assert_equal([[1, 2, 0], [1, 2, 2]], g:entries)
+ let g:entries = []
+
+ set undolevels& " start new undo block
+ undo
+ call listener_flush(bufnr())
+ call assert_equal([[1, 4, -2]], g:entries)
+ call assert_equal(text, getline(1, 2))
+
+ call listener_remove(s:ID)
+ bwipe!
+ unlet g:entries
+ delfunc Listener
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_listlbr.vim b/src/testdir/test_listlbr.vim
new file mode 100644
index 0000000..198ad8c
--- /dev/null
+++ b/src/testdir/test_listlbr.vim
@@ -0,0 +1,334 @@
+" Test for linebreak and list option (non-utf8)
+
+set encoding=latin1
+scriptencoding latin1
+
+source check.vim
+CheckOption linebreak
+CheckFeature conceal
+
+source view_util.vim
+source screendump.vim
+
+function s:screen_lines(lnum, width) abort
+ return ScreenLines(a:lnum, a:width)
+endfunction
+
+func s:compare_lines(expect, actual)
+ call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
+endfunc
+
+function s:test_windows(...)
+ call NewWindow(10, 20)
+ setl ts=8 sw=4 sts=4 linebreak sbr= wrap
+ exe get(a:000, 0, '')
+endfunction
+
+function s:close_windows(...)
+ call CloseWindow()
+ exe get(a:000, 0, '')
+endfunction
+
+func Test_set_linebreak()
+ call s:test_windows('setl ts=4 sbr=+')
+ call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ")
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ " abcdef ",
+\ "+hijklmn ",
+\ "+pqrstuvwxyz_1060ABC",
+\ "+DEFGHIJKLMNOP ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_linebreak_with_list()
+ set listchars=
+ call s:test_windows('setl ts=4 sbr=+ list listchars=')
+ call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ")
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ "^Iabcdef hijklmn^I ",
+\ "+pqrstuvwxyz_1060ABC",
+\ "+DEFGHIJKLMNOP ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+ set listchars&vim
+endfunc
+
+func Test_linebreak_with_nolist()
+ call s:test_windows('setl ts=4 sbr=+ nolist')
+ call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ")
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ " abcdef ",
+\ "+hijklmn ",
+\ "+pqrstuvwxyz_1060ABC",
+\ "+DEFGHIJKLMNOP ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_linebreak_with_list_and_number()
+ call s:test_windows('setl list listchars+=tab:>-')
+ call setline(1, ["abcdefg\thijklmnopqrstu", "v"])
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect_nonumber = [
+\ "abcdefg>------------",
+\ "hijklmnopqrstu$ ",
+\ "v$ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect_nonumber, lines)
+
+ setl number
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect_number = [
+\ " 1 abcdefg>--------",
+\ " hijklmnopqrstu$ ",
+\ " 2 v$ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect_number, lines)
+ call s:close_windows()
+endfunc
+
+func Test_should_break()
+ call s:test_windows('setl sbr=+ nolist')
+ call setline(1, "1\t" . repeat('a', winwidth(0)-2))
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ "1 ",
+\ "+aaaaaaaaaaaaaaaaaa ",
+\ "~ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_linebreak_with_conceal()
+ call s:test_windows('setl cpo&vim sbr=+ list conceallevel=2 concealcursor=nv listchars=tab:ab')
+ call setline(1, "_S_\t bla")
+ syn match ConcealVar contained /_/ conceal
+ syn match All /.*/ contains=ConcealVar
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ "Sabbbbbb bla ",
+\ "~ ",
+\ "~ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_linebreak_with_visual_operations()
+ call s:test_windows()
+ let line = '1234567890 2234567890 3234567890'
+ call setline(1, line)
+
+ " yank
+ exec "norm! ^w\<C-V>ey"
+ call assert_equal('2234567890', @@)
+ exec "norm! w\<C-V>ey"
+ call assert_equal('3234567890', @@)
+
+ " increment / decrement
+ exec "norm! ^w\<C-V>\<C-A>w\<C-V>\<C-X>"
+ call assert_equal('1234567890 3234567890 2234567890', getline(1))
+
+ " replace
+ exec "norm! ^w\<C-V>3lraw\<C-V>3lrb"
+ call assert_equal('1234567890 aaaa567890 bbbb567890', getline(1))
+
+ " tilde
+ exec "norm! ^w\<C-V>2l~w\<C-V>2l~"
+ call assert_equal('1234567890 AAAa567890 BBBb567890', getline(1))
+
+ " delete and insert
+ exec "norm! ^w\<C-V>3lc2345\<Esc>w\<C-V>3lc3456\<Esc>"
+ call assert_equal('1234567890 2345567890 3456567890', getline(1))
+ call assert_equal('BBBb', @@)
+
+ call s:close_windows()
+endfunc
+
+" Test that cursor is drawn at correct position after an operator when
+" 'linebreak' is enabled.
+func Test_linebreak_reset_restore()
+ CheckScreendump
+
+ " f_wincol() calls validate_cursor()
+ let lines =<< trim END
+ set linebreak showcmd noshowmode formatexpr=wincol()-wincol()
+ call setline(1, repeat('a', &columns - 10) .. ' bbbbbbbbbb c')
+ END
+ call writefile(lines, 'XlbrResetRestore', 'D')
+ let buf = RunVimInTerminal('-S XlbrResetRestore', {'rows': 8})
+
+ call term_sendkeys(buf, '$v$')
+ call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
+ call term_sendkeys(buf, 'zo')
+ call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
+
+ call term_sendkeys(buf, '$v$')
+ call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
+ call term_sendkeys(buf, 'gq')
+ call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
+
+ call term_sendkeys(buf, "$\<C-V>$")
+ call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
+ call term_sendkeys(buf, 'I')
+ call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
+
+ call term_sendkeys(buf, "\<Esc>$v$")
+ call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])})
+ call term_sendkeys(buf, 's')
+ call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])})
+ call VerifyScreenDump(buf, 'Test_linebreak_reset_restore_1', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_virtual_block()
+ call s:test_windows('setl sbr=+')
+ call setline(1, [
+\ "REMOVE: this not",
+\ "REMOVE: aaaaaaaaaaaaa",
+\ ])
+ exe "norm! 1/^REMOVE:"
+ exe "norm! 0\<C-V>jf x"
+ $put
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ "this not ",
+\ "aaaaaaaaaaaaa ",
+\ "REMOVE: ",
+\ "REMOVE: ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_virtual_block_and_vbA()
+ call s:test_windows()
+ call setline(1, "long line: " . repeat("foobar ", 40) . "TARGET at end")
+ exe "norm! $3B\<C-v>eAx\<Esc>"
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ let expect = [
+\ "<<<bar foobar ",
+\ "foobar foobar ",
+\ "foobar foobar ",
+\ "foobar foobar ",
+\ "foobar foobar ",
+\ "foobar foobar ",
+\ "foobar foobar ",
+\ "foobar foobar ",
+\ "foobar foobar ",
+\ "foobar TARGETx at ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_virtual_char_and_block()
+ call s:test_windows()
+ call setline(1, "1111-1111-1111-11-1111-1111-1111")
+ exe "norm! 0f-lv3lc2222\<Esc>bgj."
+ let lines = s:screen_lines([1, 2], winwidth(0))
+ let expect = [
+\ "1111-2222-1111-11- ",
+\ "1111-2222-1111 ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_undo_after_block_visual()
+ call s:test_windows()
+ call setline(1, ["aaa", "aaa", "a"])
+ exe "norm! gg\<C-V>2j~e."
+ let lines = s:screen_lines([1, 3], winwidth(0))
+ let expect = [
+\ "AaA ",
+\ "AaA ",
+\ "A ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_norm_after_block_visual()
+ call s:test_windows()
+ call setline(1, ["abcd{ef", "ghijklm", "no}pgrs"])
+ exe "norm! ggf{\<C-V>\<C-V>c%"
+ let lines = s:screen_lines([1, 3], winwidth(0))
+ let expect = [
+\ "abcdpgrs ",
+\ "~ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_block_replace_after_wrapping()
+ call s:test_windows()
+ call setline(1, repeat("a", 150))
+ exe "norm! 0yypk147|\<C-V>jr0"
+ call assert_equal(repeat("a", 146) . "0aaa", getline(1))
+ call assert_equal(repeat("a", 146) . "0aaa", getline(2))
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ let expect = [
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aaaaaa0aaa ",
+\ "@ ",
+\ "@ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_list_with_listchars()
+ call s:test_windows('setl list listchars=space:_,trail:-,tab:>-,eol:$')
+ call setline(1, "a aaaaaaaaaaaaaaaaaaaaaa\ta ")
+ let lines = s:screen_lines([1, 3], winwidth(0))
+ let expect = [
+\ "a_ ",
+\ "aaaaaaaaaaaaaaaaaaaa",
+\ "aa>-----a-$ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_list_with_tab_and_skipping_first_chars()
+ call s:test_windows('setl list listchars=tab:>- ts=70 nowrap')
+ call setline(1, ["iiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa", "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa", "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa", "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\taaaaaaaaaaaaaaaaaa"])
+ call cursor(4,64)
+ norm! 2zl
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ "---------------aaaaa",
+\ "---------------aaaaa",
+\ "---------------aaaaa",
+\ "iiiiiiiii>-----aaaaa",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_listlbr_utf8.vim b/src/testdir/test_listlbr_utf8.vim
new file mode 100644
index 0000000..ec54209
--- /dev/null
+++ b/src/testdir/test_listlbr_utf8.vim
@@ -0,0 +1,284 @@
+" Test for linebreak and list option in utf-8 mode
+
+set encoding=utf-8
+scriptencoding utf-8
+
+source check.vim
+CheckOption linebreak
+CheckFeature conceal
+CheckFeature signs
+
+source view_util.vim
+
+func s:screen_lines(lnum, width) abort
+ return ScreenLines(a:lnum, a:width)
+endfunc
+
+func s:compare_lines(expect, actual)
+ call assert_equal(a:expect, a:actual)
+endfunc
+
+func s:screen_attr(lnum, chars, ...) abort
+ let line = getline(a:lnum)
+ let attr = []
+ let prefix = get(a:000, 0, 0)
+ for i in range(a:chars[0], a:chars[1])
+ let scol = strdisplaywidth(strcharpart(line, 0, i-1)) + 1
+ let attr += [screenattr(a:lnum, scol + prefix)]
+ endfor
+ return attr
+endfunc
+
+func s:test_windows(...)
+ call NewWindow(10, 20)
+ setl ts=4 sw=4 sts=4 linebreak sbr=+ wrap
+ exe get(a:000, 0, '')
+endfunc
+
+func s:close_windows(...)
+ call CloseWindow()
+ exe get(a:000, 0, '')
+endfunc
+
+func Test_linebreak_with_fancy_listchars()
+ call s:test_windows("setl list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
+ call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz\u00a01060ABCDEFGHIJKLMNOP ")
+ redraw!
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ "▕———abcdef ",
+\ "+hijklmn▕——— ",
+\ "+pqrstuvwxyzâ£1060ABC",
+\ "+DEFGHIJKLMNOPˑ¶ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_nolinebreak_with_list()
+ call s:test_windows("setl nolinebreak list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
+ call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz\u00a01060ABCDEFGHIJKLMNOP ")
+ redraw!
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ "▕———abcdef hijklmn▕—",
+\ "+pqrstuvwxyzâ£1060ABC",
+\ "+DEFGHIJKLMNOPˑ¶ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+" this was causing a crash
+func Test_linebreak_with_list_and_tabs()
+ set linebreak list listchars=tab:⇤\ ⇥ tabstop=100
+ new
+ call setline(1, "\t\t\ttext")
+ redraw
+ bwipe!
+ set nolinebreak nolist listchars&vim tabstop=8
+endfunc
+
+func Test_linebreak_with_nolist()
+ call s:test_windows('setl nolist')
+ call setline(1, "\t*mask = nil;")
+ redraw!
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ " *mask = nil; ",
+\ "~ ",
+\ "~ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_list_and_concealing1()
+ call s:test_windows('setl list listchars=tab:>- cole=1')
+ call setline(1, [
+\ "#define ABCDE\t\t1",
+\ "#define ABCDEF\t\t1",
+\ "#define ABCDEFG\t\t1",
+\ "#define ABCDEFGH\t1",
+\ "#define MSG_MODE_FILE\t\t\t1",
+\ "#define MSG_MODE_CONSOLE\t\t2",
+\ "#define MSG_MODE_FILE_AND_CONSOLE\t3",
+\ "#define MSG_MODE_FILE_THEN_CONSOLE\t4",
+\ ])
+ vert resize 40
+ syn match Conceal conceal cchar=>'AB\|MSG_MODE'
+ redraw!
+ let lines = s:screen_lines([1, 7], winwidth(0))
+ let expect = [
+\ "#define ABCDE>-->---1 ",
+\ "#define >CDEF>-->---1 ",
+\ "#define >CDEFG>->---1 ",
+\ "#define >CDEFGH>----1 ",
+\ "#define >_FILE>--------->--->---1 ",
+\ "#define >_CONSOLE>---------->---2 ",
+\ "#define >_FILE_AND_CONSOLE>---------3 ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_list_and_concealing2()
+ call s:test_windows('setl nowrap ts=2 list listchars=tab:>- cole=2 concealcursor=n')
+ call setline(1, "bbeeeeee\t\t;\tsome text")
+ vert resize 40
+ syn clear
+ syn match meaning /;\s*\zs.*/
+ syn match hasword /^\x\{8}/ contains=word
+ syn match word /\<\x\{8}\>/ contains=beginword,endword contained
+ syn match beginword /\<\x\x/ contained conceal
+ syn match endword /\x\{6}\>/ contained
+ hi meaning guibg=blue
+ hi beginword guibg=green
+ hi endword guibg=red
+ redraw!
+ let lines = s:screen_lines([1, 1], winwidth(0))
+ let expect = [
+\ "eeeeee>--->-;>some text ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_screenattr_for_comment()
+ call s:test_windows("setl ft=c ts=7 list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
+ call setline(1, " /*\t\t and some more */")
+ norm! gg0
+ syntax on
+ hi SpecialKey term=underline ctermfg=red guifg=red
+ redraw!
+ let line = getline(1)
+ let attr = s:screen_attr(1, [1, 6])
+ call assert_notequal(attr[0], attr[1])
+ call assert_notequal(attr[1], attr[3])
+ call assert_notequal(attr[3], attr[5])
+ call s:close_windows()
+endfunc
+
+func Test_visual_block_and_selection_exclusive()
+ call s:test_windows('setl selection=exclusive')
+ call setline(1, "long line: " . repeat("foobar ", 40) . "TARGETÃ' at end")
+ exe "norm! $3B\<C-v>eAx\<Esc>"
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ let expect = [
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar foobar ",
+\ "+foobar TARGETÃx' ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_multibyte_sign_and_colorcolumn()
+ call s:test_windows("setl nolinebreak cc=3 list listchars=nbsp:\u2423,tab:\u2595\u2014,trail:\u02d1,eol:\ub6")
+ call setline(1, ["", "a b c", "a b c"])
+ exe "sign define foo text=\uff0b"
+ exe "sign place 1 name=foo line=2 buffer=" . bufnr('%')
+ redraw!
+ norm! ggj0
+ let signwidth = strdisplaywidth("\uff0b")
+ let attr1 = s:screen_attr(2, [1, 3], signwidth)
+ let attr2 = s:screen_attr(3, [1, 3], signwidth)
+ call assert_equal(attr1[0], attr2[0])
+ call assert_equal(attr1[1], attr2[1])
+ call assert_equal(attr1[2], attr2[2])
+ let lines = s:screen_lines([1, 3], winwidth(0))
+ let expect = [
+\ " ¶ ",
+\ "+a b c¶ ",
+\ " a b c¶ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_colorcolumn_priority()
+ call s:test_windows('setl cc=4 cuc hls')
+ call setline(1, ["xxyy", ""])
+ norm! gg
+ exe "normal! /xxyy\<CR>"
+ norm! G
+ redraw!
+ let line_attr = s:screen_attr(1, [1, &cc])
+ " Search wins over CursorColumn
+ call assert_equal(line_attr[1], line_attr[0])
+ " Search wins over Colorcolumn
+ call assert_equal(line_attr[2], line_attr[3])
+ call s:close_windows('setl hls&vim')
+endfunc
+
+func Test_illegal_byte_and_breakat()
+ call s:test_windows("setl sbr= brk+=<")
+ vert resize 18
+ call setline(1, repeat("\x80", 6))
+ redraw!
+ let lines = s:screen_lines([1, 2], winwidth(0))
+ let expect = [
+\ "<80><80><80><80><8",
+\ "0><80> ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('setl brk&vim')
+endfunc
+
+func Test_multibyte_wrap_and_breakat()
+ call s:test_windows("setl sbr= brk+=>")
+ call setline(1, repeat('a', 17) . repeat('ã‚', 2))
+ redraw!
+ let lines = s:screen_lines([1, 2], winwidth(0))
+ let expect = [
+\ "aaaaaaaaaaaaaaaaaã‚>",
+\ "ã‚ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows('setl brk&vim')
+endfunc
+
+func Test_chinese_char_on_wrap_column()
+ call s:test_windows("setl nolbr wrap sbr=")
+ syntax off
+ call setline(1, [
+\ 'aaaaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaaaaa中'.
+\ 'hello'])
+ call cursor(1,1)
+ norm! $
+ redraw!
+ let expect=[
+\ '<<<aaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中aaaaaaaaaaaaaaaaa>',
+\ '中hello ']
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_lua.vim b/src/testdir/test_lua.vim
new file mode 100644
index 0000000..2f9e21f
--- /dev/null
+++ b/src/testdir/test_lua.vim
@@ -0,0 +1,1235 @@
+" Tests for Lua.
+
+source check.vim
+
+" This test also works without the lua feature.
+func Test_skip_lua()
+ if 0
+ lua print("Not executed")
+ endif
+endfunc
+
+CheckFeature lua
+
+" Depending on the lua version, the error messages are different.
+let [s:major, s:minor, s:patch] = luaeval('vim.lua_version')->split('\.')->map({-> str2nr(v:val)})
+let s:lua_53_or_later = 0
+let s:lua_543 = 0
+if (s:major == 5 && s:minor >= 3) || s:major > 5
+ let s:lua_53_or_later = 1
+ if s:major == 5 && s:minor == 4 && s:patch == 3
+ let s:lua_543 = 1
+ endif
+endif
+
+func TearDown()
+ " Run garbage collection after each test to exercise luaV_setref().
+ call test_garbagecollect_now()
+endfunc
+
+" Check that switching to another buffer does not trigger ml_get error.
+func Test_lua_command_new_no_ml_get_error()
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ luado vim.command("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ %bwipe!
+endfunc
+
+" Test vim.command()
+func Test_lua_command()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ luado vim.command("1,2d_")
+ call assert_equal(['three'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_lua_luado()
+ new
+ call setline(1, ['one', 'two'])
+ luado return(linenr)
+ call assert_equal(['1', '2'], getline(1, '$'))
+ close!
+
+ " Error cases
+ call assert_fails('luado string.format()',
+ \ "[string \"vim chunk\"]:1: bad argument #1 to 'format' (string expected, got no value)")
+ if s:lua_543
+ let msg = "[string \"vim chunk\"]:1: global 'func' is not callable (a nil value)"
+ elseif s:lua_53_or_later
+ let msg = "[string \"vim chunk\"]:1: attempt to call a nil value (global 'func')"
+ else
+ let msg = "[string \"vim chunk\"]:1: attempt to call global 'func' (a nil value)"
+ endif
+ call assert_fails('luado func()', msg)
+ call assert_fails('luado error("failed")', "[string \"vim chunk\"]:1: failed")
+endfunc
+
+" Test vim.eval()
+func Test_lua_eval()
+ " lua.eval with a number
+ lua v = vim.eval('123')
+ call assert_equal('number', luaeval('vim.type(v)'))
+ call assert_equal(123, luaeval('v'))
+
+ " lua.eval with a string
+ lua v = vim.eval('"abc"')
+ call assert_equal('string', 'vim.type(v)'->luaeval())
+ call assert_equal('abc', luaeval('v'))
+
+ " lua.eval with a list
+ lua v = vim.eval("['a']")
+ call assert_equal('list', luaeval('vim.type(v)'))
+ call assert_equal(['a'], luaeval('v'))
+
+ " lua.eval with a dict
+ lua v = vim.eval("{'a':'b'}")
+ call assert_equal('dict', luaeval('vim.type(v)'))
+ call assert_equal({'a':'b'}, luaeval('v'))
+
+ " lua.eval with a blob
+ lua v = vim.eval("0z00112233.deadbeef")
+ call assert_equal('blob', luaeval('vim.type(v)'))
+ call assert_equal(0z00112233.deadbeef, luaeval('v'))
+
+ " lua.eval with a float
+ lua v = vim.eval('3.14')
+ call assert_equal('number', luaeval('vim.type(v)'))
+ call assert_equal(3.14, luaeval('v'))
+
+ " lua.eval with a bool
+ lua v = vim.eval('v:true')
+ call assert_equal('number', luaeval('vim.type(v)'))
+ call assert_equal(1, luaeval('v'))
+ lua v = vim.eval('v:false')
+ call assert_equal('number', luaeval('vim.type(v)'))
+ call assert_equal(0, luaeval('v'))
+
+ " lua.eval with a null
+ lua v = vim.eval('v:null')
+ call assert_equal('nil', luaeval('vim.type(v)'))
+ call assert_equal(v:null, luaeval('v'))
+
+ call assert_fails('lua v = vim.eval(nil)',
+ \ "[string \"vim chunk\"]:1: bad argument #1 to 'eval' (string expected, got nil)")
+ call assert_fails('lua v = vim.eval(true)',
+ \ "[string \"vim chunk\"]:1: bad argument #1 to 'eval' (string expected, got boolean)")
+ call assert_fails('lua v = vim.eval({})',
+ \ "[string \"vim chunk\"]:1: bad argument #1 to 'eval' (string expected, got table)")
+ call assert_fails('lua v = vim.eval(print)',
+ \ "[string \"vim chunk\"]:1: bad argument #1 to 'eval' (string expected, got function)")
+ call assert_fails('lua v = vim.eval(vim.buffer())',
+ \ "[string \"vim chunk\"]:1: bad argument #1 to 'eval' (string expected, got userdata)")
+
+ lua v = nil
+endfunc
+
+" Test luaeval() with lambda
+func Test_luaeval_with_lambda()
+ lua function hello_luaeval_lambda(a, cb) return a .. cb() end
+ call assert_equal('helloworld',
+ \ luaeval('hello_luaeval_lambda(_A[1], _A[2])',
+ \ ['hello', {->'world'}]))
+ lua hello_luaeval_lambda = nil
+endfunc
+
+" Test vim.window()
+func Test_lua_window()
+ e Xfoo2
+ new Xfoo1
+
+ " Window 1 (top window) contains Xfoo1
+ " Window 2 (bottom window) contains Xfoo2
+ call assert_equal('Xfoo1', luaeval('vim.window(1):buffer().name'))
+ call assert_equal('Xfoo2', luaeval('vim.window(2):buffer().name'))
+
+ " Window 3 does not exist so vim.window(3) should return nil
+ call assert_equal('nil', luaeval('tostring(vim.window(3))'))
+
+ if s:lua_543
+ let msg = "[string \"luaeval\"]:1: field 'xyz' is not callable (a nil value)"
+ elseif s:lua_53_or_later
+ let msg = "[string \"luaeval\"]:1: attempt to call a nil value (field 'xyz')"
+ else
+ let msg = "[string \"luaeval\"]:1: attempt to call field 'xyz' (a nil value)"
+ endif
+ call assert_fails("let n = luaeval('vim.window().xyz()')", msg)
+ call assert_fails('lua vim.window().xyz = 1',
+ \ "[string \"vim chunk\"]:1: invalid window property: `xyz'")
+
+ %bwipe!
+endfunc
+
+" Test vim.window().height
+func Test_lua_window_height()
+ new
+ lua vim.window().height = 2
+ call assert_equal(2, winheight(0))
+ lua vim.window().height = vim.window().height + 1
+ call assert_equal(3, winheight(0))
+ bwipe!
+endfunc
+
+" Test vim.window().width
+func Test_lua_window_width()
+ vert new
+ lua vim.window().width = 2
+ call assert_equal(2, winwidth(0))
+ lua vim.window().width = vim.window().width + 1
+ call assert_equal(3, winwidth(0))
+ bwipe!
+endfunc
+
+" Test vim.window().line and vim.window.col
+func Test_lua_window_line_col()
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ lua vim.window().line = 2
+ lua vim.window().col = 4
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ lua vim.window().line = vim.window().line + 1
+ lua vim.window().col = vim.window().col - 1
+ call assert_equal([0, 3, 3, 0], getpos('.'))
+
+ call assert_fails('lua vim.window().line = 10',
+ \ '[string "vim chunk"]:1: line out of range')
+ bwipe!
+endfunc
+
+" Test vim.call
+func Test_lua_call()
+ call assert_equal(has('lua'), luaeval('vim.call("has", "lua")'))
+ call assert_equal(printf("Hello %s", "vim"), luaeval('vim.call("printf", "Hello %s", "vim")'))
+
+ " Error cases
+ call assert_fails("call luaeval('vim.call(\"min\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21)')",
+ \ s:lua_53_or_later
+ \ ? '[string "luaeval"]:1: Function called with too many arguments'
+ \ : 'Function called with too many arguments')
+ lua co = coroutine.create(function () print("hi") end)
+ call assert_fails("call luaeval('vim.call(\"type\", co)')",
+ \ s:lua_53_or_later
+ \ ? '[string "luaeval"]:1: lua: cannot convert value'
+ \ : 'lua: cannot convert value')
+ lua co = nil
+ call assert_fails("call luaeval('vim.call(\"abc\")')",
+ \ ['E117:', s:lua_53_or_later ? '\[string "luaeval"]:1: lua: call_vim_function failed'
+ \ : 'lua: call_vim_function failed'])
+endfunc
+
+" Test vim.fn.*
+func Test_lua_fn()
+ call assert_equal(has('lua'), luaeval('vim.fn.has("lua")'))
+ call assert_equal(printf("Hello %s", "vim"), luaeval('vim.fn.printf("Hello %s", "vim")'))
+endfunc
+
+" Test setting the current window
+func Test_lua_window_set_current()
+ new Xfoo1
+ lua w1 = vim.window()
+ new Xfoo2
+ lua w2 = vim.window()
+
+ call assert_equal('Xfoo2', bufname('%'))
+ lua w1()
+ call assert_equal('Xfoo1', bufname('%'))
+ lua w2()
+ call assert_equal('Xfoo2', bufname('%'))
+
+ lua w1, w2 = nil
+ %bwipe!
+endfunc
+
+" Test vim.window().buffer
+func Test_lua_window_buffer()
+ new Xfoo1
+ lua w1 = vim.window()
+ lua b1 = w1.buffer()
+ new Xfoo2
+ lua w2 = vim.window()
+ lua b2 = w2.buffer()
+
+ lua b1()
+ call assert_equal('Xfoo1', bufname('%'))
+ lua b2()
+ call assert_equal('Xfoo2', bufname('%'))
+
+ lua b1, b2, w1, w2 = nil
+ %bwipe!
+endfunc
+
+" Test vim.window():previous() and vim.window():next()
+func Test_lua_window_next_previous()
+ new Xfoo1
+ new Xfoo2
+ new Xfoo3
+ wincmd j
+
+ call assert_equal('Xfoo2', luaeval('vim.window().buffer().name'))
+ call assert_equal('Xfoo1', luaeval('vim.window():next():buffer().name'))
+ call assert_equal('Xfoo3', luaeval('vim.window():previous():buffer().name'))
+
+ %bwipe!
+endfunc
+
+" Test vim.window():isvalid()
+func Test_lua_window_isvalid()
+ new Xfoo
+ lua w = vim.window()
+ call assert_true(luaeval('w:isvalid()'))
+
+ " FIXME: how to test the case when isvalid() returns v:false?
+ " isvalid() gives errors when the window is deleted. Is it a bug?
+
+ lua w = nil
+ bwipe!
+endfunc
+
+" Test vim.buffer() with and without argument
+func Test_lua_buffer()
+ new Xfoo1
+ let bn1 = bufnr('%')
+ new Xfoo2
+ let bn2 = bufnr('%')
+
+ " Test vim.buffer() without argument.
+ call assert_equal('Xfoo2', luaeval("vim.buffer().name"))
+
+ " Test vim.buffer() with string argument.
+ call assert_equal('Xfoo1', luaeval("vim.buffer('Xfoo1').name"))
+ call assert_equal('Xfoo2', luaeval("vim.buffer('Xfoo2').name"))
+
+ " Test vim.buffer() with integer argument.
+ call assert_equal('Xfoo1', luaeval("vim.buffer(" . bn1 . ").name"))
+ call assert_equal('Xfoo2', luaeval("vim.buffer(" . bn2 . ").name"))
+
+ lua bn1, bn2 = nil
+ %bwipe!
+endfunc
+
+" Test vim.buffer().name and vim.buffer().fname
+func Test_lua_buffer_name()
+ new
+ call assert_equal('', luaeval('vim.buffer().name'))
+ call assert_equal('', luaeval('vim.buffer().fname'))
+ bwipe!
+
+ new Xfoo
+ call assert_equal('Xfoo', luaeval('vim.buffer().name'))
+ call assert_equal(expand('%:p'), luaeval('vim.buffer().fname'))
+ bwipe!
+endfunc
+
+" Test vim.buffer().number
+func Test_lua_buffer_number()
+ " All numbers in Lua are floating points number (no integers).
+ call assert_equal(bufnr('%'), float2nr(luaeval('vim.buffer().number')))
+endfunc
+
+" Test inserting lines in buffer.
+func Test_lua_buffer_insert()
+ new
+ lua vim.buffer()[1] = '3'
+ lua vim.buffer():insert('1', 0)
+ lua vim.buffer():insert('2', 1)
+ lua vim.buffer():insert('4', 10)
+
+ call assert_equal(['1', '2', '3', '4'], getline(1, '$'))
+ call assert_equal('4', luaeval('vim.buffer()[4]'))
+ call assert_equal(v:null, luaeval('vim.buffer()[5]'))
+ call assert_equal(v:null, luaeval('vim.buffer()[{}]'))
+ if s:lua_543
+ let msg = "[string \"vim chunk\"]:1: method 'xyz' is not callable (a nil value)"
+ elseif s:lua_53_or_later
+ let msg = "[string \"vim chunk\"]:1: attempt to call a nil value (method 'xyz')"
+ else
+ let msg = "[string \"vim chunk\"]:1: attempt to call method 'xyz' (a nil value)"
+ endif
+ call assert_fails('lua vim.buffer():xyz()', msg)
+ call assert_fails('lua vim.buffer()[1] = {}',
+ \ '[string "vim chunk"]:1: wrong argument to change')
+ bwipe!
+endfunc
+
+" Test deleting line in buffer
+func Test_lua_buffer_delete()
+ new
+ call setline(1, ['1', '2', '3'])
+ call cursor(3, 1)
+ lua vim.buffer()[2] = nil
+ call assert_equal(['1', '3'], getline(1, '$'))
+
+ call assert_fails('lua vim.buffer()[3] = nil',
+ \ '[string "vim chunk"]:1: invalid line number')
+ bwipe!
+endfunc
+
+" Test #vim.buffer() i.e. number of lines in buffer
+func Test_lua_buffer_number_lines()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ call assert_equal(3, luaeval('#vim.buffer()'))
+ bwipe!
+endfunc
+
+" Test vim.buffer():next() and vim.buffer():previous()
+" Note that these functions get the next or previous buffers
+" but do not switch buffer.
+func Test_lua_buffer_next_previous()
+ new Xfoo1
+ new Xfoo2
+ new Xfoo3
+ b Xfoo2
+
+ lua bn = vim.buffer():next()
+ lua bp = vim.buffer():previous()
+
+ call assert_equal('Xfoo2', luaeval('vim.buffer().name'))
+ call assert_equal('Xfoo1', luaeval('bp.name'))
+ call assert_equal('Xfoo3', luaeval('bn.name'))
+
+ call assert_equal('Xfoo2', bufname('%'))
+
+ lua bn()
+ call assert_equal('Xfoo3', luaeval('vim.buffer().name'))
+ call assert_equal('Xfoo3', bufname('%'))
+
+ lua bp()
+ call assert_equal('Xfoo1', luaeval('vim.buffer().name'))
+ call assert_equal('Xfoo1', bufname('%'))
+
+ lua bn, bp = nil
+ %bwipe!
+endfunc
+
+" Test vim.buffer():isvalid()
+func Test_lua_buffer_isvalid()
+ new Xfoo
+ lua b = vim.buffer()
+ call assert_true(luaeval('b:isvalid()'))
+
+ " FIXME: how to test the case when isvalid() returns v:false?
+ " isvalid() gives errors when the buffer is wiped. Is it a bug?
+
+ lua b = nil
+ bwipe!
+endfunc
+
+func Test_lua_list()
+ call assert_equal([], luaeval('vim.list()'))
+
+ let l = []
+ lua l = vim.eval('l')
+ lua l:add(123)
+ lua l:add('abc')
+ lua l:add(true)
+ lua l:add(false)
+ lua l:add(nil)
+ lua l:add(vim.eval("[1, 2, 3]"))
+ lua l:add(vim.eval("{'a':1, 'b':2, 'c':3}"))
+ call assert_equal([123, 'abc', v:true, v:false, v:null, [1, 2, 3], {'a': 1, 'b': 2, 'c': 3}], l)
+ call assert_equal(7, luaeval('#l'))
+ call assert_match('^list: \%(0x\)\?\x\+$', luaeval('tostring(l)'))
+
+ lua l[1] = 124
+ lua l[6] = nil
+ lua l:insert('first')
+ lua l:insert('xx', 3)
+ call assert_fails('lua l:insert("xx", -20)',
+ \ '[string "vim chunk"]:1: invalid position')
+ call assert_equal(['first', 124, 'abc', 'xx', v:true, v:false, v:null, {'a': 1, 'b': 2, 'c': 3}], l)
+
+ lockvar 1 l
+ call assert_fails('lua l:add("x")', '[string "vim chunk"]:1: list is locked')
+ call assert_fails('lua l:insert(2)', '[string "vim chunk"]:1: list is locked')
+ call assert_fails('lua l[9] = 1', '[string "vim chunk"]:1: list is locked')
+
+ unlockvar l
+ let l = [1, 2]
+ lua ll = vim.eval('l')
+ let x = luaeval("ll[3]")
+ call assert_equal(v:null, x)
+ if s:lua_543
+ let msg = "[string \"luaeval\"]:1: method 'xyz' is not callable (a nil value)"
+ elseif s:lua_53_or_later
+ let msg = "[string \"luaeval\"]:1: attempt to call a nil value (method 'xyz')"
+ else
+ let msg = "[string \"luaeval\"]:1: attempt to call method 'xyz' (a nil value)"
+ endif
+ call assert_fails('let x = luaeval("ll:xyz(3)")', msg)
+ let y = luaeval("ll[{}]")
+ call assert_equal(v:null, y)
+
+ lua l = nil
+endfunc
+
+func Test_lua_list_table()
+ " See :help lua-vim
+ " Non-numeric keys should not be used to initialize the list
+ " so say = 'hi' should be ignored.
+ lua t = {3.14, 'hello', false, true, say = 'hi'}
+ call assert_equal([3.14, 'hello', v:false, v:true], luaeval('vim.list(t)'))
+ lua t = nil
+
+ call assert_fails('lua vim.list(1)', '[string "vim chunk"]:1: table expected, got number')
+ call assert_fails('lua vim.list("x")', '[string "vim chunk"]:1: table expected, got string')
+ call assert_fails('lua vim.list(print)', '[string "vim chunk"]:1: table expected, got function')
+ call assert_fails('lua vim.list(true)', '[string "vim chunk"]:1: table expected, got boolean')
+endfunc
+
+func Test_lua_list_table_insert_remove()
+ if !s:lua_53_or_later
+ throw 'Skipped: Lua version < 5.3'
+ endif
+
+ let l = [1, 2]
+ lua t = vim.eval('l')
+ lua table.insert(t, 10)
+ lua t[#t + 1] = 20
+ lua table.insert(t, 2, 30)
+ call assert_equal(l, [1, 30, 2, 10, 20])
+ lua table.remove(t, 2)
+ call assert_equal(l, [1, 2, 10, 20])
+ lua t[3] = nil
+ call assert_equal(l, [1, 2, 20])
+ lua removed_value = table.remove(t, 3)
+ call assert_equal(luaeval('removed_value'), 20)
+ lua t = nil
+ lua removed_value = nil
+ unlet l
+endfunc
+
+" Test l() i.e. iterator on list
+func Test_lua_list_iter()
+ lua l = vim.list():add('foo'):add('bar')
+ lua str = ''
+ lua for v in l() do str = str .. v end
+ call assert_equal('foobar', luaeval('str'))
+
+ lua str, l = nil
+endfunc
+
+func Test_lua_recursive_list()
+ lua l = vim.list():add(1):add(2)
+ lua l = l:add(l)
+
+ call assert_equal(1, luaeval('l[1]'))
+ call assert_equal(2, luaeval('l[2]'))
+
+ call assert_equal(1, luaeval('l[3][1]'))
+ call assert_equal(2, luaeval('l[3][2]'))
+
+ call assert_equal(1, luaeval('l[3][3][1]'))
+ call assert_equal(2, luaeval('l[3][3][2]'))
+
+ call assert_equal('[1, 2, [...]]', string(luaeval('l')))
+
+ call assert_match('^list: \%(0x\)\?\x\+$', luaeval('tostring(l)'))
+ call assert_equal(luaeval('tostring(l)'), luaeval('tostring(l[3])'))
+
+ call assert_equal(luaeval('l'), luaeval('l[3]'))
+ call assert_equal(luaeval('l'), luaeval('l[3][3]'))
+
+ lua l = nil
+endfunc
+
+func Test_lua_dict()
+ call assert_equal({}, luaeval('vim.dict()'))
+
+ let d = {}
+ lua d = vim.eval('d')
+ lua d[0] = 123
+ lua d[1] = "abc"
+ lua d[2] = true
+ lua d[3] = false
+ lua d[4] = vim.eval("[1, 2, 3]")
+ lua d[5] = vim.eval("{'a':1, 'b':2, 'c':3}")
+ call assert_equal({'0':123, '1':'abc', '2':v:true, '3':v:false, '4': [1, 2, 3], '5': {'a':1, 'b':2, 'c':3}}, d)
+ call assert_equal(6, luaeval('#d'))
+ call assert_match('^dict: \%(0x\)\?\x\+$', luaeval('tostring(d)'))
+
+ call assert_equal('abc', luaeval('d[1]'))
+ call assert_equal(v:null, luaeval('d[22]'))
+
+ lua d[0] = 124
+ lua d[4] = nil
+ call assert_equal({'0':124, '1':'abc', '2':v:true, '3':v:false, '5': {'a':1, 'b':2, 'c':3}}, d)
+
+ lockvar 1 d
+ call assert_fails('lua d[6] = 1', '[string "vim chunk"]:1: dict is locked')
+ unlockvar d
+
+ " Error case
+ lua d = {}
+ lua d[''] = 10
+ call assert_fails("let t = luaeval('vim.dict(d)')",
+ \ s:lua_53_or_later
+ \ ? '[string "luaeval"]:1: table has empty key'
+ \ : 'table has empty key')
+ let d = {}
+ lua x = vim.eval('d')
+ call assert_fails("lua x[''] = 10", '[string "vim chunk"]:1: empty key')
+ lua x['a'] = nil
+ call assert_equal({}, d)
+
+ " cannot assign funcrefs in the global scope
+ lua x = vim.eval('g:')
+ call assert_fails("lua x['min'] = vim.funcref('max')",
+ \ '[string "vim chunk"]:1: cannot assign funcref to builtin scope')
+
+ lua d = nil
+endfunc
+
+func Test_lua_dict_table()
+ lua t = {key1 = 'x', key2 = 3.14, key3 = true, key4 = false}
+ call assert_equal({'key1': 'x', 'key2': 3.14, 'key3': v:true, 'key4': v:false},
+ \ luaeval('vim.dict(t)'))
+
+ " Same example as in :help lua-vim.
+ lua t = {math.pi, false, say = 'hi'}
+ " FIXME: commented out as it currently does not work as documented:
+ " Expected {'say': 'hi'}
+ " but got {'1': 3.141593, '2': v:false, 'say': 'hi'}
+ " Is the documentation or the code wrong?
+ "call assert_equal({'say' : 'hi'}, luaeval('vim.dict(t)'))
+ lua t = nil
+
+ call assert_fails('lua vim.dict(1)', '[string "vim chunk"]:1: table expected, got number')
+ call assert_fails('lua vim.dict("x")', '[string "vim chunk"]:1: table expected, got string')
+ call assert_fails('lua vim.dict(print)', '[string "vim chunk"]:1: table expected, got function')
+ call assert_fails('lua vim.dict(true)', '[string "vim chunk"]:1: table expected, got boolean')
+endfunc
+
+" Test d() i.e. iterator on dictionary
+func Test_lua_dict_iter()
+ let d = {'a': 1, 'b':2}
+ lua d = vim.eval('d')
+ lua str = ''
+ lua for k,v in d() do str = str .. k ..':' .. v .. ',' end
+ call assert_equal('a:1,b:2,', luaeval('str'))
+
+ lua str, d = nil
+endfunc
+
+func Test_lua_blob()
+ call assert_equal(0z, luaeval('vim.blob("")'))
+ call assert_equal(0z31326162, luaeval('vim.blob("12ab")'))
+ call assert_equal(0z00010203, luaeval('vim.blob("\x00\x01\x02\x03")'))
+ call assert_equal(0z8081FEFF, luaeval('vim.blob("\x80\x81\xfe\xff")'))
+
+ lua b = vim.blob("\x00\x00\x00\x00")
+ call assert_equal(0z00000000, luaeval('b'))
+ call assert_equal(4, luaeval('#b'))
+ lua b[0], b[1], b[2], b[3] = 1, 32, 256, 0xff
+ call assert_equal(0z012000ff, luaeval('b'))
+ lua b[4] = string.byte("z", 1)
+ call assert_equal(0z012000ff.7a, luaeval('b'))
+ call assert_equal(5, luaeval('#b'))
+ call assert_fails('lua b[#b+1] = 0x80', '[string "vim chunk"]:1: index out of range')
+ lua b:add("12ab")
+ call assert_equal(0z012000ff.7a313261.62, luaeval('b'))
+ call assert_equal(9, luaeval('#b'))
+ call assert_fails('lua b:add(nil)', '[string "vim chunk"]:1: string expected, got nil')
+ call assert_fails('lua b:add(true)', '[string "vim chunk"]:1: string expected, got boolean')
+ call assert_fails('lua b:add({})', '[string "vim chunk"]:1: string expected, got table')
+ lua b = nil
+
+ let b = 0z0102
+ lua lb = vim.eval('b')
+ let n = luaeval('lb[1]')
+ call assert_equal(2, n)
+ let n = luaeval('lb[6]')
+ call assert_equal(v:null, n)
+ if s:lua_543
+ let msg = "[string \"luaeval\"]:1: method 'xyz' is not callable (a nil value)"
+ elseif s:lua_53_or_later
+ let msg = "[string \"luaeval\"]:1: attempt to call a nil value (method 'xyz')"
+ else
+ let msg = "[string \"luaeval\"]:1: attempt to call method 'xyz' (a nil value)"
+ endif
+ call assert_fails('let x = luaeval("lb:xyz(3)")', msg)
+ let y = luaeval("lb[{}]")
+ call assert_equal(v:null, y)
+
+ lockvar b
+ call assert_fails('lua lb[1] = 2', '[string "vim chunk"]:1: blob is locked')
+ call assert_fails('lua lb:add("12")', '[string "vim chunk"]:1: blob is locked')
+
+ " Error cases
+ lua t = {}
+ call assert_fails('lua b = vim.blob(t)',
+ \ '[string "vim chunk"]:1: string expected, got table')
+endfunc
+
+def Vim9Test(Callback: func())
+ Callback()
+enddef
+
+func Test_call_lua_func_from_vim9_func()
+ " this only tests that Vim doesn't crash
+ lua << EOF
+vim.fn.Vim9Test(function () print('Hello') end)
+EOF
+endfunc
+
+func Test_lua_funcref()
+ function I(x)
+ return a:x
+ endfunction
+ let R = function('I')
+ lua i1 = vim.funcref"I"
+ lua i2 = vim.eval"R"
+ lua msg = "funcref|test|" .. (#i2(i1) == #i1(i2) and "OK" or "FAIL")
+ lua msg = vim.funcref"tr"(msg, "|", " ")
+ call assert_equal("funcref test OK", luaeval('msg'))
+
+ " Error cases
+ call assert_fails('lua f1 = vim.funcref("")',
+ \ '[string "vim chunk"]:1: invalid function name: ')
+ call assert_fails('lua f1 = vim.funcref("10")',
+ \ '[string "vim chunk"]:1: invalid function name: 10')
+ let fname = test_null_string()
+ call assert_fails('lua f1 = vim.funcref(fname)',
+ \ "[string \"vim chunk\"]:1: bad argument #1 to 'funcref' (string expected, got nil)")
+ call assert_fails('lua vim.funcref("abc")()',
+ \ ['E117:', '\[string "vim chunk"]:1: cannot call funcref'])
+
+ " dict funcref
+ function Mylen() dict
+ return len(self.data)
+ endfunction
+ let l = [0, 1, 2, 3]
+ let mydict = {'data': l}
+ lua d = vim.eval"mydict"
+ lua d.len = vim.funcref"Mylen" -- assign d as 'self'
+ lua res = (d.len() == vim.funcref"len"(vim.eval"l")) and "OK" or "FAIL"
+ call assert_equal("OK", luaeval('res'))
+ call assert_equal(function('Mylen', {'data': l, 'len': function('Mylen')}), mydict.len)
+
+ lua i1, i2, msg, d, res = nil
+endfunc
+
+" Test vim.type()
+func Test_lua_type()
+ " The following values are identical to Lua's type function.
+ call assert_equal('string', luaeval('vim.type("foo")'))
+ call assert_equal('number', luaeval('vim.type(1)'))
+ call assert_equal('number', luaeval('vim.type(1.2)'))
+ call assert_equal('function', luaeval('vim.type(print)'))
+ call assert_equal('table', luaeval('vim.type({})'))
+ call assert_equal('boolean', luaeval('vim.type(true)'))
+ call assert_equal('boolean', luaeval('vim.type(false)'))
+ call assert_equal('nil', luaeval('vim.type(nil)'))
+
+ " The following values are specific to Vim.
+ call assert_equal('window', luaeval('vim.type(vim.window())'))
+ call assert_equal('buffer', luaeval('vim.type(vim.buffer())'))
+ call assert_equal('list', luaeval('vim.type(vim.list())'))
+ call assert_equal('dict', luaeval('vim.type(vim.dict())'))
+ call assert_equal('funcref', luaeval('vim.type(vim.funcref("Test_type"))'))
+endfunc
+
+" Test vim.open()
+func Test_lua_open()
+ call assert_notmatch('XOpen', execute('ls'))
+
+ " Open a buffer XOpen1, but do not jump to it.
+ lua b = vim.open('XOpen1')
+ call assert_equal('XOpen1', luaeval('b.name'))
+ call assert_equal('', bufname('%'))
+
+ call assert_match('XOpen1', execute('ls'))
+ call assert_notequal('XOpen2', bufname('%'))
+
+ " Open a buffer XOpen2 and jump to it.
+ lua b = vim.open('XOpen2')()
+ call assert_equal('XOpen2', luaeval('b.name'))
+ call assert_equal('XOpen2', bufname('%'))
+
+ lua b = nil
+ %bwipe!
+endfunc
+
+func Test_update_package_paths()
+ set runtimepath+=./testluaplugin
+ call assert_equal("hello from lua", luaeval("require('testluaplugin').hello()"))
+endfunc
+
+func Vim_func_call_lua_callback(Concat, Cb)
+ let l:message = a:Concat("hello", "vim")
+ call a:Cb(l:message)
+endfunc
+
+func Test_pass_lua_callback_to_vim_from_lua()
+ lua pass_lua_callback_to_vim_from_lua_result = ""
+ call assert_equal("", luaeval("pass_lua_callback_to_vim_from_lua_result"))
+ lua <<EOF
+ vim.funcref('Vim_func_call_lua_callback')(
+ function(greeting, message)
+ return greeting .. " " .. message
+ end,
+ function(message)
+ pass_lua_callback_to_vim_from_lua_result = message
+ end)
+EOF
+ call assert_equal("hello vim", luaeval("pass_lua_callback_to_vim_from_lua_result"))
+endfunc
+
+func Vim_func_call_metatable_lua_callback(Greet)
+ return a:Greet("world")
+endfunc
+
+func Test_pass_lua_metatable_callback_to_vim_from_lua()
+ let result = luaeval("vim.funcref('Vim_func_call_metatable_lua_callback')(setmetatable({ space = ' '}, { __call = function(tbl, msg) return 'hello' .. tbl.space .. msg end }) )")
+ call assert_equal("hello world", result)
+endfunc
+
+" Test vim.line()
+func Test_lua_line()
+ new
+ call setline(1, ['first line', 'second line'])
+ 1
+ call assert_equal('first line', luaeval('vim.line()'))
+ 2
+ call assert_equal('second line', luaeval('vim.line()'))
+ bwipe!
+endfunc
+
+" Test vim.beep()
+func Test_lua_beep()
+ call assert_beeps('lua vim.beep()')
+endfunc
+
+" Test errors in luaeval()
+func Test_luaeval_error()
+ " Compile error
+ call assert_fails("call luaeval('-nil')",
+ \ '[string "luaeval"]:1: attempt to perform arithmetic on a nil value')
+ call assert_fails("call luaeval(']')",
+ \ "[string \"luaeval\"]:1: unexpected symbol near ']'")
+ lua co = coroutine.create(function () print("hi") end)
+ call assert_fails('let i = luaeval("co")', 'luaeval: cannot convert value')
+ lua co = nil
+ call assert_fails('let m = luaeval("{}")', 'luaeval: cannot convert value')
+endfunc
+
+" Test :luafile foo.lua
+func Test_luafile()
+ call writefile(["str = 'hello'", "num = 123" ], 'Xlua_file', 'D')
+ call setfperm('Xlua_file', 'r-xr-xr-x')
+
+ luafile Xlua_file
+ call assert_equal('hello', luaeval('str'))
+ call assert_equal(123, luaeval('num'))
+
+ lua str, num = nil
+endfunc
+
+" Test :luafile %
+func Test_luafile_percent()
+ new Xlua_file
+ append
+ str, num = 'foo', 321.0
+ print(string.format('str=%s, num=%d', str, num))
+.
+ w!
+ luafile %
+ let msg = split(execute('message'), "\n")[-1]
+ call assert_equal('str=foo, num=321', msg)
+
+ lua str, num = nil
+ call delete('Xlua_file')
+ bwipe!
+endfunc
+
+" Test :luafile with syntax error
+func Test_luafile_error()
+ new Xlua_file
+ call writefile(['nil = 0' ], 'Xlua_file', 'D')
+ call setfperm('Xlua_file', 'r-xr-xr-x')
+
+ call assert_fails('luafile Xlua_file', "Xlua_file:1: unexpected symbol near 'nil'")
+
+ bwipe!
+endfunc
+
+" Test :luafile printing a long string
+func Test_luafile_print()
+ new Xlua_file
+ let lines =<< trim END
+ local data = ''
+ for i = 1, 130 do
+ data = data .. 'xxxxx asd as as dad sad sad xz cxz czxcxzczxc ad ad asd asd asd asd asd'
+ end
+ print(data)
+ END
+ call setline(1, lines)
+ w
+ luafile %
+
+ call delete('Xlua_file')
+ bwipe!
+endfunc
+
+" Test for dealing with strings containing newlines and null character
+func Test_lua_string_with_newline()
+ let x = execute('lua print("Hello\nWorld", 2)')
+ call assert_equal("\nHello\nWorld 2", x)
+ new
+ lua k = vim.buffer(vim.eval('bufnr()'))
+ lua k:insert("Hello\0World", 0)
+ call assert_equal(["Hello\nWorld", ''], getline(1, '$'))
+ close!
+endfunc
+
+func Test_lua_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ lua << trim EOF
+ w = vim.window()
+ w.line = 1
+ w.col = 5
+ EOF
+ call assert_equal([1, 5], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 5], [line('.'), col('.')])
+endfunc
+
+" Test for various heredoc syntax
+func Test_lua_heredoc()
+ lua << END
+vim.command('let s = "A"')
+END
+ lua <<
+vim.command('let s ..= "B"')
+.
+ lua << trim END
+ vim.command('let s ..= "C"')
+ END
+ lua << trim
+ vim.command('let s ..= "D"')
+ .
+ lua << trim eof
+ vim.command('let s ..= "E"')
+ eof
+ call assert_equal('ABCDE', s)
+endfunc
+
+" Test for adding, accessing and removing global variables using the vim.g
+" Lua table
+func Test_lua_global_var_table()
+ " Access global variables with different types of values
+ let g:Var1 = 10
+ let g:Var2 = 'Hello'
+ let g:Var3 = ['a', 'b']
+ let g:Var4 = #{x: 'edit', y: 'run'}
+ let g:Var5 = function('min')
+ call assert_equal(10, luaeval('vim.g.Var1'))
+ call assert_equal('Hello', luaeval('vim.g.Var2'))
+ call assert_equal(['a', 'b'], luaeval('vim.g.Var3'))
+ call assert_equal(#{x: 'edit', y: 'run'}, luaeval('vim.g.Var4'))
+ call assert_equal(2, luaeval('vim.g.Var5')([5, 9, 2]))
+
+ " Access list of dictionaries and dictionary of lists
+ let g:Var1 = [#{a: 10}, #{b: 20}]
+ let g:Var2 = #{p: [5, 6], q: [1.1, 2.2]}
+ call assert_equal([#{a: 10}, #{b: 20}], luaeval('vim.g.Var1'))
+ call assert_equal(#{p: [5, 6], q: [1.1, 2.2]}, luaeval('vim.g.Var2'))
+
+ " Create new global variables with different types of values
+ unlet g:Var1 g:Var2 g:Var3 g:Var4 g:Var5
+ lua << trim END
+ vim.g.Var1 = 34
+ vim.g.Var2 = 'World'
+ vim.g.Var3 = vim.list({'#', '$'})
+ vim.g.Var4 = vim.dict({model='honda', year=2020})
+ vim.g.Var5 = vim.funcref('max')
+ END
+ call assert_equal(34, g:Var1)
+ call assert_equal('World', g:Var2)
+ call assert_equal(['#', '$'], g:Var3)
+ call assert_equal(#{model: 'honda', year: 2020}, g:Var4)
+ call assert_equal(10, g:Var5([5, 10, 9]))
+
+ " Create list of dictionaries and dictionary of lists
+ unlet g:Var1 g:Var2
+ lua << trim END
+ vim.g.Var1 = vim.list({vim.dict({a=10}), vim.dict({b=20})})
+ vim.g.Var2 = vim.dict({p=vim.list({5, 6}), q=vim.list({1.1, 2.2})})
+ END
+ call assert_equal([#{a: 10}, #{b: 20}], luaeval('vim.g.Var1'))
+ call assert_equal(#{p: [5, 6], q: [1.1, 2.2]}, luaeval('vim.g.Var2'))
+
+ " Modify a global variable with a list value or a dictionary value
+ let g:Var1 = [10, 20]
+ let g:Var2 = #{one: 'mercury', two: 'mars'}
+ lua << trim END
+ vim.g.Var1[2] = Nil
+ vim.g.Var1[3] = 15
+ vim.g.Var2['two'] = Nil
+ vim.g.Var2['three'] = 'earth'
+ END
+ call assert_equal([10, 15], g:Var1)
+ call assert_equal(#{one: 'mercury', three: 'earth'}, g:Var2)
+
+ " Remove global variables with different types of values
+ let g:Var1 = 10
+ let g:Var2 = 'Hello'
+ let g:Var3 = ['a', 'b']
+ let g:Var4 = #{x: 'edit', y: 'run'}
+ let g:Var5 = function('min')
+ lua << trim END
+ vim.g.Var1 = Nil
+ vim.g.Var2 = Nil
+ vim.g.Var3 = Nil
+ vim.g.Var4 = Nil
+ vim.g.Var5 = Nil
+ END
+ call assert_false(exists('g:Var1'))
+ call assert_false(exists('g:Var2'))
+ call assert_false(exists('g:Var3'))
+ call assert_false(exists('g:Var4'))
+ call assert_false(exists('g:Var5'))
+
+ " Try to modify and remove a locked global variable
+ let g:Var1 = 10
+ lockvar g:Var1
+ call assert_fails('lua vim.g.Var1 = 20', 'variable is locked')
+ call assert_fails('lua vim.g.Var1 = Nil', 'variable is locked')
+ unlockvar g:Var1
+ let g:Var2 = [7, 14]
+ lockvar 0 g:Var2
+ lua vim.g.Var2[2] = Nil
+ lua vim.g.Var2[3] = 21
+ call assert_fails('lua vim.g.Var2 = Nil', 'variable is locked')
+ call assert_equal([7, 21], g:Var2)
+ lockvar 1 g:Var2
+ call assert_fails('lua vim.g.Var2[2] = Nil', 'list is locked')
+ call assert_fails('lua vim.g.Var2[3] = 21', 'list is locked')
+ unlockvar g:Var2
+
+ let g:TestFunc = function('len')
+ call assert_fails('lua vim.g.func = vim.g.TestFunc', ['E704:', 'Couldn''t add to dictionary'])
+ unlet g:TestFunc
+
+ " Attempt to access a non-existing global variable
+ call assert_equal(v:null, luaeval('vim.g.NonExistingVar'))
+ lua vim.g.NonExisting = Nil
+
+ unlet! g:Var1 g:Var2 g:Var3 g:Var4 g:Var5
+endfunc
+
+" Test for accessing and modifying predefined vim variables using the vim.v
+" Lua table
+func Test_lua_predefined_var_table()
+ call assert_equal(v:progpath, luaeval('vim.v.progpath'))
+ let v:errmsg = 'SomeError'
+ call assert_equal('SomeError', luaeval('vim.v.errmsg'))
+ lua vim.v.errmsg = 'OtherError'
+ call assert_equal('OtherError', v:errmsg)
+ call assert_fails('lua vim.v.errmsg = Nil', 'variable is fixed')
+ let v:oldfiles = ['one', 'two']
+ call assert_equal(['one', 'two'], luaeval('vim.v.oldfiles'))
+ lua vim.v.oldfiles = vim.list({})
+ call assert_equal([], v:oldfiles)
+ call assert_equal(v:null, luaeval('vim.v.null'))
+ call assert_fails('lua vim.v.argv[1] = Nil', 'list is locked')
+ call assert_fails('lua vim.v.newvar = 1', 'Dictionary is locked')
+endfunc
+
+" Test for adding, accessing and modifying window-local variables using the
+" vim.w Lua table
+func Test_lua_window_var_table()
+ " Access window variables with different types of values
+ new
+ let w:wvar1 = 10
+ let w:wvar2 = 'edit'
+ let w:wvar3 = 3.14
+ let w:wvar4 = 0zdeadbeef
+ let w:wvar5 = ['a', 'b']
+ let w:wvar6 = #{action: 'run'}
+ call assert_equal(10, luaeval('vim.w.wvar1'))
+ call assert_equal('edit', luaeval('vim.w.wvar2'))
+ call assert_equal(3.14, luaeval('vim.w.wvar3'))
+ call assert_equal(0zdeadbeef, luaeval('vim.w.wvar4'))
+ call assert_equal(['a', 'b'], luaeval('vim.w.wvar5'))
+ call assert_equal(#{action: 'run'}, luaeval('vim.w.wvar6'))
+ call assert_equal(v:null, luaeval('vim.w.NonExisting'))
+
+ " modify a window variable
+ lua vim.w.wvar2 = 'paste'
+ call assert_equal('paste', w:wvar2)
+
+ " change the type stored in a variable
+ let w:wvar2 = [1, 2]
+ lua vim.w.wvar2 = vim.dict({a=10, b=20})
+ call assert_equal(#{a: 10, b: 20}, w:wvar2)
+
+ " create a new window variable
+ lua vim.w.wvar7 = vim.dict({a=vim.list({1, 2}), b=20})
+ call assert_equal(#{a: [1, 2], b: 20}, w:wvar7)
+
+ " delete a window variable
+ lua vim.w.wvar2 = Nil
+ call assert_false(exists('w:wvar2'))
+
+ new
+ call assert_equal(v:null, luaeval('vim.w.wvar1'))
+ call assert_equal(v:null, luaeval('vim.w.wvar2'))
+ %bw!
+endfunc
+
+" Test for adding, accessing and modifying buffer-local variables using the
+" vim.b Lua table
+func Test_lua_buffer_var_table()
+ " Access buffer variables with different types of values
+ let b:bvar1 = 10
+ let b:bvar2 = 'edit'
+ let b:bvar3 = 3.14
+ let b:bvar4 = 0zdeadbeef
+ let b:bvar5 = ['a', 'b']
+ let b:bvar6 = #{action: 'run'}
+ call assert_equal(10, luaeval('vim.b.bvar1'))
+ call assert_equal('edit', luaeval('vim.b.bvar2'))
+ call assert_equal(3.14, luaeval('vim.b.bvar3'))
+ call assert_equal(0zdeadbeef, luaeval('vim.b.bvar4'))
+ call assert_equal(['a', 'b'], luaeval('vim.b.bvar5'))
+ call assert_equal(#{action: 'run'}, luaeval('vim.b.bvar6'))
+ call assert_equal(v:null, luaeval('vim.b.NonExisting'))
+
+ " modify a buffer variable
+ lua vim.b.bvar2 = 'paste'
+ call assert_equal('paste', b:bvar2)
+
+ " change the type stored in a variable
+ let b:bvar2 = [1, 2]
+ lua vim.b.bvar2 = vim.dict({a=10, b=20})
+ call assert_equal(#{a: 10, b: 20}, b:bvar2)
+
+ " create a new buffer variable
+ lua vim.b.bvar7 = vim.dict({a=vim.list({1, 2}), b=20})
+ call assert_equal(#{a: [1, 2], b: 20}, b:bvar7)
+
+ " delete a buffer variable
+ lua vim.b.bvar2 = Nil
+ call assert_false(exists('b:bvar2'))
+
+ new
+ call assert_equal(v:null, luaeval('vim.b.bvar1'))
+ call assert_equal(v:null, luaeval('vim.b.bvar2'))
+ %bw!
+endfunc
+
+" Test for adding, accessing and modifying tabpage-local variables using the
+" vim.t Lua table
+func Test_lua_tabpage_var_table()
+ " Access tabpage variables with different types of values
+ let t:tvar1 = 10
+ let t:tvar2 = 'edit'
+ let t:tvar3 = 3.14
+ let t:tvar4 = 0zdeadbeef
+ let t:tvar5 = ['a', 'b']
+ let t:tvar6 = #{action: 'run'}
+ call assert_equal(10, luaeval('vim.t.tvar1'))
+ call assert_equal('edit', luaeval('vim.t.tvar2'))
+ call assert_equal(3.14, luaeval('vim.t.tvar3'))
+ call assert_equal(0zdeadbeef, luaeval('vim.t.tvar4'))
+ call assert_equal(['a', 'b'], luaeval('vim.t.tvar5'))
+ call assert_equal(#{action: 'run'}, luaeval('vim.t.tvar6'))
+ call assert_equal(v:null, luaeval('vim.t.NonExisting'))
+
+ " modify a tabpage variable
+ lua vim.t.tvar2 = 'paste'
+ call assert_equal('paste', t:tvar2)
+
+ " change the type stored in a variable
+ let t:tvar2 = [1, 2]
+ lua vim.t.tvar2 = vim.dict({a=10, b=20})
+ call assert_equal(#{a: 10, b: 20}, t:tvar2)
+
+ " create a new tabpage variable
+ lua vim.t.tvar7 = vim.dict({a=vim.list({1, 2}), b=20})
+ call assert_equal(#{a: [1, 2], b: 20}, t:tvar7)
+
+ " delete a tabpage variable
+ lua vim.t.tvar2 = Nil
+ call assert_false(exists('t:tvar2'))
+
+ tabnew
+ call assert_equal(v:null, luaeval('vim.t.tvar1'))
+ call assert_equal(v:null, luaeval('vim.t.tvar2'))
+ %bw!
+endfunc
+
+" Test for vim.version()
+func Test_lua_vim_version()
+ lua << trim END
+ vimver = vim.version()
+ vimver_n = vimver.major * 100 + vimver.minor
+ END
+ call assert_equal(v:version, luaeval('vimver_n'))
+endfunc
+
+" Test for running multiple commands using vim.command()
+func Test_lua_multiple_commands()
+ lua << trim END
+ vim.command([[
+ let Var1 = []
+ for i in range(3)
+ let Var1 += [#{name: 'x'}]
+ endfor
+ augroup Luagroup
+ autocmd!
+ autocmd User Luatest echo 'Hello'
+ augroup END
+ ]])
+ END
+ call assert_equal([{'name': 'x'}, {'name': 'x'}, {'name': 'x'}], Var1)
+ call assert_true(exists('#Luagroup'))
+ call assert_true(exists('#Luagroup#User#Luatest'))
+ augroup Luagroup
+ autocmd!
+ augroup END
+ augroup! Luagroup
+endfunc
+
+func Test_lua_debug()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', {'rows': 10})
+ call term_sendkeys(buf, ":lua debug.debug()\n")
+ call WaitForAssert({-> assert_equal('lua_debug> ', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, "foo = 42\n")
+ call WaitForAssert({-> assert_equal('lua_debug> foo = 42', term_getline(buf, 9))})
+ call WaitForAssert({-> assert_equal('lua_debug> ', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, "print(foo)\n")
+ call WaitForAssert({-> assert_equal('lua_debug> print(foo)', term_getline(buf, 8))})
+ call WaitForAssert({-> assert_equal('42', term_getline(buf, 9))})
+ call WaitForAssert({-> assert_equal('lua_debug> ', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, "-\n")
+ call WaitForAssert({-> assert_equal("(debug command):1: unexpected symbol near '-'",
+ \ term_getline(buf, 9))})
+ call WaitForAssert({-> assert_equal('lua_debug> ', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, "cont\n")
+ call WaitForAssert({-> assert_match(' All$', term_getline(buf, 10))})
+
+ " Entering an empty line also exits the debugger.
+ call term_sendkeys(buf, ":lua debug.debug()\n")
+ call WaitForAssert({-> assert_equal('lua_debug> ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "\n")
+ call WaitForAssert({-> assert_match(' All$', term_getline(buf, 10))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_makeencoding.py b/src/testdir/test_makeencoding.py
new file mode 100644
index 0000000..041edad
--- /dev/null
+++ b/src/testdir/test_makeencoding.py
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Test program for :make, :grep and :cgetfile.
+
+from __future__ import print_function, unicode_literals
+import locale
+import io
+import sys
+
+def set_output_encoding(enc=None):
+ """Set the encoding of stdout and stderr
+
+ arguments:
+ enc -- Encoding name.
+ If omitted, locale.getpreferredencoding() is used.
+ """
+ if enc is None:
+ enc = locale.getpreferredencoding()
+
+ def get_text_writer(fo, **kwargs):
+ kw = dict(kwargs)
+ kw.setdefault('errors', 'backslashreplace') # use \uXXXX style
+ kw.setdefault('closefd', False)
+
+ if sys.version_info[0] < 3:
+ # Work around for Python 2.x
+ # New line conversion isn't needed here. Done in somewhere else.
+ writer = io.open(fo.fileno(), mode='w', newline='', **kw)
+ write = writer.write # save the original write() function
+ enc = locale.getpreferredencoding()
+ def convwrite(s):
+ if isinstance(s, bytes):
+ write(s.decode(enc)) # convert to unistr
+ else:
+ write(s)
+ try:
+ writer.flush() # needed on Windows
+ except IOError:
+ pass
+ writer.write = convwrite
+ else:
+ writer = io.open(fo.fileno(), mode='w', **kw)
+ return writer
+
+ sys.stdout = get_text_writer(sys.stdout, encoding=enc)
+ sys.stderr = get_text_writer(sys.stderr, encoding=enc)
+
+
+def main():
+ enc = 'utf-8'
+ if len(sys.argv) > 1:
+ enc = sys.argv[1]
+ set_output_encoding(enc)
+
+ message_tbl = {
+ 'utf-8': 'ÀÈÌÒÙ ã“ã‚“ã«ã¡ã¯ 你好',
+ 'latin1': 'ÀÈÌÒÙ',
+ 'cp932': 'ã“ã‚“ã«ã¡ã¯',
+ 'cp936': '你好',
+ }
+
+ print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/testdir/test_makeencoding.vim b/src/testdir/test_makeencoding.vim
new file mode 100644
index 0000000..f3dc82b
--- /dev/null
+++ b/src/testdir/test_makeencoding.vim
@@ -0,0 +1,120 @@
+" Tests for 'makeencoding'.
+
+source shared.vim
+source check.vim
+
+CheckFeature quickfix
+let s:python = PythonProg()
+if s:python == ''
+ throw 'Skipped: python program missing'
+endif
+
+let s:script = 'test_makeencoding.py'
+
+let s:message_tbl = {
+ \ 'utf-8': 'ÀÈÌÒÙ ã“ã‚“ã«ã¡ã¯ 你好',
+ \ 'latin1': 'ÀÈÌÒÙ',
+ \ 'cp932': 'ã“ã‚“ã«ã¡ã¯',
+ \ 'cp936': '你好',
+ \}
+
+
+" Tests for :cgetfile and :lgetfile.
+func Test_getfile()
+ set errorfile=Xerror.txt
+ set errorformat=%f(%l)\ :\ %m
+
+ " :cgetfile
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile
+ cgetfile
+ copen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ cclose
+ endfor
+
+ " :lgetfile
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile
+ lgetfile
+ lopen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ lclose
+ endfor
+
+ call delete(&errorfile)
+endfunc
+
+
+" Tests for :grep and :lgrep.
+func Test_grep()
+ let &grepprg = s:python
+ set grepformat=%f(%l)\ :\ %m
+
+ " :grep
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent grep! " . s:script . " " . enc
+ copen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ cclose
+ endfor
+
+ " :lgrep
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent lgrep! " . s:script . " " . enc
+ lopen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ lclose
+ endfor
+endfunc
+
+
+" Tests for :make and :lmake.
+func Test_make()
+ let &makeprg = s:python
+ set errorformat=%f(%l)\ :\ %m
+
+ " :make
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent make! " . s:script . " " . enc
+ copen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ cclose
+ endfor
+
+ " :lmake
+ for enc in keys(s:message_tbl)
+ let &makeencoding = enc
+ exec "silent lmake! " . s:script . " " . enc
+ lopen
+ call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")",
+ \ getline('.'))
+ lclose
+ endfor
+endfunc
+
+" Test for an error file with a long line that needs an encoding conversion
+func Test_longline_conversion()
+ new
+ call setline(1, ['Xfile:10:' .. repeat("\xe0", 2000)])
+ write ++enc=latin1 Xerr.out
+ bw!
+ set errorformat&
+ set makeencoding=latin1
+ cfile Xerr.out
+ call assert_equal(repeat("\u00e0", 2000), getqflist()[0].text)
+ call delete('Xerr.out')
+ set makeencoding&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_man.vim b/src/testdir/test_man.vim
new file mode 100644
index 0000000..f3af477
--- /dev/null
+++ b/src/testdir/test_man.vim
@@ -0,0 +1,149 @@
+" Test specifically for the Man filetype plugin.
+
+runtime ftplugin/man.vim
+
+func Test_g_ft_man_open_mode()
+ vnew
+ let l:h = winheight(1)
+ q
+ let l:w = winwidth(1)
+
+ " split horizontally
+ let wincnt = winnr('$')
+ Man vim
+ if wincnt == winnr('$')
+ " Vim manual page cannot be found.
+ return
+ endif
+
+ call assert_inrange(l:w - 2, l:w + 2, winwidth(1))
+ call assert_true(l:h > winheight(1))
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal(1, tabpagenr())
+ q
+
+ " split horizontally
+ let g:ft_man_open_mode = "horz"
+ Man vim
+ call assert_inrange(l:w - 2, l:w + 2, winwidth(1))
+ call assert_true(l:h > winheight(1))
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal(1, tabpagenr())
+ q
+
+ " split vertically
+ let g:ft_man_open_mode = "vert"
+ Man vim
+ call assert_true(l:w > winwidth(1))
+ call assert_equal(l:h, winheight(1))
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal(1, tabpagenr())
+ q
+
+ " separate tab
+ let g:ft_man_open_mode = "tab"
+ Man vim
+ call assert_inrange(l:w - 2, l:w + 2, winwidth(1))
+ call assert_inrange(l:h - 1, l:h + 1, winheight(1))
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal(2, tabpagenr())
+ q
+
+ unlet g:ft_man_open_mode
+endfunc
+
+func Test_nomodifiable()
+ let wincnt = winnr('$')
+ Man vim
+ if wincnt == winnr('$')
+ " Vim manual page cannot be found.
+ return
+ endif
+ call assert_false(&l:modifiable)
+ q
+endfunc
+
+func Test_buffer_count_hidden()
+ %bw!
+ set hidden
+
+ call assert_equal(1, len(getbufinfo()))
+
+ let wincnt = winnr('$')
+ Man vim
+ if wincnt == winnr('$')
+ " Vim manual page cannot be found.
+ return
+ endif
+
+ call assert_equal(1, len(getbufinfo({'buflisted':1})))
+ call assert_equal(2, len(getbufinfo()))
+ q
+
+ Man vim
+
+ call assert_equal(1, len(getbufinfo({'buflisted':1})))
+ call assert_equal(2, len(getbufinfo()))
+ q
+
+ set hidden&
+endfunc
+
+" Check that we do not alter the settings in the initial window.
+func Test_local_options()
+ %bw!
+ set foldcolumn=1 number
+
+ let wincnt = winnr('$')
+ Man vim
+ if wincnt == winnr('$')
+ " Vim manual page cannot be found.
+ return
+ endif
+
+ " man page
+ call assert_false(&nu)
+ call assert_equal(0, &fdc)
+
+ " initial window
+ wincmd p
+ call assert_true(&nu)
+ call assert_equal(1, &fdc)
+
+ %bw!
+ set foldcolumn& number&
+endfunc
+
+" Check that the unnamed register is not overwritten.
+func Test_keep_unnamed_register()
+ %bw!
+
+ let @" = '---'
+
+ let wincnt = winnr('$')
+ Man vim
+ if wincnt == winnr('$')
+ " Vim manual page cannot be found.
+ return
+ endif
+
+ call assert_equal('---', @")
+
+ %bw!
+endfunc
+
+" Check that underlying shell command arguments are escaped.
+func Test_Man_uses_shellescape()
+ Man `touch\ Xbar` `touch\ Xfoo`
+
+ redir => msg
+ 1messages
+ redir END
+ call assert_match('no manual entry for "`touch Xfoo`"', msg)
+
+ call assert_false(filereadable('Xbar'))
+ call assert_false(filereadable('Xfoo'))
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_map_functions.vim b/src/testdir/test_map_functions.vim
new file mode 100644
index 0000000..a750cd7
--- /dev/null
+++ b/src/testdir/test_map_functions.vim
@@ -0,0 +1,584 @@
+" Tests for maparg(), mapcheck(), mapset(), maplist()
+" Also test utf8 map with a 0x80 byte.
+
+func s:SID()
+ return str2nr(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$'))
+endfunc
+
+func Test_maparg()
+ new
+ set cpo-=<
+ set encoding=utf8
+ " Test maparg() with a string result
+ let sid = s:SID()
+ let lnum = expand('<sflnum>')
+ map foo<C-V> is<F4>foo
+ vnoremap <script> <buffer> <expr> <silent> bar isbar
+ call assert_equal("is<F4>foo", maparg('foo<C-V>'))
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>',
+ \ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16",
+ \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1,
+ \ 'rhs': 'is<F4>foo', 'buffer': 0, 'abbr': 0, 'mode_bits': 0x47},
+ \ maparg('foo<C-V>', '', 0, 1))
+ call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar',
+ \ 'lhsraw': 'bar', 'mode': 'v',
+ \ 'nowait': 0, 'expr': 1, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 2,
+ \ 'rhs': 'isbar', 'buffer': 1, 'abbr': 0, 'mode_bits': 0x42},
+ \ 'bar'->maparg('', 0, 1))
+ let lnum = expand('<sflnum>')
+ map <buffer> <nowait> foo bar
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo',
+ \ 'lhsraw': 'foo', 'mode': ' ',
+ \ 'nowait': 1, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1, 'rhs': 'bar',
+ \ 'buffer': 1, 'abbr': 0, 'mode_bits': 0x47},
+ \ maparg('foo', '', 0, 1))
+ let lnum = expand('<sflnum>')
+ tmap baz foo
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'baz',
+ \ 'lhsraw': 'baz', 'mode': 't',
+ \ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1, 'rhs': 'foo',
+ \ 'buffer': 0, 'abbr': 0, 'mode_bits': 0x80},
+ \ maparg('baz', 't', 0, 1))
+ let lnum = expand('<sflnum>')
+ iab A B
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'A',
+ \ 'lhsraw': 'A', 'mode': 'i',
+ \ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1, 'rhs': 'B',
+ \ 'buffer': 0, 'abbr': 1, 'mode_bits': 0x0010},
+ \ maparg('A', 'i', 1, 1))
+ iuna A
+
+ map abc x<char-114>x
+ call assert_equal("xrx", maparg('abc'))
+ map abc y<S-char-114>y
+ call assert_equal("yRy", maparg('abc'))
+
+ " character with K_SPECIAL byte
+ nmap abc …
+ call assert_equal('…', maparg('abc'))
+
+ " modified character with K_SPECIAL byte
+ nmap abc <M-…>
+ call assert_equal('<M-…>', maparg('abc'))
+
+ " illegal bytes
+ let str = ":\x7f:\x80:\x90:\xd0:"
+ exe 'nmap abc ' .. str
+ call assert_equal(str, maparg('abc'))
+ unlet str
+
+ omap { w
+ let d = maparg('{', 'o', 0, 1)
+ call assert_equal(['{', 'w', 'o'], [d.lhs, d.rhs, d.mode])
+ ounmap {
+
+ lmap { w
+ let d = maparg('{', 'l', 0, 1)
+ call assert_equal(['{', 'w', 'l'], [d.lhs, d.rhs, d.mode])
+ lunmap {
+
+ nmap { w
+ let d = maparg('{', 'n', 0, 1)
+ call assert_equal(['{', 'w', 'n'], [d.lhs, d.rhs, d.mode])
+ nunmap {
+
+ xmap { w
+ let d = maparg('{', 'x', 0, 1)
+ call assert_equal(['{', 'w', 'x'], [d.lhs, d.rhs, d.mode])
+ xunmap {
+
+ smap { w
+ let d = maparg('{', 's', 0, 1)
+ call assert_equal(['{', 'w', 's'], [d.lhs, d.rhs, d.mode])
+ sunmap {
+
+ map <C-I> foo
+ unmap <Tab>
+ " This used to cause a segfault
+ call maparg('<C-I>', '', 0, 1)
+ unmap <C-I>
+
+ map abc <Nop>
+ call assert_equal("<Nop>", maparg('abc'))
+ unmap abc
+
+ call feedkeys(":abbr esc \<C-V>\<C-V>\<C-V>\<C-V>\<C-V>\<Esc>\<CR>", "xt")
+ let d = maparg('esc', 'i', 1, 1)
+ call assert_equal(['esc', "\<C-V>\<C-V>\<Esc>", '!'], [d.lhs, d.rhs, d.mode])
+ abclear
+ unlet d
+endfunc
+
+def Test_vim9_maparg()
+ nmap { w
+ var one: string = maparg('{')
+ assert_equal('w', one)
+ var two: string = maparg('{', 'n')
+ assert_equal('w', two)
+ var three: string = maparg('{', 'n', 0)
+ assert_equal('w', three)
+ var four: dict<any> = maparg('{', 'n', 0, 1)
+ assert_equal(['{', 'w', 'n'], [four.lhs, four.rhs, four.mode])
+ nunmap {
+enddef
+
+func Test_mapcheck()
+ call assert_equal('', mapcheck('a'))
+ call assert_equal('', mapcheck('abc'))
+ call assert_equal('', mapcheck('ax'))
+ call assert_equal('', mapcheck('b'))
+
+ map a something
+ call assert_equal('something', mapcheck('a'))
+ call assert_equal('something', mapcheck('a', 'n'))
+ call assert_equal('', mapcheck('a', 'c'))
+ call assert_equal('', mapcheck('a', 'i'))
+ call assert_equal('something', 'abc'->mapcheck())
+ call assert_equal('something', 'ax'->mapcheck())
+ call assert_equal('', mapcheck('b'))
+ unmap a
+
+ map ab foobar
+ call assert_equal('foobar', mapcheck('a'))
+ call assert_equal('foobar', mapcheck('abc'))
+ call assert_equal('', mapcheck('ax'))
+ call assert_equal('', mapcheck('b'))
+ unmap ab
+
+ map abc barfoo
+ call assert_equal('barfoo', mapcheck('a'))
+ call assert_equal('barfoo', mapcheck('a', 'n', 0))
+ call assert_equal('', mapcheck('a', 'n', 1))
+ call assert_equal('barfoo', mapcheck('abc'))
+ call assert_equal('', mapcheck('ax'))
+ call assert_equal('', mapcheck('b'))
+ unmap abc
+
+ abbr ab abbrev
+ call assert_equal('abbrev', mapcheck('a', 'i', 1))
+ call assert_equal('', mapcheck('a', 'n', 1))
+ call assert_equal('', mapcheck('a', 'i', 0))
+ unabbr ab
+endfunc
+
+func Test_range_map()
+ new
+ " Outside of the range, minimum
+ inoremap <Char-0x1040> a
+ execute "normal a\u1040\<Esc>"
+ " Inside of the range, minimum
+ inoremap <Char-0x103f> b
+ execute "normal a\u103f\<Esc>"
+ " Inside of the range, maximum
+ inoremap <Char-0xf03f> c
+ execute "normal a\uf03f\<Esc>"
+ " Outside of the range, maximum
+ inoremap <Char-0xf040> d
+ execute "normal a\uf040\<Esc>"
+ call assert_equal("abcd", getline(1))
+endfunc
+
+func One_mapset_test(keys, rhs)
+ exe 'nnoremap ' .. a:keys .. ' ' .. a:rhs
+ let orig = maparg(a:keys, 'n', 0, 1)
+ call assert_equal(a:keys, orig.lhs)
+ call assert_equal(a:rhs, orig.rhs)
+ call assert_equal('n', orig.mode)
+
+ exe 'nunmap ' .. a:keys
+ let d = maparg(a:keys, 'n', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('n', 0, orig)
+ let d = maparg(a:keys, 'n', 0, 1)
+ call assert_equal(a:keys, d.lhs)
+ call assert_equal(a:rhs, d.rhs)
+ call assert_equal('n', d.mode)
+
+ exe 'nunmap ' .. a:keys
+endfunc
+
+func Test_mapset()
+ call One_mapset_test('K', 'original<CR>')
+ call One_mapset_test('<F3>', 'original<CR>')
+ call One_mapset_test('<F3>', '<lt>Nop>')
+
+ " Check <> key conversion
+ new
+ inoremap K one<Left>x
+ call feedkeys("iK\<Esc>", 'xt')
+ call assert_equal('onxe', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal('one<Left>x', orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ iunmap K
+ let d = maparg('K', 'i', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('onxe', getline(1))
+
+ iunmap K
+
+ " Test that <Nop> is restored properly
+ inoremap K <Nop>
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal('<Nop>', orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ inoremap K foo
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('foo', getline(1))
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ iunmap K
+
+ " Test literal <CR> using a backslash
+ let cpo_save = &cpo
+ set cpo-=B
+ inoremap K one\<CR>two
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal('one\<CR>two', orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ iunmap K
+ let d = maparg('K', 'i', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ iunmap K
+
+ " Test literal <CR> using CTRL-V
+ inoremap K one<CR>two
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal("one\x16<CR>two", orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ iunmap K
+ let d = maparg('K', 'i', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ iunmap K
+ let &cpo = cpo_save
+ bwipe!
+
+ call assert_fails('call mapset([], v:false, {})', 'E730:')
+ call assert_fails('call mapset("i", 0, "")', 'E1206:')
+ call assert_fails('call mapset("i", 0, {})', 'E460:')
+endfunc
+
+def Test_mapset_arg1_dir()
+ # This test is mostly about get_map_mode_string.
+ # Once the code gets past that, it's common with the 3 arg mapset.
+
+ # GetModes() return list of modes for 'XZ' lhs using maplist.
+ # There is one list item per mapping
+ def GetModes(abbr: bool = false): list<string>
+ return maplist(abbr)->filter((_, m) => m.lhs == 'XZ')
+ ->mapnew((_, m) => m.mode)
+ enddef
+
+ const unmap_cmds = [ 'unmap', 'unmap!', 'tunmap', 'lunmap' ]
+ def UnmapAll(lhs: string)
+ for cmd in unmap_cmds
+ try | execute(cmd .. ' ' .. lhs) | catch /E31/ | endtry
+ endfor
+ enddef
+
+ var tmap: dict<any>
+
+ # some mapset(mode, abbr, dict) tests using get_map_mode_str
+ map XZ x
+ tmap = maplist()->filter((_, m) => m.lhs == 'XZ')[0]->copy()
+ # this splits the mapping into 2 mappings
+ mapset('ox', false, tmap)
+ assert_equal(2, len(GetModes()))
+ mapset('o', false, tmap)
+ assert_equal(3, len(GetModes()))
+ # test that '' acts like ' ', and that the 3 mappings become 1
+ mapset('', false, tmap)
+ assert_equal([' '], GetModes())
+ # dict's mode/abbr are ignored
+ UnmapAll('XZ')
+ tmap.mode = '!'
+ tmap.abbr = true
+ mapset('o', false, tmap)
+ assert_equal(['o'], GetModes())
+
+ # test the 3 arg version handles bad mode string, dict not used
+ assert_fails("mapset('vi', false, {})", 'E1276:')
+
+
+ # get the abbreviations out of the way
+ abbreviate XZ ZX
+ tmap = maplist(true)->filter((_, m) => m.lhs == 'XZ')[0]->copy()
+
+ abclear
+ # 'ic' is the default ab command, shows up as '!'
+ tmap.mode = 'ic'
+ mapset(tmap)
+ assert_equal(['!'], GetModes(true))
+
+ abclear
+ tmap.mode = 'i'
+ mapset(tmap)
+ assert_equal(['i'], GetModes(true))
+
+ abclear
+ tmap.mode = 'c'
+ mapset(tmap)
+ assert_equal(['c'], GetModes(true))
+
+ abclear
+ tmap.mode = '!'
+ mapset(tmap)
+ assert_equal(['!'], GetModes(true))
+
+ assert_fails("mapset({mode: ' !', abbr: 1})", 'E1276:')
+ assert_fails("mapset({mode: 'cl', abbr: 1})", 'E1276:')
+ assert_fails("mapset({mode: 'in', abbr: 1})", 'E1276:')
+
+ # the map commands
+ map XZ x
+ tmap = maplist()->filter((_, m) => m.lhs == 'XZ')[0]->copy()
+
+ # try the combos
+ UnmapAll('XZ')
+ # 'nxso' is ' ', the unadorned :map
+ tmap.mode = 'nxso'
+ mapset(tmap)
+ assert_equal([' '], GetModes())
+
+ UnmapAll('XZ')
+ # 'ic' is '!'
+ tmap.mode = 'ic'
+ mapset(tmap)
+ assert_equal(['!'], GetModes())
+
+ UnmapAll('XZ')
+ # 'xs' is really 'v'
+ tmap.mode = 'xs'
+ mapset(tmap)
+ assert_equal(['v'], GetModes())
+
+ # try the individual modes
+ UnmapAll('XZ')
+ tmap.mode = 'n'
+ mapset(tmap)
+ assert_equal(['n'], GetModes())
+
+ UnmapAll('XZ')
+ tmap.mode = 'x'
+ mapset(tmap)
+ assert_equal(['x'], GetModes())
+
+ UnmapAll('XZ')
+ tmap.mode = 's'
+ mapset(tmap)
+ assert_equal(['s'], GetModes())
+
+ UnmapAll('XZ')
+ tmap.mode = 'o'
+ mapset(tmap)
+ assert_equal(['o'], GetModes())
+
+ UnmapAll('XZ')
+ tmap.mode = 'i'
+ mapset(tmap)
+ assert_equal(['i'], GetModes())
+
+ UnmapAll('XZ')
+ tmap.mode = 'c'
+ mapset(tmap)
+ assert_equal(['c'], GetModes())
+
+ UnmapAll('XZ')
+ tmap.mode = 't'
+ mapset(tmap)
+ assert_equal(['t'], GetModes())
+
+ UnmapAll('XZ')
+ tmap.mode = 'l'
+ mapset(tmap)
+ assert_equal(['l'], GetModes())
+
+ UnmapAll('XZ')
+
+ # get errors for modes that can't be in one mapping
+ assert_fails("mapset({mode: 'nxsoi', abbr: 0})", 'E1276:')
+ assert_fails("mapset({mode: ' !', abbr: 0})", 'E1276:')
+ assert_fails("mapset({mode: 'ix', abbr: 0})", 'E1276:')
+ assert_fails("mapset({mode: 'tl', abbr: 0})", 'E1276:')
+ assert_fails("mapset({mode: ' l', abbr: 0})", 'E1276:')
+ assert_fails("mapset({mode: ' t', abbr: 0})", 'E1276:')
+enddef
+
+func Check_ctrlb_map(d, check_alt)
+ call assert_equal('<C-B>', a:d.lhs)
+ if a:check_alt
+ call assert_equal("\x80\xfc\x04B", a:d.lhsraw)
+ call assert_equal("\x02", a:d.lhsrawalt)
+ else
+ call assert_equal("\x02", a:d.lhsraw)
+ endif
+endfunc
+
+func Test_map_local()
+ nmap a global
+ nmap <buffer>a local
+
+ let prev_map_list = split(execute('nmap a'), "\n")
+ call assert_match('n\s*a\s*@local', prev_map_list[0])
+ call assert_match('n\s*a\s*global', prev_map_list[1])
+
+ let mapping = maparg('a', 'n', 0, 1)
+ call assert_equal(1, mapping.buffer)
+ let mapping.rhs = 'new_local'
+ call mapset('n', 0, mapping)
+
+ " Check that the global mapping is left untouched.
+ let map_list = split(execute('nmap a'), "\n")
+ call assert_match('n\s*a\s*@new_local', map_list[0])
+ call assert_match('n\s*a\s*global', map_list[1])
+
+ nunmap a
+endfunc
+
+func Test_map_restore()
+ " Test restoring map with alternate keycode
+ nmap <C-B> back
+ let d = maparg('<C-B>', 'n', 0, 1)
+ call Check_ctrlb_map(d, 1)
+ let dsimp = maparg("\x02", 'n', 0, 1)
+ call Check_ctrlb_map(dsimp, 0)
+ nunmap <C-B>
+ call mapset('n', 0, d)
+ let d = maparg('<C-B>', 'n', 0, 1)
+ call Check_ctrlb_map(d, 1)
+ let dsimp = maparg("\x02", 'n', 0, 1)
+ call Check_ctrlb_map(dsimp, 0)
+
+ nunmap <C-B>
+
+endfunc
+
+def Test_maplist()
+ new
+ def ClearMappingsAbbreviations()
+ mapclear | nmapclear | vmapclear | xmapclear | smapclear | omapclear
+ mapclear! | imapclear | lmapclear | cmapclear | tmapclear
+ mapclear <buffer> | nmapclear <buffer> | vmapclear <buffer>
+ xmapclear <buffer> | smapclear <buffer> | omapclear <buffer>
+ mapclear! <buffer> | imapclear <buffer> | lmapclear <buffer>
+ cmapclear <buffer> | tmapclear <buffer>
+ abclear | abclear <buffer>
+ enddef
+
+ def AddMaps(new: list<string>, accum: list<string>)
+ if len(new) > 0 && new[0] != "No mapping found"
+ accum->extend(new)
+ endif
+ enddef
+
+ ClearMappingsAbbreviations()
+ assert_equal(0, len(maplist()))
+ assert_equal(0, len(maplist(true)))
+
+ # Set up some mappings.
+ map dup bar
+ map <buffer> dup bufbar
+ map foo<C-V> is<F4>foo
+ vnoremap <script> <buffer> <expr> <silent> bar isbar
+ tmap baz foo
+ omap h w
+ lmap i w
+ nmap j w
+ xmap k w
+ smap l w
+ map abc <Nop>
+ nmap <M-j> x
+ nmap <M-Space> y
+ # And abbreviations
+ abbreviate xy he
+ abbreviate xx she
+ abbreviate <buffer> x they
+
+ # Get a list of the mappings with the ':map' commands.
+ # Check maplist() return a list of the same size.
+ assert_equal(13, len(maplist()))
+ assert_equal(3, len(maplist(true)))
+ assert_equal(13, len(maplist(false)))
+
+ # collect all the current maps using :map commands
+ var maps_command: list<string>
+ AddMaps(split(execute('map'), '\n'), maps_command)
+ AddMaps(split(execute('map!'), '\n'), maps_command)
+ AddMaps(split(execute('tmap'), '\n'), maps_command)
+ AddMaps(split(execute('lmap'), '\n'), maps_command)
+
+ # Use maplist to get all the maps
+ var maps_maplist = maplist()
+ assert_equal(len(maps_command), len(maps_maplist))
+
+ # make sure all the mode-lhs are unique, no duplicates
+ var map_set: dict<number>
+ for d in maps_maplist
+ map_set[d.mode .. "-" .. d.lhs .. "-" .. d.buffer] = 0
+ endfor
+ assert_equal(len(maps_maplist), len(map_set))
+
+ # For everything returned by maplist, should be the same as from maparg.
+ # Except for "map dup", because maparg returns the <buffer> version
+ for d in maps_maplist
+ if d.lhs == 'dup' && d.buffer == 0
+ continue
+ endif
+ var d_maparg = maparg(d.lhs, d.mode, false, true)
+ assert_equal(d_maparg, d)
+ endfor
+
+ # Check abbr matches maparg
+ for d in maplist(true)
+ # Note, d.mode is '!', but can't use that with maparg
+ var d_maparg = maparg(d.lhs, 'i', true, true)
+ assert_equal(d_maparg, d)
+ endfor
+
+ ClearMappingsAbbreviations()
+ assert_equal(0, len(maplist()))
+ assert_equal(0, len(maplist(true)))
+enddef
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_mapping.vim b/src/testdir/test_mapping.vim
new file mode 100644
index 0000000..d631274
--- /dev/null
+++ b/src/testdir/test_mapping.vim
@@ -0,0 +1,1817 @@
+" Tests for mappings and abbreviations
+
+source shared.vim
+source check.vim
+source screendump.vim
+source term_util.vim
+import './vim9.vim' as v9
+
+func Test_abbreviation()
+ " abbreviation with 0x80 should work
+ inoreab чкпр vim
+ call feedkeys("Goчкпр \<Esc>", "xt")
+ call assert_equal('vim ', getline('$'))
+ iunab чкпр
+ set nomodified
+endfunc
+
+func Test_abclear()
+ abbrev foo foobar
+ iabbrev fooi foobari
+ cabbrev fooc foobarc
+ call assert_equal("\n\n"
+ \ .. "c fooc foobarc\n"
+ \ .. "i fooi foobari\n"
+ \ .. "! foo foobar", execute('abbrev'))
+
+ iabclear
+ call assert_equal("\n\n"
+ \ .. "c fooc foobarc\n"
+ \ .. "c foo foobar", execute('abbrev'))
+ abbrev foo foobar
+ iabbrev fooi foobari
+
+ cabclear
+ call assert_equal("\n\n"
+ \ .. "i fooi foobari\n"
+ \ .. "i foo foobar", execute('abbrev'))
+ abbrev foo foobar
+ cabbrev fooc foobarc
+
+ abclear
+ call assert_equal("\n\nNo abbreviation found", execute('abbrev'))
+ call assert_fails('%abclear', 'E481:')
+endfunc
+
+func Test_abclear_buffer()
+ abbrev foo foobar
+ new X1
+ abbrev <buffer> foo1 foobar1
+ new X2
+ abbrev <buffer> foo2 foobar2
+
+ call assert_equal("\n\n"
+ \ .. "! foo2 @foobar2\n"
+ \ .. "! foo foobar", execute('abbrev'))
+
+ abclear <buffer>
+ call assert_equal("\n\n"
+ \ .. "! foo foobar", execute('abbrev'))
+
+ b X1
+ call assert_equal("\n\n"
+ \ .. "! foo1 @foobar1\n"
+ \ .. "! foo foobar", execute('abbrev'))
+ abclear <buffer>
+ call assert_equal("\n\n"
+ \ .. "! foo foobar", execute('abbrev'))
+
+ abclear
+ call assert_equal("\n\nNo abbreviation found", execute('abbrev'))
+
+ %bwipe
+endfunc
+
+func Test_map_ctrl_c_insert()
+ " mapping of ctrl-c in Insert mode
+ set cpo-=< cpo-=k
+ inoremap <c-c> <ctrl-c>
+ cnoremap <c-c> dummy
+ cunmap <c-c>
+ call feedkeys("GoTEST2: CTRL-C |\<*C-C>A|\<Esc>", "xt")
+ call assert_equal('TEST2: CTRL-C |<ctrl-c>A|', getline('$'))
+ unmap! <c-c>
+ set nomodified
+endfunc
+
+func Test_map_ctrl_c_visual()
+ " mapping of ctrl-c in Visual mode
+ vnoremap <c-c> :<C-u>$put ='vmap works'
+ call feedkeys("GV\<*C-C>\<CR>", "xt")
+ call assert_equal('vmap works', getline('$'))
+ vunmap <c-c>
+ set nomodified
+endfunc
+
+func Test_map_langmap()
+ CheckFeature langmap
+
+ " check langmap applies in normal mode
+ set langmap=+- nolangremap
+ new
+ call setline(1, ['a', 'b', 'c'])
+ 2
+ call assert_equal('b', getline('.'))
+ call feedkeys("+", "xt")
+ call assert_equal('a', getline('.'))
+
+ " check no remapping
+ map x +
+ 2
+ call feedkeys("x", "xt")
+ call assert_equal('c', getline('.'))
+
+ " check with remapping
+ set langremap
+ 2
+ call feedkeys("x", "xt")
+ call assert_equal('a', getline('.'))
+
+ unmap x
+ bwipe!
+
+ " 'langnoremap' follows 'langremap' and vise versa
+ set langremap
+ set langnoremap
+ call assert_equal(0, &langremap)
+ set langremap
+ call assert_equal(0, &langnoremap)
+ set nolangremap
+ call assert_equal(1, &langnoremap)
+
+ " check default values
+ set langnoremap&
+ call assert_equal(0, &langnoremap)
+ call assert_equal(1, &langremap)
+ set langremap&
+ call assert_equal(0, &langnoremap)
+ call assert_equal(1, &langremap)
+
+ " langmap should not apply in insert mode, 'langremap' doesn't matter
+ set langmap=+{ nolangremap
+ call feedkeys("Go+\<Esc>", "xt")
+ call assert_equal('+', getline('$'))
+ set langmap=+{ langremap
+ call feedkeys("Go+\<Esc>", "xt")
+ call assert_equal('+', getline('$'))
+
+ " langmap used for register name in insert mode.
+ call setreg('a', 'aaaa')
+ call setreg('b', 'bbbb')
+ call setreg('c', 'cccc')
+ set langmap=ab langremap
+ call feedkeys("Go\<C-R>a\<Esc>", "xt")
+ call assert_equal('bbbb', getline('$'))
+ call feedkeys("Go\<C-R>\<C-R>a\<Esc>", "xt")
+ call assert_equal('bbbb', getline('$'))
+ " mapping does not apply
+ imap c a
+ call feedkeys("Go\<C-R>c\<Esc>", "xt")
+ call assert_equal('cccc', getline('$'))
+ imap a c
+ call feedkeys("Go\<C-R>a\<Esc>", "xt")
+ call assert_equal('bbbb', getline('$'))
+
+ " langmap should not apply in Command-line mode
+ set langmap=+{ nolangremap
+ call feedkeys(":call append(line('$'), '+')\<CR>", "xt")
+ call assert_equal('+', getline('$'))
+
+ iunmap a
+ iunmap c
+ set nomodified
+endfunc
+
+func Test_map_feedkeys()
+ " issue #212 (feedkeys insert mapping at current position)
+ nnoremap . :call feedkeys(".", "in")<cr>
+ call setline('$', ['a b c d', 'a b c d'])
+ $-1
+ call feedkeys("0qqdw.ifoo\<Esc>qj0@q\<Esc>", "xt")
+ call assert_equal(['fooc d', 'fooc d'], getline(line('$') - 1, line('$')))
+ nunmap .
+ set nomodified
+endfunc
+
+func Test_map_cursor()
+ " <c-g>U<cursor> works only within a single line
+ imapclear
+ imap ( ()<c-g>U<left>
+ call feedkeys("G2o\<Esc>ki\<CR>Test1: text with a (here some more text\<Esc>k.", "xt")
+ call assert_equal('Test1: text with a (here some more text)', getline(line('$') - 2))
+ call assert_equal('Test1: text with a (here some more text)', getline(line('$') - 1))
+
+ " test undo
+ call feedkeys("G2o\<Esc>ki\<CR>Test2: text wit a (here some more text [und undo]\<C-G>u\<Esc>k.u", "xt")
+ call assert_equal('', getline(line('$') - 2))
+ call assert_equal('Test2: text wit a (here some more text [und undo])', getline(line('$') - 1))
+ set nomodified
+ imapclear
+endfunc
+
+func Test_map_cursor_ctrl_gU()
+ " <c-g>U<cursor> works only within a single line
+ nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left>
+ call setline(1, ['foo', 'foobar', '', 'foo'])
+ call cursor(1,2)
+ call feedkeys("c<*PREFIX\<esc>.", 'xt')
+ call assert_equal(['PREFIXfoo', 'foobar', '', 'PREFIXfoo'], getline(1,'$'))
+ " break undo manually
+ set ul=1000
+ exe ":norm! uu"
+ call assert_equal(['foo', 'foobar', '', 'foo'], getline(1,'$'))
+
+ " Test that it does not work if the cursor moves to the previous line
+ " 2 times <S-Left> move to the previous line
+ nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left><C-G>U<S-Left>
+ call setline(1, ['', ' foo', 'foobar', '', 'foo'])
+ call cursor(2,3)
+ call feedkeys("c<*PREFIX\<esc>.", 'xt')
+ call assert_equal(['PREFIXPREFIX', ' foo', 'foobar', '', 'foo'], getline(1,'$'))
+ nmapclear
+endfunc
+
+
+" This isn't actually testing a mapping, but similar use of CTRL-G U as above.
+func Test_break_undo()
+ set whichwrap=<,>,[,]
+ call feedkeys("G4o2k", "xt")
+ exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>."
+ call assert_equal('new line here', getline(line('$') - 3))
+ call assert_equal('Test3: text with a (parenthesis here', getline(line('$') - 2))
+ call assert_equal('new line here', getline(line('$') - 1))
+ set nomodified
+endfunc
+
+func Test_map_meta_quotes()
+ imap <M-"> foo
+ call feedkeys("Go-\<*M-\">-\<Esc>", "xt")
+ call assert_equal("-foo-", getline('$'))
+ set nomodified
+ iunmap <M-">
+endfunc
+
+func Test_map_meta_multibyte()
+ imap <M-á> foo
+ call assert_match('i <M-á>\s*foo', execute('imap'))
+ iunmap <M-á>
+endfunc
+
+func Test_abbr_after_line_join()
+ new
+ abbr foo bar
+ set backspace=indent,eol,start
+ exe "normal o\<BS>foo "
+ call assert_equal("bar ", getline(1))
+ bwipe!
+ unabbr foo
+ set backspace&
+endfunc
+
+func Test_map_timeout()
+ CheckFeature timers
+ nnoremap aaaa :let got_aaaa = 1<CR>
+ nnoremap bb :let got_bb = 1<CR>
+ nmap b aaa
+ new
+ func ExitInsert(timer)
+ let g:line = getline(1)
+ call feedkeys("\<Esc>", "t")
+ endfunc
+ set timeout timeoutlen=200
+ let timer = timer_start(300, 'ExitInsert')
+ " After the 'b' Vim waits for another character to see if it matches 'bb'.
+ " When it times out it is expanded to "aaa", but there is no wait for
+ " "aaaa". Can't check that reliably though.
+ call feedkeys("b", "xt!")
+ call assert_equal("aa", g:line)
+ call assert_false(exists('got_aaa'))
+ call assert_false(exists('got_bb'))
+
+ bwipe!
+ nunmap aaaa
+ nunmap bb
+ nunmap b
+ set timeoutlen&
+ delfunc ExitInsert
+ call timer_stop(timer)
+endfunc
+
+func Test_map_timeout_with_timer_interrupt()
+ CheckFeature job
+ CheckFeature timers
+ let g:test_is_flaky = 1
+
+ " Confirm the timer invoked in exit_cb of the job doesn't disturb mapped key
+ " sequence.
+ new
+ let g:val = 0
+ nnoremap \12 :let g:val = 1<CR>
+ nnoremap \123 :let g:val = 2<CR>
+ set timeout timeoutlen=200
+
+ func ExitCb(job, status)
+ let g:timer = timer_start(1, {-> feedkeys("3\<Esc>", 't')})
+ endfunc
+
+ call job_start([&shell, &shellcmdflag, 'echo'], {'exit_cb': 'ExitCb'})
+ call feedkeys('\12', 'xt!')
+ call assert_equal(2, g:val)
+
+ bwipe!
+ nunmap \12
+ nunmap \123
+ set timeoutlen&
+ call WaitFor({-> exists('g:timer')})
+ call timer_stop(g:timer)
+ unlet g:timer
+ unlet g:val
+ delfunc ExitCb
+endfunc
+
+func Test_abbreviation_CR()
+ new
+ func Eatchar(pat)
+ let c = nr2char(getchar(0))
+ return (c =~ a:pat) ? '' : c
+ endfunc
+ iabbrev <buffer><silent> ~~7 <c-r>=repeat('~', 7)<CR><c-r>=Eatchar('\s')<cr>
+ call feedkeys("GA~~7 \<esc>", 'xt')
+ call assert_equal('~~~~~~~', getline('$'))
+ %d
+ call feedkeys("GA~~7\<cr>\<esc>", 'xt')
+ call assert_equal(['~~~~~~~', ''], getline(1,'$'))
+ delfunc Eatchar
+ bw!
+endfunc
+
+func Test_cabbr_visual_mode()
+ cabbr s su
+ call feedkeys(":s \<c-B>\"\<CR>", 'itx')
+ call assert_equal('"su ', getreg(':'))
+ call feedkeys(":'<,'>s \<c-B>\"\<CR>", 'itx')
+ let expected = '"'. "'<,'>su "
+ call assert_equal(expected, getreg(':'))
+ call feedkeys(": '<,'>s \<c-B>\"\<CR>", 'itx')
+ let expected = '" '. "'<,'>su "
+ call assert_equal(expected, getreg(':'))
+ call feedkeys(":'a,'bs \<c-B>\"\<CR>", 'itx')
+ let expected = '"'. "'a,'bsu "
+ call assert_equal(expected, getreg(':'))
+ cunabbr s
+endfunc
+
+func Test_motionforce_omap()
+ func GetCommand()
+ let g:m=mode(1)
+ let [g:lnum1, g:col1] = searchpos('-', 'Wb')
+ if g:lnum1 == 0
+ return "\<Esc>"
+ endif
+ let [g:lnum2, g:col2] = searchpos('-', 'W')
+ if g:lnum2 == 0
+ return "\<Esc>"
+ endif
+ return ":call Select()\<CR>"
+ endfunc
+ func Select()
+ call cursor([g:lnum1, g:col1])
+ exe "normal! 1 ". (strlen(g:m) == 2 ? 'v' : g:m[2])
+ call cursor([g:lnum2, g:col2])
+ execute "normal! \<BS>"
+ endfunc
+ new
+ onoremap <buffer><expr> i- GetCommand()
+ " 1) default omap mapping
+ %d_
+ call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
+ call cursor(2, 1)
+ norm di-
+ call assert_equal('no', g:m)
+ call assert_equal(['aaa -- eee'], getline(1, '$'))
+ " 2) forced characterwise operation
+ %d_
+ call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
+ call cursor(2, 1)
+ norm dvi-
+ call assert_equal('nov', g:m)
+ call assert_equal(['aaa -- eee'], getline(1, '$'))
+ " 3) forced linewise operation
+ %d_
+ call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
+ call cursor(2, 1)
+ norm dVi-
+ call assert_equal('noV', g:m)
+ call assert_equal([''], getline(1, '$'))
+ " 4) forced blockwise operation
+ %d_
+ call setline(1, ['aaa - bbb', 'x', 'ddd - eee'])
+ call cursor(2, 1)
+ exe "norm d\<C-V>i-"
+ call assert_equal("no\<C-V>", g:m)
+ call assert_equal(['aaabbb', 'x', 'dddeee'], getline(1, '$'))
+ bwipe!
+ delfunc Select
+ delfunc GetCommand
+endfunc
+
+func Test_error_in_map_expr()
+ " Unlike CheckRunVimInTerminal this does work in a win32 console
+ CheckFeature terminal
+ if has('win32') && has('gui_running')
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+
+ let lines =<< trim [CODE]
+ func Func()
+ " fail to create list
+ let x = [
+ endfunc
+ nmap <expr> ! Func()
+ set updatetime=50
+ [CODE]
+ call writefile(lines, 'Xtest.vim', 'D')
+
+ let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
+
+ " GC must not run during map-expr processing, which can make Vim crash.
+ call term_sendkeys(buf, '!')
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>")
+ call TermWait(buf, 50)
+ call assert_equal('run', job_status(job))
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitFor({-> job_status(job) ==# 'dead'})
+ if has('unix')
+ call assert_equal('', job_info(job).termsig)
+ endif
+
+ exe buf .. 'bwipe!'
+endfunc
+
+func Test_list_mappings()
+ " Remove default mappings
+ imapclear
+
+ " reset 'isident' to check it isn't used
+ set isident=
+ inoremap <C-m> CtrlM
+ inoremap <A-S> AltS
+ inoremap <S-/> ShiftSlash
+ set isident&
+ call assert_equal([
+ \ 'i <S-/> * ShiftSlash',
+ \ 'i <M-S> * AltS',
+ \ 'i <C-M> * CtrlM',
+ \], execute('imap')->trim()->split("\n"))
+ iunmap <C-M>
+ iunmap <A-S>
+ call assert_equal(['i <S-/> * ShiftSlash'], execute('imap')->trim()->split("\n"))
+ iunmap <S-/>
+ call assert_equal(['No mapping found'], execute('imap')->trim()->split("\n"))
+
+ " List global, buffer local and script local mappings
+ nmap ,f /^\k\+ (<CR>
+ nmap <buffer> ,f /^\k\+ (<CR>
+ nmap <script> ,fs /^\k\+ (<CR>
+ call assert_equal(['n ,f @/^\k\+ (<CR>',
+ \ 'n ,fs & /^\k\+ (<CR>',
+ \ 'n ,f /^\k\+ (<CR>'],
+ \ execute('nmap ,f')->trim()->split("\n"))
+
+ " List <Nop> mapping
+ nmap ,n <Nop>
+ call assert_equal(['n ,n <Nop>'],
+ \ execute('nmap ,n')->trim()->split("\n"))
+
+ " verbose map
+ let lines = execute('verbose map ,n')->trim()->split("\n")
+
+ " Remove "Seen modifyOtherKeys" and other optional info.
+ if lines[0] =~ 'Seen modifyOtherKeys'
+ call remove(lines, 0)
+ endif
+ if lines[0] =~ 'modifyOtherKeys detected:'
+ call remove(lines, 0)
+ endif
+ if lines[0] =~ 'Kitty keyboard protocol:'
+ call remove(lines, 0)
+ endif
+ if lines[0] == ''
+ call remove(lines, 0)
+ endif
+
+ let index = indexof(lines, 'v:val =~ "Last set"')
+ call assert_equal(1, index)
+ call assert_match("\tLast set from .*/test_mapping.vim line \\d\\+$",
+ \ lines[index])
+
+ " character with K_SPECIAL byte in rhs
+ nmap foo …
+ call assert_equal(['n foo …'],
+ \ execute('nmap foo')->trim()->split("\n"))
+
+ " modified character with K_SPECIAL byte in rhs
+ nmap foo <M-…>
+ call assert_equal(['n foo <M-…>'],
+ \ execute('nmap foo')->trim()->split("\n"))
+
+ " character with K_SPECIAL byte in lhs
+ nmap … foo
+ call assert_equal(['n … foo'],
+ \ execute('nmap …')->trim()->split("\n"))
+
+ " modified character with K_SPECIAL byte in lhs
+ nmap <M-…> foo
+ call assert_equal(['n <M-…> foo'],
+ \ execute('nmap <M-…>')->trim()->split("\n"))
+
+ " illegal bytes
+ let str = ":\x7f:\x80:\x90:\xd0:"
+ exe 'nmap foo ' .. str
+ call assert_equal(['n foo ' .. strtrans(str)],
+ \ execute('nmap foo')->trim()->split("\n"))
+ unlet str
+
+ " map to CTRL-V
+ exe "nmap ,k \<C-V>"
+ call assert_equal(['n ,k <Nop>'],
+ \ execute('nmap ,k')->trim()->split("\n"))
+
+ " map with space at the beginning
+ exe "nmap \<C-V> w <Nop>"
+ call assert_equal(['n <Space>w <Nop>'],
+ \ execute("nmap \<C-V> w")->trim()->split("\n"))
+
+ nmapclear
+endfunc
+
+func Test_expr_map_gets_cursor()
+ new
+ call setline(1, ['one', 'some w!rd'])
+ func StoreColumn()
+ let g:exprLine = line('.')
+ let g:exprCol = col('.')
+ return 'x'
+ endfunc
+ nnoremap <expr> x StoreColumn()
+ 2
+ nmap ! f!<Ignore>x
+ call feedkeys("!", 'xt')
+ call assert_equal('some wrd', getline(2))
+ call assert_equal(2, g:exprLine)
+ call assert_equal(7, g:exprCol)
+
+ bwipe!
+ unlet g:exprLine
+ unlet g:exprCol
+ delfunc StoreColumn
+ nunmap x
+ nunmap !
+endfunc
+
+func Test_expr_map_restore_cursor()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ set ls=2
+ hi! link StatusLine ErrorMsg
+ noremap <expr> <C-B> Func()
+ func Func()
+ let g:on = !get(g:, 'on', 0)
+ redraws
+ return ''
+ endfunc
+ func Status()
+ return get(g:, 'on', 0) ? '[on]' : ''
+ endfunc
+ set stl=%{Status()}
+ END
+ call writefile(lines, 'XtestExprMap', 'D')
+ let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10})
+ call term_sendkeys(buf, GetEscCodeWithModifier('C', 'B'))
+ call VerifyScreenDump(buf, 'Test_map_expr_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_map_listing()
+ CheckScreendump
+
+ let lines =<< trim END
+ nmap a b
+ END
+ call writefile(lines, 'XtestMapList', 'D')
+ let buf = RunVimInTerminal('-S XtestMapList', #{rows: 6})
+ call term_sendkeys(buf, ": nmap a\<CR>")
+ call VerifyScreenDump(buf, 'Test_map_list_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_expr_map_error()
+ CheckScreendump
+
+ let lines =<< trim END
+ func Func()
+ throw 'test'
+ return ''
+ endfunc
+
+ nnoremap <expr> <F2> Func()
+ cnoremap <expr> <F2> Func()
+
+ call test_override('ui_delay', 10)
+ END
+ call writefile(lines, 'XtestExprMap', 'D')
+ let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10})
+ call term_sendkeys(buf, "\<F2>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "\<CR>")
+ call VerifyScreenDump(buf, 'Test_map_expr_2', {})
+
+ call term_sendkeys(buf, ":abc\<F2>")
+ call VerifyScreenDump(buf, 'Test_map_expr_3', {})
+ call term_sendkeys(buf, "\<Esc>0")
+ call VerifyScreenDump(buf, 'Test_map_expr_4', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for mapping errors
+func Test_map_error()
+ call assert_fails('unmap', 'E474:')
+ call assert_fails("exe 'map ' .. repeat('a', 51) .. ' :ls'", 'E474:')
+ call assert_fails('unmap abc', 'E31:')
+ call assert_fails('unabbr abc', 'E24:')
+ call assert_equal('', maparg(''))
+ call assert_fails('echo maparg("abc", [])', 'E730:')
+
+ " unique map
+ map ,w /[#&!]<CR>
+ call assert_fails("map <unique> ,w /[#&!]<CR>", 'E227:')
+ " unique buffer-local map
+ call assert_fails("map <buffer> <unique> ,w /[.,;]<CR>", 'E225:')
+ unmap ,w
+
+ " unique abbreviation
+ abbr SP special
+ call assert_fails("abbr <unique> SP special", 'E226:')
+ " unique buffer-local map
+ call assert_fails("abbr <buffer> <unique> SP special", 'E224:')
+ unabbr SP
+
+ call assert_fails('mapclear abc', 'E474:')
+ call assert_fails('abclear abc', 'E474:')
+ call assert_fails('abbr $xyz abc', 'E474:')
+
+ " space character in an abbreviation
+ call assert_fails('abbr ab<space> ABC', 'E474:')
+
+ " invalid <expr> map
+ map <expr> ,f abc
+ call assert_fails('normal ,f', 'E121:')
+ unmap <expr> ,f
+
+ " Recursive use of :normal in a map
+ set maxmapdepth=100
+ map gq :normal gq<CR>
+ call assert_fails('normal gq', 'E192:')
+ unmap gq
+ set maxmapdepth&
+endfunc
+
+" Test for <special> key mapping
+func Test_map_special()
+ new
+ let old_cpo = &cpo
+ set cpo+=<
+ imap <F12> Blue
+ call feedkeys("i\<F12>", "x")
+ call assert_equal("<F12>", getline(1))
+ call feedkeys("ddi<F12>", "x")
+ call assert_equal("Blue", getline(1))
+ iunmap <F12>
+ imap <special> <F12> Green
+ call feedkeys("ddi\<F12>", "x")
+ call assert_equal("Green", getline(1))
+ call feedkeys("ddi<F12>", "x")
+ call assert_equal("<F12>", getline(1))
+ iunmap <special> <F12>
+ let &cpo = old_cpo
+ %bwipe!
+endfunc
+
+" Test for hasmapto()
+func Test_hasmapto()
+ call assert_equal(0, hasmapto('/^\k\+ ('))
+ map ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ ('))
+ unmap ,f
+
+ " Insert mode mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 'i'))
+ imap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ (', 'i'))
+ iunmap ,f
+
+ " Normal mode mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 'n'))
+ nmap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ ('))
+ call assert_equal(1, hasmapto('/^\k\+ (', 'n'))
+ nunmap ,f
+
+ " Visual and Select mode mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 'v'))
+ call assert_equal(0, hasmapto('/^\k\+ (', 'x'))
+ call assert_equal(0, hasmapto('/^\k\+ (', 's'))
+ vmap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ (', 'v'))
+ call assert_equal(1, hasmapto('/^\k\+ (', 'x'))
+ call assert_equal(1, hasmapto('/^\k\+ (', 's'))
+ vunmap ,f
+
+ " Visual mode mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 'x'))
+ xmap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ (', 'v'))
+ call assert_equal(1, hasmapto('/^\k\+ (', 'x'))
+ call assert_equal(0, hasmapto('/^\k\+ (', 's'))
+ xunmap ,f
+
+ " Select mode mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 's'))
+ smap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ (', 'v'))
+ call assert_equal(0, hasmapto('/^\k\+ (', 'x'))
+ call assert_equal(1, hasmapto('/^\k\+ (', 's'))
+ sunmap ,f
+
+ " Operator-pending mode mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 'o'))
+ omap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ (', 'o'))
+ ounmap ,f
+
+ " Language mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 'l'))
+ lmap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ (', 'l'))
+ lunmap ,f
+
+ " Cmdline mode mapping
+ call assert_equal(0, hasmapto('/^\k\+ (', 'c'))
+ cmap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ (', 'c'))
+ cunmap ,f
+
+ call assert_equal(0, hasmapto('/^\k\+ (', 'n', 1))
+endfunc
+
+" Test for command-line completion of maps
+func Test_mapcomplete()
+ call assert_equal(['<buffer>', '<expr>', '<nowait>', '<script>',
+ \ '<silent>', '<special>', '<unique>'],
+ \ getcompletion('', 'mapping'))
+ call assert_equal([], getcompletion(',d', 'mapping'))
+
+ call feedkeys(":unmap <buf\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"unmap <buffer>', @:)
+
+ call feedkeys(":unabbr <buf\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"unabbr <buffer>', @:)
+
+ call feedkeys(":abbr! \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"abbr! \x01", @:)
+
+ " When multiple matches have the same {lhs}, it should only appear once.
+ " The simplified form should also not be included.
+ nmap ,<C-F> /H<CR>
+ omap ,<C-F> /H<CR>
+ call feedkeys(":map ,\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"map ,<C-F>', @:)
+ mapclear
+endfunc
+
+func GetAbbrText()
+ unabbr hola
+ return 'hello'
+endfunc
+
+" Test for <expr> in abbreviation
+func Test_expr_abbr()
+ new
+ iabbr <expr> teh "the"
+ call feedkeys("iteh ", "tx")
+ call assert_equal('the ', getline(1))
+ iabclear
+ call setline(1, '')
+
+ " invalid <expr> abbreviation
+ abbr <expr> hte GetAbbr()
+ call assert_fails('normal ihte ', 'E117:')
+ call assert_equal('', getline(1))
+ unabbr <expr> hte
+
+ " evaluating the expression deletes the abbreviation
+ abbr <expr> hola GetAbbrText()
+ call assert_equal('GetAbbrText()', maparg('hola', 'i', '1'))
+ call feedkeys("ahola \<Esc>", 'xt')
+ call assert_equal('hello ', getline('.'))
+ call assert_equal('', maparg('hola', 'i', '1'))
+
+ bwipe!
+endfunc
+
+" Test for storing mappings in different modes in a vimrc file
+func Test_mkvimrc_mapmodes()
+ map a1 /a1
+ nmap a2 /a2
+ vmap a3 /a3
+ smap a4 /a4
+ xmap a5 /a5
+ omap a6 /a6
+ map! a7 /a7
+ imap a8 /a8
+ lmap a9 /a9
+ cmap a10 /a10
+ tmap a11 /a11
+ " Normal + Visual map
+ map a12 /a12
+ sunmap a12
+ ounmap a12
+ " Normal + Selectmode map
+ map a13 /a13
+ xunmap a13
+ ounmap a13
+ " Normal + OpPending map
+ map a14 /a14
+ vunmap a14
+ " Visual + Selectmode map
+ map a15 /a15
+ nunmap a15
+ ounmap a15
+ " Visual + OpPending map
+ map a16 /a16
+ nunmap a16
+ sunmap a16
+ " Selectmode + OpPending map
+ map a17 /a17
+ nunmap a17
+ xunmap a17
+ " Normal + Visual + Selectmode map
+ map a18 /a18
+ ounmap a18
+ " Normal + Visual + OpPending map
+ map a19 /a19
+ sunmap a19
+ " Normal + Selectmode + OpPending map
+ map a20 /a20
+ xunmap a20
+ " Visual + Selectmode + OpPending map
+ map a21 /a21
+ nunmap a21
+ " Mapping to Nop
+ map a22 <Nop>
+ " Script local mapping
+ map <script> a23 /a23
+
+ " Newline in {lhs} and {rhs} of a map
+ exe "map a24\<C-V>\<C-J> ia24\<C-V>\<C-J><Esc>"
+
+ " Abbreviation
+ abbr a25 A25
+ cabbr a26 A26
+ iabbr a27 A27
+
+ mkvimrc! Xvimrc
+ let l = readfile('Xvimrc')
+ call assert_equal(['map a1 /a1'], filter(copy(l), 'v:val =~ " a1 "'))
+ call assert_equal(['nmap a2 /a2'], filter(copy(l), 'v:val =~ " a2 "'))
+ call assert_equal(['vmap a3 /a3'], filter(copy(l), 'v:val =~ " a3 "'))
+ call assert_equal(['smap a4 /a4'], filter(copy(l), 'v:val =~ " a4 "'))
+ call assert_equal(['xmap a5 /a5'], filter(copy(l), 'v:val =~ " a5 "'))
+ call assert_equal(['omap a6 /a6'], filter(copy(l), 'v:val =~ " a6 "'))
+ call assert_equal(['map! a7 /a7'], filter(copy(l), 'v:val =~ " a7 "'))
+ call assert_equal(['imap a8 /a8'], filter(copy(l), 'v:val =~ " a8 "'))
+ call assert_equal(['lmap a9 /a9'], filter(copy(l), 'v:val =~ " a9 "'))
+ call assert_equal(['cmap a10 /a10'], filter(copy(l), 'v:val =~ " a10 "'))
+ call assert_equal(['tmap a11 /a11'], filter(copy(l), 'v:val =~ " a11 "'))
+ call assert_equal(['nmap a12 /a12', 'xmap a12 /a12'],
+ \ filter(copy(l), 'v:val =~ " a12 "'))
+ call assert_equal(['nmap a13 /a13', 'smap a13 /a13'],
+ \ filter(copy(l), 'v:val =~ " a13 "'))
+ call assert_equal(['nmap a14 /a14', 'omap a14 /a14'],
+ \ filter(copy(l), 'v:val =~ " a14 "'))
+ call assert_equal(['vmap a15 /a15'], filter(copy(l), 'v:val =~ " a15 "'))
+ call assert_equal(['xmap a16 /a16', 'omap a16 /a16'],
+ \ filter(copy(l), 'v:val =~ " a16 "'))
+ call assert_equal(['smap a17 /a17', 'omap a17 /a17'],
+ \ filter(copy(l), 'v:val =~ " a17 "'))
+ call assert_equal(['nmap a18 /a18', 'vmap a18 /a18'],
+ \ filter(copy(l), 'v:val =~ " a18 "'))
+ call assert_equal(['nmap a19 /a19', 'xmap a19 /a19', 'omap a19 /a19'],
+ \ filter(copy(l), 'v:val =~ " a19 "'))
+ call assert_equal(['nmap a20 /a20', 'smap a20 /a20', 'omap a20 /a20'],
+ \ filter(copy(l), 'v:val =~ " a20 "'))
+ call assert_equal(['vmap a21 /a21', 'omap a21 /a21'],
+ \ filter(copy(l), 'v:val =~ " a21 "'))
+ call assert_equal(['map a22 <Nop>'], filter(copy(l), 'v:val =~ " a22 "'))
+ call assert_equal([], filter(copy(l), 'v:val =~ " a23 "'))
+ call assert_equal(["map a24<NL> ia24<NL>\x16\e"],
+ \ filter(copy(l), 'v:val =~ " a24"'))
+
+ call assert_equal(['abbr a25 A25'], filter(copy(l), 'v:val =~ " a25 "'))
+ call assert_equal(['cabbr a26 A26'], filter(copy(l), 'v:val =~ " a26 "'))
+ call assert_equal(['iabbr a27 A27'], filter(copy(l), 'v:val =~ " a27 "'))
+ call delete('Xvimrc')
+
+ mapclear
+ nmapclear
+ vmapclear
+ xmapclear
+ smapclear
+ omapclear
+ imapclear
+ lmapclear
+ cmapclear
+ tmapclear
+endfunc
+
+" Test for recursive mapping ('maxmapdepth')
+func Test_map_recursive()
+ map x y
+ map y x
+ call assert_fails('normal x', 'E223:')
+ unmap x
+ unmap y
+endfunc
+
+" Test for removing an abbreviation using {rhs} and with space after {lhs}
+func Test_abbr_remove()
+ abbr foo bar
+ let d = maparg('foo', 'i', 1, 1)
+ call assert_equal(['foo', 'bar', '!'], [d.lhs, d.rhs, d.mode])
+ unabbr bar
+ call assert_equal({}, maparg('foo', 'i', 1, 1))
+
+ abbr foo bar
+ unabbr foo<space><tab>
+ call assert_equal({}, maparg('foo', 'i', 1, 1))
+endfunc
+
+" Trigger an abbreviation using a special key
+func Test_abbr_trigger_special()
+ new
+ iabbr teh the
+ call feedkeys("iteh\<F2>\<Esc>", 'xt')
+ call assert_equal('the<F2>', getline(1))
+ iunab teh
+ close!
+endfunc
+
+" Test for '<' in 'cpoptions'
+func Test_map_cpo_special_keycode()
+ set cpo-=<
+ imap x<Bslash>k Test
+ let d = maparg('x<Bslash>k', 'i', 0, 1)
+ call assert_equal(['x\k', 'Test', 'i'], [d.lhs, d.rhs, d.mode])
+ call feedkeys(":imap x\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"imap x\k', @:)
+ iunmap x<Bslash>k
+ set cpo+=<
+ imap x<Bslash>k Test
+ let d = maparg('x<Bslash>k', 'i', 0, 1)
+ call assert_equal(['x<Bslash>k', 'Test', 'i'], [d.lhs, d.rhs, d.mode])
+ call feedkeys(":imap x\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"imap x<Bslash>k', @:)
+ iunmap x<Bslash>k
+ set cpo-=<
+ " Modifying 'cpo' above adds some default mappings, remove them
+ mapclear
+ mapclear!
+endfunc
+
+" Test for <Cmd> key in maps to execute commands
+func Test_map_cmdkey()
+ new
+
+ " Error cases
+ let x = 0
+ noremap <F3> <Cmd><Cmd>let x = 1<CR>
+ call assert_fails('call feedkeys("\<F3>", "xt")', 'E1136:')
+ call assert_equal(0, x)
+
+ noremap <F3> <Cmd><F3>let x = 2<CR>
+ call assert_fails('call feedkeys("\<F3>", "xt")', 'E1137:')
+ call assert_equal(0, x)
+
+ noremap <F3> <Cmd>let x = 3
+ call assert_fails('call feedkeys("\<F3>", "xt!")', 'E1255:')
+ call assert_equal(0, x)
+
+ " works in various modes and sees the correct mode()
+ noremap <F3> <Cmd>let m = mode(1)<CR>
+ noremap! <F3> <Cmd>let m = mode(1)<CR>
+
+ " normal mode
+ call feedkeys("\<F3>", 'xt')
+ call assert_equal('n', m)
+
+ " visual mode
+ call feedkeys("v\<F3>", 'xt!')
+ call assert_equal('v', m)
+ " shouldn't leave the visual mode
+ call assert_equal('v', mode(1))
+ call feedkeys("\<Esc>", 'xt')
+ call assert_equal('n', mode(1))
+
+ " visual mapping in select mode
+ call feedkeys("gh\<F3>", 'xt!')
+ call assert_equal('v', m)
+ " shouldn't leave select mode
+ call assert_equal('s', mode(1))
+ call feedkeys("\<Esc>", 'xt')
+ call assert_equal('n', mode(1))
+
+ " select mode mapping
+ snoremap <F3> <Cmd>let m = mode(1)<cr>
+ call feedkeys("gh\<F3>", 'xt!')
+ call assert_equal('s', m)
+ " shouldn't leave select mode
+ call assert_equal('s', mode(1))
+ call feedkeys("\<Esc>", 'xt')
+ call assert_equal('n', mode(1))
+
+ " operator-pending mode
+ call feedkeys("d\<F3>", 'xt!')
+ call assert_equal('no', m)
+ " leaves the operator-pending mode
+ call assert_equal('n', mode(1))
+
+ " insert mode
+ call feedkeys("i\<F3>abc", 'xt')
+ call assert_equal('i', m)
+ call assert_equal('abc', getline('.'))
+
+ " replace mode
+ call feedkeys("0R\<F3>two", 'xt')
+ call assert_equal('R', m)
+ call assert_equal('two', getline('.'))
+
+ " virtual replace mode
+ call setline('.', "one\ttwo")
+ call feedkeys("4|gR\<F3>xxx", 'xt')
+ call assert_equal('Rv', m)
+ call assert_equal("onexxx\ttwo", getline('.'))
+
+ " cmdline mode
+ call feedkeys(":\<F3>\"xxx\<CR>", 'xt!')
+ call assert_equal('c', m)
+ call assert_equal('"xxx', @:)
+
+ " terminal mode
+ if CanRunVimInTerminal()
+ tnoremap <F3> <Cmd>let m = mode(1)<CR>
+ let buf = Run_shell_in_terminal({})
+ call feedkeys("\<F3>", 'xt')
+ call assert_equal('t', m)
+ call assert_equal('t', mode(1))
+ call StopShellInTerminal(buf)
+ close!
+ tunmap <F3>
+ endif
+
+ " invoke cmdline mode recursively
+ noremap! <F2> <Cmd>norm! :foo<CR>
+ %d
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys(":bar\<F2>x\<C-B>\"\r", 'xt')
+ call assert_equal('"barx', @:)
+ unmap! <F2>
+
+ " test for calling a <SID> function
+ let lines =<< trim END
+ map <F2> <Cmd>call <SID>do_it()<CR>
+ func s:do_it()
+ let g:x = 32
+ endfunc
+ END
+ call writefile(lines, 'Xscript', 'D')
+ source Xscript
+ call feedkeys("\<F2>", 'xt')
+ call assert_equal(32, g:x)
+
+ unmap <F3>
+ unmap! <F3>
+ %bw!
+
+ " command line ending in "0" is handled without errors
+ onoremap ix <cmd>eval 0<cr>
+ call feedkeys('dix.', 'xt')
+ ounmap ix
+endfunc
+
+" text object enters visual mode
+func TextObj()
+ if mode() !=# "v"
+ normal! v
+ end
+ call cursor(1, 3)
+ normal! o
+ call cursor(2, 4)
+endfunc
+
+func s:cmdmap(lhs, rhs)
+ exe 'noremap ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
+ exe 'noremap! ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
+endfunc
+
+func s:cmdunmap(lhs)
+ exe 'unmap ' .. a:lhs
+ exe 'unmap! ' .. a:lhs
+endfunc
+
+" Map various <Fx> keys used by the <Cmd> key tests
+func s:setupMaps()
+ call s:cmdmap('<F3>', 'let m = mode(1)')
+ call s:cmdmap('<F4>', 'normal! ww')
+ call s:cmdmap('<F5>', 'normal! "ay')
+ call s:cmdmap('<F6>', 'throw "very error"')
+ call s:cmdmap('<F7>', 'call TextObj()')
+ call s:cmdmap('<F8>', 'startinsert')
+ call s:cmdmap('<F9>', 'stopinsert')
+endfunc
+
+" Remove the mappings setup by setupMaps()
+func s:cleanupMaps()
+ call s:cmdunmap('<F3>')
+ call s:cmdunmap('<F4>')
+ call s:cmdunmap('<F5>')
+ call s:cmdunmap('<F6>')
+ call s:cmdunmap('<F7>')
+ call s:cmdunmap('<F8>')
+ call s:cmdunmap('<F9>')
+endfunc
+
+" Test for <Cmd> mapping in normal mode
+func Test_map_cmdkey_normal_mode()
+ new
+ call s:setupMaps()
+
+ " check v:count and v:register works
+ call s:cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]')
+ call feedkeys("\<F2>", 'xt')
+ call assert_equal(['n', 0, '"'], s)
+ call feedkeys("7\<F2>", 'xt')
+ call assert_equal(['n', 7, '"'], s)
+ call feedkeys("\"e\<F2>", 'xt')
+ call assert_equal(['n', 0, 'e'], s)
+ call feedkeys("5\"k\<F2>", 'xt')
+ call assert_equal(['n', 5, 'k'], s)
+ call s:cmdunmap('<F2>')
+
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys("\<F7>y", 'xt')
+ call assert_equal("me short lines\nof t", @")
+ call assert_equal('v', getregtype('"'))
+ call assert_equal([0, 1, 3, 0], getpos("'<"))
+ call assert_equal([0, 2, 4, 0], getpos("'>"))
+
+ " startinsert
+ %d
+ call feedkeys("\<F8>abc", 'xt')
+ call assert_equal('abc', getline(1))
+
+ " feedkeys are not executed immediately
+ noremap ,a <Cmd>call feedkeys("aalpha") \| let g:a = getline(2)<CR>
+ %d
+ call setline(1, ['some short lines', 'of test text'])
+ call cursor(2, 3)
+ call feedkeys(",a\<F3>", 'xt')
+ call assert_equal('of test text', g:a)
+ call assert_equal('n', m)
+ call assert_equal(['some short lines', 'of alphatest text'], getline(1, '$'))
+ nunmap ,a
+
+ " feedkeys(..., 'x') is executed immediately, but insert mode is aborted
+ noremap ,b <Cmd>call feedkeys("abeta", 'x') \| let g:b = getline(2)<CR>
+ call feedkeys(",b\<F3>", 'xt')
+ call assert_equal('n', m)
+ call assert_equal('of alphabetatest text', g:b)
+ nunmap ,b
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping with the :normal command
+func Test_map_cmdkey_normal_cmd()
+ new
+ noremap ,x <Cmd>call append(1, "xx") \| call append(1, "aa")<CR>
+ noremap ,f <Cmd>nosuchcommand<CR>
+ noremap ,e <Cmd>throw "very error" \| call append(1, "yy")<CR>
+ noremap ,m <Cmd>echoerr "The message." \| call append(1, "zz")<CR>
+ noremap ,w <Cmd>for i in range(5) \| if i==1 \| echoerr "Err" \| endif \| call append(1, i) \| endfor<CR>
+
+ call setline(1, ['some short lines', 'of test text'])
+ exe "norm ,x\r"
+ call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
+
+ call assert_fails('norm ,f', 'E492:')
+ call assert_fails('norm ,e', 'very error')
+ call assert_fails('norm ,m', 'The message.')
+ call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
+
+ %d
+ let caught_err = 0
+ try
+ exe "normal ,w"
+ catch /Vim(echoerr):Err/
+ let caught_err = 1
+ endtry
+ call assert_equal(1, caught_err)
+ call assert_equal(['', '0'], getline(1, '$'))
+
+ %d
+ call assert_fails('normal ,w', 'Err')
+ call assert_equal(['', '4', '3', '2' ,'1', '0'], getline(1, '$'))
+ call assert_equal(1, line('.'))
+
+ nunmap ,x
+ nunmap ,f
+ nunmap ,e
+ nunmap ,m
+ nunmap ,w
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in visual mode
+func Test_map_cmdkey_visual_mode()
+ new
+ set showmode
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys("v\<F4>", 'xt!')
+ call assert_equal(['v', 1, 12], [mode(1), col('v'), col('.')])
+
+ " can invoke an operator, ending the visual mode
+ let @a = ''
+ call feedkeys("\<F5>", 'xt!')
+ call assert_equal('n', mode(1))
+ call assert_equal('some short l', @a)
+
+ " error doesn't interrupt visual mode
+ call assert_fails('call feedkeys("ggvw\<F6>", "xt!")', 'E605:')
+ call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
+ call feedkeys("\<F7>", 'xt!')
+ call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+ " startinsert gives "-- (insert) VISUAL --" mode
+ call feedkeys("\<F8>", 'xt!')
+ call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+ redraw!
+ call assert_match('^-- (insert) VISUAL --', Screenline(&lines))
+ call feedkeys("\<Esc>new ", 'x')
+ call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
+
+ call s:cleanupMaps()
+ set showmode&
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in select mode
+func Test_map_cmdkey_select_mode()
+ new
+ set showmode
+ call s:setupMaps()
+
+ snoremap <F1> <cmd>throw "very error"<CR>
+ snoremap <F2> <cmd>normal! <c-g>"by<CR>
+ call setline(1, ['some short lines', 'of test text'])
+
+ call feedkeys("gh\<F4>", "xt!")
+ call assert_equal(['s', 1, 12], [mode(1), col('v'), col('.')])
+ redraw!
+ call assert_match('^-- SELECT --', Screenline(&lines))
+
+ " visual mapping in select mode restarts select mode after operator
+ let @a = ''
+ call feedkeys("\<F5>", 'xt!')
+ call assert_equal('s', mode(1))
+ call assert_equal('some short l', @a)
+
+ " select mode mapping works, and does not restart select mode
+ let @b = ''
+ call feedkeys("\<F2>", 'xt!')
+ call assert_equal('n', mode(1))
+ call assert_equal('some short l', @b)
+
+ " error doesn't interrupt temporary visual mode
+ call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F6>", "xt!")', 'E605:')
+ redraw!
+ call assert_match('^-- VISUAL --', Screenline(&lines))
+ " quirk: restoration of select mode is not performed
+ call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
+
+ " error doesn't interrupt select mode
+ call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F1>", "xt!")', 'E605:')
+ redraw!
+ call assert_match('^-- SELECT --', Screenline(&lines))
+ call assert_equal(['s', 1, 6], [mode(1), col('v'), col('.')])
+
+ call feedkeys("\<F7>", 'xt!')
+ redraw!
+ call assert_match('^-- SELECT --', Screenline(&lines))
+ call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+ " startinsert gives "-- SELECT (insert) --" mode
+ call feedkeys("\<F8>", 'xt!')
+ redraw!
+ call assert_match('^-- (insert) SELECT --', Screenline(&lines))
+ call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+ call feedkeys("\<Esc>new ", 'x')
+ call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
+
+ sunmap <F1>
+ sunmap <F2>
+ call s:cleanupMaps()
+ set showmode&
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in operator-pending mode
+func Test_map_cmdkey_op_pending_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys("d\<F4>", 'xt')
+ call assert_equal(['lines', 'of test text'], getline(1, '$'))
+ call assert_equal(['some short '], getreg('"', 1, 1))
+ " create a new undo point
+ let &undolevels = &undolevels
+
+ call feedkeys(".", 'xt')
+ call assert_equal(['test text'], getline(1, '$'))
+ call assert_equal(['lines', 'of '], getreg('"', 1, 1))
+ " create a new undo point
+ let &undolevels = &undolevels
+
+ call feedkeys("uu", 'xt')
+ call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
+
+ " error aborts operator-pending, operator not performed
+ call assert_fails('call feedkeys("d\<F6>", "xt")', 'E605:')
+ call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
+
+ call feedkeys("\"bd\<F7>", 'xt')
+ call assert_equal(['soest text'], getline(1, '$'))
+ call assert_equal(['me short lines', 'of t'], getreg('b', 1, 1))
+
+ " startinsert aborts operator
+ call feedkeys("d\<F8>cc", 'xt')
+ call assert_equal(['soccest text'], getline(1, '$'))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in insert mode
+func Test_map_cmdkey_insert_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ " works the same as <C-O>w<C-O>w
+ call feedkeys("iindeed \<F4>little ", 'xt')
+ call assert_equal(['indeed some short little lines', 'of test text'], getline(1, '$'))
+ call assert_fails('call feedkeys("i\<F6> 2", "xt")', 'E605:')
+ call assert_equal(['indeed some short little 2 lines', 'of test text'], getline(1, '$'))
+
+ " Note when entering visual mode from InsertEnter autocmd, an async event,
+ " or a <Cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode.
+ call feedkeys("i\<F7>stuff ", 'xt')
+ call assert_equal(['indeed some short little 2 lines', 'of stuff test text'], getline(1, '$'))
+ call assert_equal(['v', 1, 3, 2, 9], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+ call feedkeys("\<F5>", 'xt')
+ call assert_equal(['deed some short little 2 lines', 'of stuff '], getreg('a', 1, 1))
+
+ " also works as part of abbreviation
+ abbr foo <Cmd>let g:y = 17<CR>bar
+ exe "normal i\<space>foo "
+ call assert_equal(17, g:y)
+ call assert_equal('in bar deed some short little 2 lines', getline(1))
+ unabbr foo
+
+ " :startinsert does nothing
+ call setline(1, 'foo bar')
+ call feedkeys("ggi\<F8>vim", 'xt')
+ call assert_equal('vimfoo bar', getline(1))
+
+ " :stopinsert works
+ call feedkeys("ggi\<F9>Abc", 'xt')
+ call assert_equal('vimfoo barbc', getline(1))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in insert-completion mode
+func Test_map_cmdkey_insert_complete_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, 'some short lines')
+ call feedkeys("os\<C-X>\<C-N>\<F3>\<C-N> ", 'xt')
+ call assert_equal('ic', m)
+ call assert_equal(['some short lines', 'short '], getline(1, '$'))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in cmdline mode
+func Test_map_cmdkey_cmdline_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ let x = 0
+ call feedkeys(":let x\<F3>= 10\r", 'xt')
+ call assert_equal('c', m)
+ call assert_equal(10, x)
+
+ " exception doesn't leave cmdline mode
+ call assert_fails('call feedkeys(":let x\<F6>= 20\r", "xt")', 'E605:')
+ call assert_equal(20, x)
+
+ " move cursor in the buffer from cmdline mode
+ call feedkeys(":let x\<F4>= 30\r", 'xt')
+ call assert_equal(30, x)
+ call assert_equal(12, col('.'))
+
+ " :startinsert takes effect after leaving cmdline mode
+ call feedkeys(":let x\<F8>= 40\rnew ", 'xt')
+ call assert_equal(40, x)
+ call assert_equal('some short new lines', getline(1))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+func Test_map_cmdkey_redo()
+ func SelectDash()
+ call search('^---\n\zs', 'bcW')
+ norm! V
+ call search('\n\ze---$', 'W')
+ endfunc
+
+ let text =<< trim END
+ ---
+ aaa
+ ---
+ bbb
+ bbb
+ ---
+ ccc
+ ccc
+ ccc
+ ---
+ END
+ new Xcmdtext
+ call setline(1, text)
+
+ onoremap <silent> i- <Cmd>call SelectDash()<CR>
+ call feedkeys('2Gdi-', 'xt')
+ call assert_equal(['---', '---'], getline(1, 2))
+ call feedkeys('j.', 'xt')
+ call assert_equal(['---', '---', '---'], getline(1, 3))
+ call feedkeys('j.', 'xt')
+ call assert_equal(['---', '---', '---', '---'], getline(1, 4))
+
+ bwipe!
+ call delete('Xcmdtext')
+ delfunc SelectDash
+ ounmap i-
+endfunc
+
+func Test_map_script_cmd_restore()
+ let lines =<< trim END
+ vim9script
+ nnoremap <F3> <ScriptCmd>eval 1 + 2<CR>
+ END
+ call v9.CheckScriptSuccess(lines)
+ call feedkeys("\<F3>:let g:result = 3+4\<CR>", 'xtc')
+ call assert_equal(7, g:result)
+
+ nunmap <F3>
+ unlet g:result
+endfunc
+
+func Test_map_script_cmd_finds_func()
+ let lines =<< trim END
+ vim9script
+ onoremap <F3> <ScriptCmd>Func()<CR>
+ def Func()
+ g:func_called = 'yes'
+ enddef
+ END
+ call v9.CheckScriptSuccess(lines)
+ call feedkeys("y\<F3>\<Esc>", 'xtc')
+ call assert_equal('yes', g:func_called)
+
+ ounmap <F3>
+ unlet g:func_called
+endfunc
+
+func Test_map_script_cmd_survives_unmap()
+ let lines =<< trim END
+ vim9script
+ var n = 123
+ nnoremap <F4> <ScriptCmd><CR>
+ autocmd CmdlineEnter * silent! nunmap <F4>
+ nnoremap <F3> :<ScriptCmd>eval setbufvar(bufnr(), "result", n)<CR>
+ feedkeys("\<F3>\<CR>", 'xct')
+ assert_equal(123, b:result)
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ nunmap <F3>
+ unlet b:result
+ autocmd! CmdlineEnter
+endfunc
+
+func Test_map_script_cmd_redo()
+ call mkdir('Xmapcmd', 'R')
+ let lines =<< trim END
+ vim9script
+ import autoload './script.vim'
+ onoremap <F3> <ScriptCmd>script.Func()<CR>
+ END
+ call writefile(lines, 'Xmapcmd/plugin.vim')
+
+ let lines =<< trim END
+ vim9script
+ export def Func()
+ normal! dd
+ enddef
+ END
+ call writefile(lines, 'Xmapcmd/script.vim')
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ nnoremap j j
+ source Xmapcmd/plugin.vim
+ call feedkeys("d\<F3>j.", 'xt')
+ call assert_equal(['two', 'four'], getline(1, '$'))
+
+ ounmap <F3>
+ nunmap j
+ bwipe!
+endfunc
+
+" Test for using <script> with a map to remap characters in rhs
+func Test_script_local_remap()
+ new
+ inoremap <buffer> <SID>xyz mno
+ inoremap <buffer> <script> abc st<SID>xyzre
+ normal iabc
+ call assert_equal('stmnore', getline(1))
+ bwipe!
+endfunc
+
+func Test_abbreviate_multi_byte()
+ new
+ iabbrev foo bar
+ call feedkeys("ifoo…\<Esc>", 'xt')
+ call assert_equal("bar…", getline(1))
+ iunabbrev foo
+ bwipe!
+endfunc
+
+" Test for abbreviations with 'latin1' encoding
+func Test_abbreviate_latin1_encoding()
+ set encoding=latin1
+ call assert_fails('abbr ab#$c ABC', 'E474:')
+ new
+ iabbr <buffer> #i #include
+ iabbr <buffer> ## #enddef
+ exe "normal i#i\<C-]>"
+ call assert_equal('#include', getline(1))
+ exe "normal 0Di##\<C-]>"
+ call assert_equal('#enddef', getline(1))
+ %bw!
+ set encoding=utf-8
+endfunc
+
+" Test for <Plug> always being mapped, even when used with "noremap".
+func Test_plug_remap()
+ let g:foo = 0
+ nnoremap <Plug>(Increase_x) <Cmd>let g:foo += 1<CR>
+ nmap <F2> <Plug>(Increase_x)
+ nnoremap <F3> <Plug>(Increase_x)
+ call feedkeys("\<F2>", 'xt')
+ call assert_equal(1, g:foo)
+ call feedkeys("\<F3>", 'xt')
+ call assert_equal(2, g:foo)
+ nnoremap x <Nop>
+ nmap <F4> x<Plug>(Increase_x)x
+ nnoremap <F5> x<Plug>(Increase_x)x
+ call setline(1, 'Some text')
+ normal! gg$
+ call feedkeys("\<F4>", 'xt')
+ call assert_equal(3, g:foo)
+ call assert_equal('Some text', getline(1))
+ call feedkeys("\<F5>", 'xt')
+ call assert_equal(4, g:foo)
+ call assert_equal('Some te', getline(1))
+ nunmap <Plug>(Increase_x)
+ nunmap <F2>
+ nunmap <F3>
+ nunmap <F4>
+ nunmap <F5>
+ unlet g:foo
+ %bw!
+endfunc
+
+func Test_mouse_drag_mapped_start_select()
+ set mouse=a
+ set selectmode=key,mouse
+ func ClickExpr()
+ call test_setmouse(1, 1)
+ return "\<LeftMouse>"
+ endfunc
+ func DragExpr()
+ call test_setmouse(1, 2)
+ return "\<LeftDrag>"
+ endfunc
+ nnoremap <expr> <F2> ClickExpr()
+ nmap <expr> <F3> DragExpr()
+
+ nnoremap <LeftDrag> <LeftDrag><Cmd><CR>
+ exe "normal \<F2>\<F3>"
+ call assert_equal('s', mode())
+ exe "normal! \<C-\>\<C-N>"
+
+ nunmap <LeftDrag>
+ nunmap <F2>
+ nunmap <F3>
+ delfunc ClickExpr
+ delfunc DragExpr
+ set selectmode&
+ set mouse&
+endfunc
+
+func Test_mouse_drag_statusline()
+ set laststatus=2
+ set mouse=a
+ func ClickExpr()
+ call test_setmouse(&lines - 1, 1)
+ return "\<LeftMouse>"
+ endfunc
+ func DragExpr()
+ call test_setmouse(&lines - 2, 1)
+ return "\<LeftDrag>"
+ endfunc
+ nnoremap <expr> <F2> ClickExpr()
+ nnoremap <expr> <F3> DragExpr()
+
+ " this was causing a crash in win_drag_status_line()
+ call feedkeys("\<F2>:tabnew\<CR>\<F3>", 'tx')
+
+ nunmap <F2>
+ nunmap <F3>
+ delfunc ClickExpr
+ delfunc DragExpr
+ set laststatus& mouse&
+endfunc
+
+" Test for mapping <LeftDrag> in Insert mode
+func Test_mouse_drag_insert_map()
+ set mouse=a
+ func ClickExpr()
+ call test_setmouse(1, 1)
+ return "\<LeftMouse>"
+ endfunc
+ func DragExpr()
+ call test_setmouse(1, 2)
+ return "\<LeftDrag>"
+ endfunc
+ inoremap <expr> <F2> ClickExpr()
+ imap <expr> <F3> DragExpr()
+
+ inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR>
+ exe "normal i\<F2>\<F3>"
+ call assert_equal(1, g:dragged)
+ call assert_equal('v', mode())
+ exe "normal! \<C-\>\<C-N>"
+ unlet g:dragged
+
+ inoremap <LeftDrag> <LeftDrag><C-\><C-N>
+ exe "normal i\<F2>\<F3>"
+ call assert_equal('n', mode())
+
+ iunmap <LeftDrag>
+ iunmap <F2>
+ iunmap <F3>
+ delfunc ClickExpr
+ delfunc DragExpr
+ set mouse&
+endfunc
+
+func Test_unmap_simplifiable()
+ map <C-I> foo
+ map <Tab> bar
+ call assert_equal('foo', maparg('<C-I>'))
+ call assert_equal('bar', maparg('<Tab>'))
+ unmap <C-I>
+ call assert_equal('', maparg('<C-I>'))
+ call assert_equal('bar', maparg('<Tab>'))
+ unmap <Tab>
+
+ map <C-I> foo
+ unmap <Tab>
+ " This should not error
+ unmap <C-I>
+endfunc
+
+func Test_expr_map_escape_special()
+ nnoremap … <Cmd>let g:got_ellipsis += 1<CR>
+ func Func()
+ return '…'
+ endfunc
+ nmap <expr> <F2> Func()
+ let g:got_ellipsis = 0
+ call feedkeys("\<F2>", 'xt')
+ call assert_equal(1, g:got_ellipsis)
+ delfunc Func
+ nunmap <F2>
+ unlet g:got_ellipsis
+ nunmap …
+endfunc
+
+" Testing for mapping after an <Nop> mapping is triggered on timeout.
+" Test for what patch 8.1.0052 fixes.
+func Test_map_after_timed_out_nop()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set timeout timeoutlen=400
+ inoremap ab TEST
+ inoremap a <Nop>
+ END
+ call writefile(lines, 'Xtest_map_after_timed_out_nop', 'D')
+ let buf = RunVimInTerminal('-S Xtest_map_after_timed_out_nop', #{rows: 6})
+
+ " Enter Insert mode
+ call term_sendkeys(buf, 'i')
+ " Wait for the "a" mapping to timeout
+ call term_sendkeys(buf, 'a')
+ call term_wait(buf, 500)
+ " Send "a" and wait for a period shorter than 'timeoutlen'
+ call term_sendkeys(buf, 'a')
+ call term_wait(buf, 100)
+ " Send "b", should trigger the "ab" mapping
+ call term_sendkeys(buf, 'b')
+ call WaitForAssert({-> assert_equal("TEST", term_getline(buf, 1))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_using_past_typeahead()
+ nnoremap :00 0
+ exe "norm :set \x80\xfb0=0\<CR>"
+ exe "sil norm :0\x0f\<C-U>\<CR>"
+
+ exe "norm :set \x80\xfb0=\<CR>"
+ nunmap :00
+endfunc
+
+func Test_mapclear_while_listing()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nocompatible
+ mapclear
+ for i in range(1, 999)
+ exe 'map ' .. 'foo' .. i .. ' bar'
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('mapclear')})
+ END
+ call writefile(lines, 'Xmapclear', 'D')
+ let buf = RunVimInTerminal('-S Xmapclear', {'rows': 10})
+
+ " this was using freed memory
+ call term_sendkeys(buf, ":map\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "G")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_marks.vim b/src/testdir/test_marks.vim
new file mode 100644
index 0000000..20fb304
--- /dev/null
+++ b/src/testdir/test_marks.vim
@@ -0,0 +1,321 @@
+" Test for marks
+
+" Test that a deleted mark is restored after delete-undo-redo-undo.
+func Test_Restore_DelMark()
+ enew!
+ call append(0, [" textline A", " textline B", " textline C"])
+ normal! 2gg
+ set nocp viminfo+=nviminfo
+ exe "normal! i\<C-G>u\<Esc>"
+ exe "normal! maddu\<C-R>u"
+ let pos = getpos("'a")
+ call assert_equal(2, pos[1])
+ call assert_equal(1, pos[2])
+ enew!
+endfunc
+
+" Test that CTRL-A and CTRL-X updates last changed mark '[, '].
+func Test_Incr_Marks()
+ enew!
+ call append(0, ["123 123 123", "123 123 123", "123 123 123"])
+ normal! gg
+ execute "normal! \<C-A>`[v`]rAjwvjw\<C-X>`[v`]rX"
+ call assert_equal("AAA 123 123", getline(1))
+ call assert_equal("123 XXXXXXX", getline(2))
+ call assert_equal("XXX 123 123", getline(3))
+ enew!
+endfunc
+
+func Test_previous_jump_mark()
+ new
+ call setline(1, ['']->repeat(6))
+ normal Ggg
+ call assert_equal(6, getpos("''")[1])
+ normal jjjjj
+ call assert_equal(6, getpos("''")[1])
+ bwipe!
+endfunc
+
+func Test_setpos()
+ new Xone
+ let onebuf = bufnr('%')
+ let onewin = win_getid()
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ new Xtwo
+ let twobuf = bufnr('%')
+ let twowin = win_getid()
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+
+ " for the cursor the buffer number is ignored
+ call setpos(".", [0, 2, 1, 0])
+ call assert_equal([0, 2, 1, 0], getpos("."))
+ call setpos(".", [onebuf, 3, 3, 0])
+ call assert_equal([0, 3, 3, 0], getpos("."))
+
+ call setpos("''", [0, 1, 3, 0])
+ call assert_equal([0, 1, 3, 0], getpos("''"))
+ call setpos("''", [onebuf, 2, 2, 0])
+ call assert_equal([0, 2, 2, 0], getpos("''"))
+
+ " buffer-local marks
+ for mark in ["'a", "'\"", "'[", "']", "'<", "'>"]
+ call win_gotoid(twowin)
+ call setpos(mark, [0, 2, 1, 0])
+ call assert_equal([0, 2, 1, 0], getpos(mark), "for mark " . mark)
+ call setpos(mark, [onebuf, 1, 3, 0])
+ call win_gotoid(onewin)
+ call assert_equal([0, 1, 3, 0], getpos(mark), "for mark " . mark)
+ endfor
+
+ " global marks
+ call win_gotoid(twowin)
+ call setpos("'N", [0, 2, 1, 0])
+ call assert_equal([twobuf, 2, 1, 0], getpos("'N"))
+ call setpos("'N", [onebuf, 1, 3, 0])
+ call assert_equal([onebuf, 1, 3, 0], getpos("'N"))
+
+ " try invalid column and check virtcol()
+ call win_gotoid(onewin)
+ call setpos("'a", [0, 1, 2, 0])
+ call assert_equal([0, 1, 2, 0], getpos("'a"))
+ call setpos("'a", [0, 1, -5, 0])
+ call assert_equal([0, 1, 2, 0], getpos("'a"))
+ call setpos("'a", [0, 1, 0, 0])
+ call assert_equal([0, 1, 1, 0], getpos("'a"))
+ call setpos("'a", [0, 1, 4, 0])
+ call assert_equal([0, 1, 4, 0], getpos("'a"))
+ call assert_equal(4, virtcol("'a"))
+ call setpos("'a", [0, 1, 5, 0])
+ call assert_equal([0, 1, 5, 0], getpos("'a"))
+ call assert_equal(4, virtcol("'a"))
+ call setpos("'a", [0, 1, 21341234, 0])
+ call assert_equal([0, 1, 21341234, 0], getpos("'a"))
+ call assert_equal(4, virtcol("'a"))
+
+ " Test with invalid buffer number, line number and column number
+ call cursor(2, 2)
+ call setpos('.', [-1, 1, 1, 0])
+ call assert_equal([2, 2], [line('.'), col('.')])
+ call setpos('.', [0, -1, 1, 0])
+ call assert_equal([2, 2], [line('.'), col('.')])
+ call setpos('.', [0, 1, -1, 0])
+ call assert_equal([2, 2], [line('.'), col('.')])
+
+ call assert_fails("call setpos('ab', [0, 1, 1, 0])", 'E474:')
+
+ bwipe!
+ call win_gotoid(twowin)
+ bwipe!
+endfunc
+
+func Test_marks_cmd()
+ new Xone
+ call setline(1, ['aaa', 'bbb'])
+ norm! maG$mB
+ w!
+ new Xtwo
+ call setline(1, ['ccc', 'ddd'])
+ norm! $mcGmD
+ exe "norm! GVgg\<Esc>G"
+ w!
+
+ b Xone
+ let a = split(execute('marks'), "\n")
+ call assert_equal(9, len(a))
+ call assert_equal(['mark line col file/text',
+ \ " ' 2 0 bbb",
+ \ ' a 1 0 aaa',
+ \ ' B 2 2 bbb',
+ \ ' D 2 0 Xtwo',
+ \ ' " 1 0 aaa',
+ \ ' [ 1 0 aaa',
+ \ ' ] 2 0 bbb',
+ \ ' . 2 0 bbb'], a)
+
+ b Xtwo
+ let a = split(execute('marks'), "\n")
+ call assert_equal(11, len(a))
+ call assert_equal(['mark line col file/text',
+ \ " ' 1 0 ccc",
+ \ ' c 1 2 ccc',
+ \ ' B 2 2 Xone',
+ \ ' D 2 0 ddd',
+ \ ' " 2 0 ddd',
+ \ ' [ 1 0 ccc',
+ \ ' ] 2 0 ddd',
+ \ ' . 2 0 ddd',
+ \ ' < 1 0 ccc',
+ \ ' > 2 0 ddd'], a)
+ norm! Gdd
+ w!
+ let a = split(execute('marks <>'), "\n")
+ call assert_equal(3, len(a))
+ call assert_equal(['mark line col file/text',
+ \ ' < 1 0 ccc',
+ \ ' > 2 0 -invalid-'], a)
+
+ b Xone
+ delmarks aB
+ let a = split(execute('marks aBcD'), "\n")
+ call assert_equal(2, len(a))
+ call assert_equal('mark line col file/text', a[0])
+ call assert_equal(' D 2 0 Xtwo', a[1])
+
+ b Xtwo
+ delmarks cD
+ call assert_fails('marks aBcD', 'E283:')
+
+ call delete('Xone')
+ call delete('Xtwo')
+ %bwipe
+endfunc
+
+func Test_marks_cmd_multibyte()
+ new Xone
+ call setline(1, [repeat('á', &columns)])
+ norm! ma
+
+ let a = split(execute('marks a'), "\n")
+ call assert_equal(2, len(a))
+ let expected = ' a 1 0 ' . repeat('á', &columns - 16)
+ call assert_equal(expected, a[1])
+
+ bwipe!
+endfunc
+
+func Test_delmarks()
+ new
+ norm mx
+ norm `x
+ delmarks x
+ call assert_fails('norm `x', 'E20:')
+
+ " Deleting an already deleted mark should not fail.
+ delmarks x
+
+ " getpos() should return all zeros after deleting a filemark.
+ norm mA
+ delmarks A
+ call assert_equal([0, 0, 0, 0], getpos("'A"))
+
+ " Test deleting a range of marks.
+ norm ma
+ norm mb
+ norm mc
+ norm mz
+ delmarks b-z
+ norm `a
+ call assert_fails('norm `b', 'E20:')
+ call assert_fails('norm `c', 'E20:')
+ call assert_fails('norm `z', 'E20:')
+ call assert_fails('delmarks z-b', 'E475:')
+
+ call assert_fails('delmarks', 'E471:')
+ call assert_fails('delmarks /', 'E475:')
+
+ " Test delmarks!
+ norm mx
+ norm `x
+ delmarks!
+ call assert_fails('norm `x', 'E20:')
+ call assert_fails('delmarks! x', 'E474:')
+
+ bwipe!
+endfunc
+
+func Test_mark_error()
+ call assert_fails('mark', 'E471:')
+ call assert_fails('mark xx', 'E488:')
+ call assert_fails('mark _', 'E191:')
+ call assert_beeps('normal! m~')
+
+ call setpos("'k", [0, 100, 1, 0])
+ call assert_fails("normal 'k", 'E19:')
+endfunc
+
+" Test for :lockmarks when pasting content
+func Test_lockmarks_with_put()
+ new
+ call append(0, repeat(['sky is blue'], 4))
+ normal gg
+ 1,2yank r
+ put r
+ normal G
+ lockmarks put r
+ call assert_equal(2, line("'["))
+ call assert_equal(3, line("']"))
+
+ bwipe!
+endfunc
+
+" Test for :k command to set a mark
+func Test_marks_k_cmd()
+ new
+ call setline(1, ['foo', 'bar', 'baz', 'qux'])
+ 1,3kr
+ call assert_equal([0, 3, 1, 0], getpos("'r"))
+ close!
+endfunc
+
+" Test for file marks (A-Z)
+func Test_file_mark()
+ new Xone
+ call setline(1, ['aaa', 'bbb'])
+ norm! G$mB
+ w!
+ new Xtwo
+ call setline(1, ['ccc', 'ddd'])
+ norm! GmD
+ w!
+
+ enew
+ normal! `B
+ call assert_equal('Xone', bufname())
+ call assert_equal([2, 3], [line('.'), col('.')])
+ normal! 'D
+ call assert_equal('Xtwo', bufname())
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ call delete('Xone')
+ call delete('Xtwo')
+endfunc
+
+" Test for the getmarklist() function
+func Test_getmarklist()
+ new
+ " global marks
+ delmarks A-Z 0-9 \" ^.[]
+ call assert_equal([], getmarklist())
+ call setline(1, ['one', 'two', 'three'])
+ mark A
+ call cursor(3, 5)
+ normal mN
+ call assert_equal([{'file' : '', 'mark' : "'A", 'pos' : [bufnr(), 1, 1, 0]},
+ \ {'file' : '', 'mark' : "'N", 'pos' : [bufnr(), 3, 5, 0]}],
+ \ getmarklist())
+ " buffer local marks
+ delmarks!
+ call assert_equal([{'mark' : "''", 'pos' : [bufnr(), 1, 1, 0]},
+ \ {'mark' : "'\"", 'pos' : [bufnr(), 1, 1, 0]}], getmarklist(bufnr()))
+ call cursor(2, 2)
+ normal mr
+ call assert_equal({'mark' : "'r", 'pos' : [bufnr(), 2, 2, 0]},
+ \ bufnr()->getmarklist()[0])
+ call assert_equal([], {}->getmarklist())
+ close!
+endfunc
+
+" This was using freed memory
+func Test_jump_mark_autocmd()
+ next 00
+ edit 0
+ sargument
+ au BufEnter 0 all
+ sil norm 
+
+ au! BufEnter
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_match.vim b/src/testdir/test_match.vim
new file mode 100644
index 0000000..1896158
--- /dev/null
+++ b/src/testdir/test_match.vim
@@ -0,0 +1,437 @@
+" Test for :match, :2match, :3match, clearmatches(), getmatches(), matchadd(),
+" matchaddpos(), matcharg(), matchdelete(), and setmatches().
+
+source screendump.vim
+source check.vim
+
+function Test_match()
+ highlight MyGroup1 term=bold ctermbg=red guibg=red
+ highlight MyGroup2 term=italic ctermbg=green guibg=green
+ highlight MyGroup3 term=underline ctermbg=blue guibg=blue
+
+ " --- Check that "matcharg()" returns the correct group and pattern if a match
+ " --- is defined.
+ match MyGroup1 /TODO/
+ 2match MyGroup2 /FIXME/
+ 3match MyGroup3 /XXX/
+ call assert_equal(['MyGroup1', 'TODO'], matcharg(1))
+ call assert_equal(['MyGroup2', 'FIXME'], 2->matcharg())
+ call assert_equal(['MyGroup3', 'XXX'], matcharg(3))
+
+ " --- Check that "matcharg()" returns an empty list if the argument is not 1,
+ " --- 2 or 3 (only 0 and 4 are tested).
+ call assert_equal([], matcharg(0))
+ call assert_equal([], matcharg(4))
+
+ " --- Check that "matcharg()" returns ['', ''] if a match is not defined.
+ match
+ 2match
+ 3match
+ call assert_equal(['', ''], matcharg(1))
+ call assert_equal(['', ''], matcharg(2))
+ call assert_equal(['', ''], matcharg(3))
+
+ " --- Check that "matchadd()" and "getmatches()" agree on added matches and
+ " --- that default values apply.
+ let m1 = matchadd("MyGroup1", "TODO")
+ let m2 = matchadd("MyGroup2", "FIXME", 42)
+ let m3 = matchadd("MyGroup3", "XXX", 60, 17)
+ let ans = [{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1000},
+ \ {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 42, 'id': 1001},
+ \ {'group': 'MyGroup3', 'pattern': 'XXX', 'priority': 60, 'id': 17}]
+ call assert_equal(ans, getmatches())
+
+ " --- Check that "matchdelete()" deletes the matches defined in the previous
+ " --- test correctly.
+ call matchdelete(m1)
+ eval m2->matchdelete()
+ call matchdelete(m3)
+ call assert_equal([], getmatches())
+
+ " --- Check that "matchdelete()" returns 0 if successful and otherwise -1.
+ let m = matchadd("MyGroup1", "TODO")
+ call assert_equal(0, matchdelete(m))
+ call assert_fails('call matchdelete(42)', 'E803:')
+
+ " --- Check that "clearmatches()" clears all matches defined by ":match" and
+ " --- "matchadd()".
+ let m1 = matchadd("MyGroup1", "TODO")
+ let m2 = "MyGroup2"->matchadd("FIXME", 42)
+ let m3 = matchadd("MyGroup3", "XXX", 60, 17)
+ match MyGroup1 /COFFEE/
+ 2match MyGroup2 /HUMPPA/
+ 3match MyGroup3 /VIM/
+ call clearmatches()
+ call assert_equal([], getmatches())
+
+ " --- Check that "setmatches()" restores a list of matches saved by
+ " --- "getmatches()" without changes. (Matches with equal priority must also
+ " --- remain in the same order.)
+ let m1 = matchadd("MyGroup1", "TODO")
+ let m2 = matchadd("MyGroup2", "FIXME", 42)
+ let m3 = matchadd("MyGroup3", "XXX", 60, 17)
+ match MyGroup1 /COFFEE/
+ 2match MyGroup2 /HUMPPA/
+ 3match MyGroup3 /VIM/
+ let ml = getmatches()
+ call clearmatches()
+ call setmatches(ml)
+ call assert_equal(ml, getmatches())
+ call clearmatches()
+
+ " --- Check that "setmatches()" will not add two matches with the same ID. The
+ " --- expected behaviour (for now) is to add the first match but not the
+ " --- second and to return 0 (even though it is a matter of debate whether
+ " --- this can be considered successful behaviour).
+ let data = [{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1},
+ \ {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 10, 'id': 1}]
+ call assert_fails('call setmatches(data)', 'E801:')
+ call assert_equal([data[0]], getmatches())
+ call clearmatches()
+
+ " --- Check that "setmatches()" returns 0 if successful and otherwise -1.
+ " --- (A range of valid and invalid input values are tried out to generate the
+ " --- return values.)
+ call assert_equal(0, setmatches([]))
+ call assert_equal(0, setmatches([{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1}]))
+ call clearmatches()
+ call assert_fails('call setmatches(0)', 'E1211:')
+ call assert_fails('call setmatches([0])', 'E474:')
+ call assert_fails("call setmatches([{'wrong key': 'wrong value'}])", 'E474:')
+ call assert_equal(-1, setmatches([{'group' : 'Search', 'priority' : 10, 'id' : 5, 'pos1' : {}}]))
+
+ call setline(1, 'abcdefghijklmnopq')
+ call matchaddpos("MyGroup1", [[1, 5], [1, 8, 3]], 10, 3)
+ 1
+ redraw!
+ let v1 = screenattr(1, 1)
+ let v5 = screenattr(1, 5)
+ let v6 = screenattr(1, 6)
+ let v8 = screenattr(1, 8)
+ let v10 = screenattr(1, 10)
+ let v11 = screenattr(1, 11)
+ call assert_notequal(v1, v5)
+ call assert_equal(v6, v1)
+ call assert_equal(v8, v5)
+ call assert_equal(v10, v5)
+ call assert_equal(v11, v1)
+ call assert_equal([{'group': 'MyGroup1', 'id': 3, 'priority': 10, 'pos1': [1, 5, 1], 'pos2': [1, 8, 3]}], getmatches())
+ call clearmatches()
+
+ call setline(1, 'abcdΣabcdef')
+ eval "MyGroup1"->matchaddpos([[1, 4, 2], [1, 9, 2]], 10, 42)
+ 1
+ redraw!
+ let v1 = screenattr(1, 1)
+ let v4 = screenattr(1, 4)
+ let v5 = screenattr(1, 5)
+ let v6 = screenattr(1, 6)
+ let v7 = screenattr(1, 7)
+ let v8 = screenattr(1, 8)
+ let v9 = screenattr(1, 9)
+ let v10 = screenattr(1, 10)
+ call assert_equal([{'group': 'MyGroup1', 'id': 42, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1, 9, 2]}], getmatches())
+ call assert_notequal(v1, v4)
+ call assert_equal(v5, v4)
+ call assert_equal(v6, v1)
+ call assert_equal(v7, v1)
+ call assert_equal(v8, v4)
+ call assert_equal(v9, v4)
+ call assert_equal(v10, v1)
+
+ " Check, that setmatches() can correctly restore the matches from matchaddpos()
+ call matchadd('MyGroup1', '\%2lmatchadd')
+ let m=getmatches()
+ call clearmatches()
+ call setmatches(m)
+ call assert_equal([{'group': 'MyGroup1', 'id': 42, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1,9, 2]}, {'group': 'MyGroup1', 'pattern': '\%2lmatchadd', 'priority': 10, 'id': 1106}], getmatches())
+
+ highlight MyGroup1 NONE
+ highlight MyGroup2 NONE
+ highlight MyGroup3 NONE
+endfunc
+
+func Test_match_error()
+ call assert_fails('match Error', 'E475:')
+ call assert_fails('match Error /', 'E475:')
+ call assert_fails('4match Error /x/', 'E476:')
+ call assert_fails('match Error /x/ x', 'E488:')
+endfunc
+
+func Test_matchadd_error()
+ call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:')
+ call assert_fails("call matchadd('Search', '\\(')", 'E54:')
+ call assert_fails("call matchadd('Search', 'XXX', 1, 123, 1)", 'E715:')
+ call assert_fails("call matchadd('Error', 'XXX', 1, 3)", 'E798:')
+ call assert_fails("call matchadd('Error', 'XXX', 1, 0)", 'E799:')
+ call assert_fails("call matchadd('Error', 'XXX', [], 0)", 'E745:')
+ call assert_equal(-1, matchadd('', 'pat'))
+ call assert_equal(-1, matchadd('Search', ''))
+endfunc
+
+func Test_matchaddpos()
+ syntax on
+ set hlsearch
+
+ call setline(1, ['12345', 'NP'])
+ call matchaddpos('Error', [[1,2], [1,6], [2,2]])
+ redraw!
+ call assert_notequal(screenattr(2,2), 0)
+ call assert_equal(screenattr(2,2), screenattr(1,2))
+ call assert_notequal(screenattr(2,2), screenattr(1,6))
+ 1
+ call matchadd('Search', 'N\|\n')
+ redraw!
+ call assert_notequal(screenattr(2,1), 0)
+ call assert_equal(screenattr(2,1), screenattr(1,6))
+ exec "norm! i0\<Esc>"
+ redraw!
+ call assert_equal(screenattr(2,2), screenattr(1,6))
+
+ " Check overlapping pos
+ call clearmatches()
+ call setline(1, ['1234567890', 'NH'])
+ call matchaddpos('Error', [[1,1,5], [1,3,5], [2,2]])
+ redraw!
+ call assert_notequal(screenattr(2,2), 0)
+ call assert_equal(screenattr(2,2), screenattr(1,5))
+ call assert_equal(screenattr(2,2), screenattr(1,7))
+ call assert_notequal(screenattr(2,2), screenattr(1,8))
+
+ call clearmatches()
+ call matchaddpos('Error', [[1], [2,2]])
+ redraw!
+ call assert_equal(screenattr(2,2), screenattr(1,1))
+ call assert_equal(screenattr(2,2), screenattr(1,10))
+ call assert_notequal(screenattr(2,2), screenattr(1,11))
+
+ " matchaddpos() with line number as 0
+ call clearmatches()
+ let id = matchaddpos('Search', [[0], [3], [0]])
+ call assert_equal([{'group' : 'Search', 'priority' : 10, 'id' : id, 'pos1' : [3]}], getmatches())
+ call clearmatches()
+ let id = matchaddpos('Search', [0, 3, 0])
+ call assert_equal([{'group' : 'Search', 'priority' : 10, 'id' : id, 'pos1' : [3]}], getmatches())
+
+ nohl
+ call clearmatches()
+ syntax off
+ set hlsearch&
+endfunc
+
+" Add 12 match positions (previously the limit was 8 positions).
+func Test_matchaddpos_dump()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['1234567890123']->repeat(14))
+ call matchaddpos('Search', range(1, 12)->map({i, v -> [v, v]}))
+ END
+ call writefile(lines, 'Xmatchaddpos', 'D')
+ let buf = RunVimInTerminal('-S Xmatchaddpos', #{rows: 14})
+ call VerifyScreenDump(buf, 'Test_matchaddpos_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_matchaddpos_otherwin()
+ syntax on
+ new
+ call setline(1, ['12345', 'NP'])
+ let winid = win_getid()
+
+ wincmd w
+ call matchadd('Search', '4', 10, -1, {'window': winid})
+ call matchaddpos('Error', [[1,2], [2,2]], 10, -1, {'window': winid})
+ redraw!
+ call assert_notequal(screenattr(1,2), 0)
+ call assert_notequal(screenattr(1,4), 0)
+ call assert_notequal(screenattr(2,2), 0)
+ call assert_equal(screenattr(1,2), screenattr(2,2))
+ call assert_notequal(screenattr(1,2), screenattr(1,4))
+
+ let savematches = getmatches(winid)
+ let expect = [
+ \ {'group': 'Search', 'pattern': '4', 'priority': 10, 'id': 1000},
+ \ {'group': 'Error', 'id': 1001, 'priority': 10, 'pos1': [1, 2, 1], 'pos2': [2, 2, 1]},
+ \]
+ call assert_equal(expect, savematches)
+
+ eval winid->clearmatches()
+ call assert_equal([], getmatches(winid))
+ call assert_fails('echo getmatches(-1)', 'E957:')
+
+ call setmatches(savematches, winid)
+ call assert_equal(expect, savematches)
+
+ wincmd w
+ bwipe!
+ call clearmatches()
+ syntax off
+endfunc
+
+func Test_matchaddpos_using_negative_priority()
+ set hlsearch
+
+ call clearmatches()
+
+ call setline(1, 'x')
+ let @/='x'
+ redraw!
+ let search_attr = screenattr(1,1)
+
+ let @/=''
+ call matchaddpos('Error', [1], 10)
+ redraw!
+ let error_attr = screenattr(1,1)
+
+ call setline(2, '-1 match priority')
+ call matchaddpos('Error', [2], -1)
+ redraw!
+ let negative_match_priority_attr = screenattr(2,1)
+
+ call assert_notequal(negative_match_priority_attr, search_attr, "Match with negative priority is incorrectly highlighted with Search highlight.")
+ call assert_equal(negative_match_priority_attr, error_attr)
+
+ nohl
+ set hlsearch&
+endfunc
+
+func Test_matchaddpos_error()
+ call assert_fails("call matchaddpos('Error', 1)", 'E686:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 1)", 'E798:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 2)", 'E798:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 0)", 'E799:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 123, 1)", 'E715:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 5, {'window':12345})", 'E957:')
+ " Why doesn't the following error have an error code E...?
+ call assert_fails("call matchaddpos('Error', [{}])", 'E290:')
+ call assert_equal(-1, matchaddpos('Error', test_null_list()))
+ call assert_fails("call matchaddpos('Error', [1], [], 1)", 'E745:')
+ call assert_equal(-1, matchaddpos('Search', [[]]))
+ call assert_fails("call matchaddpos('Search', [[{}]])", 'E728:')
+ call assert_fails("call matchaddpos('Search', [[2, {}]])", 'E728:')
+ call assert_fails("call matchaddpos('Search', [[3, 4, {}]])", 'E728:')
+endfunc
+
+func OtherWindowCommon()
+ let lines =<< trim END
+ call setline(1, 'Hello Vim world')
+ let mid = matchadd('Error', 'world', 1)
+ let winid = win_getid()
+ new
+ END
+ call writefile(lines, 'XscriptMatchCommon')
+ let buf = RunVimInTerminal('-S XscriptMatchCommon', #{rows: 12})
+ call TermWait(buf)
+ return buf
+endfunc
+
+func Test_matchdelete_other_window()
+ CheckScreendump
+
+ let buf = OtherWindowCommon()
+ call term_sendkeys(buf, ":call matchdelete(mid, winid)\<CR>")
+ call VerifyScreenDump(buf, 'Test_matchdelete_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XscriptMatchCommon')
+endfunc
+
+func Test_matchdelete_error()
+ call assert_fails("call matchdelete(0)", 'E802:')
+ call assert_fails("call matchdelete(1, -1)", 'E957:')
+endfunc
+
+func Test_matchclear_other_window()
+ CheckRunVimInTerminal
+ let buf = OtherWindowCommon()
+ call term_sendkeys(buf, ":call clearmatches(winid)\<CR>")
+ call VerifyScreenDump(buf, 'Test_matchclear_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XscriptMatchCommon')
+endfunc
+
+func Test_matchadd_other_window()
+ CheckRunVimInTerminal
+ let buf = OtherWindowCommon()
+ call term_sendkeys(buf, ":call matchadd('Search', 'Hello', 1, -1, #{window: winid})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_matchadd_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XscriptMatchCommon')
+endfunc
+
+func Test_match_in_linebreak()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set breakindent linebreak breakat+=]
+ call printf('%s]%s', repeat('x', 50), repeat('x', 70))->setline(1)
+ call matchaddpos('ErrorMsg', [[1, 51]])
+ END
+ call writefile(lines, 'XscriptMatchLinebreak', 'D')
+ let buf = RunVimInTerminal('-S XscriptMatchLinebreak', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_match_linebreak', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_match_with_incsearch()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set incsearch
+ call setline(1, range(20))
+ call matchaddpos('ErrorMsg', [3])
+ END
+ call writefile(lines, 'XmatchWithIncsearch', 'D')
+ let buf = RunVimInTerminal('-S XmatchWithIncsearch', #{rows: 6})
+ call VerifyScreenDump(buf, 'Test_match_with_incsearch_1', {})
+
+ call term_sendkeys(buf, ":s/0")
+ call VerifyScreenDump(buf, 'Test_match_with_incsearch_2', {})
+
+ call term_sendkeys(buf, "\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for deleting matches outside of the screen redraw top/bottom lines
+" This should cause a redraw of those lines.
+func Test_matchdelete_redraw()
+ new
+ call setline(1, range(1, 500))
+ call cursor(250, 1)
+ let m1 = matchaddpos('Search', [[250]])
+ let m2 = matchaddpos('Search', [[10], [450]])
+ redraw!
+ let m3 = matchaddpos('Search', [[240], [260]])
+ call matchdelete(m2)
+ let m = getmatches()
+ call assert_equal(2, len(m))
+ call assert_equal([250], m[0].pos1)
+ redraw!
+ call matchdelete(m1)
+ call assert_equal(1, len(getmatches()))
+ bw!
+endfunc
+
+func Test_match_tab_with_linebreak()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set linebreak
+ call setline(1, "\tix")
+ call matchadd('ErrorMsg', '\t')
+ END
+ call writefile(lines, 'XscriptMatchTabLinebreak', 'D')
+ let buf = RunVimInTerminal('-S XscriptMatchTabLinebreak', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_match_tab_linebreak', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_matchadd_conceal.vim b/src/testdir/test_matchadd_conceal.vim
new file mode 100644
index 0000000..1602c4f
--- /dev/null
+++ b/src/testdir/test_matchadd_conceal.vim
@@ -0,0 +1,426 @@
+" Test for matchadd() and conceal feature
+
+source check.vim
+CheckFeature conceal
+
+if !has('gui_running') && has('unix')
+ set term=ansi
+endif
+
+source shared.vim
+source term_util.vim
+source view_util.vim
+
+func Test_simple_matchadd()
+ new
+
+ 1put='# This is a Test'
+ " 1234567890123456
+ let expect = '# This is a Test'
+
+ call cursor(1, 1)
+ call matchadd('Conceal', '\%2l ')
+ redraw!
+ let lnum = 2
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ quit!
+endfunc
+
+func Test_simple_matchadd_and_conceal()
+ new
+ setlocal concealcursor=n conceallevel=1
+
+ 1put='# This is a Test'
+ " 1234567890123456
+ let expect = '#XThisXisXaXTest'
+
+ call cursor(1, 1)
+ call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'X'})
+ redraw!
+ let lnum = 2
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ quit!
+endfunc
+
+func Test_matchadd_and_conceallevel_3()
+ new
+
+ setlocal conceallevel=3
+ " set filetype and :syntax on to change screenattr()
+ setlocal filetype=conf
+ syntax on
+
+ 1put='# This is a Test $'
+ " 1234567890123
+ let expect = '#ThisisaTest$'
+
+ call cursor(1, 1)
+ call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'X'})
+ redraw!
+ let lnum = 2
+ call assert_equal(expect, Screenline(lnum))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 13))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 14))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ " more matchadd()
+ " 12345678901234
+ let expect = '#Thisisa Test$'
+
+ call matchadd('ErrorMsg', '\%2l Test', 20, -1, {'conceal': 'X'})
+ redraw!
+ call assert_equal(expect, Screenline(lnum))
+ call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 7))
+ call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 10), screenattr(lnum, 13))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 14))
+ call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 16))
+ call assert_notequal(screenattr(lnum, 10), screenattr(lnum, 16))
+
+ syntax off
+ quit!
+endfunc
+
+func Test_default_conceal_char()
+ new
+ setlocal concealcursor=n conceallevel=1
+
+ 1put='# This is a Test'
+ " 1234567890123456
+ let expect = '# This is a Test'
+
+ call cursor(1, 1)
+ call matchadd('Conceal', '\%2l ', 10, -1, {})
+ redraw!
+ let lnum = 2
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ " 1234567890123456
+ let expect = '#+This+is+a+Test'
+ let listchars_save = &listchars
+ set listchars=conceal:+
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ let &listchars = listchars_save
+ quit!
+endfunc
+
+func Test_syn_and_match_conceal()
+ new
+ setlocal concealcursor=n conceallevel=1
+
+ 1put='# This is a Test '
+
+ let lnum = 2
+ call cursor(1, 1)
+
+ " 123456789012345678
+ let expect = '#ZThisZisZaZTestZZ'
+ call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'Z'})
+ syntax match MyConceal /\%2l / conceal containedin=ALL
+ hi MyConceal ctermbg=4 ctermfg=2
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ syntax clear MyConceal
+ syntax match MyConceal /\%2l / conceal containedin=ALL cchar=*
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ " 123456789012345678
+ let expect = '#*This*is*a*Test**'
+ call clearmatches()
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ " 123456789012345678
+ let expect = '#*ThisXis*a*Test**'
+ call matchadd('Conceal', '\%2l\%7c ', 10, -1, {'conceal': 'X'})
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ " 123456789012345678
+ let expect = '#*ThisXis*a*Test**'
+ call matchadd('ErrorMsg', '\%2l Test', 20, -1)
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13))
+ call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18))
+ call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19))
+
+ " 123456789012345678
+ let expect = '# ThisXis a Test'
+ syntax clear MyConceal
+ syntax match MyConceal /\%2l / conceal containedin=ALL
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 12))
+ call assert_notequal(screenattr(lnum, 12), screenattr(lnum, 13))
+ call assert_equal(screenattr(lnum, 13), screenattr(lnum, 16))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 17))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 18))
+ call assert_notequal(screenattr(lnum, 18), screenattr(lnum, 19))
+
+ syntax off
+ quit!
+endfunc
+
+func Test_clearmatches()
+ new
+ setlocal concealcursor=n conceallevel=1
+
+ 1put='# This is a Test'
+ " 1234567890123456
+ let expect = '# This is a Test'
+
+ call cursor(1, 1)
+ call matchadd('Conceal', '\%2l ', 10, -1, {'conceal': 'Z'})
+ let a = getmatches()
+ call clearmatches()
+ redraw!
+
+ let lnum = 2
+ call assert_equal(expect, Screenline(lnum))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ " reset match using setmatches()
+ " 1234567890123456
+ let expect = '#ZThisZisZaZTest'
+ call setmatches(a)
+ redraw!
+
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+ call assert_equal({'group': 'Conceal', 'pattern': '\%2l ', 'priority': 10, 'id': a[0].id, 'conceal': 'Z'}, a[0])
+
+ quit!
+endfunc
+
+func Test_using_matchaddpos()
+ new
+ setlocal concealcursor=n conceallevel=1
+ " set filetype and :syntax on to change screenattr()
+ setlocal filetype=conf
+ syntax on
+
+ 1put='# This is a Test'
+ " 1234567890123456
+ let expect = '#Pis a Test'
+
+ call cursor(1, 1)
+ call matchaddpos('Conceal', [[2,2,6]], 10, -1, {'conceal': 'P'})
+ let a = getmatches()
+ redraw!
+
+ let lnum = 2
+ call assert_equal(expect, Screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 2))
+ call assert_notequal(screenattr(lnum, 2) , screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 1) , screenattr(lnum, 10))
+ call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 12))
+ call assert_notequal(screenattr(lnum, 1) , screenattr(lnum, 16))
+ call assert_equal(screenattr(lnum, 12), screenattr(lnum, 16))
+ call assert_equal({'group': 'Conceal', 'id': a[0].id, 'priority': 10, 'pos1': [2, 2, 6], 'conceal': 'P'}, a[0])
+
+ syntax off
+ quit!
+endfunc
+
+func Test_matchadd_repeat_conceal_with_syntax_off()
+ new
+
+ " To test targets in the same line string is replaced with conceal char
+ " correctly, repeat 'TARGET'
+ 1put ='TARGET_TARGETTARGET'
+ call cursor(1, 1)
+ redraw
+ call assert_equal('TARGET_TARGETTARGET', Screenline(2))
+
+ setlocal conceallevel=2
+ call matchadd('Conceal', 'TARGET', 10, -1, {'conceal': 't'})
+
+ redraw
+ call assert_equal('t_tt', Screenline(2))
+
+ quit!
+endfunc
+
+func Test_matchadd_and_syn_conceal()
+ new
+ let cnt='Inductive bool : Type := | true : bool | false : bool.'
+ let expect = 'Inductive - : Type := | true : - | false : -.'
+ 0put =cnt
+ " set filetype and :syntax on to change screenattr()
+ set cole=1 cocu=nv
+ hi link CheckedByCoq WarningMsg
+ syntax on
+ syntax keyword coqKwd bool conceal cchar=-
+ redraw!
+ call assert_equal(expect, Screenline(1))
+ call assert_notequal(screenattr(1, 10) , screenattr(1, 11))
+ call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
+ call assert_equal(screenattr(1, 11) , screenattr(1, 32))
+ call matchadd('CheckedByCoq', '\%<2l\%>9c\%<16c')
+ redraw!
+ call assert_equal(expect, Screenline(1))
+ call assert_notequal(screenattr(1, 10) , screenattr(1, 11))
+ call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
+ call assert_equal(screenattr(1, 11) , screenattr(1, 32))
+endfunc
+
+func Test_interaction_matchadd_syntax()
+ new
+ " Test for issue #7268 fix.
+ " When redrawing the second column, win_line() was comparing the sequence
+ " number of the syntax-concealed region with a bogus zero value that was
+ " returned for the matchadd-concealed region. Before 8.0.0672 the sequence
+ " number was never reset, thus masking the problem.
+ call setline(1, 'aaa|bbb|ccc')
+ call matchadd('Conceal', '^..', 10, -1, #{conceal: 'X'})
+ syn match foobar '^.'
+ setl concealcursor=n conceallevel=1
+ redraw!
+
+ call assert_equal('Xa|bbb|ccc', Screenline(1))
+ call assert_notequal(screenattr(1, 1), screenattr(1, 2))
+
+ bwipe!
+endfunc
+
+func Test_cursor_column_in_concealed_line_after_window_scroll()
+ CheckRunVimInTerminal
+
+ " Test for issue #5012 fix.
+ " For a concealed line with cursor, there should be no window's cursor
+ " position invalidation during win_update() after scrolling attempt that is
+ " not successful and no real topline change happens. The invalidation would
+ " cause a window's cursor position recalc outside of win_line() where it's
+ " not possible to take conceal into account.
+ let lines =<< trim END
+ 3split
+ let m = matchadd('Conceal', '=')
+ setl conceallevel=2 concealcursor=nc
+ normal gg
+ "==expr==
+ END
+ call writefile(lines, 'Xcolesearch', 'D')
+ let buf = RunVimInTerminal('Xcolesearch', {})
+ call TermWait(buf, 50)
+
+ " Jump to something that is beyond the bottom of the window,
+ " so there's a scroll down.
+ call term_sendkeys(buf, ":so %\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "/expr\<CR>")
+ call TermWait(buf, 50)
+
+ " Are the concealed parts of the current line really hidden?
+ let cursor_row = term_scrape(buf, '.')->map({_, e -> e.chars})->join('')
+ call assert_equal('"expr', cursor_row)
+
+ " BugFix check: Is the window's cursor column properly updated for hidden
+ " parts of the current line?
+ call assert_equal(2, term_getcursor(buf)[1])
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_cursor_column_in_concealed_line_after_leftcol_change()
+ CheckRunVimInTerminal
+
+ " Test for issue #5214 fix.
+ let lines =<< trim END
+ 0put = 'ab' .. repeat('-', &columns) .. 'c'
+ call matchadd('Conceal', '-')
+ set nowrap ss=0 cole=3 cocu=n
+ END
+ call writefile(lines, 'Xcurs-columns', 'D')
+ let buf = RunVimInTerminal('-S Xcurs-columns', {})
+
+ " Go to the end of the line (3 columns beyond the end of the screen).
+ " Horizontal scroll would center the cursor in the screen line, but conceal
+ " makes it go to screen column 1.
+ call term_sendkeys(buf, "$")
+ call TermWait(buf)
+
+ " Are the concealed parts of the current line really hidden?
+ call WaitForAssert({-> assert_equal('c', term_getline(buf, '.'))})
+
+ " BugFix check: Is the window's cursor column properly updated for conceal?
+ call assert_equal(1, term_getcursor(buf)[1])
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_matchadd_conceal_utf8.vim b/src/testdir/test_matchadd_conceal_utf8.vim
new file mode 100644
index 0000000..83849e1
--- /dev/null
+++ b/src/testdir/test_matchadd_conceal_utf8.vim
@@ -0,0 +1,45 @@
+" Test for matchadd() and conceal feature using utf-8.
+
+source check.vim
+CheckFeature conceal
+
+if !has('gui_running') && has('unix')
+ set term=ansi
+endif
+
+func s:screenline(lnum) abort
+ let line = []
+ for c in range(1, winwidth(0))
+ call add(line, nr2char(a:lnum->screenchar(c)))
+ endfor
+ return s:trim(join(line, ''))
+endfunc
+
+func s:trim(str) abort
+ return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$')
+endfunc
+
+func Test_match_using_multibyte_conceal_char()
+ new
+ setlocal concealcursor=n conceallevel=1
+
+ 1put='# This is a Test'
+ " 1234567890123456
+ let expect = '#ˑThisˑisˑaˑTest'
+
+ call cursor(1, 1)
+ call matchadd('Conceal', '\%2l ', 20, -1, {'conceal': "\u02d1"})
+ redraw!
+
+ let lnum = 2
+ call assert_equal(expect, s:screenline(lnum))
+ call assert_notequal(screenattr(lnum, 1), screenattr(lnum, 2))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 7))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 10))
+ call assert_equal(screenattr(lnum, 2), screenattr(lnum, 12))
+ call assert_equal(screenattr(lnum, 1), screenattr(lnum, 16))
+
+ quit!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_matchfuzzy.vim b/src/testdir/test_matchfuzzy.vim
new file mode 100644
index 0000000..502d136
--- /dev/null
+++ b/src/testdir/test_matchfuzzy.vim
@@ -0,0 +1,256 @@
+" Tests for fuzzy matching
+
+source shared.vim
+source check.vim
+
+" Test for matchfuzzy()
+func Test_matchfuzzy()
+ call assert_fails('call matchfuzzy(10, "abc")', 'E686:')
+ call assert_fails('call matchfuzzy(["abc"], [])', 'E730:')
+ call assert_fails("let x = matchfuzzy(test_null_list(), 'foo')", 'E686:')
+ call assert_fails('call matchfuzzy(["abc"], test_null_string())', 'E475:')
+ call assert_equal([], matchfuzzy([], 'abc'))
+ call assert_equal([], matchfuzzy(['abc'], ''))
+ call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac'))
+ call assert_equal([], matchfuzzy([10, 20], 'ac'))
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra'))
+ call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa'))
+ call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one'))
+ call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
+ call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
+ call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
+ call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len())
+ call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
+ " matches with same score should not be reordered
+ let l = ['abc1', 'abc2', 'abc3']
+ call assert_equal(l, l->matchfuzzy('abc'))
+
+ " Tests for match preferences
+ " preference for camel case match
+ call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo'))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo'))
+ " preference for leading letter match
+ call assert_equal(['onetwo', 'xonetwo'], ['xonetwo', 'onetwo']->matchfuzzy('onetwo'))
+ " preference for sequential match
+ call assert_equal(['onetwo', 'oanbectdweo'], ['oanbectdweo', 'onetwo']->matchfuzzy('onetwo'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xonetwo', 'xxonetwo'], ['xxonetwo', 'xonetwo']->matchfuzzy('onetwo'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['one', 'onex', 'onexx'], ['onexx', 'one', 'onex']->matchfuzzy('one'))
+ " prefer complete matches over separator matches
+ call assert_equal(['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c'], ['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c']->matchfuzzy('vimrc'))
+ " gap penalty
+ call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab'))
+ " path separator vs word separator
+ call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo'))
+ call assert_equal([], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('one two'))
+ call assert_equal([], ['foo bar']->matchfuzzy(" \t "))
+
+ " test for matching a sequence of words
+ call assert_equal(['bar foo'], ['foo bar', 'bar foo', 'foobar', 'barfoo']->matchfuzzy('bar foo', {'matchseq' : 1}))
+ call assert_equal([#{text: 'two one'}], [#{text: 'one two'}, #{text: 'two one'}]->matchfuzzy('two one', #{key: 'text', matchseq: v:true}))
+
+ %bw!
+ eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)})
+ let l = getbufinfo()->map({_, v -> fnamemodify(v.name, ':t')})->matchfuzzy('ndl')
+ call assert_equal(1, len(l))
+ call assert_match('needle', l[0])
+
+ " Test for fuzzy matching dicts
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E1206:')
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}}))
+ call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
+ call assert_equal([], matchfuzzy(l, 'cam'))
+ call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', test_null_dict())", 'E1297:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : test_null_string()})", 'E475:')
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+ " matches with same score should not be reordered
+ let l = [#{text: 'abc', id: 1}, #{text: 'abc', id: 2}, #{text: 'abc', id: 3}]
+ call assert_equal(l, l->matchfuzzy('abc', #{key: 'text'}))
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:')
+
+ " Test in latin1 encoding
+ let save_enc = &encoding
+ set encoding=latin1
+ call assert_equal(['abc'], matchfuzzy(['abc'], 'abc'))
+ let &encoding = save_enc
+endfunc
+
+" Test for the matchfuzzypos() function
+func Test_matchfuzzypos()
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl'))
+ call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
+ call assert_equal([['hello', 'hello world hello world'],
+ \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]],
+ \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
+ call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
+ call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a '))
+ call assert_equal([[], [], []], matchfuzzypos(['a b'], ' '))
+ call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab'))
+ let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257)))
+ call assert_equal([[], [], []], matchfuzzypos([], 'abc'))
+
+ " match in a long string
+ call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc'))
+
+ " preference for camel case match
+ call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab'))
+ " preference for leading letter match
+ call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab'))
+ " preference for sequential match
+ call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one'))
+ " best recursive match
+ call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one'))
+
+ " match multiple words (separated by space)
+ call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
+ call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo', {'matchseq': 1}))
+ call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz'))
+ call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [326]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1}))
+ call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
+ call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
+ call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
+
+ let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
+ \ matchfuzzypos(l, 'cam', {'key' : 'val'}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
+ call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E1206:')
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}}))
+ call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}}))
+ call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
+ call assert_equal([[], [], []], matchfuzzypos(l, 'cam'))
+ call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', test_null_dict())", 'E1297:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : test_null_string()})", 'E475:')
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
+
+ let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
+ call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:')
+endfunc
+
+" Test for matchfuzzy() with multibyte characters
+func Test_matchfuzzy_mbyte()
+ CheckFeature multi_lang
+ call assert_equal(['ンヹㄇヺヴ'], matchfuzzy(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([], matchfuzzy(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal(['αβΩxxx', 'xαxβxΩx'],
+ \ matchfuzzy(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal(['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ matchfuzzy(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+
+ " match multiple words (separated by space)
+ call assert_equal(['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€'], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzy('ë¼ì§€ 마리ì˜'))
+ call assert_equal([], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzy('파란 하늘'))
+
+ " preference for camel case match
+ call assert_equal(['oneĄwo', 'oneąwo'],
+ \ ['oneąwo', 'oneĄwo']->matchfuzzy('oneąwo'))
+ " preference for complete match then match after separator (_ or space)
+ call assert_equal(['â… â…¡abã„Ÿã„ '] + sort(['â… â…¡a_bã„Ÿã„ ', 'â… â…¡a bã„Ÿã„ ']),
+ \ ['â… â…¡abã„Ÿã„ ', 'â… â…¡a bã„Ÿã„ ', 'â… â…¡a_bã„Ÿã„ ']->matchfuzzy('â… â…¡abã„Ÿã„ '))
+ " preference for match after a separator (_ or space)
+ call assert_equal(['ã„“ã„”abã„Ÿã„ ', 'ã„“ã„”a_bã„Ÿã„ ', 'ã„“ã„”a bã„Ÿã„ '],
+ \ ['ã„“ã„”a_bã„Ÿã„ ', 'ã„“ã„”a bã„Ÿã„ ', 'ã„“ã„”abã„Ÿã„ ']->matchfuzzy('ã„“ã„”abã„Ÿã„ '))
+ " preference for leading letter match
+ call assert_equal(['Å—Åţũŵż', 'xÅ—Åţũŵż'],
+ \ ['xÅ—Åţũŵż', 'Å—Åţũŵż']->matchfuzzy('Å—Åţũŵż'))
+ " preference for sequential match
+ call assert_equal(['ㄞㄡㄤffï¬ï¬‚', 'ã„žaã„¡bㄤcffdï¬efl'],
+ \ ['ã„žaã„¡bㄤcffdï¬efl', 'ㄞㄡㄤffï¬ï¬‚']->matchfuzzy('ㄞㄡㄤffï¬ï¬‚'))
+ " non-matching leading letter(s) penalty
+ call assert_equal(['xㄞㄡㄤffï¬ï¬‚', 'xxㄞㄡㄤffï¬ï¬‚'],
+ \ ['xxㄞㄡㄤffï¬ï¬‚', 'xㄞㄡㄤffï¬ï¬‚']->matchfuzzy('ㄞㄡㄤffï¬ï¬‚'))
+ " total non-matching letter(s) penalty
+ call assert_equal(['Å—ÅÅ£', 'Å—ÅÅ£x', 'Å—ÅÅ£xx'],
+ \ ['Å—ÅÅ£xx', 'Å—ÅÅ£', 'Å—ÅÅ£x']->matchfuzzy('Å—ÅÅ£'))
+endfunc
+
+" Test for matchfuzzypos() with multibyte characters
+func Test_matchfuzzypos_mbyte()
+ CheckFeature multi_lang
+ call assert_equal([['ã“ã‚“ã«ã¡ã¯ä¸–ç•Œ'], [[0, 1, 2, 3, 4]], [273]],
+ \ matchfuzzypos(['ã“ã‚“ã«ã¡ã¯ä¸–ç•Œ'], 'ã“ã‚“ã«ã¡ã¯'))
+ call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
+ " reverse the order of characters
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ'))
+ call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]],
+ \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
+ call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
+ \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]],
+ \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
+ call assert_equal([['ααααααα'], [[0, 1, 2]], [191]],
+ \ matchfuzzypos(['ααααααα'], 'ααα'))
+
+ call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'Å—ÅÅ£'], 'ffï¬ï¬‚'))
+ let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256))
+ call assert_equal(range(256), x[1][0])
+ call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257)))
+
+ " match multiple words (separated by space)
+ call assert_equal([['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€'], [[9, 10, 2, 3, 4]], [328]], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzypos('ë¼ì§€ 마리ì˜'))
+ call assert_equal([[], [], []], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzypos('파란 하늘'))
+
+ " match in a long string
+ call assert_equal([[repeat('ã¶', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]],
+ \ matchfuzzypos([repeat('ã¶', 300) .. 'ẼẼẼ'], 'ẼẼẼ'))
+ " preference for camel case match
+ call assert_equal([['xѳѵÒxxѳѴÒ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵÒxxѳѴÒ'], 'ѳѵÒ'))
+ " preference for match after a separator (_ or space)
+ call assert_equal([['xã¡ã x_ã¡ã '], [[5, 6]], [145]], matchfuzzypos(['xã¡ã x_ã¡ã '], 'ã¡ã '))
+ " preference for leading letter match
+ call assert_equal([['ѳѵÒxѳѵÒ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵÒxѳѵÒ'], 'ѳѵ'))
+ " preference for sequential match
+ call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
+ " best recursive match
+ call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
+endfunc
+
+" Test for matchfuzzy() with limit
+func Test_matchfuzzy_limit()
+ let x = ['1', '2', '3', '2']
+ call assert_equal(['2', '2'], x->matchfuzzy('2'))
+ call assert_equal(['2', '2'], x->matchfuzzy('2', #{}))
+ call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 0}))
+ call assert_equal(['2'], x->matchfuzzy('2', #{limit: 1}))
+ call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 2}))
+ call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 3}))
+ call assert_fails("call matchfuzzy(x, '2', #{limit: '2'})", 'E475:')
+
+ let l = [{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}]
+ call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}}))
+ call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{key: 'val'}))
+ call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}, limit: 0}))
+ call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{key: 'val', limit: 0}))
+ call assert_equal([{'id': 5, 'val': 'crayon'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}, limit: 1}))
+ call assert_equal([{'id': 5, 'val': 'crayon'}], l->matchfuzzy('c', #{key: 'val', limit: 1}))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_memory_usage.vim b/src/testdir/test_memory_usage.vim
new file mode 100644
index 0000000..13e7eec
--- /dev/null
+++ b/src/testdir/test_memory_usage.vim
@@ -0,0 +1,165 @@
+" Tests for memory usage.
+
+source check.vim
+CheckFeature terminal
+CheckNotGui
+
+" Skip tests on Travis CI ASAN build because it's difficult to estimate memory
+" usage.
+CheckNotAsan
+
+source shared.vim
+
+func s:pick_nr(str) abort
+ return substitute(a:str, '[^0-9]', '', 'g') * 1
+endfunc
+
+if has('win32')
+ if !executable('wmic')
+ throw 'Skipped: wmic program missing'
+ endif
+ func s:memory_usage(pid) abort
+ let cmd = printf('wmic process where processid=%d get WorkingSetSize', a:pid)
+ return s:pick_nr(system(cmd)) / 1024
+ endfunc
+elseif has('unix')
+ if !executable('ps')
+ throw 'Skipped: ps program missing'
+ endif
+ func s:memory_usage(pid) abort
+ return s:pick_nr(system('ps -o rss= -p ' . a:pid))
+ endfunc
+else
+ throw 'Skipped: not win32 or unix'
+endif
+
+" Wait for memory usage to level off.
+func s:monitor_memory_usage(pid) abort
+ let proc = {}
+ let proc.pid = a:pid
+ let proc.hist = []
+ let proc.max = 0
+
+ func proc.op() abort
+ " Check the last 200ms.
+ let val = s:memory_usage(self.pid)
+ if self.max < val
+ let self.max = val
+ endif
+ call add(self.hist, val)
+ if len(self.hist) < 20
+ return 0
+ endif
+ let sample = remove(self.hist, 0)
+ return len(uniq([sample] + self.hist)) == 1
+ endfunc
+
+ call WaitFor({-> proc.op()}, 10000)
+ return {'last': get(proc.hist, -1), 'max': proc.max}
+endfunc
+
+let s:term_vim = {}
+
+func s:term_vim.start(...) abort
+ let self.buf = term_start([GetVimProg()] + a:000)
+ let self.job = term_getjob(self.buf)
+ call WaitFor({-> job_status(self.job) ==# 'run'})
+ let self.pid = job_info(self.job).process
+
+ " running an external command may fail once in a while
+ let g:test_is_flaky = 1
+endfunc
+
+func s:term_vim.stop() abort
+ call term_sendkeys(self.buf, ":qall!\<CR>")
+ call WaitFor({-> job_status(self.job) ==# 'dead'})
+ exe self.buf . 'bwipe!'
+endfunc
+
+func s:vim_new() abort
+ return copy(s:term_vim)
+endfunc
+
+func Test_memory_func_capture_vargs()
+ " Case: if a local variable captures a:000, funccall object will be free
+ " just after it finishes.
+ let testfile = 'Xtest.vim'
+ let lines =<< trim END
+ func s:f(...)
+ let x = a:000
+ endfunc
+ for _ in range(10000)
+ call s:f(0)
+ endfor
+ END
+ call writefile(lines, testfile, 'D')
+
+ let vim = s:vim_new()
+ call vim.start('--clean', '-c', 'set noswapfile', testfile)
+ let before = s:monitor_memory_usage(vim.pid).last
+
+ call term_sendkeys(vim.buf, ":so %\<CR>")
+ call WaitFor({-> term_getcursor(vim.buf)[0] == 1})
+ let after = s:monitor_memory_usage(vim.pid)
+
+ " Estimate the limit of max usage as 2x initial usage.
+ " The lower limit can fluctuate a bit, use 97%.
+ call assert_inrange(before * 97 / 100, 2 * before, after.max)
+
+ " In this case, garbage collecting is not needed.
+ " The value might fluctuate a bit, allow for 3% tolerance below and 5% above.
+ " Based on various test runs.
+ let lower = after.last * 97 / 100
+ let upper = after.last * 105 / 100
+ call assert_inrange(lower, upper, after.max)
+
+ call vim.stop()
+endfunc
+
+func Test_memory_func_capture_lvars()
+ " Case: if a local variable captures l: dict, funccall object will not be
+ " free until garbage collector runs, but after that memory usage doesn't
+ " increase so much even when rerun Xtest.vim since system memory caches.
+ let testfile = 'Xtest.vim'
+ let lines =<< trim END
+ func s:f()
+ let x = l:
+ endfunc
+ for _ in range(10000)
+ call s:f()
+ endfor
+ END
+ call writefile(lines, testfile, 'D')
+
+ let vim = s:vim_new()
+ call vim.start('--clean', '-c', 'set noswapfile', testfile)
+ let before = s:monitor_memory_usage(vim.pid).last
+
+ call term_sendkeys(vim.buf, ":so %\<CR>")
+ call WaitFor({-> term_getcursor(vim.buf)[0] == 1})
+ let after = s:monitor_memory_usage(vim.pid)
+
+ " Rerun Xtest.vim.
+ for _ in range(3)
+ call term_sendkeys(vim.buf, ":so %\<CR>")
+ call WaitFor({-> term_getcursor(vim.buf)[0] == 1})
+ let last = s:monitor_memory_usage(vim.pid).last
+ endfor
+
+ " The usage may be a bit less than the last value, use 80%.
+ " Allow for 20% tolerance at the upper limit. That's very permissive, but
+ " otherwise the test fails sometimes. On Cirrus CI with FreeBSD we need to
+ " be even much more permissive.
+ if has('bsd')
+ let multiplier = 19
+ else
+ let multiplier = 12
+ endif
+ let lower = before * 8 / 10
+ let upper = (after.max + (after.last - before)) * multiplier / 10
+ call assert_inrange(lower, upper, last)
+
+ call vim.stop()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_menu.vim b/src/testdir/test_menu.vim
new file mode 100644
index 0000000..1247359
--- /dev/null
+++ b/src/testdir/test_menu.vim
@@ -0,0 +1,597 @@
+" Test that the system menu can be loaded.
+
+source check.vim
+CheckFeature menu
+
+source screendump.vim
+
+func Test_load_menu()
+ try
+ source $VIMRUNTIME/menu.vim
+ catch
+ call assert_report('error while loading menus: ' . v:exception)
+ endtry
+ call assert_match('browse confirm w', execute(':menu File.Save'))
+
+ let v:errmsg = ''
+ doautocmd LoadBufferMenu VimEnter
+ call assert_equal('', v:errmsg)
+
+ source $VIMRUNTIME/delmenu.vim
+ call assert_equal('', v:errmsg)
+endfunc
+
+func Test_buffer_menu_special_buffers()
+ " Load in runtime menus
+ try
+ source $VIMRUNTIME/menu.vim
+ catch
+ call assert_report('error while loading menus: ' . v:exception)
+ endtry
+
+ let v:errmsg = ''
+ doautocmd LoadBufferMenu VimEnter
+ call assert_equal('', v:errmsg)
+
+ let orig_buffer_menus = execute("nmenu Buffers")
+
+ " Test that regular new buffer results in a new buffer menu item.
+ new
+ let new_buffer_menus = execute('nmenu Buffers')
+ call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(new_buffer_menus, "\n")))
+ bwipe!
+ call assert_equal(orig_buffer_menus, execute("nmenu Buffers"))
+
+ " Make a new command-line window, test that it does not create a new buffer
+ " menu.
+ call feedkeys("q::let cmdline_buffer_menus=execute('nmenu Buffers')\<CR>:q\<CR>", 'ntx')
+ call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(cmdline_buffer_menus, "\n")))
+ call assert_equal(orig_buffer_menus, execute("nmenu Buffers"))
+
+ if has('terminal')
+ " Open a terminal window and test that it does not create a buffer menu
+ " item.
+ terminal
+ let term_buffer_menus = execute('nmenu Buffers')
+ call assert_equal(len(split(orig_buffer_menus, "\n")) + 2, len(split(term_buffer_menus, "\n")))
+ bwipe!
+ call assert_equal(orig_buffer_menus, execute("nmenu Buffers"))
+ endif
+
+ " Remove menus to clean up
+ source $VIMRUNTIME/delmenu.vim
+ call assert_equal('', v:errmsg)
+endfunc
+
+func Test_translate_menu()
+ CheckFeature multi_lang
+ if !filereadable($VIMRUNTIME . '/lang/menu_de_de.latin1.vim')
+ throw 'Skipped: translated menu not found'
+ endif
+
+ " First delete any English menus.
+ source $VIMRUNTIME/delmenu.vim
+ set langmenu=de_de
+ source $VIMRUNTIME/menu.vim
+ call assert_match('browse confirm w', execute(':menu Datei.Speichern'))
+
+ source $VIMRUNTIME/delmenu.vim
+endfunc
+
+func Test_menu_commands()
+ nmenu 2 Test.FooBar :let g:did_menu = 'normal'<CR>
+ vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR>
+ smenu 2 Test.FooBar :let g:did_menu = 'select'<CR>
+ omenu 2 Test.FooBar :let g:did_menu = 'op-pending'<CR>
+ tlmenu 2 Test.FooBar :let g:did_menu = 'terminal'<CR>
+ imenu 2 Test.FooBar :let g:did_menu = 'insert'<CR>
+ cmenu 2 Test.FooBar :let g:did_menu = 'cmdline'<CR>
+ emenu n Test.FooBar
+
+ call feedkeys(":menu Test.FooB\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"menu Test.FooBar', @:)
+
+ call assert_equal('normal', g:did_menu)
+ emenu v Test.FooBar
+ call assert_equal('visual', g:did_menu)
+ emenu s Test.FooBar
+ call assert_equal('select', g:did_menu)
+ emenu o Test.FooBar
+ call assert_equal('op-pending', g:did_menu)
+ emenu t Test.FooBar
+ call assert_equal('terminal', g:did_menu)
+ emenu i Test.FooBar
+ call assert_equal('insert', g:did_menu)
+ emenu c Test.FooBar
+ call assert_equal('cmdline', g:did_menu)
+
+ nunmenu Test.FooBar
+ call assert_fails('emenu n Test.FooBar', 'E335: Menu not defined for Normal mode')
+ vunmenu Test.FooBar
+ call assert_fails('emenu v Test.FooBar', 'E335: Menu not defined for Visual mode')
+ vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR>
+ sunmenu Test.FooBar
+ call assert_fails('emenu s Test.FooBar', 'E335: Menu not defined for Select mode')
+ ounmenu Test.FooBar
+ call assert_fails('emenu o Test.FooBar', 'E335: Menu not defined for Op-pending mode')
+ iunmenu Test.FooBar
+ call assert_fails('emenu i Test.FooBar', 'E335: Menu not defined for Insert mode')
+ cunmenu Test.FooBar
+ call assert_fails('emenu c Test.FooBar', 'E335: Menu not defined for Cmdline mode')
+ tlunmenu Test.FooBar
+ call assert_fails('emenu t Test.FooBar', 'E335: Menu not defined for Terminal mode')
+
+ aunmenu Test.FooBar
+ call assert_fails('emenu n Test.FooBar', 'E334:')
+
+ nmenu 2 Test.FooBar.Child :let g:did_menu = 'foobar'<CR>
+ call assert_fails('emenu n Test.FooBar', 'E333:')
+ nunmenu Test.FooBar.Child
+
+ unlet g:did_menu
+endfun
+
+" Test various menu related errors
+func Test_menu_errors()
+ menu Test.Foo :version<CR>
+
+ " Error cases
+ call assert_fails('menu .Test.Foo :ls<CR>', 'E475:')
+ call assert_fails('menu Test. :ls<CR>', 'E330:')
+ call assert_fails('menu Foo. :ls<CR>', 'E331:')
+ call assert_fails('unmenu Test.Foo abc', 'E488:')
+ call assert_fails('menu <Tab>:ls :ls<CR>', 'E792:')
+ call assert_fails('menu Test.<Tab>:ls :ls<CR>', 'E792:')
+ call assert_fails('menu Test.Foo.Bar :ls<CR>', 'E327:')
+ call assert_fails('menu Test.-Sep-.Baz :ls<CR>', 'E332:')
+ call assert_fails('menu Foo.Bar.--.Baz :ls<CR>', 'E332:')
+ call assert_fails('menu disable Test.Foo.Bar', 'E327:')
+ call assert_fails('menu disable T.Foo', 'E329:')
+ call assert_fails('unmenu Test.Foo.Bar', 'E327:')
+ call assert_fails('cunmenu Test.Foo', 'E328:')
+ call assert_fails('unmenu Test.Bar', 'E329:')
+ call assert_fails('menu Test.Foo.Bar', 'E327:')
+ call assert_fails('cmenu Test.Foo', 'E328:')
+ call assert_fails('emenu x Test.Foo', 'E475:')
+ call assert_fails('emenu Test.Foo.Bar', 'E327:')
+ call assert_fails('menutranslate Test', 'E474:')
+
+ silent! unmenu Foo
+ unmenu Test
+endfunc
+
+" Test for menu item completion in command line
+func Test_menu_expand()
+ " Create the menu itmes for test
+ menu Dummy.Nothing lll
+ for i in range(1, 4)
+ let m = 'menu Xmenu.A' .. i .. '.A' .. i
+ for j in range(1, 4)
+ exe m .. 'B' .. j .. ' :echo "A' .. i .. 'B' .. j .. '"' .. "<CR>"
+ endfor
+ endfor
+ set wildmenu
+
+ " Test for <CR> selecting a submenu
+ call feedkeys(":emenu Xmenu.A\<Tab>\<CR>\<Right>x\<BS>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"emenu Xmenu.A1.A1B2', @:)
+
+ " Test for <Down> selecting a submenu
+ call feedkeys(":emenu Xmenu.A\<Tab>\<Right>\<Right>\<Down>" ..
+ \ "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"emenu Xmenu.A3.A3B1 A3B2 A3B3 A3B4', @:)
+
+ " Test for <Up> to go up a submenu
+ call feedkeys(":emenu Xmenu.A\<Tab>\<Down>\<Up>\<Right>\<Right>" ..
+ \ "\<Left>\<Down>\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"emenu Xmenu.A2.A2B1 A2B2 A2B3 A2B4', @:)
+
+ " Test for <Up> to go up a menu
+ call feedkeys(":emenu Xmenu.A\<Tab>\<Down>\<Up>\<Up>\<Up>" ..
+ \ "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"emenu Dummy. Xmenu.', @:)
+
+ " Test for expanding only submenus
+ call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"popup Xmenu.A1 A2 A3 A4', @:)
+
+ " Test for expanding menus after enable/disable
+ call feedkeys(":menu enable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"menu enable Xmenu.A1. A2. A3. A4.', @:)
+ call feedkeys(":menu disable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"menu disable Xmenu.A1. A2. A3. A4.', @:)
+
+ " Test for expanding non-existing menu path
+ call feedkeys(":menu xyz.\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"menu xyz.', @:)
+ call feedkeys(":menu Xmenu.A1.A1B1.xyz.\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"menu Xmenu.A1.A1B1.xyz.', @:)
+
+ set wildmenu&
+ unmenu Xmenu
+ unmenu Dummy
+
+ " Test for expanding popup menus with some hidden items
+ menu Xmenu.foo.A1 a1
+ menu Xmenu.]bar bar
+ menu Xmenu.]baz.B1 b1
+ menu Xmenu.-sep- :
+ call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"popup Xmenu.foo', @:)
+ unmenu Xmenu
+endfunc
+
+" Test for the menu_info() function
+func Test_menu_info()
+ " Define menus with various attributes
+ 10nnoremenu 10.10 T&est.F&oo :echo 'foo'<CR>
+ 10nmenu <silent> 10.20 T&est.B&ar<Tab>:bar :echo 'bar'<CR>
+ 10nmenu <script> 10.30.5 T&est.Ba&z.Qu&x :echo 'qux'<CR>
+
+ let d = #{name: "B&ar\t:bar", display: 'Bar', modes: 'n', shortcut: 'a',
+ \ accel: ':bar', priority: 20, enabled: v:true, silent: v:true,
+ \ noremenu: v:false, script: v:false, rhs: ":echo 'bar'<CR>"}
+ call assert_equal(d, menu_info('Test.Bar'))
+
+ let d = #{name: 'Ba&z', display: 'Baz', modes: 'n', shortcut: 'z',
+ \ priority: 30, submenus: ['Qux']}
+ call assert_equal(d, menu_info('Test.Baz'))
+
+ let d = #{name: 'T&est', display: 'Test', modes: 'n', shortcut: 'e',
+ \ priority: 10, submenus: ['Foo', 'Bar', 'Baz']}
+ call assert_equal(d, menu_info('Test'))
+ call assert_equal({}, menu_info('Test.Dummy'))
+ call assert_equal({}, menu_info('Dummy'))
+
+ nmenu disable Test.Foo
+ call assert_equal(v:false, menu_info('Test.Foo').enabled)
+ nmenu enable Test.Foo
+ call assert_equal(v:true, menu_info('Test.Foo').enabled)
+
+ call assert_equal(menu_info('Test.Foo'), menu_info('Test.Foo', ''))
+ nmenu Test.abc <Nop>
+ call assert_equal('<Nop>', menu_info('Test.abc').rhs)
+ call assert_fails('call menu_info([])', 'E730:')
+ call assert_fails('call menu_info("", [])', 'E730:')
+ nunmenu Test
+
+ " Test for defining menus in different modes
+ menu Test.menu :menu<CR>
+ menu! Test.menu! :menu!<CR>
+ amenu Test.amenu :amenu<CR>
+ nmenu Test.nmenu :nmenu<CR>
+ omenu Test.omenu :omenu<CR>
+ vmenu Test.vmenu :vmenu<CR>
+ xmenu Test.xmenu :xmenu<CR>
+ smenu Test.smenu :smenu<CR>
+ imenu <silent> <script> Test.imenu :imenu<CR>
+ cmenu Test.cmenu :cmenu<CR>
+ tlmenu Test.tlmenu :tlmenu<CR>
+ tmenu Test.nmenu Normal mode menu
+ tmenu Test.omenu Op-pending mode menu
+ noremenu Test.noremenu :noremenu<CR>
+ noremenu! Test.noremenu! :noremenu!<CR>
+ anoremenu Test.anoremenu :anoremenu<CR>
+ nnoremenu Test.nnoremenu :nnoremenu<CR>
+ onoremenu Test.onoremenu :onoremenu<CR>
+ vnoremenu Test.vnoremenu :vnoremenu<CR>
+ xnoremenu Test.xnoremenu :xnoremenu<CR>
+ snoremenu Test.snoremenu :snoremenu<CR>
+ inoremenu <silent> Test.inoremenu :inoremenu<CR>
+ cnoremenu Test.cnoremenu :cnoremenu<CR>
+ tlnoremenu Test.tlnoremenu :tlnoremenu<CR>
+ call assert_equal(#{name: 'menu', priority: 500, shortcut: '',
+ \ display: 'menu', modes: ' ', enabled: v:true, silent: v:false,
+ \ rhs: ":menu<CR>", noremenu: v:false, script: v:false},
+ \ menu_info('Test.menu'))
+ call assert_equal(#{name: 'menu!', priority: 500, shortcut: '',
+ \ display: 'menu!', modes: '!', enabled: v:true, silent: v:false,
+ \ rhs: ":menu!<CR>", noremenu: v:false, script: v:false},
+ \ menu_info('Test.menu!', '!'))
+ call assert_equal(#{name: 'amenu', priority: 500, shortcut: '',
+ \ display: 'amenu', modes: 'a', enabled: v:true, silent: v:false,
+ \ rhs: ":amenu<CR>", noremenu: v:false, script: v:false},
+ \ menu_info('Test.amenu', 'a'))
+ call assert_equal(#{name: 'nmenu', priority: 500, shortcut: '',
+ \ display: 'nmenu', modes: 'n', enabled: v:true, silent: v:false,
+ \ rhs: ':nmenu<CR>', noremenu: v:false, script: v:false},
+ \ menu_info('Test.nmenu', 'n'))
+ call assert_equal(#{name: 'omenu', priority: 500, shortcut: '',
+ \ display: 'omenu', modes: 'o', enabled: v:true, silent: v:false,
+ \ rhs: ':omenu<CR>', noremenu: v:false, script: v:false},
+ \ menu_info('Test.omenu', 'o'))
+ call assert_equal(#{name: 'vmenu', priority: 500, shortcut: '',
+ \ display: 'vmenu', modes: 'v', enabled: v:true, silent: v:false,
+ \ rhs: ':vmenu<CR>', noremenu: v:false, script: v:false},
+ \ menu_info('Test.vmenu', 'v'))
+ call assert_equal(#{name: 'xmenu', priority: 500, shortcut: '',
+ \ display: 'xmenu', modes: 'x', enabled: v:true, silent: v:false,
+ \ rhs: ':xmenu<CR>', noremenu: v:false, script: v:false},
+ \ menu_info('Test.xmenu', 'x'))
+ call assert_equal(#{name: 'smenu', priority: 500, shortcut: '',
+ \ display: 'smenu', modes: 's', enabled: v:true, silent: v:false,
+ \ rhs: ':smenu<CR>', noremenu: v:false, script: v:false},
+ \ menu_info('Test.smenu', 's'))
+ call assert_equal(#{name: 'imenu', priority: 500, shortcut: '',
+ \ display: 'imenu', modes: 'i', enabled: v:true, silent: v:true,
+ \ rhs: ':imenu<CR>', noremenu: v:false, script: v:true},
+ \ menu_info('Test.imenu', 'i'))
+ call assert_equal(#{ name: 'cmenu', priority: 500, shortcut: '',
+ \ display: 'cmenu', modes: 'c', enabled: v:true, silent: v:false,
+ \ rhs: ':cmenu<CR>', noremenu: v:false, script: v:false},
+ \ menu_info('Test.cmenu', 'c'))
+ call assert_equal(#{name: 'tlmenu', priority: 500, shortcut: '',
+ \ display: 'tlmenu', modes: 'tl', enabled: v:true, silent: v:false,
+ \ rhs: ':tlmenu<CR>', noremenu: v:false, script: v:false},
+ \ menu_info('Test.tlmenu', 'tl'))
+ call assert_equal(#{name: 'noremenu', priority: 500, shortcut: '',
+ \ display: 'noremenu', modes: ' ', enabled: v:true, silent: v:false,
+ \ rhs: ":noremenu<CR>", noremenu: v:true, script: v:false},
+ \ menu_info('Test.noremenu'))
+ call assert_equal(#{name: 'noremenu!', priority: 500, shortcut: '',
+ \ display: 'noremenu!', modes: '!', enabled: v:true, silent: v:false,
+ \ rhs: ":noremenu!<CR>", noremenu: v:true, script: v:false},
+ \ menu_info('Test.noremenu!', '!'))
+ call assert_equal(#{name: 'anoremenu', priority: 500, shortcut: '',
+ \ display: 'anoremenu', modes: 'a', enabled: v:true, silent: v:false,
+ \ rhs: ":anoremenu<CR>", noremenu: v:true, script: v:false},
+ \ menu_info('Test.anoremenu', 'a'))
+ call assert_equal(#{name: 'nnoremenu', priority: 500, shortcut: '',
+ \ display: 'nnoremenu', modes: 'n', enabled: v:true, silent: v:false,
+ \ rhs: ':nnoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.nnoremenu', 'n'))
+ call assert_equal(#{name: 'onoremenu', priority: 500, shortcut: '',
+ \ display: 'onoremenu', modes: 'o', enabled: v:true, silent: v:false,
+ \ rhs: ':onoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.onoremenu', 'o'))
+ call assert_equal(#{name: 'vnoremenu', priority: 500, shortcut: '',
+ \ display: 'vnoremenu', modes: 'v', enabled: v:true, silent: v:false,
+ \ rhs: ':vnoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.vnoremenu', 'v'))
+ call assert_equal(#{name: 'xnoremenu', priority: 500, shortcut: '',
+ \ display: 'xnoremenu', modes: 'x', enabled: v:true, silent: v:false,
+ \ rhs: ':xnoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.xnoremenu', 'x'))
+ call assert_equal(#{name: 'snoremenu', priority: 500, shortcut: '',
+ \ display: 'snoremenu', modes: 's', enabled: v:true, silent: v:false,
+ \ rhs: ':snoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.snoremenu', 's'))
+ call assert_equal(#{name: 'inoremenu', priority: 500, shortcut: '',
+ \ display: 'inoremenu', modes: 'i', enabled: v:true, silent: v:true,
+ \ rhs: ':inoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.inoremenu', 'i'))
+ call assert_equal(#{ name: 'cnoremenu', priority: 500, shortcut: '',
+ \ display: 'cnoremenu', modes: 'c', enabled: v:true, silent: v:false,
+ \ rhs: ':cnoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.cnoremenu', 'c'))
+ call assert_equal(#{name: 'tlnoremenu', priority: 500, shortcut: '',
+ \ display: 'tlnoremenu', modes: 'tl', enabled: v:true, silent: v:false,
+ \ rhs: ':tlnoremenu<CR>', noremenu: v:true, script: v:false},
+ \ menu_info('Test.tlnoremenu', 'tl'))
+
+ " Test for getting all the top-level menu names
+ call assert_notequal(menu_info('').submenus, [])
+
+ aunmenu Test
+ tlunmenu Test
+ call assert_equal({}, menu_info('Test'))
+ call assert_equal({}, menu_info('Test', '!'))
+ call assert_equal({}, menu_info('Test', 'a'))
+ call assert_equal({}, menu_info('Test', 'n'))
+ call assert_equal({}, menu_info('Test', 'o'))
+ call assert_equal({}, menu_info('Test', 'v'))
+ call assert_equal({}, menu_info('Test', 'x'))
+ call assert_equal({}, menu_info('Test', 's'))
+ call assert_equal({}, menu_info('Test', 'i'))
+ call assert_equal({}, menu_info('Test', 'c'))
+ call assert_equal({}, menu_info('Test', 't'))
+ call assert_equal({}, menu_info('Test', 'tl'))
+
+ amenu Test.amenu :amenu<CR>
+ call assert_equal(':amenu<CR>', menu_info('Test.amenu', '').rhs)
+ call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', '!').rhs)
+ call assert_equal(':amenu<CR>', menu_info('Test.amenu', 'n').rhs)
+ call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
+ \ menu_info('Test.amenu', 'o').rhs)
+ call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
+ \ menu_info('Test.amenu', 'v').rhs)
+ call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
+ \ menu_info('Test.amenu', 'x').rhs)
+ call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
+ \ menu_info('Test.amenu', 's').rhs)
+ call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', 'i').rhs)
+ call assert_equal('<C-C>:amenu<CR><C-\><C-G>',
+ \ menu_info('Test.amenu', 'c').rhs)
+ aunmenu Test.amenu
+
+ " Test for hidden menus
+ menu ]Test.menu :menu<CR>
+ call assert_equal(#{name: ']Test', display: ']Test', priority: 500,
+ \ shortcut: '', modes: ' ', submenus: ['menu']},
+ \ menu_info(']Test'))
+ unmenu ]Test
+endfunc
+
+" Test for <special> keyword in a menu with 'cpo' containing '<'
+func Test_menu_special()
+ new
+ set cpo+=<
+ nmenu Test.Sign am<Tab>n<Esc>
+ call feedkeys(":emenu n Test.Sign\<CR>", 'x')
+ call assert_equal("m<Tab>n<Esc>", getline(1))
+ nunmenu Test.Sign
+ nmenu <special> Test.Sign am<Tab>n<Esc>
+ call setline(1, '')
+ call feedkeys(":emenu n Test.Sign\<CR>", 'x')
+ call assert_equal("m\tn", getline(1))
+ set cpo-=<
+ close!
+ nunmenu Test.Sign
+endfunc
+
+" Test for "icon=filename" in a toolbar
+func Test_menu_icon()
+ CheckFeature toolbar
+ nmenu icon=myicon.xpm Toolbar.Foo :echo "Foo"<CR>
+ call assert_equal('myicon.xpm', "Toolbar.Foo"->menu_info().icon)
+ nunmenu Toolbar.Foo
+
+ " Test for using the builtin icon
+ amenu ToolBar.BuiltIn22 :echo "BuiltIn22"<CR>
+ call assert_equal(#{name: 'BuiltIn22', display: 'BuiltIn22',
+ \ enabled: v:true, shortcut: '', modes: 'a', script: v:false,
+ \ iconidx: 22, priority: 500, silent: v:false,
+ \ rhs: ':echo "BuiltIn22"<CR>', noremenu: v:false},
+ \ menu_info("ToolBar.BuiltIn22"))
+ aunmenu ToolBar.BuiltIn22
+endfunc
+
+" Test for ":emenu" command in different modes
+func Test_emenu_cmd()
+ new
+ xmenu Test.foo rx
+ call setline(1, ['aaaa', 'bbbb'])
+ normal ggVj
+ %emenu Test.foo
+ call assert_equal(['xxxx', 'xxxx'], getline(1, 2))
+ call setline(1, ['aaaa', 'bbbb'])
+ exe "normal ggVj\<Esc>"
+ %emenu Test.foo
+ call assert_equal(['xxxx', 'xxxx'], getline(1, 2))
+ call setline(1, ['aaaa', 'bbbb'])
+ exe "normal ggV\<Esc>"
+ 2emenu Test.foo
+ call assert_equal(['aaaa', 'xxxx'], getline(1, 2))
+ xunmenu Test.foo
+ close!
+endfunc
+
+" Test for PopUp menus
+func Test_popup_menu()
+ 20menu PopUp.foo :echo 'foo'<CR>
+ 20menu PopUp.bar :echo 'bar'<CR>
+ call assert_equal(#{name: 'PopUp', display: 'PopUp', priority: 20,
+ \ shortcut: '', modes: ' ', submenus: ['foo', 'bar']},
+ \ menu_info('PopUp'))
+ menu disable PopUp.bar
+ call assert_equal(v:true, "PopUp.foo"->menu_info().enabled)
+ call assert_equal(v:false, "PopUp.bar"->menu_info().enabled)
+ menu enable PopUp.bar
+ call assert_equal(v:true, "PopUp.bar"->menu_info().enabled)
+ unmenu PopUp
+endfunc
+
+" Test for MenuPopup autocommand
+func Test_autocmd_MenuPopup()
+ CheckNotGui
+
+ set mouse=a
+ set mousemodel=popup
+ aunmenu *
+ autocmd MenuPopup * exe printf(
+ \ 'anoremenu PopUp.Foo <Cmd>let g:res = ["%s", "%s"]<CR>',
+ \ expand('<afile>'), expand('<amatch>'))
+
+ call feedkeys("\<RightMouse>\<Down>\<CR>", 'tnix')
+ call assert_equal(['n', 'n'], g:res)
+
+ call feedkeys("v\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix')
+ call assert_equal(['v', 'v'], g:res)
+
+ call feedkeys("gh\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix')
+ call assert_equal(['s', 's'], g:res)
+
+ call feedkeys("i\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix')
+ call assert_equal(['i', 'i'], g:res)
+
+ autocmd! MenuPopup
+ aunmenu PopUp.Foo
+ unlet g:res
+ set mouse& mousemodel&
+endfunc
+
+" Test for listing the menus using the :menu command
+func Test_show_menus()
+ " In the GUI, tear-off menu items are present in the output below
+ " So skip this test
+ CheckNotGui
+ aunmenu *
+ call assert_equal(['--- Menus ---'], split(execute('menu'), "\n"))
+ nmenu <script> 200.10 Test.nmenu1 :nmenu1<CR>
+ nmenu 200.20 Test.nmenu2 :nmenu2<CR>
+ nnoremenu 200.30 Test.nmenu3 :nmenu3<CR>
+ nmenu 200.40 Test.nmenu4 :nmenu4<CR>
+ nmenu 200.50 disable Test.nmenu4
+ let exp =<< trim [TEXT]
+ --- Menus ---
+ 200 Test
+ 10 nmenu1
+ n& :nmenu1<CR>
+ 20 nmenu2
+ n :nmenu2<CR>
+ 30 nmenu3
+ n* :nmenu3<CR>
+ 40 nmenu4
+ n - :nmenu4<CR>
+ [TEXT]
+ call assert_equal(exp, split(execute('nmenu'), "\n"))
+ nunmenu Test
+endfunc
+
+" Test for menu tips
+func Test_tmenu()
+ tunmenu *
+ call assert_equal(['--- Menus ---'], split(execute('tmenu'), "\n"))
+ tmenu Test.nmenu1 nmenu1
+ tmenu Test.nmenu2.sub1 nmenu2.sub1
+ let exp =<< trim [TEXT]
+ --- Menus ---
+ 500 Test
+ 500 nmenu1
+ t - nmenu1
+ 500 nmenu2
+ 500 sub1
+ t - nmenu2.sub1
+ [TEXT]
+ call assert_equal(exp, split(execute('tmenu'), "\n"))
+ tunmenu Test
+endfunc
+
+func Test_only_modifier()
+ exe "tmenu a.b \x80\xfc0"
+ let exp =<< trim [TEXT]
+ --- Menus ---
+ 500 a
+ 500 b
+ t - <T-2-^@>
+ [TEXT]
+ call assert_equal(exp, split(execute('tmenu'), "\n"))
+
+ tunmenu a.b
+endfunc
+
+func Test_unmenu_while_listing_menus()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nocompatible
+ unmenu *
+ for i in range(1, 999)
+ exe 'menu ' .. 'foo.' .. i .. ' bar'
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('unmenu *')})
+ END
+ call writefile(lines, 'Xmenuclear', 'D')
+ let buf = RunVimInTerminal('-S Xmenuclear', {'rows': 10})
+
+ " this was using freed memory
+ call term_sendkeys(buf, ":menu\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "G")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_messages.vim b/src/testdir/test_messages.vim
new file mode 100644
index 0000000..ff705fe
--- /dev/null
+++ b/src/testdir/test_messages.vim
@@ -0,0 +1,593 @@
+" Tests for :messages, :echomsg, :echoerr
+
+source check.vim
+source shared.vim
+source term_util.vim
+source view_util.vim
+source screendump.vim
+
+func Test_messages()
+ let oldmore = &more
+ try
+ set nomore
+
+ let arr = map(range(10), '"hello" . v:val')
+ for s in arr
+ echomsg s | redraw
+ endfor
+
+ " get last two messages
+ redir => result
+ 2messages | redraw
+ redir END
+ let msg_list = split(result, "\n")
+ call assert_equal(["hello8", "hello9"], msg_list)
+
+ " clear messages without last one
+ 1messages clear
+ let msg_list = GetMessages()
+ call assert_equal(['hello9'], msg_list)
+
+ " clear all messages
+ messages clear
+ let msg_list = GetMessages()
+ call assert_equal([], msg_list)
+ finally
+ let &more = oldmore
+ endtry
+
+ call assert_fails('message 1', 'E474:')
+endfunc
+
+" Patch 7.4.1696 defined the "clearmode()" function for clearing the mode
+" indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message
+" output could then be disturbed when 'cmdheight' was greater than one.
+" This test ensures that the bugfix for this issue remains in place.
+func Test_stopinsert_does_not_break_message_output()
+ set cmdheight=2
+ redraw!
+
+ stopinsert | echo 'test echo'
+ call assert_equal(116, screenchar(&lines - 1, 1))
+ call assert_equal(32, screenchar(&lines, 1))
+ redraw!
+
+ stopinsert | echomsg 'test echomsg'
+ call assert_equal(116, screenchar(&lines - 1, 1))
+ call assert_equal(32, screenchar(&lines, 1))
+ redraw!
+
+ set cmdheight&
+endfunc
+
+func Test_message_completion()
+ call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"message clear', @:)
+endfunc
+
+func Test_echomsg()
+ call assert_equal("\nhello", execute(':echomsg "hello"'))
+ call assert_equal("\n", execute(':echomsg ""'))
+ call assert_equal("\n12345", execute(':echomsg 12345'))
+ call assert_equal("\n[]", execute(':echomsg []'))
+ call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]'))
+ call assert_equal("\n[1, 2, []]", execute(':echomsg [1, 2, test_null_list()]'))
+ call assert_equal("\n{}", execute(':echomsg {}'))
+ call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}'))
+ call assert_equal("\n1.23", execute(':echomsg 1.23'))
+ call assert_match("function('<lambda>\\d*')", execute(':echomsg {-> 1234}'))
+endfunc
+
+func Test_echoerr()
+ call test_ignore_error('IgNoRe')
+ call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"'))
+ call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"'))
+ call assert_equal("\n[1, 2, 'IgNoRe']", execute(':echoerr [1, 2, "IgNoRe"]'))
+ call assert_equal("\n{'IgNoRe': 2, 'a': 1}", execute(':echoerr {"a": 1, "IgNoRe": 2}'))
+ call assert_equal("\n1.23 IgNoRe", execute(':echoerr 1.23 "IgNoRe"'))
+ eval '<lambda>'->test_ignore_error()
+ call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}'))
+ call test_ignore_error('RESET')
+endfunc
+
+func Test_mode_message_at_leaving_insert_by_ctrl_c()
+ CheckFeature terminal
+ CheckNotGui
+
+ " Set custom statusline built by user-defined function.
+ let testfile = 'Xtest.vim'
+ let lines =<< trim END
+ func StatusLine() abort
+ return ""
+ endfunc
+ set statusline=%!StatusLine()
+ set laststatus=2
+ END
+ call writefile(lines, testfile, 'D')
+
+ let rows = 10
+ let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
+ call TermWait(buf, 100)
+ call assert_equal('run', job_status(term_getjob(buf)))
+
+ call term_sendkeys(buf, "i")
+ call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))})
+ call term_sendkeys(buf, "\<C-C>")
+ call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))})
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))})
+
+ exe buf . 'bwipe!'
+endfunc
+
+func Test_mode_message_at_leaving_insert_with_esc_mapped()
+ CheckFeature terminal
+ CheckNotGui
+
+ " Set custom statusline built by user-defined function.
+ let testfile = 'Xtest.vim'
+ let lines =<< trim END
+ set laststatus=2
+ inoremap <Esc> <Esc>00
+ END
+ call writefile(lines, testfile, 'D')
+
+ let rows = 10
+ let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
+ call WaitForAssert({-> assert_match('0,0-1\s*All$', term_getline(buf, rows - 1))})
+ call assert_equal('run', job_status(term_getjob(buf)))
+
+ call term_sendkeys(buf, "i")
+ call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, rows))})
+ call term_sendkeys(buf, "\<Esc>")
+ call WaitForAssert({-> assert_match('^\s*$', term_getline(buf, rows))})
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))})
+
+ exe buf . 'bwipe!'
+endfunc
+
+func Test_echospace()
+ set noruler noshowcmd laststatus=1
+ call assert_equal(&columns - 1, v:echospace)
+ split
+ call assert_equal(&columns - 1, v:echospace)
+ set ruler
+ call assert_equal(&columns - 1, v:echospace)
+ close
+ call assert_equal(&columns - 19, v:echospace)
+ set showcmd noruler
+ call assert_equal(&columns - 12, v:echospace)
+ set showcmd ruler
+ call assert_equal(&columns - 29, v:echospace)
+
+ set ruler& showcmd&
+endfunc
+
+func Test_warning_scroll()
+ CheckRunVimInTerminal
+ let lines =<< trim END
+ call test_override('ui_delay', 50)
+ set noruler
+ set readonly
+ undo
+ END
+ call writefile(lines, 'XTestWarningScroll', 'D')
+ let buf = RunVimInTerminal('', #{rows: 8})
+
+ " When the warning comes from a script, messages are scrolled so that the
+ " stacktrace is visible.
+ call term_sendkeys(buf, ":source XTestWarningScroll\n")
+ " only match the final colon in the line that shows the source
+ call WaitForAssert({-> assert_match(':$', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('line 4:W10: Warning: Changing a readonly file', term_getline(buf, 6))})
+ call WaitForAssert({-> assert_equal('Already at oldest change', term_getline(buf, 7))})
+ call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 8))})
+ call term_sendkeys(buf, "\n")
+
+ " When the warning does not come from a script, messages are not scrolled.
+ call term_sendkeys(buf, ":enew\n")
+ call term_sendkeys(buf, ":set readonly\n")
+ call term_sendkeys(buf, 'u')
+ call WaitForAssert({-> assert_equal('W10: Warning: Changing a readonly file', term_getline(buf, 8))})
+ call WaitForAssert({-> assert_equal('Already at oldest change', term_getline(buf, 8))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test more-prompt (see :help more-prompt).
+func Test_message_more()
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('', {'rows': 6})
+ call term_sendkeys(buf, ":call setline(1, range(1, 100))\n")
+
+ call term_sendkeys(buf, ":%pfoo\<C-H>\<C-H>\<C-H>#")
+ call WaitForAssert({-> assert_equal(':%p#', term_getline(buf, 6))})
+ call term_sendkeys(buf, "\n")
+ call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
+
+ call term_sendkeys(buf, '?')
+ call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit ', term_getline(buf, 6))})
+
+ " Down a line with j, <CR>, <NL> or <Down>.
+ call term_sendkeys(buf, "j")
+ call WaitForAssert({-> assert_equal(' 6 6', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
+ call term_sendkeys(buf, "\<NL>")
+ call WaitForAssert({-> assert_equal(' 7 7', term_getline(buf, 5))})
+ call term_sendkeys(buf, "\<CR>")
+ call WaitForAssert({-> assert_equal(' 8 8', term_getline(buf, 5))})
+ call term_sendkeys(buf, "\<Down>")
+ call WaitForAssert({-> assert_equal(' 9 9', term_getline(buf, 5))})
+
+ " Down a screen with <Space>, f, or <PageDown>.
+ call term_sendkeys(buf, 'f')
+ call WaitForAssert({-> assert_equal(' 14 14', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
+ call term_sendkeys(buf, ' ')
+ call WaitForAssert({-> assert_equal(' 19 19', term_getline(buf, 5))})
+ call term_sendkeys(buf, "\<PageDown>")
+ call WaitForAssert({-> assert_equal(' 24 24', term_getline(buf, 5))})
+
+ " Down a page (half a screen) with d.
+ call term_sendkeys(buf, 'd')
+ call WaitForAssert({-> assert_equal(' 27 27', term_getline(buf, 5))})
+
+ " Down all the way with 'G'.
+ call term_sendkeys(buf, 'G')
+ call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
+
+ " Up a line k, <BS> or <Up>.
+ call term_sendkeys(buf, 'k')
+ call WaitForAssert({-> assert_equal(' 99 99', term_getline(buf, 5))})
+ call term_sendkeys(buf, "\<BS>")
+ call WaitForAssert({-> assert_equal(' 98 98', term_getline(buf, 5))})
+ call term_sendkeys(buf, "\<Up>")
+ call WaitForAssert({-> assert_equal(' 97 97', term_getline(buf, 5))})
+
+ " Up a screen with b or <PageUp>.
+ call term_sendkeys(buf, 'b')
+ call WaitForAssert({-> assert_equal(' 92 92', term_getline(buf, 5))})
+ call term_sendkeys(buf, "\<PageUp>")
+ call WaitForAssert({-> assert_equal(' 87 87', term_getline(buf, 5))})
+
+ " Up a page (half a screen) with u.
+ call term_sendkeys(buf, 'u')
+ call WaitForAssert({-> assert_equal(' 84 84', term_getline(buf, 5))})
+
+ " Up all the way with 'g'.
+ call term_sendkeys(buf, 'g')
+ call WaitForAssert({-> assert_equal(' 4 4', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal(':%p#', term_getline(buf, 1))})
+ call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
+
+ " All the way down. Pressing f should do nothing but pressing
+ " space should end the more prompt.
+ call term_sendkeys(buf, 'G')
+ call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
+ call term_sendkeys(buf, 'f')
+ call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
+ call term_sendkeys(buf, ' ')
+ call WaitForAssert({-> assert_equal('100', term_getline(buf, 5))})
+
+ " Pressing g< shows the previous command output.
+ call term_sendkeys(buf, 'g<')
+ call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
+
+ " A command line that doesn't print text is appended to scrollback,
+ " even if it invokes a nested command line.
+ call term_sendkeys(buf, ":\<C-R>=':'\<CR>:\<CR>g<")
+ call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 4))})
+ call WaitForAssert({-> assert_equal(':::', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))})
+
+ call term_sendkeys(buf, ":%p#\n")
+ call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))})
+ call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
+
+ " Stop command output with q, <Esc> or CTRL-C.
+ call term_sendkeys(buf, 'q')
+ call WaitForAssert({-> assert_equal('100', term_getline(buf, 5))})
+
+ " Execute a : command from the more prompt
+ call term_sendkeys(buf, ":%p#\n")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))})
+ call term_sendkeys(buf, ":")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_equal(':', term_getline(buf, 6))})
+ call term_sendkeys(buf, "echo 'Hello'\n")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_equal('Hello ', term_getline(buf, 5))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test more-prompt scrollback
+func Test_message_more_scrollback()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set t_ut=
+ hi Normal ctermfg=15 ctermbg=0
+ for i in range(100)
+ echo i
+ endfor
+ END
+ call writefile(lines, 'XmoreScrollback', 'D')
+ let buf = RunVimInTerminal('-S XmoreScrollback', {'rows': 10})
+ call VerifyScreenDump(buf, 'Test_more_scrollback_1', {})
+
+ call term_sendkeys(buf, 'f')
+ call TermWait(buf)
+ call term_sendkeys(buf, 'b')
+ call VerifyScreenDump(buf, 'Test_more_scrollback_2', {})
+
+ call term_sendkeys(buf, 'q')
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test verbose message before echo command
+func Test_echo_verbose_system()
+ CheckRunVimInTerminal
+ CheckUnix " needs the "seq" command
+ CheckNotMac " doesn't use /tmp
+
+ let buf = RunVimInTerminal('', {'rows': 10})
+ call term_sendkeys(buf, ":4 verbose echo system('seq 20')\<CR>")
+ " Note that the screendump is filtered to remove the name of the temp file
+ call VerifyScreenDump(buf, 'Test_verbose_system_1', {})
+
+ " display a page and go back, results in exactly the same view
+ call term_sendkeys(buf, ' ')
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, 'b')
+ call VerifyScreenDump(buf, 'Test_verbose_system_1', {})
+
+ " do the same with 'cmdheight' set to 2
+ call term_sendkeys(buf, 'q')
+ call TermWait(buf)
+ call term_sendkeys(buf, ":set ch=2\<CR>")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":4 verbose echo system('seq 20')\<CR>")
+ call VerifyScreenDump(buf, 'Test_verbose_system_2', {})
+
+ call term_sendkeys(buf, ' ')
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, 'b')
+ call VerifyScreenDump(buf, 'Test_verbose_system_2', {})
+
+ call term_sendkeys(buf, 'q')
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+endfunc
+
+
+func Test_ask_yesno()
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('', {'rows': 6})
+ call term_sendkeys(buf, ":call setline(1, range(1, 2))\n")
+
+ call term_sendkeys(buf, ":2,1s/^/n/\n")
+ call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))})
+ call term_sendkeys(buf, "n")
+ call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?n *1,1 *All$', term_getline(buf, 6))})
+ call WaitForAssert({-> assert_equal('1', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, ":2,1s/^/Esc/\n")
+ call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))})
+ call term_sendkeys(buf, "\<Esc>")
+ call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?n *1,1 *All$', term_getline(buf, 6))})
+ call WaitForAssert({-> assert_equal('1', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, ":2,1s/^/y/\n")
+ call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))})
+ call term_sendkeys(buf, "y")
+ call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?y *2,1 *All$', term_getline(buf, 6))})
+ call WaitForAssert({-> assert_equal('y1', term_getline(buf, 1))})
+ call WaitForAssert({-> assert_equal('y2', term_getline(buf, 2))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_null()
+ echom test_null_list()
+ echom test_null_dict()
+ echom test_null_blob()
+ echom test_null_string()
+ echom test_null_function()
+ echom test_null_partial()
+ if has('job')
+ echom test_null_job()
+ echom test_null_channel()
+ endif
+endfunc
+
+func Test_mapping_at_hit_return_prompt()
+ nnoremap <C-B> :echo "hit ctrl-b"<CR>
+ call feedkeys(":ls\<CR>", "xt")
+ call feedkeys("\<*C-B>", "xt")
+ call assert_match('hit ctrl-b', Screenline(&lines - 1))
+ nunmap <C-B>
+endfunc
+
+func Test_quit_long_message()
+ CheckScreendump
+
+ let content =<< trim END
+ echom range(9999)->join("\x01")
+ END
+ call writefile(content, 'Xtest_quit_message', 'D')
+ let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 10, wait_for_ruler: 0})
+ call WaitForAssert({-> assert_match('^-- More --', term_getline(buf, 10))})
+ call term_sendkeys(buf, "q")
+ call VerifyScreenDump(buf, 'Test_quit_long_message', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" this was missing a terminating NUL
+func Test_echo_string_partial()
+ function CountSpaces()
+ endfunction
+ call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}])))
+endfunc
+
+" Message output was previously overwritten by the fileinfo display, shown
+" when switching buffers. If a buffer is switched to, then a message if
+" echoed, we should show the message, rather than overwriting it with
+" fileinfo.
+func Test_fileinfo_after_echo()
+ CheckScreendump
+
+ let content =<< trim END
+ file a.txt
+
+ hide edit b.txt
+ call setline(1, "hi")
+ setlocal modified
+
+ hide buffer a.txt
+
+ autocmd CursorHold * buf b.txt | w | echo "'b' written"
+ END
+
+ call writefile(content, 'Xtest_fileinfo_after_echo', 'D')
+ let buf = RunVimInTerminal('-S Xtest_fileinfo_after_echo', #{rows: 6})
+ call term_sendkeys(buf, ":set updatetime=50\<CR>")
+ call term_sendkeys(buf, "0$")
+ call VerifyScreenDump(buf, 'Test_fileinfo_after_echo', {})
+
+ call term_sendkeys(buf, ":q\<CR>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('b.txt')
+endfunc
+
+func Test_echowindow()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, 'some text')
+ func ShowMessage(arg)
+ echowindow a:arg
+ endfunc
+ echowindow 'first line'
+ func ManyMessages()
+ for n in range(20)
+ echowindow 'line' n
+ endfor
+ endfunc
+
+ def TwoMessages()
+ popup_clear()
+ set cmdheight=2
+ redraw
+ timer_start(100, (_) => {
+ echowin 'message'
+ })
+ echo 'one'
+ echo 'two'
+ enddef
+
+ def ThreeMessages()
+ popup_clear()
+ redraw
+ timer_start(100, (_) => {
+ echowin 'later message'
+ })
+ echo 'one'
+ echo 'two'
+ echo 'three'
+ enddef
+
+ def HideWin()
+ popup_hide(popup_findecho())
+ enddef
+ END
+ call writefile(lines, 'XtestEchowindow', 'D')
+ let buf = RunVimInTerminal('-S XtestEchowindow', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_echowindow_1', {})
+
+ call term_sendkeys(buf, ":call ShowMessage('second line')\<CR>")
+ call VerifyScreenDump(buf, 'Test_echowindow_2', {})
+
+ call term_sendkeys(buf, ":call popup_clear()\<CR>")
+ call VerifyScreenDump(buf, 'Test_echowindow_3', {})
+
+ call term_sendkeys(buf, ":call ManyMessages()\<CR>")
+ call VerifyScreenDump(buf, 'Test_echowindow_4', {})
+
+ call term_sendkeys(buf, ":call TwoMessages()\<CR>")
+ call VerifyScreenDump(buf, 'Test_echowindow_5', {})
+
+ call term_sendkeys(buf, ":call ThreeMessages()\<CR>")
+ sleep 120m
+ call VerifyScreenDump(buf, 'Test_echowindow_6', {})
+
+ call term_sendkeys(buf, "\<CR>")
+ call VerifyScreenDump(buf, 'Test_echowindow_7', {})
+
+ call term_sendkeys(buf, ":tabnew\<CR>")
+ call term_sendkeys(buf, ":7echowin 'more'\<CR>")
+ call VerifyScreenDump(buf, 'Test_echowindow_8', {})
+
+ call term_sendkeys(buf, ":call HideWin()\<CR>")
+ call VerifyScreenDump(buf, 'Test_echowindow_9', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" messages window should not be used while evaluating the :echowin argument
+func Test_echowin_eval()
+ CheckScreendump
+
+ let lines =<< trim END
+ func ShowMessage()
+ echo 123
+ return 'test'
+ endfunc
+ echowindow ShowMessage()
+ END
+ call writefile(lines, 'XtestEchowindow', 'D')
+ let buf = RunVimInTerminal('-S XtestEchowindow', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_echowin_eval', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" messages window should not be used for showing the mode
+func Test_echowin_showmode()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['one', 'two'])
+ timer_start(100, (_) => {
+ echowin 'echo window'
+ })
+ normal V
+ END
+ call writefile(lines, 'XtestEchowinMode', 'D')
+ let buf = RunVimInTerminal('-S XtestEchowinMode', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_echowin_showmode', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_method.vim b/src/testdir/test_method.vim
new file mode 100644
index 0000000..2ca66fd
--- /dev/null
+++ b/src/testdir/test_method.vim
@@ -0,0 +1,175 @@
+" Tests for ->method()
+
+source check.vim
+
+func Test_list_method()
+ let l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4))
+ eval l->assert_equal(l)
+ eval l->assert_equal(l, 'wrong')
+ eval l->assert_notequal([3, 2, 1])
+ eval l->assert_notequal([3, 2, 1], 'wrong')
+ call assert_equal(l, l->copy())
+ call assert_equal(l, l->deepcopy())
+ call assert_equal(1, l->count(2))
+ call assert_false(l->empty())
+ call assert_true([]->empty())
+ call assert_equal(579, ['123', '+', '456']->join()->eval())
+ call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5]))
+ call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2'))
+ call assert_equal(2, l->get(1))
+ call assert_equal(1, l->index(2))
+ call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0))
+ call assert_equal('1 2 3', l->join())
+ call assert_fails('eval l->keys()', 'E1206:')
+ call assert_equal(3, l->len())
+ call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1'))
+ call assert_equal(3, l->max())
+ call assert_equal(1, l->min())
+ call assert_equal(2, [1, 2, 3]->remove(1))
+ call assert_equal([1, 2, 3, 1, 2, 3], l->repeat(2))
+ call assert_equal([3, 2, 1], [1, 2, 3]->reverse())
+ call assert_equal([1, 2, 3, 4], [4, 2, 3, 1]->sort())
+ call assert_equal('[1, 2, 3]', l->string())
+ call assert_equal(v:t_list, l->type())
+ call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq())
+ call assert_fails('eval l->values()', 'E1206:')
+ call assert_fails('echo []->len', 'E107:')
+endfunc
+
+func Test_dict_method()
+ let d = #{one: 1, two: 2, three: 3}
+
+ call assert_equal(d, d->copy())
+ call assert_equal(d, d->deepcopy())
+ call assert_equal(1, d->count(2))
+ call assert_false(d->empty())
+ call assert_true({}->empty())
+ call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4}))
+ call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4'))
+ call assert_equal(2, d->get('two'))
+ call assert_fails("let x = d->index(2)", 'E897:')
+ call assert_fails("let x = d->insert(0)", 'E899:')
+ call assert_true(d->has_key('two'))
+ call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items())
+ call assert_fails("let x = d->join()", 'E1211:')
+ call assert_equal(['one', 'two', 'three'], d->keys())
+ call assert_equal(3, d->len())
+ call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1'))
+ call assert_equal(#{one: 1, two: 2, three: 3}, d->map('v:val - 1'))
+ call assert_equal(3, d->max())
+ call assert_equal(1, d->min())
+ call assert_equal(2, d->remove("two"))
+ let d.two = 2
+ call assert_fails('let x = d->repeat(2)', 'E731:')
+ call assert_fails('let x = d->reverse()', 'E899:')
+ call assert_fails('let x = d->sort()', 'E686:')
+ call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
+ call assert_equal(v:t_dict, d->type())
+ call assert_fails('let x = d->uniq()', 'E686:')
+ call assert_equal([1, 2, 3], d->values())
+endfunc
+
+func Test_string_method()
+ eval '1 2 3'->split()->assert_equal(['1', '2', '3'])
+ eval '1 2 3'->split()->map({i, v -> str2nr(v)})->assert_equal([1, 2, 3])
+ eval 'ABC'->str2list()->assert_equal([65, 66, 67])
+ eval 'ABC'->strlen()->assert_equal(3)
+ eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c')
+ eval "aã‚b"->strwidth()->assert_equal(4)
+ eval 'abc'->substitute('b', 'x', '')->assert_equal('axc')
+ call assert_fails('eval 123->items()', 'E1225:')
+
+ eval 'abc'->printf('the %s arg')->assert_equal('the abc arg')
+endfunc
+
+func Test_method_append()
+ new
+ eval ['one', 'two', 'three']->append(1)
+ call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
+
+ %del
+ let bnr = bufnr('')
+ wincmd w
+ eval ['one', 'two', 'three']->appendbufline(bnr, 1)
+ call assert_equal(['', 'one', 'two', 'three'], getbufline(bnr, 1, '$'))
+
+ exe 'bwipe! ' .. bnr
+endfunc
+
+func Test_method_funcref()
+ func Concat(one, two, three)
+ return a:one .. a:two .. a:three
+ endfunc
+ let FuncRef = function('Concat')
+ eval 'foo'->FuncRef('bar', 'tail')->assert_equal('foobartail')
+
+ " not enough arguments
+ call assert_fails("eval 'foo'->FuncRef('bar')", 'E119:')
+ " too many arguments
+ call assert_fails("eval 'foo'->FuncRef('bar', 'tail', 'four')", 'E118:')
+
+ let Partial = function('Concat', ['two'])
+ eval 'one'->Partial('three')->assert_equal('onetwothree')
+
+ " not enough arguments
+ call assert_fails("eval 'one'->Partial()", 'E119:')
+ " too many arguments
+ call assert_fails("eval 'one'->Partial('three', 'four')", 'E118:')
+
+ delfunc Concat
+endfunc
+
+func Test_method_float()
+ eval 1.234->string()->assert_equal('1.234')
+ eval -1.234->string()->assert_equal('-1.234')
+endfunc
+
+func Test_method_syntax()
+ eval [1, 2, 3] ->sort( )
+ eval [1, 2, 3]
+ \ ->sort(
+ \ )
+ eval [1, 2, 3]->sort()
+
+ call assert_fails('eval [1, 2, 3]->sort ()', 'E274:')
+ call assert_fails('eval [1, 2, 3] ->sort ()', 'E274:')
+ call assert_fails('eval [1, 2, 3]-> sort ()', 'E274:')
+ call assert_fails('eval [1, 2, 3]-> sort()', 'E274:')
+endfunc
+
+func Test_method_lambda()
+ eval "text"->{x -> x .. " extended"}()->assert_equal('text extended')
+ eval "text"->{x, y -> x .. " extended " .. y}('more')->assert_equal('text extended more')
+
+ call assert_fails('eval "text"->{x -> x .. " extended"} ()', 'E274:')
+
+ " todo: lambda accepts more arguments than it consumes
+ " call assert_fails('eval "text"->{x -> x .. " extended"}("more")', 'E99:')
+
+ let l = [1, 2, 3]
+ eval l->{x -> x}()
+ call assert_equal(1, test_refcount(l))
+endfunc
+
+func Test_method_not_supported()
+ call assert_fails('eval 123->changenr()', 'E276:')
+ call assert_fails('echo "abc"->invalidfunc()', 'E117:')
+ " Test for too many or too few arguments to a method
+ call assert_fails('let n="abc"->len(2)', 'E118:')
+ call assert_fails('let n=10->setwinvar()', 'E119:')
+endfunc
+
+" Test for passing optional arguments to methods
+func Test_method_args()
+ let v:errors = []
+ let n = 10->assert_inrange(1, 5, "Test_assert_inrange")
+ if v:errors[0] !~ 'Test_assert_inrange'
+ call assert_report(v:errors[0])
+ else
+ " Test passed
+ let v:errors = []
+ endif
+endfunc
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_mksession.vim b/src/testdir/test_mksession.vim
new file mode 100644
index 0000000..1ce5a28
--- /dev/null
+++ b/src/testdir/test_mksession.vim
@@ -0,0 +1,1265 @@
+" Test for :mksession, :mkview and :loadview in latin1 encoding
+
+set encoding=latin1
+scriptencoding latin1
+
+source check.vim
+CheckFeature mksession
+
+source shared.vim
+source term_util.vim
+
+" Test for storing global and local argument list in a session file
+" This one must be done first.
+func Test__mksession_arglocal()
+ enew | only
+ n a b c
+ new
+ arglocal
+ mksession! Xtest_mks.out
+
+ %bwipe!
+ %argdelete
+ argglobal
+ source Xtest_mks.out
+ call assert_equal(2, winnr('$'))
+ call assert_equal(2, arglistid(1))
+ call assert_equal(0, arglistid(2))
+
+ %bwipe!
+ %argdelete
+ argglobal
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession()
+ tabnew
+ let wrap_save = &wrap
+ set sessionoptions=buffers splitbelow fileencoding=latin1
+ call setline(1, [
+ \ 'start:',
+ \ 'no multibyte chAracter',
+ \ ' one leaDing tab',
+ \ ' four leadinG spaces',
+ \ 'two consecutive tabs',
+ \ 'two tabs in one line',
+ \ 'one ä multibyteCharacter',
+ \ 'aä Ä two multiByte characters',
+ \ 'Aäöü three mulTibyte characters',
+ \ 'short line',
+ \ ])
+ let tmpfile = 'Xtemp'
+ exec 'w! ' . tmpfile
+ /^start:
+ set wrap
+ vsplit
+ norm! j16|
+ split
+ norm! j16|
+ split
+ norm! j16|
+ split
+ norm! j8|
+ split
+ norm! j8|
+ split
+ norm! j16|
+ split
+ norm! j16|
+ split
+ norm! j16|
+ split
+ norm! j$
+ wincmd l
+
+ set nowrap
+ /^start:
+ norm! j16|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j08|3zl
+ split
+ norm! j08|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j016|3zl
+ split
+ call wincol()
+ mksession! Xtest_mks.out
+ let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "\\(^ *normal! [0$]\\|^ *exe ''normal!\\)"')
+ let expected = [
+ \ 'normal! 016|',
+ \ 'normal! 016|',
+ \ 'normal! 016|',
+ \ 'normal! 08|',
+ \ 'normal! 08|',
+ \ 'normal! 016|',
+ \ 'normal! 016|',
+ \ 'normal! 016|',
+ \ 'normal! $',
+ \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
+ \ " normal! 016|",
+ \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
+ \ " normal! 016|",
+ \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
+ \ " normal! 016|",
+ \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'",
+ \ " normal! 08|",
+ \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'",
+ \ " normal! 08|",
+ \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
+ \ " normal! 016|",
+ \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
+ \ " normal! 016|",
+ \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
+ \ " normal! 016|",
+ \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
+ \ " normal! 016|"
+ \ ]
+ call assert_equal(expected, li)
+ tabclose!
+
+ call delete('Xtest_mks.out')
+ call delete(tmpfile)
+ let &wrap = wrap_save
+ set sessionoptions&
+endfunc
+
+def Test_mksession_skiprtp()
+ mksession! Xtest_mks.out
+ var found_rtp = 0
+ var found_pp = 0
+ for line in readfile('Xtest_mks.out')
+ if line =~ 'set runtimepath'
+ found_rtp += 1
+ endif
+ if line =~ 'set packpath'
+ found_pp += 1
+ endif
+ endfor
+ assert_equal(1, found_rtp)
+ assert_equal(1, found_pp)
+ delete('Xtest_mks.out')
+
+ set sessionoptions+=skiprtp
+ mksession! Xtest_mks.out
+ var found = 0
+ for line in readfile('Xtest_mks.out')
+ if line =~ 'set \(runtimepath\|packpath\)'
+ found = 1
+ break
+ endif
+ endfor
+ assert_equal(0, found)
+ delete('Xtest_mks.out')
+ set sessionoptions&
+enddef
+
+func Test_mksession_winheight()
+ new
+ set winheight=10
+ set winminheight=2
+ mksession! Xtest_mks.out
+ source Xtest_mks.out
+
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_large_winheight()
+ set winheight=999
+ mksession! Xtest_mks_winheight.out
+ set winheight&
+ source Xtest_mks_winheight.out
+ call delete('Xtest_mks_winheight.out')
+endfunc
+
+func Test_mksession_zero_winheight()
+ set winminheight=0
+ edit SomeFile
+ split
+ wincmd _
+ mksession! Xtest_mks_zero
+ set winminheight&
+ let text = readfile('Xtest_mks_zero')->join()
+ call delete('Xtest_mks_zero')
+ close
+ " check there is no divide by zero
+ call assert_notmatch('/ 0[^0-9]', text)
+endfunc
+
+func Test_mksession_rtp()
+ " TODO: fix problem with backslashes on Win32
+ CheckNotMSWindows
+
+ new
+ let _rtp=&rtp
+ " Make a real long (invalid) runtimepath value,
+ " that should exceed PATH_MAX (hopefully)
+ let newrtp=&rtp.',~'.repeat('/foobar', 1000)
+ let newrtp.=",".expand("$HOME")."/.vim"
+ let &rtp=newrtp
+
+ " determine expected value
+ let expected=split(&rtp, ',')
+ let expected = map(expected, '"set runtimepath+=".v:val')
+ let expected = ['set runtimepath='] + expected
+ let expected = map(expected, {v,w -> substitute(w, $HOME, "~", "g")})
+
+ mksession! Xtest_mks.out
+ let &rtp=_rtp
+ let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "runtimepath"')
+ call assert_equal(expected, li)
+
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_arglist()
+ %argdel
+ next file1 file2 file3 file4
+ new
+ next | next
+ mksession! Xtest_mks.out
+ source Xtest_mks.out
+ call assert_equal(['file1', 'file2', 'file3', 'file4'], argv())
+ call assert_equal(2, argidx())
+ wincmd w
+ call assert_equal(0, argidx())
+
+ call delete('Xtest_mks.out')
+ enew | only
+ argdel *
+endfunc
+
+func Test_mksession_one_buffer_two_windows()
+ edit Xtest1
+ new Xtest2
+ split
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let count1 = 0
+ let count2 = 0
+ let count2buf = 0
+ let bufexists = 0
+ for line in lines
+ if line =~ 'edit \f*Xtest1$'
+ let count1 += 1
+ endif
+ if line =~ 'edit \f\{-}Xtest2'
+ let count2 += 1
+ endif
+ if line =~ 'buffer \f\{-}Xtest2'
+ let count2buf += 1
+ endif
+ if line =~ 'bufexists(fnamemodify(.*, ":p")'
+ let bufexists += 1
+ endif
+ endfor
+ call assert_equal(1, count1, 'Xtest1 count')
+ call assert_equal(2, count2, 'Xtest2 count')
+ call assert_equal(2, count2buf, 'Xtest2 buffer count')
+ call assert_equal(2, bufexists)
+
+ close
+ bwipe!
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_lcd_multiple_tabs()
+ tabnew
+ tabnew
+ lcd .
+ tabfirst
+ lcd .
+ mksession! Xtest_mks.out
+ tabonly
+ source Xtest_mks.out
+ call assert_true(haslocaldir(), 'Tab 1 localdir')
+ tabnext 2
+ call assert_true(!haslocaldir(), 'Tab 2 localdir')
+ tabnext 3
+ call assert_true(haslocaldir(), 'Tab 3 localdir')
+ call delete('Xtest_mks.out')
+endfunc
+
+" Test for tabpage-local directory
+func Test_mksession_tcd_multiple_tabs()
+ let save_cwd = getcwd()
+ call mkdir('Xtopdir')
+ cd Xtopdir
+ call mkdir('Xtabdir1')
+ call mkdir('Xtabdir2')
+ call mkdir('Xtabdir3')
+ call mkdir('Xwindir1')
+ call mkdir('Xwindir2')
+ call mkdir('Xwindir3')
+ tcd Xtabdir1
+ botright new
+ wincmd t
+ lcd ../Xwindir1
+ tabnew
+ tcd ../Xtabdir2
+ botright new
+ lcd ../Xwindir2
+ tabnew
+ tcd ../Xtabdir3
+ botright new
+ lcd ../Xwindir3
+ tabfirst
+ 1wincmd w
+ mksession! Xtest_mks.out
+ only | tabonly
+ source Xtest_mks.out
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(-1, 1), ':t'))
+ call assert_equal('Xwindir1', fnamemodify(getcwd(1, 1), ':t'))
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(2, 1), ':t'))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(-1, 2), ':t'))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('Xwindir2', fnamemodify(getcwd(2, 2), ':t'))
+ call assert_equal('Xtabdir3', fnamemodify(getcwd(-1, 3), ':t'))
+ call assert_equal('Xtabdir3', fnamemodify(getcwd(1, 3), ':t'))
+ call assert_equal('Xwindir3', fnamemodify(getcwd(2, 3), ':t'))
+ %bwipe
+ call chdir(save_cwd)
+ call delete("Xtopdir", "rf")
+endfunc
+
+func Test_mksession_blank_tabs()
+ tabnew
+ tabnew
+ tabnew
+ tabnext 3
+ mksession! Xtest_mks.out
+ tabnew
+ tabnew
+ tabnext 2
+ source Xtest_mks.out
+ call assert_equal(4, tabpagenr('$'), 'session restore should restore number of tabs')
+ call assert_equal(3, tabpagenr(), 'session restore should restore the active tab')
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_buffer_count()
+ set hidden
+
+ " Edit exactly three files in the current session.
+ %bwipe!
+ e Xfoo | tabe Xbar | tabe Xbaz
+ tabdo write
+ mksession! Xtest_mks.out
+
+ " Verify that loading the session does not create additional buffers.
+ %bwipe!
+ source Xtest_mks.out
+ call assert_equal(3, len(getbufinfo()))
+
+ " Clean up.
+ call delete('Xfoo')
+ call delete('Xbar')
+ call delete('Xbaz')
+ call delete('Xtest_mks.out')
+ %bwipe!
+ set hidden&
+endfunc
+
+func Test_mksession_buffer_order()
+ %bwipe!
+ e Xfoo | e Xbar | e Xbaz | e Xqux
+ bufdo write
+ mksession! Xtest_mks.out
+
+ " Verify that loading the session preserves order of buffers
+ %bwipe!
+ source Xtest_mks.out
+
+ let s:buf_info = getbufinfo()
+ call assert_true(s:buf_info[0]['name'] =~# 'Xfoo$')
+ call assert_true(s:buf_info[1]['name'] =~# 'Xbar$')
+ call assert_true(s:buf_info[2]['name'] =~# 'Xbaz$')
+ call assert_true(s:buf_info[3]['name'] =~# 'Xqux$')
+
+ " Clean up.
+ call delete('Xfoo')
+ call delete('Xbar')
+ call delete('Xbaz')
+ call delete('Xqux')
+ call delete('Xtest_mks.out')
+ %bwipe!
+endfunc
+
+if has('extra_search')
+
+func Test_mksession_hlsearch()
+ set hlsearch
+ mksession! Xtest_mks.out
+ nohlsearch
+ source Xtest_mks.out
+ call assert_equal(1, v:hlsearch, 'session should restore search highlighting state')
+ nohlsearch
+ mksession! Xtest_mks.out
+ source Xtest_mks.out
+ call assert_equal(0, v:hlsearch, 'session should restore search highlighting state')
+ call delete('Xtest_mks.out')
+endfunc
+
+endif
+
+
+func Test_mksession_blank_windows()
+ split
+ split
+ split
+ 3 wincmd w
+ mksession! Xtest_mks.out
+ split
+ split
+ 2 wincmd w
+ source Xtest_mks.out
+ call assert_equal(4, winnr('$'), 'session restore should restore number of windows')
+ call assert_equal(3, winnr(), 'session restore should restore the active window')
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_shell()
+ CheckFeature terminal
+ CheckFeature quickfix
+
+ terminal
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ let term_cmd = line
+ elseif line =~ 'badd.*' . &shell
+ call assert_report('unexpected shell line: ' . line)
+ endif
+ endfor
+ call assert_match('terminal ++curwin ++cols=\d\+ ++rows=\d\+\s*.*$', term_cmd)
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_restore_cmdarg()
+ CheckFeature terminal
+
+ terminal ++norestore
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_restore_funcarg()
+ CheckFeature terminal
+
+ let buf = Run_shell_in_terminal({'norestore': 1})
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(buf)
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_restore_func()
+ CheckFeature terminal
+
+ terminal
+ call term_setrestore(bufnr('%'), 'NONE')
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_ssop()
+ CheckFeature terminal
+
+ terminal
+ set sessionoptions-=terminal
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
+func Test_mksession_terminal_restore_other()
+ CheckFeature terminal
+ CheckFeature quickfix
+
+ terminal
+ eval bufnr('%')->term_setrestore('other')
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ let term_cmd = line
+ endif
+ endfor
+ call assert_match('terminal ++curwin ++cols=\d\+ ++rows=\d\+.*other', term_cmd)
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_shared_windows()
+ CheckFeature terminal
+
+ terminal
+ let term_buf = bufnr()
+ new
+ execute "buffer" term_buf
+ mksession! Xtest_mks.out
+
+ let lines = readfile('Xtest_mks.out')
+ let found_creation = 0
+ let found_use = 0
+
+ for line in lines
+ if line =~ '^terminal'
+ let found_creation = 1
+ call assert_match('terminal ++curwin ++cols=\d\+ ++rows=\d\+', line)
+ elseif line =~ "^execute 'buffer ' . s:term_buf_" . term_buf . "$"
+ let found_use = 1
+ endif
+ endfor
+
+ call assert_true(found_creation && found_use)
+
+ call StopShellInTerminal(term_buf)
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mkview_terminal_windows()
+ CheckFeature terminal
+
+ " create two window on the same terminal to check this is handled OK
+ terminal
+ let term_buf = bufnr()
+ exe 'sbuf ' .. term_buf
+ mkview! Xtestview
+
+ call StopShellInTerminal(term_buf)
+ call delete('Xtestview')
+endfunc
+
+func Test_mkview_open_folds()
+ enew!
+
+ call append(0, ['a', 'b', 'c'])
+ 1,3fold
+ write! Xtestfile
+
+ call assert_notequal(-1, foldclosed(1))
+ call assert_notequal(-1, foldclosed(2))
+ call assert_notequal(-1, foldclosed(3))
+
+ " Save the view with folds closed
+ mkview! Xtestview
+
+ " zR affects 'foldlevel', make sure the option is applied after the folds
+ " have been recreated.
+ " Open folds to ensure they get closed when restoring the view
+ normal zR
+
+ call assert_equal(-1, foldclosed(1))
+ call assert_equal(-1, foldclosed(2))
+ call assert_equal(-1, foldclosed(3))
+
+ source Xtestview
+
+ call assert_notequal(-1, foldclosed(1))
+ call assert_notequal(-1, foldclosed(2))
+ call assert_notequal(-1, foldclosed(3))
+
+ call delete('Xtestview')
+ call delete('Xtestfile')
+ %bwipe
+endfunc
+
+func Test_mkview_no_balt()
+ edit Xtestfile1
+ edit Xtestfile2
+
+ mkview! Xtestview
+ bdelete Xtestfile1
+
+ source Xtestview
+ call assert_equal(0, buflisted('Xtestfile1'))
+
+ call delete('Xtestview')
+ %bwipe
+endfunc
+
+func Test_mksession_no_balt()
+ edit Xtestfile1
+ edit Xtestfile2
+
+ bdelete Xtestfile1
+ mksession! Xtestview
+
+ source Xtestview
+ call assert_equal(0, buflisted('Xtestfile1'))
+
+ call delete('Xtestview')
+ %bwipe
+endfunc
+
+" Test :mkview with a file argument.
+func Test_mkview_file()
+ " Create a view with line number and a fold.
+ help :mkview
+ set number
+ norm! V}zf0
+ let pos = getpos('.')
+ let linefoldclosed1 = foldclosed('.')
+ mkview! Xview
+ set nonumber
+ norm! zrj
+ " We can close the help window, as mkview with a file name should
+ " generate a command to edit the file.
+ helpclose
+
+ source Xview
+ call assert_equal(1, &number)
+ call assert_match('\*:mkview\*$', getline('.'))
+ call assert_equal(pos, getpos('.'))
+ call assert_equal(linefoldclosed1, foldclosed('.'))
+
+ " Creating a view again with the same file name should fail (file
+ " already exists). But with a !, the previous view should be
+ " overwritten without error.
+ help :loadview
+ call assert_fails('mkview Xview', 'E189:')
+ call assert_match('\*:loadview\*$', getline('.'))
+ mkview! Xview
+ call assert_match('\*:loadview\*$', getline('.'))
+
+ call delete('Xview')
+ bwipe
+endfunc
+
+" Test :mkview and :loadview with a custom 'viewdir'.
+func Test_mkview_loadview_with_viewdir()
+ set viewdir=Xviewdir
+
+ help :mkview
+ set number
+ norm! V}zf
+ let pos = getpos('.')
+ let linefoldclosed1 = foldclosed('.')
+ mkview 1
+ set nonumber
+ norm! zrj
+
+ loadview 1
+
+ " The directory Xviewdir/ should have been created and the view
+ " should be stored in that directory.
+ call assert_equal('Xviewdir/' .
+ \ substitute(
+ \ substitute(
+ \ expand('%:p'), '/', '=+', 'g'), ':', '=-', 'g') . '=1.vim',
+ \ glob('Xviewdir/*'))
+ call assert_equal(1, &number)
+ call assert_match('\*:mkview\*$', getline('.'))
+ call assert_equal(pos, getpos('.'))
+ call assert_equal(linefoldclosed1, foldclosed('.'))
+
+ call delete('Xviewdir', 'rf')
+ set viewdir&
+ helpclose
+endfunc
+
+func Test_mkview_no_file_name()
+ new
+ " :mkview or :mkview {nr} should fail in a unnamed buffer.
+ call assert_fails('mkview', 'E32:')
+ call assert_fails('mkview 1', 'E32:')
+
+ " :mkview {file} should succeed in a unnamed buffer.
+ mkview Xview
+ help
+ source Xview
+ call assert_equal('', bufname('%'))
+
+ call delete('Xview')
+ %bwipe
+endfunc
+
+func Test_mkview_loadview_jumplist()
+ set viewdir=Xviewdir
+ au BufWinLeave * silent mkview
+ au BufWinEnter * silent loadview
+
+ edit Xfile1
+ call setline(1, ['a', 'bbbbbbb', 'c'])
+ normal j3l
+ call assert_equal([2, 4], getcurpos()[1:2])
+ write
+
+ edit Xfile2
+ call setline(1, ['d', 'eeeeeee', 'f'])
+ normal j5l
+ call assert_equal([2, 6], getcurpos()[1:2])
+ write
+
+ edit Xfile3
+ call setline(1, ['g', 'h', 'iiiii'])
+ normal jj3l
+ call assert_equal([3, 4], getcurpos()[1:2])
+ write
+
+ edit Xfile1
+ call assert_equal([2, 4], getcurpos()[1:2])
+ edit Xfile2
+ call assert_equal([2, 6], getcurpos()[1:2])
+ edit Xfile3
+ call assert_equal([3, 4], getcurpos()[1:2])
+
+ exe "normal \<C-O>"
+ call assert_equal('Xfile2', expand('%'))
+ call assert_equal([2, 6], getcurpos()[1:2])
+ exe "normal \<C-O>"
+ call assert_equal('Xfile1', expand('%'))
+ call assert_equal([2, 4], getcurpos()[1:2])
+
+ au! BufWinLeave
+ au! BufWinEnter
+ bwipe!
+ call delete('Xviewdir', 'rf')
+ call delete('Xfile1')
+ call delete('Xfile2')
+ call delete('Xfile3')
+ set viewdir&
+endfunc
+
+" A clean session (one empty buffer, one window, and one tab) should not
+" set any error messages when sourced because no commands should fail.
+func Test_mksession_no_errmsg()
+ let v:errmsg = ''
+ %bwipe!
+ mksession! Xtest_mks.out
+ source Xtest_mks.out
+ call assert_equal('', v:errmsg)
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_quote_in_filename()
+ " only Unix can handle this weird filename
+ CheckUnix
+
+ let v:errmsg = ''
+ %bwipe!
+ split another
+ split x'y\"z
+ mksession! Xtest_mks_quoted.out
+ %bwipe!
+ source Xtest_mks_quoted.out
+ call assert_true(bufexists("x'y\"z"))
+
+ %bwipe!
+ call delete('Xtest_mks_quoted.out')
+endfunc
+
+" Test for storing global variables in a session file
+func Test_mksession_globals()
+ set sessionoptions+=globals
+
+ " create different global variables
+ let g:Global_string = "Sun is shining\r\n"
+ let g:Global_count = 100
+ let g:Global_pi = 3.14
+ let g:Global_neg_float = -2.68
+
+ mksession! Xtest_mks.out
+
+ unlet g:Global_string
+ unlet g:Global_count
+ unlet g:Global_pi
+ unlet g:Global_neg_float
+
+ source Xtest_mks.out
+ call assert_equal("Sun is shining\r\n", g:Global_string)
+ call assert_equal(100, g:Global_count)
+ call assert_equal(3.14, g:Global_pi)
+ call assert_equal(-2.68, g:Global_neg_float)
+
+ unlet g:Global_string
+ unlet g:Global_count
+ unlet g:Global_pi
+ unlet g:Global_neg_float
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
+" Test for changing backslash to forward slash in filenames
+func Test_mksession_slash()
+ if exists('+shellslash')
+ throw 'Skipped: cannot use backslash in file name'
+ endif
+ enew
+ %bwipe!
+ e a\\b\\c
+ mksession! Xtest_mks1.out
+ set sessionoptions+=slash
+ mksession! Xtest_mks2.out
+
+ %bwipe!
+ source Xtest_mks1.out
+ call assert_equal('a\b\c', bufname(''))
+ %bwipe!
+ source Xtest_mks2.out
+ call assert_equal('a/b/c', bufname(''))
+
+ %bwipe!
+ call delete('Xtest_mks1.out')
+ call delete('Xtest_mks2.out')
+ set sessionoptions&
+endfunc
+
+" Test for changing directory to the session file directory
+func Test_mksession_sesdir()
+ let save_cwd = getcwd()
+ call mkdir('Xproj')
+ mksession! Xproj/Xtest_mks1.out
+ set sessionoptions-=curdir
+ set sessionoptions+=sesdir
+ mksession! Xproj/Xtest_mks2.out
+
+ source Xproj/Xtest_mks1.out
+ call assert_equal('testdir', fnamemodify(getcwd(), ':t'))
+ source Xproj/Xtest_mks2.out
+ call assert_equal('Xproj', fnamemodify(getcwd(), ':t'))
+ call chdir(save_cwd)
+ %bwipe
+
+ set sessionoptions&
+ call delete('Xproj', 'rf')
+endfunc
+
+" Test for saving and restoring the tab-local working directory when there is
+" only a single tab and 'tabpages' is not in 'sessionoptions'.
+func Test_mksession_tcd_single_tabs()
+ only | tabonly
+
+ let save_cwd = getcwd()
+ set sessionoptions-=tabpages
+ set sessionoptions+=curdir
+ call mkdir('Xtopdir1')
+ call mkdir('Xtopdir2')
+
+ " There are two tab pages, the current one has local cwd set to 'Xtopdir2'.
+ exec 'tcd ' .. save_cwd .. '/Xtopdir1'
+ tabnew
+ exec 'tcd ' .. save_cwd .. '/Xtopdir2'
+ mksession! Xtest_tcd_single
+
+ source Xtest_tcd_single
+ call assert_equal(2, haslocaldir())
+ call assert_equal('Xtopdir2', fnamemodify(getcwd(-1, 0), ':t'))
+ %bwipe
+
+ set sessionoptions&
+ call chdir(save_cwd)
+ call delete('Xtopdir1', 'rf')
+ call delete('Xtopdir2', 'rf')
+endfunc
+
+" Test for storing the 'lines' and 'columns' settings
+func Test_mksession_resize()
+ mksession! Xtest_mks1.out
+ set sessionoptions+=resize
+ mksession! Xtest_mks2.out
+
+ let lines = readfile('Xtest_mks1.out')
+ let found_resize = v:false
+ for line in lines
+ if line =~ '^set lines='
+ let found_resize = v:true
+ break
+ endif
+ endfor
+ call assert_false(found_resize)
+ let lines = readfile('Xtest_mks2.out')
+ let found_resize = v:false
+ for line in lines
+ if line =~ '^set lines='
+ let found_resize = v:true
+ break
+ endif
+ endfor
+ call assert_true(found_resize)
+
+ call delete('Xtest_mks1.out')
+ call delete('Xtest_mks2.out')
+ set sessionoptions&
+endfunc
+
+" Test for mksession with a named scratch buffer
+func Test_mksession_scratch()
+ enew | only
+ file Xscratch
+ set buftype=nofile
+ mksession! Xtest_mks.out
+ %bwipe
+ source Xtest_mks.out
+ call assert_equal('Xscratch', bufname(''))
+ call assert_equal('nofile', &buftype)
+ %bwipe
+ call delete('Xtest_mks.out')
+endfunc
+
+" Test for mksession with fold options
+func Test_mksession_foldopt()
+ set sessionoptions-=options
+ set sessionoptions+=folds
+ new
+ setlocal foldenable
+ setlocal foldmethod=expr
+ setlocal foldmarker=<<<,>>>
+ setlocal foldignore=%
+ setlocal foldlevel=2
+ setlocal foldminlines=10
+ setlocal foldnestmax=15
+ mksession! Xtest_mks.out
+ close
+ %bwipe
+
+ source Xtest_mks.out
+ call assert_true(&foldenable)
+ call assert_equal('expr', &foldmethod)
+ call assert_equal('<<<,>>>', &foldmarker)
+ call assert_equal('%', &foldignore)
+ call assert_equal(2, &foldlevel)
+ call assert_equal(10, &foldminlines)
+ call assert_equal(15, &foldnestmax)
+
+ close
+ %bwipe
+ set sessionoptions&
+endfunc
+
+" Test for mksession with "help" but not "options" in 'sessionoptions'
+func Test_mksession_help_noopt()
+ set sessionoptions-=options
+ set sessionoptions+=help
+ help
+ let fname = expand('%')
+ mksession! Xtest_mks.out
+ bwipe
+
+ source Xtest_mks.out
+ call assert_equal('help', &buftype)
+ call assert_equal('help', &filetype)
+ call assert_equal(fname, expand('%'))
+ call assert_false(&modifiable)
+ call assert_true(&readonly)
+
+ helpclose
+ help index
+ let fname = expand('%')
+ mksession! Xtest_mks.out
+ bwipe
+
+ source Xtest_mks.out
+ call assert_equal('help', &buftype)
+ call assert_equal(fname, expand('%'))
+
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
+" Test for mksession with window position
+func Test_mksession_winpos()
+ " Only applicable in GUI Vim
+ CheckGui
+
+ set sessionoptions+=winpos
+ mksession! Xtest_mks.out
+ let found_winpos = v:false
+ let lines = readfile('Xtest_mks.out')
+ for line in lines
+ if line =~ '^winpos '
+ let found_winpos = v:true
+ break
+ endif
+ endfor
+ call assert_true(found_winpos)
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
+" Test for mksession without options restores winminheight
+func Test_mksession_winminheight()
+ set sessionoptions-=options
+ split
+ mksession! Xtest_mks.out
+ let found_restore = 0
+ let lines = readfile('Xtest_mks.out')
+ for line in lines
+ if line =~ '= s:save_winmin\(width\|height\)'
+ let found_restore += 1
+ endif
+ endfor
+ call assert_equal(2, found_restore)
+ call delete('Xtest_mks.out')
+ close
+ set sessionoptions&
+endfunc
+
+" Test for mksession with and without options restores shortmess
+func Test_mksession_shortmess()
+ " Without options
+ set sessionoptions-=options
+ split
+ mksession! Xtest_mks.out
+ let found_save = 0
+ let found_restore = 0
+ let lines = readfile('Xtest_mks.out')
+ for line in lines
+ let line = trim(line)
+
+ if line ==# 'let s:shortmess_save = &shortmess'
+ let found_save += 1
+ endif
+
+ if found_save !=# 0 && line ==# 'let &shortmess = s:shortmess_save'
+ let found_restore += 1
+ endif
+ endfor
+ call assert_equal(1, found_save)
+ call assert_equal(1, found_restore)
+ call delete('Xtest_mks.out')
+ close
+ set sessionoptions&
+
+ " With options
+ set sessionoptions+=options
+ split
+ mksession! Xtest_mks.out
+ let found_restore = 0
+ let lines = readfile('Xtest_mks.out')
+ for line in lines
+ if line =~# 's:shortmess_save'
+ let found_restore += 1
+ endif
+ endfor
+ call assert_equal(0, found_restore)
+ call delete('Xtest_mks.out')
+ close
+ set sessionoptions&
+endfunc
+
+" Test that when Vim loading session has 'A' in 'shortmess' it does not
+" complain about an existing swapfile.
+func Test_mksession_shortmess_with_A()
+ edit Xtestfile
+ write
+ let fname = swapname('%')
+ let cont = readblob(fname)
+ set sessionoptions-=options
+ mksession Xtestsession
+ bwipe!
+
+ " Recreate the swap file to pretend the file is being edited
+ call writefile(cont, fname, 'D')
+ set shortmess+=A
+ source Xtestsession
+
+ set shortmess&
+ set sessionoptions&
+ call delete('Xtestsession')
+endfunc
+
+" Test for mksession with 'compatible' option
+func Test_mksession_compatible()
+ mksession! Xtest_mks1.out
+ set compatible
+ mksession! Xtest_mks2.out
+ set nocp
+
+ let test_success = v:false
+ let lines = readfile('Xtest_mks1.out')
+ for line in lines
+ if line =~ '^if &cp | set nocp | endif'
+ let test_success = v:true
+ break
+ endif
+ endfor
+ call assert_true(test_success)
+
+ let test_success = v:false
+ let lines = readfile('Xtest_mks2.out')
+ for line in lines
+ if line =~ '^if !&cp | set cp | endif'
+ let test_success = v:true
+ break
+ endif
+ endfor
+ call assert_true(test_success)
+
+ call delete('Xtest_mks1.out')
+ call delete('Xtest_mks2.out')
+ set compatible&
+ set sessionoptions&
+endfunc
+
+func s:ClearMappings()
+ mapclear
+ omapclear
+ mapclear!
+ lmapclear
+ tmapclear
+endfunc
+
+func Test_mkvimrc()
+ let entries = [
+ \ ['', 'nothing', '<Nop>'],
+ \ ['n', 'normal', 'NORMAL'],
+ \ ['v', 'visual', 'VISUAL'],
+ \ ['s', 'select', 'SELECT'],
+ \ ['x', 'visualonly', 'VISUALONLY'],
+ \ ['o', 'operator', 'OPERATOR'],
+ \ ['i', 'insert', 'INSERT'],
+ \ ['l', 'lang', 'LANG'],
+ \ ['c', 'command', 'COMMAND'],
+ \ ['t', 'terminal', 'TERMINAL'],
+ \ ]
+ for entry in entries
+ exe entry[0] .. 'map ' .. entry[1] .. ' ' .. entry[2]
+ endfor
+
+ mkvimrc Xtestvimrc
+
+ call s:ClearMappings()
+ for entry in entries
+ call assert_equal('', maparg(entry[1], entry[0]))
+ endfor
+
+ source Xtestvimrc
+
+ for entry in entries
+ call assert_equal(entry[2], maparg(entry[1], entry[0]))
+ endfor
+
+ call s:ClearMappings()
+
+ " the 'pastetoggle', 'wildchar' and 'wildcharm' option values should be
+ " stored as key names in the vimrc file
+ set pastetoggle=<F5>
+ set wildchar=<F6>
+ set wildcharm=<F7>
+ call assert_fails('mkvimrc Xtestvimrc')
+ mkvimrc! Xtestvimrc
+ call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set pastetoggle=<F5>'))
+ call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildchar=<F6>'))
+ call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildcharm=<F7>'))
+ set pastetoggle& wildchar& wildcharm&
+
+ call delete('Xtestvimrc')
+endfunc
+
+func Test_scrolloff()
+ set sessionoptions+=localoptions
+ setlocal so=1 siso=1
+ mksession! Xtest_mks.out
+ setlocal so=-1 siso=-1
+ source Xtest_mks.out
+ call assert_equal(1, &l:so)
+ call assert_equal(1, &l:siso)
+ call delete('Xtest_mks.out')
+ setlocal so& siso&
+ set sessionoptions&
+endfunc
+
+func Test_altfile()
+ edit Xone
+ split Xtwo
+ edit Xtwoalt
+ edit #
+ wincmd w
+ edit Xonealt
+ edit #
+ mksession! Xtest_altfile
+ only
+ bwipe Xonealt
+ bwipe Xtwoalt
+ bwipe!
+ source Xtest_altfile
+ call assert_equal('Xone', bufname())
+ call assert_equal('Xonealt', bufname('#'))
+ wincmd w
+ call assert_equal('Xtwo', bufname())
+ call assert_equal('Xtwoalt', bufname('#'))
+ only
+ bwipe!
+ call delete('Xtest_altfile')
+endfunc
+
+" Test for creating views with manual folds
+func Test_mkview_manual_fold()
+ call writefile(range(1,10), 'Xmkvfile', 'D')
+ new Xmkvfile
+ " create recursive folds
+ 5,6fold
+ 4,7fold
+ mkview Xview
+ normal zE
+ source Xview
+ call assert_equal([-1, 4, 4, 4, 4, -1], [foldclosed(3), foldclosed(4),
+ \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
+ " open one level of fold
+ 4foldopen
+ mkview! Xview
+ normal zE
+ source Xview
+ call assert_equal([-1, -1, 5, 5, -1, -1], [foldclosed(3), foldclosed(4),
+ \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
+ " open all the folds
+ %foldopen!
+ mkview! Xview
+ normal zE
+ source Xview
+ call assert_equal([-1, -1, -1, -1, -1, -1], [foldclosed(3), foldclosed(4),
+ \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)])
+ call delete('Xview')
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_mksession_utf8.vim b/src/testdir/test_mksession_utf8.vim
new file mode 100644
index 0000000..4e593cc
--- /dev/null
+++ b/src/testdir/test_mksession_utf8.vim
@@ -0,0 +1,105 @@
+" Test for :mksession, :mkview and :loadview in utf-8 encoding
+
+set encoding=utf-8
+scriptencoding utf-8
+
+source check.vim
+CheckFeature mksession
+
+func Test_mksession_utf8()
+ tabnew
+ let wrap_save = &wrap
+ set sessionoptions=buffers splitbelow fileencoding=utf-8
+ call setline(1, [
+ \ 'start:',
+ \ 'no multibyte chAracter',
+ \ ' one leaDing tab',
+ \ ' four leadinG spaces',
+ \ 'two consecutive tabs',
+ \ 'two tabs in one line',
+ \ 'one … multibyteCharacter',
+ \ 'a “b†two multiByte characters',
+ \ '“câ€1€ three mulTibyte characters'
+ \ ])
+ let tmpfile = tempname()
+ exec 'w! ' . tmpfile
+ /^start:
+ set wrap
+ vsplit
+ norm! j16|
+ split
+ norm! j16|
+ split
+ norm! j16|
+ split
+ norm! j8|
+ split
+ norm! j8|
+ split
+ norm! j16|
+ split
+ norm! j16|
+ split
+ norm! j16|
+ wincmd l
+
+ set nowrap
+ /^start:
+ norm! j16|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j08|3zl
+ split
+ norm! j08|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j016|3zl
+ split
+ norm! j016|3zl
+ split
+ call wincol()
+ mksession! test_mks.out
+ let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"')
+ let expected =<< trim [DATA]
+ normal! 016|
+ normal! 016|
+ normal! 016|
+ normal! 08|
+ normal! 08|
+ normal! 016|
+ normal! 016|
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 8 . '|'
+ normal! 08|
+ exe 'normal! ' . s:c . '|zs' . 8 . '|'
+ normal! 08|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ exe 'normal! ' . s:c . '|zs' . 16 . '|'
+ normal! 016|
+ [DATA]
+
+ call assert_equal(expected, li)
+ tabclose!
+
+ call delete('test_mks.out')
+ call delete(tmpfile)
+ let &wrap = wrap_save
+ set sessionoptions& splitbelow& fileencoding&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_modeless.vim b/src/testdir/test_modeless.vim
new file mode 100644
index 0000000..281c248
--- /dev/null
+++ b/src/testdir/test_modeless.vim
@@ -0,0 +1,407 @@
+" Test for modeless selection
+
+" This only works for Unix in a terminal
+source check.vim
+CheckNotGui
+CheckUnix
+
+source shared.vim
+source mouse.vim
+
+" Test for modeless characterwise selection (single click)
+func Test_modeless_characterwise_selection()
+ CheckFeature clipboard_working
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm mousetime=200
+ call WaitForResponses()
+
+ new
+ call setline(1, ['one two three', 'foo bar baz'])
+ redraw!
+
+ " Wait a bit for any terminal responses to get processed.
+ sleep 50m
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ " select multiple characters within a line
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 6)
+ let keys ..= MouseLeftDragCode(1, 10)
+ let keys ..= MouseLeftReleaseCode(1, 10)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("wo th", @*, msg)
+
+ " select multiple characters including the end of line
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 11)
+ let keys ..= MouseLeftDragCode(1, 16)
+ let keys ..= MouseLeftReleaseCode(1, 16)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("ree\n", @*, msg)
+
+ " extend a selection using right mouse click
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ set mousemodel=extend
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 2)
+ let keys ..= MouseLeftDragCode(1, 5)
+ let keys ..= MouseLeftReleaseCode(1, 5)
+ let keys ..= MouseRightClickCode(1, 10)
+ let keys ..= MouseRightReleaseCode(1, 10)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("ne two th", @*, msg)
+ set mousemodel&
+
+ " extend a selection backwards using right mouse click
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ set mousemodel=extend
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 7)
+ let keys ..= MouseLeftDragCode(1, 11)
+ let keys ..= MouseLeftReleaseCode(1, 11)
+ let keys ..= MouseRightClickCode(1, 3)
+ let keys ..= MouseRightReleaseCode(1, 3)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("e two thr", @*, msg)
+ set mousemodel&
+
+ " select multiple characters within a line backwards
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 9)
+ let keys ..= MouseLeftDragCode(1, 3)
+ let keys ..= MouseLeftReleaseCode(1, 3)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("e two t", @*, msg)
+
+ " select multiple characters across lines with (end row > start row) and
+ " (end column < start column)
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 9)
+ let keys ..= MouseLeftDragCode(2, 3)
+ let keys ..= MouseLeftReleaseCode(2, 3)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("three\nfoo", @*, msg)
+
+ " select multiple characters across lines with (end row > start row) and
+ " (end column > start column)
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 4)
+ let keys ..= MouseLeftDragCode(2, 8)
+ let keys ..= MouseLeftReleaseCode(2, 8)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal(" two three\nfoo bar ", @*, msg)
+
+ " select multiple characters across lines with (end row < start row) and
+ " (end column < start column)
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 7)
+ let keys ..= MouseLeftDragCode(1, 5)
+ let keys ..= MouseLeftReleaseCode(1, 5)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("two three\nfoo bar", @*, msg)
+
+ " select multiple characters across lines with (end row < start row) and
+ " (end column > start column)
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 11)
+ let keys ..= MouseLeftDragCode(1, 13)
+ let keys ..= MouseLeftReleaseCode(1, 13)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("e\nfoo bar baz", @*, msg)
+
+ " select multiple characters across lines with (end row < start row) and
+ " the end column is greater than the line length
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 7)
+ let keys ..= MouseLeftDragCode(1, 16)
+ let keys ..= MouseLeftReleaseCode(1, 16)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("\nfoo bar", @*, msg)
+
+ " select multiple characters across lines with start/end row and start/end
+ " column outside the lines in the buffer
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(4, 3)
+ let keys ..= MouseLeftDragCode(3, 2)
+ let keys ..= MouseLeftReleaseCode(3, 2)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("\n~ ", @*, msg)
+
+ " change selection using right mouse click within the selected text
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ set mousemodel=extend
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 5)
+ let keys ..= MouseLeftDragCode(1, 13)
+ let keys ..= MouseLeftReleaseCode(1, 13)
+ let keys ..= MouseRightClickCode(1, 7)
+ let keys ..= MouseRightReleaseCode(1, 7)
+ let keys ..= MouseRightClickCode(1, 11)
+ let keys ..= MouseRightReleaseCode(1, 11)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("o thr", @*, msg)
+ set mousemodel&
+
+ " select text multiple times at different places
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 3)
+ let keys ..= MouseLeftDragCode(1, 5)
+ let keys ..= MouseLeftReleaseCode(1, 5)
+ let keys ..= MouseLeftClickCode(2, 7)
+ let keys ..= MouseLeftDragCode(2, 9)
+ let keys ..= MouseLeftReleaseCode(2, 9)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("r b", @*, msg)
+
+ " Test for 'clipboard' set to 'autoselectml' to automatically copy the
+ " modeless selection to the clipboard
+ set clipboard=autoselectml
+ let @* = 'clean'
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 5)
+ let keys ..= MouseLeftDragCode(2, 7)
+ let keys ..= MouseLeftReleaseCode(2, 7)
+ let keys ..= "\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("bar", @*)
+ set clipboard&
+
+ " quadruple click should start characterwise selectmode
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 10)
+ let keys ..= MouseLeftReleaseCode(1, 10)
+ let keys ..= MouseLeftClickCode(1, 10)
+ let keys ..= MouseLeftReleaseCode(1, 10)
+ let keys ..= MouseLeftClickCode(1, 10)
+ let keys ..= MouseLeftReleaseCode(1, 10)
+ let keys ..= MouseLeftClickCode(1, 10)
+ let keys ..= MouseLeftDragCode(1, 11)
+ let keys ..= MouseLeftReleaseCode(1, 11)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("hree", @*, msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for modeless word selection (double click)
+func Test_modeless_word_selection()
+ CheckFeature clipboard_working
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm mousetime=200
+ call WaitForResponses()
+
+ new
+ call setline(1, ['one two three', 'foo bar baz'])
+ redraw!
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ " select multiple words within a line
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 6)
+ let keys ..= MouseLeftReleaseCode(1, 6)
+ let keys ..= MouseLeftClickCode(1, 6)
+ let keys ..= MouseLeftDragCode(1, 10)
+ let keys ..= MouseLeftReleaseCode(1, 10)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("two three", @*, msg)
+
+ " select a single word
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 6)
+ let keys ..= MouseLeftReleaseCode(2, 6)
+ let keys ..= MouseLeftClickCode(2, 6)
+ let keys ..= MouseLeftReleaseCode(2, 6)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("bar", @*, msg)
+
+ " select multiple words backwards within a line
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 11)
+ let keys ..= MouseLeftReleaseCode(2, 11)
+ let keys ..= MouseLeftClickCode(2, 11)
+ let keys ..= MouseLeftDragCode(2, 7)
+ let keys ..= MouseLeftReleaseCode(2, 7)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("bar baz", @*, msg)
+
+ " select multiple words backwards across lines
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 7)
+ let keys ..= MouseLeftReleaseCode(2, 7)
+ let keys ..= MouseLeftClickCode(2, 7)
+ let keys ..= MouseLeftDragCode(1, 6)
+ let keys ..= MouseLeftReleaseCode(1, 6)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("two three\nfoo bar", @*, msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for modeless line selection (triple click)
+func Test_modeless_line_selection()
+ CheckFeature clipboard_working
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm mousetime=200
+ call WaitForResponses()
+
+ new
+ call setline(1, ['one two three', 'foo bar baz'])
+ redraw!
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ " select single line
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 6)
+ let keys ..= MouseLeftReleaseCode(2, 6)
+ let keys ..= MouseLeftClickCode(2, 6)
+ let keys ..= MouseLeftReleaseCode(2, 6)
+ let keys ..= MouseLeftClickCode(2, 6)
+ let keys ..= MouseLeftReleaseCode(2, 6)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("foo bar baz\n", @*, msg)
+
+ " select multiple lines
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(1, 6)
+ let keys ..= MouseLeftReleaseCode(1, 6)
+ let keys ..= MouseLeftClickCode(1, 6)
+ let keys ..= MouseLeftReleaseCode(1, 6)
+ let keys ..= MouseLeftClickCode(1, 6)
+ let keys ..= MouseLeftDragCode(2, 12)
+ let keys ..= MouseLeftReleaseCode(2, 12)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("one two three\nfoo bar baz\n", @*, msg)
+
+ " select multiple lines backwards
+ let @* = 'clean'
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+ let keys = ":"
+ let keys ..= MouseLeftClickCode(2, 10)
+ let keys ..= MouseLeftReleaseCode(2, 10)
+ let keys ..= MouseLeftClickCode(2, 10)
+ let keys ..= MouseLeftReleaseCode(2, 10)
+ let keys ..= MouseLeftClickCode(2, 10)
+ let keys ..= MouseLeftDragCode(1, 3)
+ let keys ..= MouseLeftReleaseCode(1, 3)
+ let keys ..= "\<C-Y>\<CR>"
+ call feedkeys(keys, "x")
+ call assert_equal("one two three\nfoo bar baz\n", @*, msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_modeline.vim b/src/testdir/test_modeline.vim
new file mode 100644
index 0000000..9fb233c
--- /dev/null
+++ b/src/testdir/test_modeline.vim
@@ -0,0 +1,361 @@
+" Tests for parsing the modeline.
+
+source check.vim
+
+func Test_modeline_invalid()
+ " This was reading allocated memory in the past.
+ call writefile(['vi:0', 'nothing'], 'Xmodeline', 'D')
+ let modeline = &modeline
+ set modeline
+ call assert_fails('split Xmodeline', 'E518:')
+
+ " Missing end colon (ignored).
+ call writefile(['// vim: set ts=2'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Missing colon at beginning (ignored).
+ call writefile(['// vim set ts=2:'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Missing space after vim (ignored).
+ call writefile(['// vim:ts=2:'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ let &modeline = modeline
+ bwipe!
+endfunc
+
+func Test_modeline_filetype()
+ call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype', 'D')
+ let modeline = &modeline
+ set modeline
+ filetype plugin on
+ split Xmodeline_filetype
+ call assert_equal("c", &filetype)
+ call assert_equal(1, b:did_ftplugin)
+ call assert_equal("ccomplete#Complete", &ofu)
+
+ bwipe!
+ let &modeline = modeline
+ filetype plugin off
+endfunc
+
+func Test_modeline_syntax()
+ call writefile(['vim: set syn=c :', 'nothing'], 'Xmodeline_syntax', 'D')
+ let modeline = &modeline
+ set modeline
+ syntax enable
+ split Xmodeline_syntax
+ call assert_equal("c", &syntax)
+ call assert_equal("c", b:current_syntax)
+
+ bwipe!
+ let &modeline = modeline
+ syntax off
+endfunc
+
+func Test_modeline_keymap()
+ CheckFeature keymap
+ call writefile(['vim: set keymap=greek :', 'nothing'], 'Xmodeline_keymap', 'D')
+ let modeline = &modeline
+ set modeline
+ split Xmodeline_keymap
+ call assert_equal("greek", &keymap)
+ call assert_match('greek\|grk', b:keymap_name)
+
+ bwipe!
+ let &modeline = modeline
+ set keymap= iminsert=0 imsearch=-1
+endfunc
+
+func Test_modeline_version()
+ let modeline = &modeline
+ set modeline
+
+ " Test with vim:{vers}: (version {vers} or later).
+ call writefile(['// vim' .. v:version .. ': ts=2:'], 'Xmodeline_version', 'D')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bw!
+
+ " Test with vim>{vers}: (version after {vers}).
+ call writefile(['// vim>' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim>' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim>' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Test with vim<{vers}: (version before {vers}).
+ call writefile(['// vim<' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim<' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim<' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ " Test with vim={vers}: (version {vers} only).
+ call writefile(['// vim=' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim=' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim=' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ let &modeline = modeline
+endfunc
+
+func Test_modeline_colon()
+ let modeline = &modeline
+ set modeline
+
+ call writefile(['// vim: set showbreak=\: ts=2: sw=2'], 'Xmodeline_colon', 'D')
+ edit Xmodeline_colon
+
+ " backlash colon should become colon.
+ call assert_equal(':', &showbreak)
+
+ " 'ts' should be set.
+ " 'sw' should be ignored because it is after the end colon.
+ call assert_equal(2, &ts)
+ call assert_equal(8, &sw)
+
+ let &modeline = modeline
+endfunc
+
+func s:modeline_fails(what, text, error)
+ call CheckOption(a:what)
+ let fname = "Xmodeline_fails_" . a:what
+ call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname, 'D')
+ let modeline = &modeline
+ set modeline
+ filetype plugin on
+ syntax enable
+ call assert_fails('split ' . fname, a:error)
+ call assert_equal("", &filetype)
+ call assert_equal("", &syntax)
+
+ bwipe!
+ let &modeline = modeline
+ filetype plugin off
+ syntax off
+endfunc
+
+func Test_modeline_filetype_fails()
+ call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:')
+endfunc
+
+func Test_modeline_syntax_fails()
+ call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:')
+endfunc
+
+func Test_modeline_keymap_fails()
+ call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:')
+endfunc
+
+func Test_modeline_fails_always()
+ call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:')
+ call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:')
+ call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:')
+ call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:')
+ call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:')
+ call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:')
+ call s:modeline_fails('directory', 'directory=Something()', 'E520:')
+ call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:')
+ call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:')
+ call s:modeline_fails('exrc', 'exrc=Something()', 'E520:')
+ call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:')
+ call s:modeline_fails('fsync', 'fsync=Something()', 'E520:')
+ call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:')
+ call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:')
+ call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:')
+ call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:')
+ call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:')
+ call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:')
+ call s:modeline_fails('langmap', 'langmap=Something()', 'E520:')
+ call s:modeline_fails('luadll', 'luadll=Something()', 'E520:')
+ call s:modeline_fails('makeef', 'makeef=Something()', 'E520:')
+ call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:')
+ call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:')
+ call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:')
+ call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:')
+ call s:modeline_fails('modelineexpr', 'modelineexpr', 'E520:')
+ call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:')
+ call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:')
+ call s:modeline_fails('perldll', 'perldll=Something()', 'E520:')
+ call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:')
+ call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:')
+ call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:')
+ call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:')
+ call s:modeline_fails('pythonhome', 'pythonhome=Something()', 'E520:')
+ call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:')
+ call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:')
+ call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:')
+ call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:')
+ call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:')
+ call s:modeline_fails('secure', 'secure=Something()', 'E520:')
+ call s:modeline_fails('shell', 'shell=Something()', 'E520:')
+ call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:')
+ call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:')
+ call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:')
+ call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:')
+ call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:')
+ call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:')
+ call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:')
+ call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:')
+ call s:modeline_fails('titleold', 'titleold=Something()', 'E520:')
+ call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:')
+ call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:')
+ call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:')
+ call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:')
+ call s:modeline_fails('undodir', 'undodir=Something()', 'E520:')
+ " only check a few terminal options
+ call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:')
+ call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:')
+ call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:')
+ call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:')
+endfunc
+
+func Test_modeline_fails_modelineexpr()
+ call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:')
+ call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:')
+ call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:')
+ call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:')
+ call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:')
+ call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:')
+ call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:')
+ call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:')
+ call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:')
+ call s:modeline_fails('statusline', 'statusline=Something()', 'E992:')
+ call s:modeline_fails('tabline', 'tabline=Something()', 'E992:')
+ call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:')
+endfunc
+
+func Test_modeline_setoption_verbose()
+ let modeline = &modeline
+ set modeline
+
+ let lines =<< trim END
+ 1 vim:ts=2
+ 2 two
+ 3 three
+ 4 four
+ 5 five
+ 6 six
+ 7 seven
+ 8 eight
+ END
+ call writefile(lines, 'Xmodeline', 'D')
+ edit Xmodeline
+ let info = split(execute('verbose set tabstop?'), "\n")
+ call assert_match('^\s*Last set from modeline line 1$', info[-1])
+ bwipe!
+
+ let lines =<< trim END
+ 1 one
+ 2 two
+ 3 three
+ 4 vim:ts=4
+ 5 five
+ 6 six
+ 7 seven
+ 8 eight
+ END
+ call writefile(lines, 'Xmodeline')
+ edit Xmodeline
+ let info = split(execute('verbose set tabstop?'), "\n")
+ call assert_match('^\s*Last set from modeline line 4$', info[-1])
+ bwipe!
+
+ let lines =<< trim END
+ 1 one
+ 2 two
+ 3 three
+ 4 four
+ 5 five
+ 6 six
+ 7 seven
+ 8 vim:ts=8
+ END
+ call writefile(lines, 'Xmodeline')
+ edit Xmodeline
+ let info = split(execute('verbose set tabstop?'), "\n")
+ call assert_match('^\s*Last set from modeline line 8$', info[-1])
+ bwipe!
+
+ let &modeline = modeline
+endfunc
+
+" Test for the 'modeline' default value in compatible and non-compatible modes
+" for root and non-root accounts
+func Test_modeline_default()
+ set compatible
+ call assert_false(&modeline)
+ set nocompatible
+ call assert_equal(IsRoot() ? 0 : 1, &modeline)
+ set compatible&vi
+ call assert_false(&modeline)
+ set compatible&vim
+ call assert_equal(IsRoot() ? 0 : 1, &modeline)
+ set compatible& modeline&
+endfunc
+
+" Some options cannot be set from the modeline when 'diff' option is set
+func Test_modeline_diff_buffer()
+ call writefile(['vim: diff foldmethod=marker wrap'], 'Xmdifile', 'D')
+ set foldmethod& nowrap
+ new Xmdifile
+ call assert_equal('manual', &foldmethod)
+ call assert_false(&wrap)
+ set wrap&
+ bw
+endfunc
+
+func Test_modeline_disable()
+ set modeline
+ call writefile(['vim: sw=2', 'vim: nomodeline', 'vim: sw=3'], 'Xmodeline_disable', 'D')
+ edit Xmodeline_disable
+ call assert_equal(2, &sw)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_move.vim b/src/testdir/test_move.vim
new file mode 100644
index 0000000..0412097
--- /dev/null
+++ b/src/testdir/test_move.vim
@@ -0,0 +1,70 @@
+" Test the ":move" command.
+
+source check.vim
+source screendump.vim
+
+func Test_move()
+ enew!
+ call append(0, ['line 1', 'line 2', 'line 3'])
+ g /^$/ delete _
+ set nomodified
+
+ move .
+ call assert_equal(['line 1', 'line 2', 'line 3'], getline(1, 3))
+ call assert_false(&modified)
+
+ 1,2move 0
+ call assert_equal(['line 1', 'line 2', 'line 3'], getline(1, 3))
+ call assert_false(&modified)
+
+ 1,3move 3
+ call assert_equal(['line 1', 'line 2', 'line 3'], getline(1, 3))
+ call assert_false(&modified)
+
+ 1move 2
+ call assert_equal(['line 2', 'line 1', 'line 3'], getline(1, 3))
+ call assert_true(&modified)
+ set nomodified
+
+ 3move 0
+ call assert_equal(['line 3', 'line 2', 'line 1'], getline(1, 3))
+ call assert_true(&modified)
+ set nomodified
+
+ 2,3move 0
+ call assert_equal(['line 2', 'line 1', 'line 3'], getline(1, 3))
+ call assert_true(&modified)
+ set nomodified
+
+ call assert_fails('1,2move 1', 'E134:')
+ call assert_fails('2,3move 2', 'E134:')
+ call assert_fails("move -100", 'E16:')
+ call assert_fails("move +100", 'E16:')
+ call assert_fails('move', 'E16:')
+ call assert_fails("move 'r", 'E20:')
+
+ %bwipeout!
+endfunc
+
+func Test_move_undo()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['First', 'Second', 'Third', 'Fourth'])
+ END
+ call writefile(lines, 'Xtest_move_undo.vim', 'D')
+ let buf = RunVimInTerminal('-S Xtest_move_undo.vim', #{rows: 10, cols: 60, statusoff: 2})
+
+ call term_sendkeys(buf, "gg:move +1\<CR>")
+ call VerifyScreenDump(buf, 'Test_move_undo_1', {})
+
+ " here the display would show the last few lines scrolled down
+ call term_sendkeys(buf, "u")
+ call term_sendkeys(buf, ":\<Esc>")
+ call VerifyScreenDump(buf, 'Test_move_undo_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_mswin_event.vim b/src/testdir/test_mswin_event.vim
new file mode 100644
index 0000000..e2add3b
--- /dev/null
+++ b/src/testdir/test_mswin_event.vim
@@ -0,0 +1,1006 @@
+" Test MS-Windows input event handling.
+" Most of this works the same in Windows GUI as well as Windows console.
+
+source check.vim
+CheckMSWindows
+source mouse.vim
+
+" Helper function for sending a grouped sequence of low level key presses
+" The modifer key(s) can be included as VK Key Codes in the sequence
+" Keydown events will be sent, to to the end of the group, then keyup events
+" will be sent in reverse order to release the keys.
+func SendKeyGroup(keygroup)
+ for k in a:keygroup
+ call test_mswin_event("key", {'event': "keydown", 'keycode': k})
+ endfor
+ for k in reverse(copy(a:keygroup))
+ call test_mswin_event("key", {'event': "keyup", 'keycode': k})
+ endfor
+endfunc
+
+" Send individual key press and release events.
+" the modifers for the key press can be specified in the modifiers arg.
+func SendKeyWithModifiers(key, modifiers)
+ let args = { }
+ let args.keycode = a:key
+ let args.modifiers = a:modifiers
+ let args.event = "keydown"
+ call test_mswin_event("key", args)
+ let args.event = "keyup"
+ call test_mswin_event("key", args)
+ unlet args
+endfunc
+
+" Send an individual key press, without modifiers.
+func SendKey(key)
+ call SendKeyWithModifiers(a:key, 0)
+endfunc
+
+" Send a string of individual key-press events, without modifiers.
+func SendKeyStr(keystring)
+ for k in a:keystring
+ call SendKey(k)
+ endfor
+endfunc
+
+" This tells Vim to execute the buffered keys as user commands,
+" ie. same as feekdeys with mode X would do.
+func ExecuteBufferedKeys()
+ if has('gui_running')
+ call feedkeys("\<Esc>", 'Lx!')
+ else
+ call test_mswin_event("key", {'execute': v:true})
+ endif
+endfunc
+
+" Refer to the following page for the virtual key codes:
+" https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
+let s:VK = {
+ \ 'ENTER' : 0x0D,
+ \ 'SPACE' : 0x20,
+ \ 'SHIFT' : 0x10,
+ \ 'LSHIFT' : 0xA0,
+ \ 'RSHIFT' : 0xA1,
+ \ 'CONTROL' : 0x11,
+ \ 'LCONTROL' : 0xA2,
+ \ 'RCONTROL' : 0xA3,
+ \ 'MENU' : 0x12,
+ \ 'ALT' : 0x12,
+ \ 'LMENU' : 0xA4,
+ \ 'LALT' : 0xA4,
+ \ 'RMENU' : 0xA5,
+ \ 'RALT' : 0xA5,
+ \ 'OEM_1' : 0xBA,
+ \ 'OEM_2' : 0xBF,
+ \ 'OEM_3' : 0xC0,
+ \ 'OEM_4' : 0xDB,
+ \ 'OEM_5' : 0xDC,
+ \ 'OEM_6' : 0xDD,
+ \ 'OEM_7' : 0xDE,
+ \ 'OEM_PLUS' : 0xBB,
+ \ 'OEM_COMMA' : 0xBC,
+ \ 'OEM_MINUS' : 0xBD,
+ \ 'OEM_PERIOD' : 0xBE,
+ \ 'PRIOR' : 0x21,
+ \ 'NEXT' : 0x22,
+ \ 'END' : 0x23,
+ \ 'HOME' : 0x24,
+ \ 'LEFT' : 0x25,
+ \ 'UP' : 0x26,
+ \ 'RIGHT' : 0x27,
+ \ 'DOWN' : 0x28,
+ \ 'KEY_0' : 0x30,
+ \ 'KEY_1' : 0x31,
+ \ 'KEY_2' : 0x32,
+ \ 'KEY_3' : 0x33,
+ \ 'KEY_4' : 0x34,
+ \ 'KEY_5' : 0x35,
+ \ 'KEY_6' : 0x36,
+ \ 'KEY_7' : 0x37,
+ \ 'KEY_8' : 0x38,
+ \ 'KEY_9' : 0x39,
+ \ 'KEY_A' : 0x41,
+ \ 'KEY_B' : 0x42,
+ \ 'KEY_C' : 0x43,
+ \ 'KEY_D' : 0x44,
+ \ 'KEY_E' : 0x45,
+ \ 'KEY_F' : 0x46,
+ \ 'KEY_G' : 0x47,
+ \ 'KEY_H' : 0x48,
+ \ 'KEY_I' : 0x49,
+ \ 'KEY_J' : 0x4A,
+ \ 'KEY_K' : 0x4B,
+ \ 'KEY_L' : 0x4C,
+ \ 'KEY_M' : 0x4D,
+ \ 'KEY_N' : 0x4E,
+ \ 'KEY_O' : 0x4F,
+ \ 'KEY_P' : 0x50,
+ \ 'KEY_Q' : 0x51,
+ \ 'KEY_R' : 0x52,
+ \ 'KEY_S' : 0x53,
+ \ 'KEY_T' : 0x54,
+ \ 'KEY_U' : 0x55,
+ \ 'KEY_V' : 0x56,
+ \ 'KEY_W' : 0x57,
+ \ 'KEY_X' : 0x58,
+ \ 'KEY_Y' : 0x59,
+ \ 'KEY_Z' : 0x5A,
+ \ 'NUMPAD0' : 0x60,
+ \ 'NUMPAD1' : 0x61,
+ \ 'NUMPAD2' : 0x62,
+ \ 'NUMPAD3' : 0x63,
+ \ 'NUMPAD4' : 0x64,
+ \ 'NUMPAD5' : 0x65,
+ \ 'NUMPAD6' : 0x66,
+ \ 'NUMPAD7' : 0x67,
+ \ 'NUMPAD8' : 0x68,
+ \ 'NUMPAD9' : 0x69,
+ \ 'MULTIPLY' : 0x6A,
+ \ 'ADD' : 0x6B,
+ \ 'SUBTRACT' : 0x6D,
+ \ 'F1' : 0x70,
+ \ 'F2' : 0x71,
+ \ 'F3' : 0x72,
+ \ 'F4' : 0x73,
+ \ 'F5' : 0x74,
+ \ 'F6' : 0x75,
+ \ 'F7' : 0x76,
+ \ 'F8' : 0x77,
+ \ 'F9' : 0x78,
+ \ 'F10' : 0x79,
+ \ 'F11' : 0x7A,
+ \ 'F12' : 0x7B,
+ \ 'DELETE' : 0x2E,
+ \ 'BACK' : 0x08,
+ \ 'ESCAPE' : 0x1B
+ \ }
+
+ let s:MOD_MASK_SHIFT = 0x02
+ let s:MOD_MASK_CTRL = 0x04
+ let s:MOD_MASK_ALT = 0x08
+
+ let s:vim_key_modifiers = [
+ \ ["", 0, []],
+ \ ["S-", 2, [s:VK.LSHIFT]],
+ \ ["C-", 4, [s:VK.LCONTROL]],
+ \ ["C-S-", 6, [s:VK.LCONTROL, s:VK.LSHIFT]],
+ \ ["A-", 8, [s:VK.LMENU]],
+ \ ["A-S-", 10, [s:VK.LMENU, s:VK.LSHIFT]],
+ \ ["A-C-", 12, [s:VK.LMENU, s:VK.LCONTROL]],
+ \ ["A-C-S-", 14, [s:VK.LMENU, s:VK.LCONTROL, s:VK.LSHIFT]],
+ \]
+
+ " Assuming Standard US PC Keyboard layout
+ let s:test_ascii_key_chars = [
+ \ [[s:VK.SPACE], ' '],
+ \ [[s:VK.OEM_1], ';'],
+ \ [[s:VK.OEM_2], '/'],
+ \ [[s:VK.OEM_3], '`'],
+ \ [[s:VK.OEM_4], '['],
+ \ [[s:VK.OEM_5], '\'],
+ \ [[s:VK.OEM_6], ']'],
+ \ [[s:VK.OEM_7], ''''],
+ \ [[s:VK.OEM_PLUS], '='],
+ \ [[s:VK.OEM_COMMA], ','],
+ \ [[s:VK.OEM_MINUS], '-'],
+ \ [[s:VK.OEM_PERIOD], '.'],
+ \ [[s:VK.SHIFT, s:VK.OEM_1], ':'],
+ \ [[s:VK.SHIFT, s:VK.OEM_2], '?'],
+ \ [[s:VK.SHIFT, s:VK.OEM_3], '~'],
+ \ [[s:VK.SHIFT, s:VK.OEM_4], '{'],
+ \ [[s:VK.SHIFT, s:VK.OEM_5], '|'],
+ \ [[s:VK.SHIFT, s:VK.OEM_6], '}'],
+ \ [[s:VK.SHIFT, s:VK.OEM_7], '"'],
+ \ [[s:VK.SHIFT, s:VK.OEM_PLUS], '+'],
+ \ [[s:VK.SHIFT, s:VK.OEM_COMMA], '<'],
+ \ [[s:VK.SHIFT, s:VK.OEM_MINUS], '_'],
+ \ [[s:VK.SHIFT, s:VK.OEM_PERIOD], '>'],
+ \ [[s:VK.KEY_1], '1'],
+ \ [[s:VK.KEY_2], '2'],
+ \ [[s:VK.KEY_3], '3'],
+ \ [[s:VK.KEY_4], '4'],
+ \ [[s:VK.KEY_5], '5'],
+ \ [[s:VK.KEY_6], '6'],
+ \ [[s:VK.KEY_7], '7'],
+ \ [[s:VK.KEY_8], '8'],
+ \ [[s:VK.KEY_9], '9'],
+ \ [[s:VK.KEY_0], '0'],
+ \ [[s:VK.SHIFT, s:VK.KEY_1], '!'],
+ \ [[s:VK.SHIFT, s:VK.KEY_2], '@'],
+ \ [[s:VK.SHIFT, s:VK.KEY_3], '#'],
+ \ [[s:VK.SHIFT, s:VK.KEY_4], '$'],
+ \ [[s:VK.SHIFT, s:VK.KEY_5], '%'],
+ \ [[s:VK.SHIFT, s:VK.KEY_6], '^'],
+ \ [[s:VK.SHIFT, s:VK.KEY_7], '&'],
+ \ [[s:VK.SHIFT, s:VK.KEY_8], '*'],
+ \ [[s:VK.SHIFT, s:VK.KEY_9], '('],
+ \ [[s:VK.SHIFT, s:VK.KEY_0], ')'],
+ \ [[s:VK.KEY_A], 'a'],
+ \ [[s:VK.KEY_B], 'b'],
+ \ [[s:VK.KEY_C], 'c'],
+ \ [[s:VK.KEY_D], 'd'],
+ \ [[s:VK.KEY_E], 'e'],
+ \ [[s:VK.KEY_F], 'f'],
+ \ [[s:VK.KEY_G], 'g'],
+ \ [[s:VK.KEY_H], 'h'],
+ \ [[s:VK.KEY_I], 'i'],
+ \ [[s:VK.KEY_J], 'j'],
+ \ [[s:VK.KEY_K], 'k'],
+ \ [[s:VK.KEY_L], 'l'],
+ \ [[s:VK.KEY_M], 'm'],
+ \ [[s:VK.KEY_N], 'n'],
+ \ [[s:VK.KEY_O], 'o'],
+ \ [[s:VK.KEY_P], 'p'],
+ \ [[s:VK.KEY_Q], 'q'],
+ \ [[s:VK.KEY_R], 'r'],
+ \ [[s:VK.KEY_S], 's'],
+ \ [[s:VK.KEY_T], 't'],
+ \ [[s:VK.KEY_U], 'u'],
+ \ [[s:VK.KEY_V], 'v'],
+ \ [[s:VK.KEY_W], 'w'],
+ \ [[s:VK.KEY_X], 'x'],
+ \ [[s:VK.KEY_Y], 'y'],
+ \ [[s:VK.KEY_Z], 'z'],
+ \ [[s:VK.SHIFT, s:VK.KEY_A], 'A'],
+ \ [[s:VK.SHIFT, s:VK.KEY_B], 'B'],
+ \ [[s:VK.SHIFT, s:VK.KEY_C], 'C'],
+ \ [[s:VK.SHIFT, s:VK.KEY_D], 'D'],
+ \ [[s:VK.SHIFT, s:VK.KEY_E], 'E'],
+ \ [[s:VK.SHIFT, s:VK.KEY_F], 'F'],
+ \ [[s:VK.SHIFT, s:VK.KEY_G], 'G'],
+ \ [[s:VK.SHIFT, s:VK.KEY_H], 'H'],
+ \ [[s:VK.SHIFT, s:VK.KEY_I], 'I'],
+ \ [[s:VK.SHIFT, s:VK.KEY_J], 'J'],
+ \ [[s:VK.SHIFT, s:VK.KEY_K], 'K'],
+ \ [[s:VK.SHIFT, s:VK.KEY_L], 'L'],
+ \ [[s:VK.SHIFT, s:VK.KEY_M], 'M'],
+ \ [[s:VK.SHIFT, s:VK.KEY_N], 'N'],
+ \ [[s:VK.SHIFT, s:VK.KEY_O], 'O'],
+ \ [[s:VK.SHIFT, s:VK.KEY_P], 'P'],
+ \ [[s:VK.SHIFT, s:VK.KEY_Q], 'Q'],
+ \ [[s:VK.SHIFT, s:VK.KEY_R], 'R'],
+ \ [[s:VK.SHIFT, s:VK.KEY_S], 'S'],
+ \ [[s:VK.SHIFT, s:VK.KEY_T], 'T'],
+ \ [[s:VK.SHIFT, s:VK.KEY_U], 'U'],
+ \ [[s:VK.SHIFT, s:VK.KEY_V], 'V'],
+ \ [[s:VK.SHIFT, s:VK.KEY_W], 'W'],
+ \ [[s:VK.SHIFT, s:VK.KEY_X], 'X'],
+ \ [[s:VK.SHIFT, s:VK.KEY_Y], 'Y'],
+ \ [[s:VK.SHIFT, s:VK.KEY_Z], 'Z'],
+ \ [[s:VK.CONTROL, s:VK.KEY_A], 0x01],
+ \ [[s:VK.CONTROL, s:VK.KEY_B], 0x02],
+ \ [[s:VK.CONTROL, s:VK.KEY_C], 0x03],
+ \ [[s:VK.CONTROL, s:VK.KEY_D], 0x04],
+ \ [[s:VK.CONTROL, s:VK.KEY_E], 0x05],
+ \ [[s:VK.CONTROL, s:VK.KEY_F], 0x06],
+ \ [[s:VK.CONTROL, s:VK.KEY_G], 0x07],
+ \ [[s:VK.CONTROL, s:VK.KEY_H], 0x08],
+ \ [[s:VK.CONTROL, s:VK.KEY_I], 0x09],
+ \ [[s:VK.CONTROL, s:VK.KEY_J], 0x0A],
+ \ [[s:VK.CONTROL, s:VK.KEY_K], 0x0B],
+ \ [[s:VK.CONTROL, s:VK.KEY_L], 0x0C],
+ \ [[s:VK.CONTROL, s:VK.KEY_M], 0x0D],
+ \ [[s:VK.CONTROL, s:VK.KEY_N], 0x0E],
+ \ [[s:VK.CONTROL, s:VK.KEY_O], 0x0F],
+ \ [[s:VK.CONTROL, s:VK.KEY_P], 0x10],
+ \ [[s:VK.CONTROL, s:VK.KEY_Q], 0x11],
+ \ [[s:VK.CONTROL, s:VK.KEY_R], 0x12],
+ \ [[s:VK.CONTROL, s:VK.KEY_S], 0x13],
+ \ [[s:VK.CONTROL, s:VK.KEY_T], 0x14],
+ \ [[s:VK.CONTROL, s:VK.KEY_U], 0x15],
+ \ [[s:VK.CONTROL, s:VK.KEY_V], 0x16],
+ \ [[s:VK.CONTROL, s:VK.KEY_W], 0x17],
+ \ [[s:VK.CONTROL, s:VK.KEY_X], 0x18],
+ \ [[s:VK.CONTROL, s:VK.KEY_Y], 0x19],
+ \ [[s:VK.CONTROL, s:VK.KEY_Z], 0x1A],
+ \ [[s:VK.CONTROL, s:VK.OEM_4], 0x1B],
+ \ [[s:VK.CONTROL, s:VK.OEM_5], 0x1C],
+ \ [[s:VK.CONTROL, s:VK.OEM_6], 0x1D],
+ \ [[s:VK.CONTROL, s:VK.KEY_6], 0x1E],
+ \ [[s:VK.CONTROL, s:VK.OEM_MINUS], 0x1F],
+ \ ]
+
+let s:test_extra_key_chars = [
+ \ [[s:VK.ALT, s:VK.KEY_1], '±'],
+ \ [[s:VK.ALT, s:VK.KEY_2], '²'],
+ \ [[s:VK.ALT, s:VK.KEY_3], '³'],
+ \ [[s:VK.ALT, s:VK.KEY_4], '´'],
+ \ [[s:VK.ALT, s:VK.KEY_5], 'µ'],
+ \ [[s:VK.ALT, s:VK.KEY_6], '¶'],
+ \ [[s:VK.ALT, s:VK.KEY_7], '·'],
+ \ [[s:VK.ALT, s:VK.KEY_8], '¸'],
+ \ [[s:VK.ALT, s:VK.KEY_9], '¹'],
+ \ [[s:VK.ALT, s:VK.KEY_0], '°'],
+ \ [[s:VK.ALT, s:VK.KEY_A], 'á'],
+ \ [[s:VK.ALT, s:VK.KEY_B], 'â'],
+ \ [[s:VK.ALT, s:VK.KEY_C], 'ã'],
+ \ [[s:VK.ALT, s:VK.KEY_D], 'ä'],
+ \ [[s:VK.ALT, s:VK.KEY_E], 'Ã¥'],
+ \ [[s:VK.ALT, s:VK.KEY_F], 'æ'],
+ \ [[s:VK.ALT, s:VK.KEY_G], 'ç'],
+ \ [[s:VK.ALT, s:VK.KEY_H], 'è'],
+ \ [[s:VK.ALT, s:VK.KEY_I], 'é'],
+ \ [[s:VK.ALT, s:VK.KEY_J], 'ê'],
+ \ [[s:VK.ALT, s:VK.KEY_K], 'ë'],
+ \ [[s:VK.ALT, s:VK.KEY_L], 'ì'],
+ \ [[s:VK.ALT, s:VK.KEY_M], 'í'],
+ \ [[s:VK.ALT, s:VK.KEY_N], 'î'],
+ \ [[s:VK.ALT, s:VK.KEY_O], 'ï'],
+ \ [[s:VK.ALT, s:VK.KEY_P], 'ð'],
+ \ [[s:VK.ALT, s:VK.KEY_Q], 'ñ'],
+ \ [[s:VK.ALT, s:VK.KEY_R], 'ò'],
+ \ [[s:VK.ALT, s:VK.KEY_S], 'ó'],
+ \ [[s:VK.ALT, s:VK.KEY_T], 'ô'],
+ \ [[s:VK.ALT, s:VK.KEY_U], 'õ'],
+ \ [[s:VK.ALT, s:VK.KEY_V], 'ö'],
+ \ [[s:VK.ALT, s:VK.KEY_W], '÷'],
+ \ [[s:VK.ALT, s:VK.KEY_X], 'ø'],
+ \ [[s:VK.ALT, s:VK.KEY_Y], 'ù'],
+ \ [[s:VK.ALT, s:VK.KEY_Z], 'ú'],
+ \ ]
+
+func s:LoopTestKeyArray(arr)
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+
+ for [kcodes, kstr] in a:arr
+ " Send as a sequence of key presses.
+ call SendKeyGroup(kcodes)
+ let ch = getcharstr(0)
+ " need to deal a bit differently with the non-printable ascii chars < 0x20
+ if kstr < 0x20 && index([s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL], kcodes[0]) >= 0
+ call assert_equal(nr2char(kstr), $"{ch}")
+ else
+ call assert_equal(kstr, $"{ch}")
+ endif
+ let mod_mask = getcharmod()
+ " the mod_mask is zero when no modifiers are used
+ " and when the virtual termcap maps the character
+ call assert_equal(0, mod_mask, $"key = {kstr}")
+
+ " Send as a single key press with a modifers mask.
+ let modifiers = 0
+ let key = kcodes[0]
+ for key in kcodes
+ if index([s:VK.SHIFT, s:VK.LSHIFT, s:VK.RSHIFT], key) >= 0
+ let modifiers = modifiers + s:MOD_MASK_SHIFT
+ endif
+ if index([s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL], key) >= 0
+ let modifiers = modifiers + s:MOD_MASK_CTRL
+ endif
+ if index([s:VK.ALT, s:VK.LALT, s:VK.RALT], key) >= 0
+ let modifiers = modifiers + s:MOD_MASK_ALT
+ endif
+ endfor
+ call SendKeyWithModifiers(key, modifiers)
+ let ch = getcharstr(0)
+ " need to deal a bit differently with the non-printable ascii chars < 0x20
+ if kstr < 0x20 && index([s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL], kcodes[0]) >= 0
+ call assert_equal(nr2char(kstr), $"{ch}")
+ else
+ call assert_equal(kstr, $"{ch}")
+ endif
+ let mod_mask = getcharmod()
+ " the mod_mask is zero when no modifiers are used
+ " and when the virtual termcap maps the character
+ call assert_equal(0, mod_mask, $"key = {kstr}")
+ endfor
+
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+
+endfunc
+
+" Test MS-Windows key events
+func Test_mswin_event_character_keys()
+ CheckMSWindows
+ new
+
+ call s:LoopTestKeyArray(s:test_ascii_key_chars)
+
+ if !has('gui_running')
+ call s:LoopTestKeyArray(s:test_extra_key_chars)
+ endif
+
+" Test keyboard codes for digits
+" (0x30 - 0x39) : VK_0 - VK_9 are the same as ASCII '0' - '9'
+ for kc in range(48, 57)
+ call SendKey(kc)
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc), ch)
+ call SendKeyWithModifiers(kc, 0)
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc), ch)
+ endfor
+
+" Test keyboard codes for Alt-0 to Alt-9
+" Expect +128 from the digit char codes
+ for modkey in [s:VK.ALT, s:VK.LALT, s:VK.RALT]
+ for kc in range(48, 57)
+ call SendKeyGroup([modkey, kc])
+ let ch = getchar(0)
+ call assert_equal(kc+128, ch)
+ call SendKeyWithModifiers(kc, s:MOD_MASK_ALT)
+ let ch = getchar(0)
+ call assert_equal(kc+128, ch)
+ endfor
+ endfor
+
+" Test for lowercase 'a' to 'z', VK codes 65(0x41) - 90(0x5A)
+" Note: VK_A-VK_Z virtual key codes coincide with uppercase ASCII codes A-Z.
+" eg VK_A is 65, and the ASCII character code for uppercase 'A' is also 65.
+" Caution: these are interpreted as lowercase when Shift is NOT pressed.
+" eg, sending VK_A (65) 'A' Key code without shift modifier, will produce ASCII
+" char 'a' (91) as the output. The ASCII codes for the lowercase letters are
+" numbered 32 higher than their uppercase versions.
+ for kc in range(65, 90)
+ call SendKey(kc)
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc + 32), ch)
+ call SendKeyWithModifiers(kc, 0)
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc + 32), ch)
+ endfor
+
+" Test for Uppercase 'A' - 'Z' keys
+" ie. with VK_SHIFT, expect the keycode = character code.
+ for modkey in [s:VK.SHIFT, s:VK.LSHIFT, s:VK.RSHIFT]
+ for kc in range(65, 90)
+ call SendKeyGroup([modkey, kc])
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc), ch)
+ call SendKeyWithModifiers(kc, s:MOD_MASK_SHIFT)
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc), ch)
+ endfor
+ endfor
+
+ " Test for <Ctrl-A> to <Ctrl-Z> keys
+ " Expect the unicode characters 0x01 to 0x1A
+ for modkey in [s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL]
+ for kc in range(65, 90)
+ call SendKeyGroup([modkey, kc])
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc - 64), ch)
+ call SendKeyWithModifiers(kc, s:MOD_MASK_CTRL)
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(kc - 64), ch)
+ endfor
+ endfor
+
+ " Windows intercepts some of these keys in the GUI.
+ if !has("gui_running")
+ " Test for <Alt-A> to <Alt-Z> keys
+ " Expect the unicode characters 0xE1 to 0xFA
+ " ie. 160 higher than the lowercase equivalent
+ for modkey in [s:VK.ALT, s:VK.LALT, s:VK.RALT]
+ for kc in range(65, 90)
+ call SendKeyGroup([modkey, kc])
+ let ch = getchar(0)
+ call assert_equal(kc+160, ch)
+ call SendKeyWithModifiers(kc, s:MOD_MASK_ALT)
+ let ch = getchar(0)
+ call assert_equal(kc+160, ch)
+ endfor
+ endfor
+ endif
+
+endfun
+
+ " Test for Function Keys 'F1' to 'F12'
+ " VK codes 112(0x70) - 123(0x7B)
+ " Also with ALL permutatios of modifiers; Shift, Ctrl & Alt
+func Test_mswin_event_function_keys()
+
+ if has('gui_running')
+ let g:test_is_flaky = 1
+ endif
+
+ " NOTE: Windows intercepts these combinations in the GUI
+ let gui_nogo = ["A-F1", "A-F2", "A-F3", "A-F4", "A-S-F4", "A-C-S-F4",
+ \ "A-F5", "A-F6", "A-F7", "A-F8", "A-C-F8", "A-F9",
+ \ "A-F10", "A-F11" , "A-C-F11", "A-C-F12"]
+
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+
+ for [mod_str, vim_mod_mask, mod_keycodes] in s:vim_key_modifiers
+ for n in range(1, 12)
+ let expected_mod_mask = vim_mod_mask
+ let kstr = $"{mod_str}F{n}"
+ if !has('gui_running') || (has('gui_running') && n != 10
+ \ && index(gui_nogo, kstr) == -1)
+ let keycode = eval('"\<' .. kstr .. '>"')
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+ call SendKeyWithModifiers(111+n, vim_mod_mask)
+ let ch = getcharstr(0)
+ let mod_mask = getcharmod()
+ call assert_equal(keycode, $"{ch}", $"key = {kstr}")
+ " workaround for the virtual termcap maps changing the character
+ "instead of sending Shift
+ for mod_key in mod_keycodes
+ if index([s:VK.SHIFT, s:VK.LSHIFT, s:VK.RSHIFT], mod_key) >= 0
+ let expected_mod_mask -= s:MOD_MASK_SHIFT
+ break
+ endif
+ endfor
+ call assert_equal(expected_mod_mask, mod_mask, $"mod = {expected_mod_mask} for key = {kstr}")
+ endif
+ endfor
+ endfor
+endfunc
+
+func ExtractModifiers(mod_keycodes)
+ let has_shift = 0
+ let has_ctrl = 0
+ let has_alt = 0
+ for mod_key in a:mod_keycodes
+ if index([s:VK.SHIFT, s:VK.LSHIFT, s:VK.RSHIFT], mod_key) >= 0
+ let has_shift = 1
+ endif
+ if index([s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL], mod_key) >= 0
+ let has_ctrl = 1
+ endif
+ if index([s:VK.MENU, s:VK.LMENU, s:VK.RMENU], mod_key) >= 0
+ let has_alt = 1
+ endif
+ endfor
+ return [has_shift, has_ctrl, has_alt]
+endfunc
+
+ " Test for Movement Keys;
+ " VK_PRIOR 0x21, VK_NEXT 0x22,
+ " VK_END 0x23, VK_HOME 0x24,
+ " VK_LEFT 0x25, VK_UP 0x26,
+ " VK_RIGHT 0x27, VK_DOWN 0x28
+ " With ALL permutations of modifiers; none, Shift, Ctrl & Alt
+func Test_mswin_event_movement_keys()
+
+ if has('gui_running')
+ let g:test_is_flaky = 1
+ endif
+
+ let movement_keys = [
+ \ [s:VK.PRIOR, "PageUp"],
+ \ [s:VK.NEXT, "PageDown"],
+ \ [s:VK.END, "End"],
+ \ [s:VK.HOME, "Home"],
+ \ [s:VK.LEFT, "Left"],
+ \ [s:VK.UP, "Up"],
+ \ [s:VK.RIGHT, "Right"],
+ \ [s:VK.DOWN, "Down"],
+ \ ]
+
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+
+ for [mod_str, vim_mod_mask, mod_keycodes] in s:vim_key_modifiers
+ for [kcode, kname] in movement_keys
+ let exp_mod_mask = vim_mod_mask
+ let kstr = $"{mod_str}{kname}"
+ let chstr_eval = eval('"\<' .. kstr .. '>"')
+
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+ execute 'call feedkeys("\<' .. kstr .. '>")'
+ let chstr_fk = getcharstr(0)
+ call assert_equal(chstr_eval, chstr_fk, $"feedkeys = <{kstr}>")
+
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+ call SendKey(kcode)
+ let chstr_alone = getcharstr(0)
+ let chstr_alone_end = chstr_alone[len(chstr_alone)-2:len(chstr_alone)-1]
+
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+ call SendKeyGroup(mod_keycodes + [kcode])
+ let chstr_mswin = getcharstr(0)
+ let chstr_mswin_end = chstr_mswin[len(chstr_mswin)-2:len(chstr_mswin)-1]
+ let mod_mask = getcharmod()
+
+ " The virtual termcap maps may** change the character and either;
+ " - remove the Shift modifier, or
+ " - remove the Ctrl modifier if the Shift modifier was not removed.
+ let [has_shift, has_ctrl, has_alt] = ExtractModifiers(mod_keycodes)
+ if chstr_alone_end != chstr_mswin_end
+ if has_shift != 0
+ let exp_mod_mask -= s:MOD_MASK_SHIFT
+ elseif has_ctrl != 0
+ let exp_mod_mask -= s:MOD_MASK_CTRL
+ endif
+ endif
+ " **Note: The appveyor Windows GUI test environments, from VS2017 on,
+ " consistently intercepts the Shift modifier WITHOUT changing the
+ " MOVEMENT character. This issue does not happen in any github actions
+ " CI Windows test environments. Attempted to reproduce this manually
+ " on Windows versions; 7, 8.1, 10, 11, Server 2019 and Server 2022, but
+ " the issue did not occur on any of those environments.
+ " Below is a workaround for the issue.
+ if has('gui_running') && has_shift != 0
+ if exp_mod_mask != mod_mask && chstr_eval != chstr_mswin
+ let kstr_sub = substitute(kstr, "S-", "", "")
+ let chstr_eval = eval('"\<' .. kstr_sub .. '>"')
+ if exp_mod_mask - s:MOD_MASK_SHIFT == mod_mask
+ let exp_mod_mask -= s:MOD_MASK_SHIFT
+ elseif has_ctrl != 0 && exp_mod_mask - s:MOD_MASK_CTRL == mod_mask
+ let exp_mod_mask -= s:MOD_MASK_CTRL
+ endif
+ endif
+ endif
+ call assert_equal(chstr_eval, chstr_mswin, $"key = {kstr}")
+ call assert_equal(exp_mod_mask, mod_mask, $"mod_mask for key = {kstr}")
+ endfor
+ endfor
+
+ bw!
+endfunc
+
+
+" Test for QWERTY Ctrl+- which should result in ^_
+" issue #10817
+func Test_QWERTY_Ctrl_minus()
+ CheckMSWindows
+ new
+
+ call SendKeyGroup([s:VK.CONTROL, s:VK.OEM_MINUS])
+ let ch = getcharstr(0)
+ call assert_equal(nr2char(0x1f),ch)
+
+ call SendKey(s:VK.KEY_I)
+ call SendKeyGroup([s:VK.CONTROL, s:VK.SUBTRACT])
+ call SendKey(s:VK.ESCAPE)
+ call ExecuteBufferedKeys()
+ call assert_equal('-', getline('$'))
+
+ %d _
+ imapclear
+ imap <C-_> BINGO
+ call SendKey(s:VK.KEY_I)
+ call SendKeyGroup([s:VK.CONTROL, s:VK.OEM_MINUS])
+ call SendKey(s:VK.ESCAPE)
+ call ExecuteBufferedKeys()
+ call assert_equal('BINGO', getline('$'))
+
+ %d _
+ imapclear
+ exec "imap \x1f BILBO"
+ call SendKey(s:VK.KEY_I)
+ call SendKeyGroup([s:VK.CONTROL, s:VK.OEM_MINUS])
+ call SendKey(s:VK.ESCAPE)
+ call ExecuteBufferedKeys()
+ call assert_equal('BILBO', getline('$'))
+
+ imapclear
+ bw!
+endfunc
+
+" Test MS-Windows mouse events
+func Test_mswin_event_mouse()
+ CheckMSWindows
+ new
+
+ set mousemodel=extend
+ call test_override('no_query_mouse', 1)
+ call WaitForResponses()
+
+ let msg = ''
+
+ call setline(1, ['one two three', 'four five six'])
+
+ " Test mouse movement
+ " by default, no mouse move events are generated
+ " this setting enables it to generate move events
+ set mousemev
+
+ if !has('gui_running')
+ " console version needs a button pressed,
+ " otherwise it ignores mouse movements.
+ call MouseLeftClick(2, 3)
+ endif
+ call MSWinMouseEvent(0x700, 8, 13, 0, 0, 0)
+ if has('gui_running')
+ call feedkeys("\<Esc>", 'Lx!')
+ endif
+ let pos = getmousepos()
+ call assert_equal(8, pos.screenrow)
+ call assert_equal(13, pos.screencol)
+
+ if !has('gui_running')
+ call MouseLeftClick(2, 3)
+ call MSWinMouseEvent(0x700, 6, 4, 1, 0, 0)
+ let pos = getmousepos()
+ call assert_equal(6, pos.screenrow)
+ call assert_equal(4, pos.screencol)
+ endif
+
+ " test cells vs pixels
+ if has('gui_running')
+ let args = { }
+ let args.row = 9
+ let args.col = 7
+ let args.move = 1
+ let args.cell = 1
+ call test_mswin_event("mouse", args)
+ call feedkeys("\<Esc>", 'Lx!')
+ let pos = getmousepos()
+ call assert_equal(9, pos.screenrow)
+ call assert_equal(7, pos.screencol)
+
+ let args.cell = 0
+ call test_mswin_event("mouse", args)
+ call feedkeys("\<Esc>", 'Lx!')
+ let pos = getmousepos()
+ call assert_equal(1, pos.screenrow)
+ call assert_equal(1, pos.screencol)
+
+ unlet args
+ endif
+
+ " finish testing mouse movement
+ set mousemev&
+
+ " place the cursor using left click and release in normal mode
+ call MouseLeftClick(2, 4)
+ call MouseLeftRelease(2, 4)
+ if has('gui_running')
+ call feedkeys("\<Esc>", 'Lx!')
+ endif
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+
+ " select and yank a word
+ let @" = ''
+ call MouseLeftClick(1, 9)
+ let args = #{button: 0, row: 1, col: 9, multiclick: 1, modifiers: 0}
+ call test_mswin_event('mouse', args)
+ call MouseLeftRelease(1, 9)
+ call feedkeys("y", 'Lx!')
+ call assert_equal('three', @")
+
+ " create visual selection using right click
+ let @" = ''
+
+ call MouseLeftClick(2 ,6)
+ call MouseLeftRelease(2, 6)
+ call MouseRightClick(2, 13)
+ call MouseRightRelease(2, 13)
+ call feedkeys("y", 'Lx!')
+ call assert_equal('five six', @")
+
+ " paste using middle mouse button
+ let @* = 'abc '
+ call feedkeys('""', 'Lx!')
+ call MouseMiddleClick(1, 9)
+ call MouseMiddleRelease(1, 9)
+ if has('gui_running')
+ call feedkeys("\<Esc>", 'Lx!')
+ endif
+ call assert_equal(['one two abc three', 'four five six'], getline(1, '$'))
+
+ " test mouse scrolling (aka touchpad scrolling.)
+ %d _
+ set scrolloff=0
+ call setline(1, range(1, 100))
+
+ " Scroll Down
+ call MouseWheelDown(2, 1)
+ call MouseWheelDown(2, 1)
+ call MouseWheelDown(2, 1)
+ call feedkeys("H", 'Lx!')
+ call assert_equal(10, line('.'))
+
+ " Scroll Up
+ call MouseWheelUp(2, 1)
+ call MouseWheelUp(2, 1)
+ call feedkeys("H", 'Lx!')
+ call assert_equal(4, line('.'))
+
+ " Shift Scroll Down
+ call MouseShiftWheelDown(2, 1)
+ call feedkeys("H", 'Lx!')
+ " should scroll from where it is (4) + visible buffer height - cmdheight
+ let shift_scroll_height = line('w$') - line('w0') - &cmdheight
+ call assert_equal(4 + shift_scroll_height, line('.'))
+
+ " Shift Scroll Up
+ call MouseShiftWheelUp(2, 1)
+ call feedkeys("H", 'Lx!')
+ call assert_equal(4, line('.'))
+
+ if !has('gui_running')
+ " Shift Scroll Down (using MOD)
+ call MSWinMouseEvent(0x100, 2, 1, 0, 0, 0x04)
+ call feedkeys("H", 'Lx!')
+ " should scroll from where it is (4) + visible buffer height - cmdheight
+ let shift_scroll_height = line('w$') - line('w0') - &cmdheight
+ call assert_equal(4 + shift_scroll_height, line('.'))
+
+ " Shift Scroll Up (using MOD)
+ call MSWinMouseEvent(0x200, 2, 1, 0, 0, 0x04)
+ call feedkeys("H", 'Lx!')
+ call assert_equal(4, line('.'))
+ endif
+
+ set scrolloff&
+
+ %d _
+ set nowrap
+ " make the buffer 500 wide.
+ call setline(1, range(10)->join('')->repeat(50))
+ " Scroll Right
+ call MouseWheelRight(1, 5)
+ call MouseWheelRight(1, 10)
+ call MouseWheelRight(1, 15)
+ call feedkeys('g0', 'Lx!')
+ call assert_equal(19, col('.'))
+
+ " Scroll Left
+ call MouseWheelLeft(1, 15)
+ call MouseWheelLeft(1, 10)
+ call feedkeys('g0', 'Lx!')
+ call assert_equal(7, col('.'))
+
+ " Shift Scroll Right
+ call MouseShiftWheelRight(1, 10)
+ call feedkeys('g0', 'Lx!')
+ " should scroll from where it is (7) + window width
+ call assert_equal(7 + winwidth(0), col('.'))
+
+ " Shift Scroll Left
+ call MouseShiftWheelLeft(1, 50)
+ call feedkeys('g0', 'Lx!')
+ call assert_equal(7, col('.'))
+ set wrap&
+
+ %d _
+ call setline(1, repeat([repeat('a', 60)], 10))
+
+ " record various mouse events
+ let mouseEventNames = [
+ \ 'LeftMouse', 'LeftRelease', '2-LeftMouse', '3-LeftMouse',
+ \ 'S-LeftMouse', 'A-LeftMouse', 'C-LeftMouse', 'MiddleMouse',
+ \ 'MiddleRelease', '2-MiddleMouse', '3-MiddleMouse',
+ \ 'S-MiddleMouse', 'A-MiddleMouse', 'C-MiddleMouse',
+ \ 'RightMouse', 'RightRelease', '2-RightMouse',
+ \ '3-RightMouse', 'S-RightMouse', 'A-RightMouse', 'C-RightMouse',
+ \ ]
+ let mouseEventCodes = map(copy(mouseEventNames), "'<' .. v:val .. '>'")
+ let g:events = []
+ for e in mouseEventCodes
+ exe 'nnoremap ' .. e .. ' <Cmd>call add(g:events, "' ..
+ \ substitute(e, '[<>]', '', 'g') .. '")<CR>'
+ endfor
+
+ " Test various mouse buttons
+ "(0 - Left, 1 - Middle, 2 - Right,
+ " 0x300 - MOUSE_X1/FROM_LEFT_3RD_BUTTON,
+ " 0x400 - MOUSE_X2/FROM_LEFT_4TH_BUTTON)
+ for button in [0, 1, 2, 0x300, 0x400]
+ " Single click
+ let args = #{button: button, row: 2, col: 5, multiclick: 0, modifiers: 0}
+ call test_mswin_event('mouse', args)
+ let args.button = 3
+ call test_mswin_event('mouse', args)
+
+ " Double Click
+ let args.button = button
+ call test_mswin_event('mouse', args)
+ let args.multiclick = 1
+ call test_mswin_event('mouse', args)
+ let args.button = 3
+ let args.multiclick = 0
+ call test_mswin_event('mouse', args)
+
+ " Triple Click
+ let args.button = button
+ call test_mswin_event('mouse', args)
+ let args.multiclick = 1
+ call test_mswin_event('mouse', args)
+ call test_mswin_event('mouse', args)
+ let args.button = 3
+ let args.multiclick = 0
+ call test_mswin_event('mouse', args)
+
+ " Shift click
+ let args = #{button: button, row: 3, col: 7, multiclick: 0, modifiers: 4}
+ call test_mswin_event('mouse', args)
+ let args.button = 3
+ call test_mswin_event('mouse', args)
+
+ " Alt click
+ let args.button = button
+ let args.modifiers = 8
+ call test_mswin_event('mouse', args)
+ let args.button = 3
+ call test_mswin_event('mouse', args)
+
+ " Ctrl click
+ let args.button = button
+ let args.modifiers = 16
+ call test_mswin_event('mouse', args)
+ let args.button = 3
+ call test_mswin_event('mouse', args)
+
+ call feedkeys("\<Esc>", 'Lx!')
+ endfor
+
+ if has('gui_running')
+ call assert_equal(['LeftMouse', 'LeftRelease', 'LeftMouse',
+ \ '2-LeftMouse', 'LeftMouse', '2-LeftMouse', '3-LeftMouse',
+ \ 'S-LeftMouse', 'A-LeftMouse', 'C-LeftMouse', 'MiddleMouse',
+ \ 'MiddleRelease', 'MiddleMouse', '2-MiddleMouse', 'MiddleMouse',
+ \ '2-MiddleMouse', '3-MiddleMouse', 'S-MiddleMouse', 'A-MiddleMouse',
+ \ 'C-MiddleMouse', 'RightMouse', 'RightRelease', 'RightMouse',
+ \ '2-RightMouse', 'RightMouse', '2-RightMouse', '3-RightMouse',
+ \ 'S-RightMouse', 'A-RightMouse', 'C-RightMouse'],
+ \ g:events)
+ else
+ call assert_equal(['MiddleRelease', 'LeftMouse', '2-LeftMouse',
+ \ '3-LeftMouse', 'S-LeftMouse', 'MiddleMouse', '2-MiddleMouse',
+ \ '3-MiddleMouse', 'MiddleMouse', 'S-MiddleMouse', 'RightMouse',
+ \ '2-RightMouse', '3-RightMouse'],
+ \ g:events)
+ endif
+
+ for e in mouseEventCodes
+ exe 'nunmap ' .. e
+ endfor
+
+ bw!
+ call test_override('no_query_mouse', 0)
+ set mousemodel&
+endfunc
+
+
+" Test MS-Windows test_mswin_event error handling
+func Test_mswin_event_error_handling()
+
+ let args = #{button: 0xfff, row: 2, col: 4, move: 0, multiclick: 0, modifiers: 0}
+ if !has('gui_running')
+ call assert_fails("call test_mswin_event('mouse', args)",'E475:')
+ endif
+ let args = #{button: 0, row: 2, col: 4, move: 0, multiclick: 0, modifiers: 0}
+ call assert_fails("call test_mswin_event('a1b2c3', args)", 'E475:')
+ call assert_fails("call test_mswin_event(test_null_string(), {})", 'E475:')
+
+ call assert_fails("call test_mswin_event([], args)", 'E1174:')
+ call assert_fails("call test_mswin_event('abc', [])", 'E1206:')
+
+ call assert_false(test_mswin_event('mouse', test_null_dict()))
+ let args = #{row: 2, col: 4, multiclick: 0, modifiers: 0}
+ call assert_false(test_mswin_event('mouse', args))
+ let args = #{button: 0, col: 4, multiclick: 0, modifiers: 0}
+ call assert_false(test_mswin_event('mouse', args))
+ let args = #{button: 0, row: 2, multiclick: 0, modifiers: 0}
+ call assert_false(test_mswin_event('mouse', args))
+ let args = #{button: 0, row: 2, col: 4, modifiers: 0}
+ call assert_false(test_mswin_event('mouse', args))
+ let args = #{button: 0, row: 2, col: 4, multiclick: 0}
+ call assert_false(test_mswin_event('mouse', args))
+
+ call assert_false(test_mswin_event('key', test_null_dict()))
+ call assert_fails("call test_mswin_event('key', [])", 'E1206:')
+ call assert_fails("call test_mswin_event('key', {'event': 'keydown', 'keycode': 0x0})", 'E1291:')
+ call assert_fails("call test_mswin_event('key', {'event': 'keydown', 'keycode': [15]})", 'E745:')
+ call assert_fails("call test_mswin_event('key', {'event': 'keys', 'keycode': 0x41})", 'E475:')
+ call assert_fails("call test_mswin_event('key', {'keycode': 0x41})", 'E417:')
+ call assert_fails("call test_mswin_event('key', {'event': 'keydown'})", 'E1291:')
+
+ call assert_fails("sandbox call test_mswin_event('key', {'event': 'keydown', 'keycode': 61 })", 'E48:')
+
+ " flush out the typeahead buffer
+ while getchar(0)
+ endwhile
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_mzscheme.vim b/src/testdir/test_mzscheme.vim
new file mode 100644
index 0000000..0bc9842
--- /dev/null
+++ b/src/testdir/test_mzscheme.vim
@@ -0,0 +1,62 @@
+" Test for MzScheme interface and mzeval() function
+
+source check.vim
+CheckFeature mzscheme
+
+func MzRequire()
+ redir => l:mzversion
+ mz (version)
+ redir END
+ if strpart(l:mzversion, 1, 1) < "4"
+ " MzScheme versions < 4.x:
+ mz (require (prefix vim- vimext))
+ else
+ " newer versions:
+ mz (require (prefix-in vim- 'vimext))
+ mz (require r5rs)
+ endif
+endfunc
+
+func Test_mzscheme()
+ new
+ let lines =<< trim END
+ 1 line 1
+ 2 line 2
+ 3 line 3
+ END
+ call setline(1, lines)
+
+ call MzRequire()
+ mz (define l '("item0" "dictionary with list OK" "item2"))
+ mz (define h (make-hash))
+ mz (hash-set! h "list" l)
+
+ call cursor(1, 1)
+ " change buffer contents
+ mz (vim-set-buff-line (vim-eval "line('.')") "1 changed line 1")
+ call assert_equal('1 changed line 1', getline(1))
+
+ " scalar test
+ let tmp_string = mzeval('"string"')
+ let tmp_1000 = '1000'->mzeval()
+ call assert_equal('string1000', tmp_string .. tmp_1000)
+
+ " dictionary containing a list
+ call assert_equal('dictionary with list OK', mzeval("h")["list"][1])
+
+ call cursor(2, 1)
+ " circular list (at the same time test lists containing lists)
+ mz (set-car! (cddr l) l)
+ let l2 = mzeval("h")["list"]
+ call assert_equal(l2[2], l2)
+
+ " funcrefs
+ mz (define vim:max (vim-eval "function('max')"))
+ mz (define m (vim:max '(1 100 8)))
+ let m = mzeval('m')
+ call assert_equal(100, m)
+
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_nested_function.vim b/src/testdir/test_nested_function.vim
new file mode 100644
index 0000000..5599655
--- /dev/null
+++ b/src/testdir/test_nested_function.vim
@@ -0,0 +1,70 @@
+" Tests for nested functions
+
+source check.vim
+
+func NestedFunc()
+ func! Func1()
+ let g:text .= 'Func1 '
+ endfunc
+ call Func1()
+ func! s:func2()
+ let g:text .= 's:func2 '
+ endfunc
+ call s:func2()
+ func! s:_func3()
+ let g:text .= 's:_func3 '
+ endfunc
+ call s:_func3()
+ let fn = 'Func4'
+ func! {fn}()
+ let g:text .= 'Func4 '
+ endfunc
+ call {fn}()
+ let fn = 'func5'
+ func! s:{fn}()
+ let g:text .= 's:func5'
+ endfunc
+ call s:{fn}()
+endfunc
+
+func Test_nested_functions()
+ let g:text = ''
+ call NestedFunc()
+ call assert_equal('Func1 s:func2 s:_func3 Func4 s:func5', g:text)
+endfunction
+
+func Test_nested_argument()
+ func g:X()
+ let g:Y = function('sort')
+ endfunc
+ let g:Y = function('sort')
+ echo g:Y([], g:X())
+ delfunc g:X
+ unlet g:Y
+endfunc
+
+func Recurse(count)
+ if a:count > 0
+ call Recurse(a:count - 1)
+ endif
+endfunc
+
+func Test_max_nesting()
+ " TODO: why does this fail on Windows? Runs out of stack perhaps?
+ CheckNotMSWindows
+
+ let call_depth_here = 2
+ let ex_depth_here = 5
+ set mfd&
+
+ call Recurse(99 - call_depth_here)
+ call assert_fails('call Recurse(' . (100 - call_depth_here) . ')', 'E132:')
+
+ set mfd=210
+ call Recurse(209 - ex_depth_here)
+ call assert_fails('call Recurse(' . (210 - ex_depth_here) . ')', 'E169:')
+
+ set mfd&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_netbeans.py b/src/testdir/test_netbeans.py
new file mode 100644
index 0000000..0d6b096
--- /dev/null
+++ b/src/testdir/test_netbeans.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+#
+# Server that will communicate with Vim through the netbeans interface.
+# Used by test_netbeans.vim.
+#
+# This requires Python 2.6 or later.
+
+from __future__ import print_function
+import socket
+import sys
+import time
+import threading
+import re
+
+try:
+ # Python 3
+ import socketserver
+except ImportError:
+ # Python 2
+ import SocketServer as socketserver
+
+class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
+
+ def process_msgs(self, msgbuf):
+ # Process all the received netbeans commands/responses/events from Vim.
+ # Each one is separated by a newline character. If a partial command
+ # is received, process it later after the rest of it is received.
+ while True:
+ (line, sep, rest) = msgbuf.partition('\n')
+ if sep == '':
+ # received partial line
+ return line
+ msgbuf = rest
+
+ # Process a command only after receiving a newline.
+ response = ''
+ if line.find('Xcmdbuf') > 0:
+ name = line.split('"')[1]
+ response = '1:putBufferNumber!15 "' + name + '"\n'
+ response += '1:startDocumentListen!16\n'
+ elif re.match('1:insert=.* "\\\\n"', line):
+ # extract the command from the previous line
+ cmd = re.search('.*"(.*)"', self.prev_line).group(1)
+
+ # map of test names and the netbeans commands/functions
+ testmap = {
+ 'getCursor_Test' : '0:getCursor/30\n',
+ 'E627_Test' : '0 setReadOnly!31\n',
+ 'E628_Test' : '0:setReadOnly 32\n',
+ 'E632_Test' : '0:getLength/33\n',
+ 'E633_Test' : '0:getText/34\n',
+ 'E634_Test' : '0:remove/35 1 1\n',
+ 'E635_Test' : '0:insert/36 0 "line1\\n"\n',
+ 'E636_Test' : '0:create!37\n',
+ 'E637_Test' : '0:startDocumentListen!38\n',
+ 'E638_Test' : '0:stopDocumentListen!39\n',
+ 'E639_Test' : '0:setTitle!40 "Title"\n',
+ 'E640_Test' : '0:initDone!41\n',
+ 'E641_Test' : '0:putBufferNumber!42 "XSomeBuf"\n',
+ 'E642_Test' : '9:putBufferNumber!43 "XInvalidBuf"\n',
+ 'E643_Test' : '0:setFullName!44 "XSomeBuf"\n',
+ 'E644_Test' : '0:editFile!45 "Xfile3"\n',
+ 'E645_Test' : '0:setVisible!46 T\n',
+ 'E646_Test' : '0:setModified!47 T\n',
+ 'E647_Test' : '0:setDot!48 1/1\n',
+ 'E648_Test' : '0:close!49\n',
+ 'E650_Test' : '0:defineAnnoType!50 1 "abc" "a" "a" 1 1\n',
+ 'E651_Test' : '0:addAnno!51 1 1 1 1\n',
+ 'E652_Test' : '0:getAnno/52 8\n',
+ 'editFile_Test' : '2:editFile!53 "Xfile3"\n',
+ 'getLength_Test' : '2:getLength/54\n',
+ 'getModified_Test' : '2:getModified/55\n',
+ 'getText_Test' : '2:getText/56\n',
+ 'setDot_Test' : '2:setDot!57 3/6\n',
+ 'setDot2_Test' : '2:setDot!57 9\n',
+ 'startDocumentListen_Test' : '2:startDocumentListen!58\n',
+ 'stopDocumentListen_Test' : '2:stopDocumentListen!59\n',
+ 'define_anno_Test' : '2:defineAnnoType!60 1 "s1" "x" "=>" blue none\n',
+ 'E532_Test' : '2:defineAnnoType!61 1 "s1" "x" "=>" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa none\n',
+ 'add_anno_Test' : '2:addAnno!62 1 1 2/1 0\n',
+ 'get_anno_Test' : '2:getAnno/63 1\n',
+ 'remove_anno_Test' : '2:removeAnno!64 1\n',
+ 'getModifiedAll_Test' : '0:getModified/65\n',
+ 'create_Test' : '3:create!66\n',
+ 'setTitle_Test' : '3:setTitle!67 "Xfile4"\n',
+ 'setFullName_Test' : '3:setFullName!68 "Xfile4"\n',
+ 'initDone_Test' : '3:initDone!69\n',
+ 'setVisible_Test' : '3:setVisible!70 T\n',
+ 'setModtime_Test' : '3:setModtime!71 6\n',
+ 'insert_Test' : '3:insert/72 0 "line1\\nline2\\n"\n',
+ 'remove_Test' : '3:remove/73 3 4\n',
+ 'remove_invalid_offset_Test' : '3:remove/74 900 4\n',
+ 'remove_invalid_count_Test' : '3:remove/75 1 800\n',
+ 'guard_Test' : '3:guard!76 8 7\n',
+ 'setModified_Test' : '3:setModified!77 T\n',
+ 'setModifiedClear_Test' : '3:setModified!77 F\n',
+ 'insertDone_Test' : '3:insertDone!78 T F\n',
+ 'saveDone_Test' : '3:saveDone!79\n',
+ 'invalidcmd_Test' : '3:invalidcmd!80\n',
+ 'invalidfunc_Test' : '3:invalidfunc/81\n',
+ 'removeAnno_fail_Test' : '0:removeAnno/82 1\n',
+ 'guard_fail_Test' : '0:guard/83 1 1\n',
+ 'save_fail_Test' : '0:save/84\n',
+ 'netbeansBuffer_fail_Test' : '0:netbeansBuffer/85 T\n',
+ 'setExitDelay_Test' : '0:setExitDelay!86 2\n',
+ 'setReadOnly_Test' : '3:setReadOnly!87 T\n',
+ 'setReadOnlyClear_Test' : '3:setReadOnly!88 F\n',
+ 'save_Test' : '3:save!89\n',
+ 'close_Test' : '3:close!90\n',
+ 'specialKeys_Test' : '0:specialKeys!91 "F12 F13 C-F13"\n',
+ 'nbbufwrite_Test' : '4:editFile!92 "XnbBuffer"\n4:netbeansBuffer!93 T\n',
+ 'startAtomic_Test' : '0:startAtomic!94\n',
+ 'endAtomic_Test' : '0:endAtomic!95\n',
+ 'AnnoScale_Test' : "".join(['2:defineAnnoType!60 ' + str(i) + ' "s' + str(i) + '" "x" "=>" blue none\n' for i in range(2, 26)]),
+ 'detach_Test' : '2:close!96\n1:close!97\nDETACH\n'
+ }
+ # execute the specified test
+ if cmd not in testmap:
+ print("=== invalid command %s ===" % (cmd))
+ else:
+ response = testmap[cmd]
+ elif line.find('disconnect') > 0:
+ # we're done
+ self.server.shutdown()
+ return
+
+ # save the current line, this is used as the test to run after
+ # receiving a newline only line.
+ self.prev_line = line
+
+ if len(response) > 0:
+ self.request.sendall(response.encode('utf-8'))
+ # Write the response into the file, so that the test can knows
+ # the command was sent.
+ with open("Xnetbeans", "a") as myfile:
+ myfile.write('send: ' + response)
+ if self.debug:
+ with open("save_Xnetbeans", "a") as myfile:
+ myfile.write('send: ' + response)
+
+ def handle(self):
+ print("=== socket opened ===")
+ # To preserve the Xnetbeans file as save_Xnetbeans, set debug to 1
+ self.debug = 0
+ self.prev_line = ''
+ msgbuf = ''
+ while True:
+ try:
+ received = self.request.recv(4096).decode('utf-8')
+ except socket.error:
+ print("=== socket error ===")
+ break
+ except IOError:
+ print("=== socket closed ===")
+ break
+ if received == '':
+ print("=== socket closed ===")
+ break
+ print("received: {0}".format(received))
+
+ # Write the received line into the file, so that the test can check
+ # what happened.
+ with open("Xnetbeans", "a") as myfile:
+ myfile.write(received)
+ if self.debug:
+ with open("save_Xnetbeans", "a") as myfile:
+ myfile.write(received)
+
+ # Can receive more than one line in a response or a partial line.
+ # Accumulate all the received characters and process one line at
+ # a time.
+ msgbuf += received
+ msgbuf = self.process_msgs(msgbuf)
+
+class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
+ pass
+
+def writePortInFile(port):
+ # Write the port number in Xportnr, so that the test knows it.
+ f = open("Xportnr", "w")
+ f.write("{0}".format(port))
+ f.close()
+
+if __name__ == "__main__":
+ HOST, PORT = "localhost", 0
+
+ addrs = socket.getaddrinfo(HOST, PORT, 0, 0, socket.IPPROTO_TCP)
+ # Each addr is a (family, type, proto, canonname, sockaddr) tuple
+ sockaddr = addrs[0][4]
+ ThreadedTCPServer.address_family = addrs[0][0]
+
+ server = ThreadedTCPServer(sockaddr[0:2], ThreadedTCPRequestHandler)
+ ip, port = server.server_address[0:2]
+
+ # Start a thread with the server. That thread will then start a new thread
+ # for each connection.
+ server_thread = threading.Thread(target=server.serve_forever)
+ server_thread.start()
+
+ writePortInFile(port)
+
+ print("Listening on port {0}".format(port))
+
+ # Main thread terminates, but the server continues running
+ # until server.shutdown() is called.
+ try:
+ while server_thread.is_alive():
+ server_thread.join(1)
+ except (KeyboardInterrupt, SystemExit):
+ server.shutdown()
diff --git a/src/testdir/test_netbeans.vim b/src/testdir/test_netbeans.vim
new file mode 100644
index 0000000..e458e38
--- /dev/null
+++ b/src/testdir/test_netbeans.vim
@@ -0,0 +1,973 @@
+" Test the netbeans interface.
+
+source check.vim
+CheckFeature netbeans_intg
+
+source shared.vim
+
+let s:python = PythonProg()
+if s:python == ''
+ throw 'Skipped: python program missing'
+endif
+
+" Run "testfunc" after starting the server and stop the server afterwards.
+func s:run_server(testfunc, ...)
+ call RunServer('test_netbeans.py', a:testfunc, a:000)
+endfunc
+
+" Wait for an exception (error) to be thrown. This is used to check whether a
+" message from the netbeans server causes an error. It takes some time for Vim
+" to process a netbeans message. So a sleep is used below to account for this.
+func WaitForError(errcode)
+ let save_exception = ''
+ for i in range(200)
+ try
+ sleep 5m
+ catch
+ let save_exception = v:exception
+ break
+ endtry
+ endfor
+ call assert_match(a:errcode, save_exception)
+endfunc
+
+" Read the "Xnetbeans" file and filter out geometry messages.
+func ReadXnetbeans()
+ let l = readfile("Xnetbeans")
+ " Xnetbeans may include '0:geometry=' messages in the GUI Vim if the window
+ " position, size, or z order are changed. Remove these messages because
+ " these messages will break the assert for the output.
+ return filter(l, 'v:val !~ "^0:geometry="')
+endfunc
+
+func Nb_basic(port)
+ call writefile([], "Xnetbeans", 'D')
+
+ " Last line number in the Xnetbeans file. Used to verify the result of the
+ " communication with the netbeans server
+ let g:last = 0
+
+ " Establish the connection with the netbeans server
+ exe 'nbstart :localhost:' .. a:port .. ':bunny'
+ call assert_true(has("netbeans_enabled"))
+ call WaitFor('len(ReadXnetbeans()) > (g:last + 2)')
+ let l = ReadXnetbeans()
+ call assert_equal(['AUTH bunny',
+ \ '0:version=0 "2.5"',
+ \ '0:startupDone=0'], l[-3:])
+ let g:last += 3
+
+ " Trying to connect again to netbeans server should fail
+ call assert_fails("exe 'nbstart :localhost:' . a:port . ':bunny'", 'E511:')
+
+ " Open the command buffer to communicate with the server
+ split Xcmdbuf
+ let cmdbufnr = bufnr()
+ call WaitFor('len(ReadXnetbeans()) > (g:last + 2)')
+ let l = ReadXnetbeans()
+ call assert_equal('0:fileOpened=0 "Xcmdbuf" T F',
+ \ substitute(l[-3], '".*/', '"', ''))
+ call assert_equal('send: 1:putBufferNumber!15 "Xcmdbuf"',
+ \ substitute(l[-2], '".*/', '"', ''))
+ call assert_equal('1:startDocumentListen!16', l[-1])
+ let g:last += 3
+
+ " Keep the command buffer loaded for communication
+ hide
+
+ sleep 1m
+
+ " getCursor test
+ call writefile(['foo bar', 'foo bar', 'foo bar'], 'Xfile1', 'D')
+ split Xfile1
+ call cursor(3, 4)
+ sleep 10m
+ call appendbufline(cmdbufnr, '$', 'getCursor_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 5)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:getCursor/30', '30 -1 3 3 19'], l[-2:])
+ let g:last += 5
+
+ " Test for E627
+ call appendbufline(cmdbufnr, '$', 'E627_Test')
+ call WaitForError('E627:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0 setReadOnly!31', l[-1])
+ let g:last += 3
+
+ " Test for E628
+ call appendbufline(cmdbufnr, '$', 'E628_Test')
+ call WaitForError('E628:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:setReadOnly 32', l[-1])
+ let g:last += 3
+
+ " Test for E632
+ call appendbufline(cmdbufnr, '$', 'E632_Test')
+ call WaitForError('E632:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:getLength/33', '33 0'], l[-2:])
+ let g:last += 4
+
+ " Test for E633
+ call appendbufline(cmdbufnr, '$', 'E633_Test')
+ call WaitForError('E633:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:getText/34', '34 '], l[-2:])
+ let g:last += 4
+
+ " Test for E634
+ call appendbufline(cmdbufnr, '$', 'E634_Test')
+ call WaitForError('E634:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:remove/35 1 1', '35'], l[-2:])
+ let g:last += 4
+
+ " Test for E635
+ call appendbufline(cmdbufnr, '$', 'E635_Test')
+ call WaitForError('E635:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:insert/36 0 "line1\n"', '36'], l[-2:])
+ let g:last += 4
+
+ " Test for E636
+ call appendbufline(cmdbufnr, '$', 'E636_Test')
+ call WaitForError('E636:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:create!37', l[-1])
+ let g:last += 3
+
+ " Test for E637
+ call appendbufline(cmdbufnr, '$', 'E637_Test')
+ call WaitForError('E637:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:startDocumentListen!38', l[-1])
+ let g:last += 3
+
+ " Test for E638
+ call appendbufline(cmdbufnr, '$', 'E638_Test')
+ call WaitForError('E638:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:stopDocumentListen!39', l[-1])
+ let g:last += 3
+
+ " Test for E639
+ call appendbufline(cmdbufnr, '$', 'E639_Test')
+ call WaitForError('E639:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:setTitle!40 "Title"', l[-1])
+ let g:last += 3
+
+ " Test for E640
+ call appendbufline(cmdbufnr, '$', 'E640_Test')
+ call WaitForError('E640:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:initDone!41', l[-1])
+ let g:last += 3
+
+ " Test for E641
+ call appendbufline(cmdbufnr, '$', 'E641_Test')
+ call WaitForError('E641:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:putBufferNumber!42 "XSomeBuf"', l[-1])
+ let g:last += 3
+
+ " Test for E642
+ call appendbufline(cmdbufnr, '$', 'E642_Test')
+ call WaitForError('E642:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 9:putBufferNumber!43 "XInvalidBuf"', l[-1])
+ let g:last += 3
+
+ " Test for E643
+ call appendbufline(cmdbufnr, '$', 'E643_Test')
+ call WaitForError('E643:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:setFullName!44 "XSomeBuf"', l[-1])
+ let g:last += 3
+
+ enew!
+
+ " Test for E644
+ call appendbufline(cmdbufnr, '$', 'E644_Test')
+ call WaitForError('E644:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:editFile!45 "Xfile3"', l[-1])
+ let g:last += 3
+
+ " Test for E645 (shown only when verbose > 0)
+ call appendbufline(cmdbufnr, '$', 'E645_Test')
+ set verbose=1
+ call WaitForError('E645:')
+ set verbose&
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:setVisible!46 T', l[-1])
+ let g:last += 3
+
+ " Test for E646 (shown only when verbose > 0)
+ call appendbufline(cmdbufnr, '$', 'E646_Test')
+ set verbose=1
+ call WaitForError('E646:')
+ set verbose&
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:setModified!47 T', l[-1])
+ let g:last += 3
+
+ " Test for E647
+ call appendbufline(cmdbufnr, '$', 'E647_Test')
+ call WaitForError('E647:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:setDot!48 1/1', l[-1])
+ let g:last += 3
+
+ " Test for E648
+ call appendbufline(cmdbufnr, '$', 'E648_Test')
+ call WaitForError('E648:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:close!49', l[-1])
+ let g:last += 3
+
+ " Test for E650
+ call appendbufline(cmdbufnr, '$', 'E650_Test')
+ call WaitForError('E650:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:defineAnnoType!50 1 "abc" "a" "a" 1 1', l[-1])
+ let g:last += 3
+
+ " Test for E651
+ call appendbufline(cmdbufnr, '$', 'E651_Test')
+ call WaitForError('E651:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:addAnno!51 1 1 1 1', l[-1])
+ let g:last += 3
+
+ " Test for E652
+ call appendbufline(cmdbufnr, '$', 'E652_Test')
+ call WaitForError('E652:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:getAnno/52 8', '52 0'], l[-2:])
+ let g:last += 4
+
+ " editFile test
+ call writefile(['foo bar1', 'foo bar2', 'foo bar3'], 'Xfile3', 'D')
+ call appendbufline(cmdbufnr, '$', 'editFile_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:editFile!53 "Xfile3"', l[-2])
+ call assert_match('0:fileOpened=0 ".*/Xfile3" T F', l[-1])
+ call assert_equal('Xfile3', bufname())
+ let g:last += 4
+
+ " getLength test
+ call appendbufline(cmdbufnr, '$', 'getLength_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 2:getLength/54', '54 27'], l[-2:])
+ let g:last += 4
+
+ " getModified test
+ call appendbufline(cmdbufnr, '$', 'getModified_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 2:getModified/55', '55 0'], l[-2:])
+ let g:last += 4
+
+ " getText test
+ call appendbufline(cmdbufnr, '$', 'getText_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 2:getText/56',
+ \ '56 "foo bar1\nfoo bar2\nfoo bar3\n"'], l[-2:])
+ let g:last += 4
+
+ " setDot test with lnum/col
+ call cursor(1, 1)
+ call appendbufline(cmdbufnr, '$', 'setDot_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:setDot!57 3/6', l[-1])
+ sleep 10m
+ call assert_equal([0, 3, 7, 0], getpos('.'))
+ let g:last += 3
+
+ " setDot test with an offset
+ call cursor(1, 1)
+ call appendbufline(cmdbufnr, '$', 'setDot2_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:setDot!57 9', l[-1])
+ sleep 10m
+ call assert_equal([0, 2, 1, 0], getpos('.'))
+ let g:last += 3
+
+ " startDocumentListen test
+ call appendbufline(cmdbufnr, '$', 'startDocumentListen_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:startDocumentListen!58', l[-1])
+ let g:last += 3
+
+ " make some changes to the buffer and check whether the netbeans server
+ " received the notifications
+ call append(2, 'blue sky')
+ 1d
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_match('2:insert=\d\+ 18 "blue sky"', l[-3])
+ call assert_match('2:insert=\d\+ 26 "\\n"', l[-2])
+ call assert_match('2:remove=\d\+ 0 9', l[-1])
+ let g:last += 3
+
+ " Change case using the ~ command with 'whichwrap' containing '~'
+ set whichwrap+=~
+ normal 2G$~
+ set whichwrap&
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 2)')
+ let l = ReadXnetbeans()
+ call assert_match('2:remove=\d\+ 16 1', l[-4])
+ call assert_match('2:insert=\d\+ 16 "Y"', l[-3])
+ call assert_match('2:remove=\d\+ 18 0', l[-2])
+ call assert_match('2:insert=\d\+ 18 ""', l[-1])
+ let g:last += 4
+
+ " Test for replacing spaces with a tab character using 'softtabstop' and
+ " 'noexpandtab'
+ setlocal softtabstop=4
+ setlocal noexpandtab
+ exe "normal I\<Tab>\<Tab>"
+ setlocal expandtab&
+ setlocal softtabstop&
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 18)')
+ let l = ReadXnetbeans()
+ call assert_match('2:insert=\d\+ 18 " foo bar3"', l[-3])
+ call assert_match('2:remove=\d\+ 26 8', l[-2])
+ call assert_match('2:insert=\d\+ 26 "\t"', l[-1])
+ let g:last += 18
+
+ " Test for changing case of multiple lines using ~
+ normal ggVG~
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 6)')
+ let l = ReadXnetbeans()
+ call assert_match('2:remove=\d\+ 0 8', l[-6])
+ call assert_match('2:insert=\d\+ 0 "FOO BAR2"', l[-5])
+ call assert_match('2:remove=\d\+ 9 8', l[-4])
+ call assert_match('2:insert=\d\+ 9 "BLUE SKy"', l[-3])
+ call assert_match('2:remove=\d\+ 18 9', l[-2])
+ call assert_match('2:insert=\d\+ 18 "\tFOO BAR3"', l[-1])
+ let g:last += 6
+
+ " Test for changing case of a visual block using ~
+ exe "normal ggw\<C-V>$~"
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 2)')
+ let l = ReadXnetbeans()
+ call assert_match('2:remove=\d\+ 4 4', l[-2])
+ call assert_match('2:insert=\d\+ 4 "bar2"', l[-1])
+ let g:last += 2
+
+ " Increment a number using <C-A> in visual mode
+ exe "normal! gg$v6\<C-A>"
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 6)')
+ let l = ReadXnetbeans()
+ call assert_match('2:remove=\d\+ 0 9', l[-4])
+ call assert_match('2:insert=\d\+ 0 "FOO bar8"', l[-3])
+ call assert_match('2:remove=\d\+ 7 1', l[-2])
+ call assert_match('2:insert=\d\+ 7 "8"', l[-1])
+ let g:last += 6
+
+ " Decrement a number using <C-X> in visual mode
+ exe "normal! gg$v3\<C-X>"
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 6)')
+ let l = ReadXnetbeans()
+ call assert_match('2:remove=\d\+ 0 9', l[-4])
+ call assert_match('2:insert=\d\+ 0 "FOO bar5"', l[-3])
+ call assert_match('2:remove=\d\+ 7 1', l[-2])
+ call assert_match('2:insert=\d\+ 7 "5"', l[-1])
+ let g:last += 6
+
+ " stopDocumentListen test
+ call appendbufline(cmdbufnr, '$', 'stopDocumentListen_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:stopDocumentListen!59', l[-1])
+ let g:last += 3
+
+ " Wait for vim to process the previous netbeans message
+ sleep 1m
+
+ " modify the buffer and make sure that the netbeans server is not notified
+ call append(2, 'clear sky')
+ 1d
+
+ " defineAnnoType test
+ call appendbufline(cmdbufnr, '$', 'define_anno_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:defineAnnoType!60 1 "s1" "x" "=>" blue none', l[-1])
+ sleep 1m
+ call assert_equal({'name': '1', 'texthl': 'NB_s1', 'text': '=>'},
+ \ sign_getdefined()->get(0, {}))
+ let g:last += 3
+
+ " defineAnnoType with a long color name
+ call appendbufline(cmdbufnr, '$', 'E532_Test')
+ call WaitForError('E532:')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:defineAnnoType!61 1 "s1" "x" "=>" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa none', l[-1])
+ let g:last += 3
+
+ " addAnno test
+ call appendbufline(cmdbufnr, '$', 'add_anno_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:addAnno!62 1 1 2/1 0', l[-1])
+ sleep 1m
+ call assert_equal([{'lnum': 2, 'id': 1, 'name': '1', 'priority': 10,
+ \ 'group': ''}], sign_getplaced()[0].signs)
+ let g:last += 3
+
+ " getAnno test
+ call appendbufline(cmdbufnr, '$', 'get_anno_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 2:getAnno/63 1', '63 2'], l[-2:])
+ let g:last += 4
+
+ " removeAnno test
+ call appendbufline(cmdbufnr, '$', 'remove_anno_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 2:removeAnno!64 1', l[-1])
+ sleep 1m
+ call assert_equal([], sign_getplaced())
+ let g:last += 3
+
+ " getModified test to get the number of modified buffers
+ call appendbufline(cmdbufnr, '$', 'getModifiedAll_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:getModified/65', '65 2'], l[-2:])
+ let g:last += 4
+
+ let bufcount = len(getbufinfo())
+
+ " create test to create a new buffer
+ call appendbufline(cmdbufnr, '$', 'create_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:create!66', l[-1])
+ " Wait for vim to process the previous netbeans message
+ sleep 10m
+ call assert_equal(bufcount + 1, len(getbufinfo()))
+ let g:last += 3
+
+ " setTitle test
+ call appendbufline(cmdbufnr, '$', 'setTitle_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setTitle!67 "Xfile4"', l[-1])
+ let g:last += 3
+
+ " setFullName test
+ call appendbufline(cmdbufnr, '$', 'setFullName_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 5)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setFullName!68 "Xfile4"', l[-3])
+ call assert_match('0:fileOpened=0 ".*/Xfile4" T F', l[-1])
+ call assert_equal('Xfile4', bufname())
+ let g:last += 5
+
+ " initDone test
+ call appendbufline(cmdbufnr, '$', 'initDone_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:initDone!69', l[-1])
+ let g:last += 3
+
+ " setVisible test
+ hide enew
+ call appendbufline(cmdbufnr, '$', 'setVisible_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setVisible!70 T', l[-1])
+ let g:last += 3
+
+ " setModtime test
+ call appendbufline(cmdbufnr, '$', 'setModtime_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setModtime!71 6', l[-1])
+ let g:last += 3
+
+ " insert test
+ call appendbufline(cmdbufnr, '$', 'insert_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 3:insert/72 0 "line1\nline2\n"', '72'], l[-2:])
+ call assert_equal(['line1', 'line2'], getline(1, '$'))
+ let g:last += 4
+
+ " remove test
+ call appendbufline(cmdbufnr, '$', 'remove_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 3:remove/73 3 4', '73'], l[-2:])
+ call assert_equal(['linine2'], getline(1, '$'))
+ let g:last += 4
+
+ " remove with invalid offset
+ call appendbufline(cmdbufnr, '$', 'remove_invalid_offset_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 3:remove/74 900 4', '74 !bad position'], l[-2:])
+ let g:last += 4
+
+ " remove with invalid count
+ call appendbufline(cmdbufnr, '$', 'remove_invalid_count_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 3:remove/75 1 800', '75 !bad count'], l[-2:])
+ let g:last += 4
+
+ " guard test
+ %d
+ call setline(1, ['foo bar', 'foo bar', 'foo bar'])
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 8)')
+ let g:last += 8
+
+ call appendbufline(cmdbufnr, '$', 'guard_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:guard!76 8 7', l[-1])
+ sleep 1m
+ " second line is guarded. Try modifying the line
+ call assert_fails('normal 2GIbaz', 'E463:')
+ call assert_fails('normal 2GAbaz', 'E463:')
+ call assert_fails('normal dd', 'E463:')
+ call assert_equal([{'name': '1', 'texthl': 'NB_s1', 'text': '=>'},
+ \ {'name': '10000', 'linehl': 'NBGuarded'}],
+ \ sign_getdefined())
+ let s = sign_getplaced()[0].signs[0]
+ call assert_equal(2, s.lnum)
+ call assert_equal('10000', s.name)
+ let g:last += 3
+
+ " setModified test
+ call appendbufline(cmdbufnr, '$', 'setModified_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setModified!77 T', l[-1])
+ sleep 1m
+ call assert_equal(1, &modified)
+ let g:last += 3
+
+ " clear setModified test
+ call appendbufline(cmdbufnr, '$', 'setModifiedClear_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setModified!77 F', l[-1])
+ sleep 1m
+ call assert_equal(0, &modified)
+ let g:last += 3
+
+ " insertDone test
+ let v:statusmsg = ''
+ call appendbufline(cmdbufnr, '$', 'insertDone_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:insertDone!78 T F', l[-1])
+ sleep 1m
+ call assert_match('.*/Xfile4" 3L, 0B', v:statusmsg)
+ let g:last += 3
+
+ " saveDone test
+ let v:statusmsg = ''
+ call appendbufline(cmdbufnr, '$', 'saveDone_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:saveDone!79', l[-1])
+ sleep 1m
+ call assert_match('.*/Xfile4" 3L, 0B', v:statusmsg)
+ let g:last += 3
+
+ " unimplemented command test
+ call appendbufline(cmdbufnr, '$', 'invalidcmd_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:invalidcmd!80', l[-1])
+ let g:last += 3
+
+ " unimplemented function test
+ call appendbufline(cmdbufnr, '$', 'invalidfunc_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 3:invalidfunc/81', '81'], l[-2:])
+ let g:last += 4
+
+ " Test for removeAnno cmd failure
+ call appendbufline(cmdbufnr, '$', 'removeAnno_fail_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:removeAnno/82 1', '82'], l[-2:])
+ let g:last += 4
+
+ " Test for guard cmd failure
+ call appendbufline(cmdbufnr, '$', 'guard_fail_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:guard/83 1 1', '83'], l[-2:])
+ let g:last += 4
+
+ " Test for save cmd failure
+ call appendbufline(cmdbufnr, '$', 'save_fail_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:save/84', '84'], l[-2:])
+ let g:last += 4
+
+ " Test for netbeansBuffer cmd failure
+ call appendbufline(cmdbufnr, '$', 'netbeansBuffer_fail_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal(['send: 0:netbeansBuffer/85 T', '85'], l[-2:])
+ let g:last += 4
+
+ " nbkey test
+ call cursor(3, 3)
+ nbkey "\<C-F2>"
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal(['3:newDotAndMark=85 18 18',
+ \ '3:keyCommand=85 ""\<C-F2>""',
+ \ '3:keyAtPos=85 ""\<C-F2>"" 18 3/2'], l[-3:])
+ let g:last += 3
+
+ " setExitDelay test
+ call appendbufline(cmdbufnr, '$', 'setExitDelay_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:setExitDelay!86 2', l[-1])
+ let g:last += 3
+
+ " setReadonly test
+ call appendbufline(cmdbufnr, '$', 'setReadOnly_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setReadOnly!87 T', l[-1])
+ sleep 1m
+ call assert_equal(1, &readonly)
+ let g:last += 3
+
+ " clear setReadonly test
+ call appendbufline(cmdbufnr, '$', 'setReadOnlyClear_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:setReadOnly!88 F', l[-1])
+ sleep 1m
+ call assert_equal(0, &readonly)
+ let g:last += 3
+
+ " save test
+ call setbufvar(bufnr('Xfile4'), '&modified', 1)
+ call appendbufline(cmdbufnr, '$', 'save_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:save!89', l[-1])
+ sleep 1m
+ call assert_true(filereadable('Xfile4'))
+ let g:last += 3
+
+ " close test. Don't use buffer 10 after this
+ call appendbufline(cmdbufnr, '$', 'close_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 4)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 3:close!90', l[-2])
+ call assert_equal('3:killed=90', l[-1])
+ call assert_equal(1, winnr('$'))
+ let g:last += 4
+
+ " specialKeys test
+ call appendbufline(cmdbufnr, '$', 'specialKeys_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:specialKeys!91 "F12 F13 C-F13"', l[-1])
+ sleep 1m
+ call assert_equal(':nbkey F12<CR>', maparg('<F12>', 'n'))
+ call assert_equal(':nbkey F13<CR>', maparg('<F13>', 'n'))
+ call assert_equal(':nbkey C-F13<CR>', maparg('<C-F13>', 'n'))
+ let g:last += 3
+
+ " Open a buffer not monitored by netbeans
+ enew | only!
+ nbkey "\<C-F3>"
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 1)')
+ let l = ReadXnetbeans()
+ call assert_equal('0:fileOpened=0 "" T F', l[-1])
+ let g:last += 1
+
+ " Test for writing a netbeans buffer
+ call appendbufline(cmdbufnr, '$', 'nbbufwrite_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 5)')
+ call assert_fails('write', 'E656:')
+ call setline(1, ['one', 'two'])
+ call assert_fails('1write!', 'E657:')
+ write
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 10)')
+ let g:last += 10
+
+ if has('mouse')
+ " Test for mouse button release
+ let save_mouse = &mouse
+ set mouse=a
+ call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
+ let &mouse = save_mouse
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 2)')
+ let l = ReadXnetbeans()
+ call assert_equal('4:newDotAndMark=93 0 0', l[-2])
+ call assert_equal('4:buttonRelease=93 0 1 -1', l[-1])
+ let g:last += 2
+ endif
+
+ " Test for startAtomic
+ call appendbufline(cmdbufnr, '$', 'startAtomic_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:startAtomic!94', l[-1])
+ let g:last += 3
+
+ " Test for endAtomic
+ call appendbufline(cmdbufnr, '$', 'endAtomic_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_equal('send: 0:endAtomic!95', l[-1])
+ let g:last += 3
+
+ " Test for invoking a netbeans key binding
+ let special_keys = [
+ \ ["\<F1>", 'F1'], ["\<S-F1>", 'S-F1'],
+ \ ["\<F2>", 'F2'], ["\<S-F2>", 'S-F2'],
+ \ ["\<F3>", 'F3'], ["\<S-F3>", 'S-F3'],
+ \ ["\<F4>", 'F4'], ["\<S-F4>", 'S-F4'],
+ \ ["\<F5>", 'F5'], ["\<S-F5>", 'S-F5'],
+ \ ["\<F6>", 'F6'], ["\<S-F6>", 'S-F6'],
+ \ ["\<F7>", 'F7'], ["\<S-F7>", 'S-F7'],
+ \ ["\<F8>", 'F8'], ["\<S-F8>", 'S-F8'],
+ \ ["\<F9>", 'F9'], ["\<S-F9>", 'S-F9'],
+ \ ["\<F11>", 'F11'], ["\<S-F11>", 'S-F11'],
+ \ ["\<F12>", 'F12'], ["\<S-F12>", 'S-F12'], ['!', '!']
+ \ ]
+ for [key, name] in special_keys
+ call feedkeys("\<F21>" .. key, 'xt')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_match('4:keyCommand=\d\+ "' .. name .. '"', l[-2])
+ call assert_match('4:keyAtPos=\d\+ "' .. name .. '" 0 1/0', l[-1])
+ let g:last += 3
+ endfor
+ call feedkeys("\<F21>\<C-S-M-F9>", 'xt')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 3)')
+ let l = ReadXnetbeans()
+ call assert_match('4:keyCommand=\d\+ "CSM-F9"', l[-2])
+ call assert_match('4:keyAtPos=\d\+ "CSM-F9" 0 1/0', l[-1])
+ let g:last += 3
+
+ if has('signs') && has('mouse')
+ sign define S1 linehl=Search text==>
+ sign define S2 linehl=ErrorMsg text=!!
+ sign place 10 line=1 name=S1
+ sign place 20 line=1 name=S2
+
+ let save_mouse = &mouse
+ set mouse=a
+ call assert_equal('S2', sign_getplaced()[0].signs[0].name)
+ call test_setmouse(1, 1)
+ call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
+ call assert_equal('S1', sign_getplaced()[0].signs[0].name)
+ call test_setmouse(1, 1)
+ call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
+ call assert_equal('S2', sign_getplaced()[0].signs[0].name)
+ let &mouse = save_mouse
+
+ sign unplace 10
+ sign unplace 20
+ sign undefine S1
+ sign undefine S2
+ endif
+
+ " define a large number of annotations
+ call appendbufline(cmdbufnr, '$', 'AnnoScale_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 26)')
+ let l = ReadXnetbeans()
+ call assert_equal('2:defineAnnoType!60 25 "s25" "x" "=>" blue none', l[-1])
+ sleep 1m
+ call assert_true(len(sign_getdefined()) >= 25)
+ let g:last += 26
+
+ " detach
+ call appendbufline(cmdbufnr, '$', 'detach_Test')
+ call WaitFor('len(ReadXnetbeans()) >= (g:last + 8)')
+ call WaitForAssert({-> assert_equal('0:disconnect=97', ReadXnetbeans()[-1])})
+
+ " the connection was closed
+ call assert_false(has("netbeans_enabled"))
+
+ " Remove all the signs
+ call sign_unplace('*')
+ call sign_undefine()
+
+ call delete('Xfile4')
+endfunc
+
+func Test_nb_basic()
+ call ch_log('Test_nb_basic')
+ call s:run_server('Nb_basic')
+endfunc
+
+func Nb_file_auth(port)
+ call delete("Xnetbeans")
+ call writefile([], "Xnetbeans", 'D')
+
+ call assert_fails('nbstart =notexist', 'E660:')
+ call writefile(['host=localhost', 'port=' . a:port, 'auth=bunny'], 'Xnbauth', 'D')
+ if has('unix')
+ call setfperm('Xnbauth', "rw-r--r--")
+ call assert_fails('nbstart =Xnbauth', 'E668:')
+ endif
+ call setfperm('Xnbauth', "rw-------")
+ exe 'nbstart =Xnbauth'
+ call assert_true(has("netbeans_enabled"))
+
+ call WaitFor('len(ReadXnetbeans()) > 2')
+ nbclose
+ let lines = ReadXnetbeans()
+ call assert_equal('AUTH bunny', lines[0])
+ call assert_equal('0:version=0 "2.5"', lines[1])
+ call assert_equal('0:startupDone=0', lines[2])
+
+ call delete("Xnbauth")
+endfunc
+
+func Test_nb_file_auth()
+ call ch_log('Test_nb_file_auth')
+ call s:run_server('Nb_file_auth')
+endfunc
+
+" Test for quitting Vim with an open netbeans connection
+func Nb_quit_with_conn(port)
+ call delete("Xnetbeans")
+ call writefile([], "Xnetbeans", 'D')
+ let after =<< trim END
+ source shared.vim
+ set cpo&vim
+
+ func ReadXnetbeans()
+ let l = readfile("Xnetbeans")
+ return filter(l, 'v:val !~ "^0:geometry="')
+ endfunc
+
+ try
+ " Establish the connection with the netbeans server
+ exe 'nbstart :localhost:' .. g:port .. ':star'
+ call assert_true(has("netbeans_enabled"))
+ call WaitFor('len(ReadXnetbeans()) >= 3')
+ let l = ReadXnetbeans()
+ call assert_equal(['AUTH star',
+ \ '0:version=0 "2.5"',
+ \ '0:startupDone=0'], l[-3:])
+
+ " Open the command buffer to communicate with the server
+ split Xcmdbuf
+ call WaitFor('len(ReadXnetbeans()) >= 6')
+ let l = ReadXnetbeans()
+ call assert_equal('0:fileOpened=0 "Xcmdbuf" T F',
+ \ substitute(l[-3], '".*/', '"', ''))
+ call assert_equal('send: 1:putBufferNumber!15 "Xcmdbuf"',
+ \ substitute(l[-2], '".*/', '"', ''))
+ call assert_equal('1:startDocumentListen!16', l[-1])
+ sleep 1m
+
+ quit!
+ quit!
+ finally
+ qall!
+ endtry
+ END
+ if RunVim(['let g:port = ' .. a:port], after, '')
+ call WaitFor('len(ReadXnetbeans()) >= 9')
+ let l = ReadXnetbeans()
+ call assert_equal('1:unmodified=16', l[-3])
+ call assert_equal('1:killed=16', l[-2])
+ call assert_equal('0:disconnect=16', l[-1])
+ endif
+endfunc
+
+func Test_nb_quit_with_conn()
+ " Exiting Vim with a netbeans connection doesn't work properly on
+ " MS-Windows.
+ CheckUnix
+ call s:run_server('Nb_quit_with_conn')
+endfunc
+
+func Nb_bwipe_buffer(port)
+ call delete("Xnetbeans")
+ call writefile([], "Xnetbeans")
+
+ " Last line number in the Xnetbeans file. Used to verify the result of the
+ " communication with the netbeans server
+ let g:last = 0
+
+ " Establish the connection with the netbeans server
+ exe 'nbstart :localhost:' .. a:port .. ':bunny'
+ call WaitFor('len(ReadXnetbeans()) > (g:last + 2)')
+ let l = ReadXnetbeans()
+ call assert_equal(['AUTH bunny',
+ \ '0:version=0 "2.5"',
+ \ '0:startupDone=0'], l[-3:])
+ let g:last += 3
+
+ " Open the command buffer to communicate with the server
+ split Xcmdbuf
+ call WaitFor('len(ReadXnetbeans()) > (g:last + 2)')
+ let l = ReadXnetbeans()
+ call assert_equal('0:fileOpened=0 "Xcmdbuf" T F',
+ \ substitute(l[-3], '".*/', '"', ''))
+ call assert_equal('send: 1:putBufferNumber!15 "Xcmdbuf"',
+ \ substitute(l[-2], '".*/', '"', ''))
+ call assert_equal('1:startDocumentListen!16', l[-1])
+ let g:last += 3
+
+ sleep 10m
+endfunc
+
+" This test used to reference a buffer after it was freed leading to an ASAN
+" error.
+func Test_nb_bwipe_buffer()
+ call s:run_server('Nb_bwipe_buffer')
+ silent! %bwipe!
+ sleep 100m
+ nbclose
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim
new file mode 100644
index 0000000..6db5af7
--- /dev/null
+++ b/src/testdir/test_normal.vim
@@ -0,0 +1,3924 @@
+" Test for various Normal mode commands
+
+source shared.vim
+source check.vim
+source view_util.vim
+import './vim9.vim' as v9
+source screendump.vim
+
+func Setup_NewWindow()
+ 10new
+ call setline(1, range(1,100))
+endfunc
+
+func MyFormatExpr()
+ " Adds '->$' at lines having numbers followed by trailing whitespace
+ for ln in range(v:lnum, v:lnum+v:count-1)
+ let line = getline(ln)
+ if getline(ln) =~# '\d\s\+$'
+ call setline(ln, substitute(line, '\s\+$', '', '') . '->$')
+ endif
+ endfor
+endfunc
+
+func CountSpaces(type, ...)
+ " for testing operatorfunc
+ " will count the number of spaces
+ " and return the result in g:a
+ let sel_save = &selection
+ let &selection = "inclusive"
+ let reg_save = @@
+
+ if a:0 " Invoked from Visual mode, use gv command.
+ silent exe "normal! gvy"
+ elseif a:type == 'line'
+ silent exe "normal! '[V']y"
+ else
+ silent exe "normal! `[v`]y"
+ endif
+ let g:a = strlen(substitute(@@, '[^ ]', '', 'g'))
+ let &selection = sel_save
+ let @@ = reg_save
+endfunc
+
+func OpfuncDummy(type, ...)
+ " for testing operatorfunc
+ let g:opt = &linebreak
+
+ if a:0 " Invoked from Visual mode, use gv command.
+ silent exe "normal! gvy"
+ elseif a:type == 'line'
+ silent exe "normal! '[V']y"
+ else
+ silent exe "normal! `[v`]y"
+ endif
+ " Create a new dummy window
+ new
+ let g:bufnr = bufnr('%')
+endfunc
+
+func Test_normal00_optrans()
+ new
+ call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line'])
+ 1
+ exe "norm! Sfoobar\<esc>"
+ call assert_equal(['foobar', '2 This is the second line', '3 this is the third line', ''], getline(1,'$'))
+ 2
+ exe "norm! $vbsone"
+ call assert_equal(['foobar', '2 This is the second one', '3 this is the third line', ''], getline(1,'$'))
+ norm! VS Second line here
+ call assert_equal(['foobar', ' Second line here', '3 this is the third line', ''], getline(1, '$'))
+ %d
+ call append(0, ['4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line'])
+ call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line'])
+
+ 1
+ norm! 2D
+ call assert_equal(['3 this is the third line', '4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line', ''], getline(1,'$'))
+ set cpo+=#
+ norm! 4D
+ call assert_equal(['', '4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line', ''], getline(1,'$'))
+
+ " clean up
+ set cpo-=#
+ bw!
+endfunc
+
+func Test_normal01_keymodel()
+ call Setup_NewWindow()
+ " Test 1: depending on 'keymodel' <s-down> does something different
+ 50
+ call feedkeys("V\<S-Up>y", 'tx')
+ call assert_equal(['47', '48', '49', '50'], getline("'<", "'>"))
+ set keymodel=startsel
+ 50
+ call feedkeys("V\<S-Up>y", 'tx')
+ call assert_equal(['49', '50'], getline("'<", "'>"))
+ " Start visual mode when keymodel = startsel
+ 50
+ call feedkeys("\<S-Up>y", 'tx')
+ call assert_equal(['49', '5'], getreg(0, 0, 1))
+ " Use the different Shift special keys
+ 50
+ call feedkeys("\<S-Right>\<S-Left>\<S-Up>\<S-Down>\<S-Home>\<S-End>y", 'tx')
+ call assert_equal(['50'], getline("'<", "'>"))
+ call assert_equal(['50', ''], getreg(0, 0, 1))
+
+ " Do not start visual mode when keymodel=
+ set keymodel=
+ 50
+ call feedkeys("\<S-Up>y$", 'tx')
+ call assert_equal(['42'], getreg(0, 0, 1))
+ " Stop visual mode when keymodel=stopsel
+ set keymodel=stopsel
+ 50
+ call feedkeys("Vkk\<Up>yy", 'tx')
+ call assert_equal(['47'], getreg(0, 0, 1))
+
+ set keymodel=
+ 50
+ call feedkeys("Vkk\<Up>yy", 'tx')
+ call assert_equal(['47', '48', '49', '50'], getreg(0, 0, 1))
+
+ " Test for using special keys to start visual selection
+ %d
+ call setline(1, ['red fox tail', 'red fox tail', 'red fox tail'])
+ set keymodel=startsel
+ " Test for <S-PageUp> and <S-PageDown>
+ call cursor(1, 1)
+ call feedkeys("\<S-PageDown>y", 'xt')
+ call assert_equal([0, 1, 1, 0], getpos("'<"))
+ call assert_equal([0, 3, 1, 0], getpos("'>"))
+ call feedkeys("Gz\<CR>8|\<S-PageUp>y", 'xt')
+ call assert_equal([0, 2, 1, 0], getpos("'<"))
+ call assert_equal([0, 3, 8, 0], getpos("'>"))
+ " Test for <S-C-Home> and <S-C-End>
+ call cursor(2, 12)
+ call feedkeys("\<S-C-Home>y", 'xt')
+ call assert_equal([0, 1, 1, 0], getpos("'<"))
+ call assert_equal([0, 2, 12, 0], getpos("'>"))
+ call cursor(1, 4)
+ call feedkeys("\<S-C-End>y", 'xt')
+ call assert_equal([0, 1, 4, 0], getpos("'<"))
+ call assert_equal([0, 3, 13, 0], getpos("'>"))
+ " Test for <S-C-Left> and <S-C-Right>
+ call cursor(2, 5)
+ call feedkeys("\<S-C-Right>y", 'xt')
+ call assert_equal([0, 2, 5, 0], getpos("'<"))
+ call assert_equal([0, 2, 9, 0], getpos("'>"))
+ call cursor(2, 9)
+ call feedkeys("\<S-C-Left>y", 'xt')
+ call assert_equal([0, 2, 5, 0], getpos("'<"))
+ call assert_equal([0, 2, 9, 0], getpos("'>"))
+
+ set keymodel&
+
+ " clean up
+ bw!
+endfunc
+
+func Test_normal03_join()
+ " basic join test
+ call Setup_NewWindow()
+ 50
+ norm! VJ
+ call assert_equal('50 51', getline('.'))
+ $
+ norm! J
+ call assert_equal('100', getline('.'))
+ $
+ norm! V9-gJ
+ call assert_equal('919293949596979899100', getline('.'))
+ call setline(1, range(1,100))
+ $
+ :j 10
+ call assert_equal('100', getline('.'))
+ call assert_beeps('normal GVJ')
+ " clean up
+ bw!
+endfunc
+
+" basic filter test
+func Test_normal04_filter()
+ " only test on non windows platform
+ CheckNotMSWindows
+ call Setup_NewWindow()
+ 1
+ call feedkeys("!!sed -e 's/^/| /'\n", 'tx')
+ call assert_equal('| 1', getline('.'))
+ 90
+ :sil :!echo one
+ call feedkeys('.', 'tx')
+ call assert_equal('| 90', getline('.'))
+ 95
+ set cpo+=!
+ " 2 <CR>, 1: for executing the command,
+ " 2: clear hit-enter-prompt
+ call feedkeys("!!\n", 'tx')
+ call feedkeys(":!echo one\n\n", 'tx')
+ call feedkeys(".", 'tx')
+ call assert_equal('one', getline('.'))
+ set cpo-=!
+ bw!
+endfunc
+
+func Test_normal05_formatexpr()
+ " basic formatexpr test
+ call Setup_NewWindow()
+ %d_
+ call setline(1, ['here: 1 ', '2', 'here: 3 ', '4', 'not here: '])
+ 1
+ set formatexpr=MyFormatExpr()
+ norm! gqG
+ call assert_equal(['here: 1->$', '2', 'here: 3->$', '4', 'not here: '], getline(1,'$'))
+ set formatexpr=
+ bw!
+endfunc
+
+func Test_normal05_formatexpr_newbuf()
+ " Edit another buffer in the 'formatexpr' function
+ new
+ func! Format()
+ edit another
+ endfunc
+ set formatexpr=Format()
+ norm gqG
+ bw!
+ set formatexpr=
+endfunc
+
+func Test_normal05_formatexpr_setopt()
+ " Change the 'formatexpr' value in the function
+ new
+ func! Format()
+ set formatexpr=
+ endfunc
+ set formatexpr=Format()
+ norm gqG
+ bw!
+ set formatexpr=
+endfunc
+
+" When 'formatexpr' returns non-zero, internal formatting is used.
+func Test_normal_formatexpr_returns_nonzero()
+ new
+ call setline(1, ['one', 'two'])
+ func! Format()
+ return 1
+ endfunc
+ setlocal formatexpr=Format()
+ normal VGgq
+ call assert_equal(['one two'], getline(1, '$'))
+
+ setlocal formatexpr=
+ delfunc Format
+ bwipe!
+endfunc
+
+" Test for using a script-local function for 'formatexpr'
+func Test_formatexpr_scriptlocal_func()
+ func! s:Format()
+ let g:FormatArgs = [v:lnum, v:count]
+ endfunc
+ set formatexpr=s:Format()
+ call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ new | only
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 2GVjgq
+ call assert_equal([2, 2], g:FormatArgs)
+ bw!
+ set formatexpr=<SID>Format()
+ call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ new | only
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 4GVjgq
+ call assert_equal([4, 2], g:FormatArgs)
+ bw!
+ let &formatexpr = 's:Format()'
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ new | only
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 6GVjgq
+ call assert_equal([6, 2], g:FormatArgs)
+ bw!
+ let &formatexpr = '<SID>Format()'
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ new | only
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 8GVjgq
+ call assert_equal([8, 2], g:FormatArgs)
+ bw!
+ setlocal formatexpr=
+ setglobal formatexpr=s:Format()
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ call assert_equal('', &formatexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 10GVjgq
+ call assert_equal([10, 2], g:FormatArgs)
+ bw!
+ setglobal formatexpr=<SID>Format()
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ call assert_equal('', &formatexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 12GVjgq
+ call assert_equal([12, 2], g:FormatArgs)
+ bw!
+ let &g:formatexpr = 's:Format()'
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ call assert_equal('', &formatexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 14GVjgq
+ call assert_equal([14, 2], g:FormatArgs)
+ bw!
+ let &g:formatexpr = '<SID>Format()'
+ call assert_equal(expand('<SID>') .. 'Format()', &g:formatexpr)
+ call assert_equal('', &formatexpr)
+ new
+ call assert_equal(expand('<SID>') .. 'Format()', &formatexpr)
+ call setline(1, range(1, 40))
+ let g:FormatArgs = []
+ normal! 16GVjgq
+ call assert_equal([16, 2], g:FormatArgs)
+ bw!
+ set formatexpr=
+ delfunc s:Format
+ bw!
+endfunc
+
+" basic test for formatprg
+func Test_normal06_formatprg()
+ " only test on non windows platform
+ CheckNotMSWindows
+
+ " uses sed to number non-empty lines
+ call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh', 'D')
+ call system('chmod +x ./Xsed_format.sh')
+ let text = ['a', '', 'c', '', ' ', 'd', 'e']
+ let expected = ['1 a', '', '3 c', '', '5 ', '6 d', '7 e']
+
+ 10new
+ call setline(1, text)
+ set formatprg=./Xsed_format.sh
+ norm! gggqG
+ call assert_equal(expected, getline(1, '$'))
+ %d
+
+ call setline(1, text)
+ set formatprg=donothing
+ setlocal formatprg=./Xsed_format.sh
+ norm! gggqG
+ call assert_equal(expected, getline(1, '$'))
+ %d
+
+ " Check for the command-line ranges added to 'formatprg'
+ set formatprg=cat
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ call feedkeys('gggqG', 'xt')
+ call assert_equal('.,$!cat', @:)
+ call feedkeys('2Ggq2j', 'xt')
+ call assert_equal('.,.+2!cat', @:)
+
+ bw!
+ " clean up
+ set formatprg=
+ setlocal formatprg=
+endfunc
+
+func Test_normal07_internalfmt()
+ " basic test for internal formmatter to textwidth of 12
+ let list=range(1,11)
+ call map(list, 'v:val." "')
+ 10new
+ call setline(1, list)
+ set tw=12
+ norm! ggVGgq
+ call assert_equal(['1 2 3', '4 5 6', '7 8 9', '10 11 '], getline(1, '$'))
+ " clean up
+ set tw=0
+ bw!
+endfunc
+
+" basic tests for foldopen/folddelete
+func Test_normal08_fold()
+ CheckFeature folding
+ call Setup_NewWindow()
+ 50
+ setl foldenable fdm=marker
+ " First fold
+ norm! V4jzf
+ " check that folds have been created
+ call assert_equal(['50/*{{{*/', '51', '52', '53', '54/*}}}*/'], getline(50,54))
+ " Second fold
+ 46
+ norm! V10jzf
+ " check that folds have been created
+ call assert_equal('46/*{{{*/', getline(46))
+ call assert_equal('60/*}}}*/', getline(60))
+ norm! k
+ call assert_equal('45', getline('.'))
+ norm! j
+ call assert_equal('46/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('61', getline('.'))
+ norm! k
+ " open a fold
+ norm! Vzo
+ norm! k
+ call assert_equal('45', getline('.'))
+ norm! j
+ call assert_equal('46/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('47', getline('.'))
+ norm! k
+ norm! zcVzO
+ call assert_equal('46/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('47', getline('.'))
+ norm! j
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51', getline('.'))
+ " delete folds
+ :46
+ " collapse fold
+ norm! V14jzC
+ " delete all folds recursively
+ norm! VzD
+ call assert_equal(['46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60'], getline(46,60))
+
+ " clean up
+ setl nofoldenable fdm=marker
+ bw!
+endfunc
+
+func Test_normal09a_operatorfunc()
+ " Test operatorfunc
+ call Setup_NewWindow()
+ " Add some spaces for counting
+ 50,60s/$/ /
+ unlet! g:a
+ let g:a=0
+ nmap <buffer><silent> ,, :set opfunc=CountSpaces<CR>g@
+ vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR>
+ 50
+ norm V2j,,
+ call assert_equal(6, g:a)
+ norm V,,
+ call assert_equal(2, g:a)
+ norm ,,l
+ call assert_equal(0, g:a)
+ 50
+ exe "norm 0\<c-v>10j2l,,"
+ call assert_equal(11, g:a)
+ 50
+ norm V10j,,
+ call assert_equal(22, g:a)
+
+ " clean up
+ unmap <buffer> ,,
+ set opfunc=
+ unlet! g:a
+ bw!
+endfunc
+
+func Test_normal09b_operatorfunc()
+ " Test operatorfunc
+ call Setup_NewWindow()
+ " Add some spaces for counting
+ 50,60s/$/ /
+ unlet! g:opt
+ set linebreak
+ nmap <buffer><silent> ,, :set opfunc=OpfuncDummy<CR>g@
+ 50
+ norm ,,j
+ exe "bd!" g:bufnr
+ call assert_true(&linebreak)
+ call assert_equal(g:opt, &linebreak)
+ set nolinebreak
+ norm ,,j
+ exe "bd!" g:bufnr
+ call assert_false(&linebreak)
+ call assert_equal(g:opt, &linebreak)
+
+ " clean up
+ unmap <buffer> ,,
+ set opfunc=
+ call assert_fails('normal Vg@', 'E774:')
+ bw!
+ unlet! g:opt
+endfunc
+
+func OperatorfuncRedo(_)
+ let g:opfunc_count = v:count
+endfunc
+
+func Underscorize(_)
+ normal! '[V']r_
+endfunc
+
+func Test_normal09c_operatorfunc()
+ " Test redoing operatorfunc
+ new
+ call setline(1, 'some text')
+ set operatorfunc=OperatorfuncRedo
+ normal v3g@
+ call assert_equal(3, g:opfunc_count)
+ let g:opfunc_count = 0
+ normal .
+ call assert_equal(3, g:opfunc_count)
+
+ bw!
+ unlet g:opfunc_count
+
+ " Test redoing Visual mode
+ set operatorfunc=Underscorize
+ new
+ call setline(1, ['first', 'first', 'third', 'third', 'second'])
+ normal! 1GVjg@
+ normal! 5G.
+ normal! 3G.
+ call assert_equal(['_____', '_____', '_____', '_____', '______'], getline(1, '$'))
+ bwipe!
+ set operatorfunc=
+endfunc
+
+" Test for different ways of setting the 'operatorfunc' option
+func Test_opfunc_callback()
+ new
+ func OpFunc1(callnr, type)
+ let g:OpFunc1Args = [a:callnr, a:type]
+ endfunc
+ func OpFunc2(type)
+ let g:OpFunc2Args = [a:type]
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &opfunc = 'g:OpFunc2'
+ LET g:OpFunc2Args = []
+ normal! g@l
+ call assert_equal(['char'], g:OpFunc2Args)
+
+ #" Test for using a function()
+ set opfunc=function('g:OpFunc1',\ [10])
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([10, 'char'], g:OpFunc1Args)
+
+ #" Using a funcref variable to set 'operatorfunc'
+ VAR Fn = function('g:OpFunc1', [11])
+ LET &opfunc = Fn
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([11, 'char'], g:OpFunc1Args)
+
+ #" Using a string(funcref_variable) to set 'operatorfunc'
+ LET Fn = function('g:OpFunc1', [12])
+ LET &operatorfunc = string(Fn)
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([12, 'char'], g:OpFunc1Args)
+
+ #" Test for using a funcref()
+ set operatorfunc=funcref('g:OpFunc1',\ [13])
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([13, 'char'], g:OpFunc1Args)
+
+ #" Using a funcref variable to set 'operatorfunc'
+ LET Fn = funcref('g:OpFunc1', [14])
+ LET &opfunc = Fn
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([14, 'char'], g:OpFunc1Args)
+
+ #" Using a string(funcref_variable) to set 'operatorfunc'
+ LET Fn = funcref('g:OpFunc1', [15])
+ LET &opfunc = string(Fn)
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([15, 'char'], g:OpFunc1Args)
+
+ #" Test for using a lambda function using set
+ VAR optval = "LSTART a LMIDDLE OpFunc1(16, a) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set opfunc=" .. optval
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([16, 'char'], g:OpFunc1Args)
+
+ #" Test for using a lambda function using LET
+ LET &opfunc = LSTART a LMIDDLE OpFunc1(17, a) LEND
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([17, 'char'], g:OpFunc1Args)
+
+ #" Set 'operatorfunc' to a string(lambda expression)
+ LET &opfunc = 'LSTART a LMIDDLE OpFunc1(18, a) LEND'
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([18, 'char'], g:OpFunc1Args)
+
+ #" Set 'operatorfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a LMIDDLE OpFunc1(19, a) LEND
+ LET &opfunc = Lambda
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([19, 'char'], g:OpFunc1Args)
+
+ #" Set 'operatorfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a LMIDDLE OpFunc1(20, a) LEND
+ LET &opfunc = string(Lambda)
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([20, 'char'], g:OpFunc1Args)
+
+ #" Try to use 'operatorfunc' after the function is deleted
+ func g:TmpOpFunc1(type)
+ let g:TmpOpFunc1Args = [21, a:type]
+ endfunc
+ LET &opfunc = function('g:TmpOpFunc1')
+ delfunc g:TmpOpFunc1
+ call test_garbagecollect_now()
+ LET g:TmpOpFunc1Args = []
+ call assert_fails('normal! g@l', 'E117:')
+ call assert_equal([], g:TmpOpFunc1Args)
+
+ #" Try to use a function with two arguments for 'operatorfunc'
+ func g:TmpOpFunc2(x, y)
+ let g:TmpOpFunc2Args = [a:x, a:y]
+ endfunc
+ set opfunc=TmpOpFunc2
+ LET g:TmpOpFunc2Args = []
+ call assert_fails('normal! g@l', 'E119:')
+ call assert_equal([], g:TmpOpFunc2Args)
+ delfunc TmpOpFunc2
+
+ #" Try to use a lambda function with two arguments for 'operatorfunc'
+ LET &opfunc = LSTART a, b LMIDDLE OpFunc1(22, b) LEND
+ LET g:OpFunc1Args = []
+ call assert_fails('normal! g@l', 'E119:')
+ call assert_equal([], g:OpFunc1Args)
+
+ #" Test for clearing the 'operatorfunc' option
+ set opfunc=''
+ set opfunc&
+ call assert_fails("set opfunc=function('abc')", "E700:")
+ call assert_fails("set opfunc=funcref('abc')", "E700:")
+
+ #" set 'operatorfunc' to a non-existing function
+ LET &opfunc = function('g:OpFunc1', [23])
+ call assert_fails("set opfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &opfunc = function('NonExistingFunc')", 'E700:')
+ LET g:OpFunc1Args = []
+ normal! g@l
+ call assert_equal([23, 'char'], g:OpFunc1Args)
+ END
+ call v9.CheckTransLegacySuccess(lines)
+
+ " Test for using a script-local function name
+ func s:OpFunc3(type)
+ let g:OpFunc3Args = [a:type]
+ endfunc
+ set opfunc=s:OpFunc3
+ let g:OpFunc3Args = []
+ normal! g@l
+ call assert_equal(['char'], g:OpFunc3Args)
+
+ let &opfunc = 's:OpFunc3'
+ let g:OpFunc3Args = []
+ normal! g@l
+ call assert_equal(['char'], g:OpFunc3Args)
+ delfunc s:OpFunc3
+
+ " Using Vim9 lambda expression in legacy context should fail
+ set opfunc=(a)\ =>\ OpFunc1(24,\ a)
+ let g:OpFunc1Args = []
+ call assert_fails('normal! g@l', 'E117:')
+ call assert_equal([], g:OpFunc1Args)
+
+ " set 'operatorfunc' to a partial with dict. This used to cause a crash.
+ func SetOpFunc()
+ let operator = {'execute': function('OperatorExecute')}
+ let &opfunc = operator.execute
+ endfunc
+ func OperatorExecute(_) dict
+ endfunc
+ call SetOpFunc()
+ call test_garbagecollect_now()
+ set operatorfunc=
+ delfunc SetOpFunc
+ delfunc OperatorExecute
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ def g:Vim9opFunc(val: number, type: string): void
+ g:OpFunc1Args = [val, type]
+ enddef
+
+ # Test for using a def function with opfunc
+ set opfunc=function('g:Vim9opFunc',\ [60])
+ g:OpFunc1Args = []
+ normal! g@l
+ assert_equal([60, 'char'], g:OpFunc1Args)
+
+ # Test for using a global function name
+ &opfunc = g:OpFunc2
+ g:OpFunc2Args = []
+ normal! g@l
+ assert_equal(['char'], g:OpFunc2Args)
+ bw!
+
+ # Test for using a script-local function name
+ def LocalOpFunc(type: string): void
+ g:LocalOpFuncArgs = [type]
+ enddef
+ &opfunc = LocalOpFunc
+ g:LocalOpFuncArgs = []
+ normal! g@l
+ assert_equal(['char'], g:LocalOpFuncArgs)
+ bw!
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ " setting 'opfunc' to a script local function outside of a script context
+ " should fail
+ let cleanup =<< trim END
+ call writefile([execute('messages')], 'Xtest.out')
+ qall
+ END
+ call writefile(cleanup, 'Xverify.vim', 'D')
+ call RunVim([], [], "-c \"set opfunc=s:abc\" -S Xverify.vim")
+ call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+ call delete('Xtest.out')
+
+ " cleanup
+ set opfunc&
+ delfunc OpFunc1
+ delfunc OpFunc2
+ unlet g:OpFunc1Args g:OpFunc2Args
+ %bw!
+endfunc
+
+func Test_normal10_expand()
+ " Test for expand()
+ 10new
+ call setline(1, ['1', 'ifooar,,cbar'])
+ 2
+ norm! $
+ call assert_equal('cbar', expand('<cword>'))
+ call assert_equal('ifooar,,cbar', expand('<cWORD>'))
+
+ call setline(1, ['prx = list[idx];'])
+ 1
+ let expected = ['', 'prx', 'prx', 'prx',
+ \ 'list', 'list', 'list', 'list', 'list', 'list', 'list',
+ \ 'idx', 'idx', 'idx', 'idx',
+ \ 'list[idx]',
+ \ '];',
+ \ ]
+ for i in range(1, 16)
+ exe 'norm ' . i . '|'
+ call assert_equal(expected[i], expand('<cexpr>'), 'i == ' . i)
+ endfor
+
+ " Test for <cexpr> in state.val and ptr->val
+ call setline(1, 'x = state.val;')
+ call cursor(1, 10)
+ call assert_equal('state.val', expand('<cexpr>'))
+ call setline(1, 'x = ptr->val;')
+ call cursor(1, 9)
+ call assert_equal('ptr->val', expand('<cexpr>'))
+
+ if executable('echo')
+ " Test expand(`...`) i.e. backticks command expansion.
+ call assert_equal('abcde', expand('`echo abcde`'))
+ endif
+
+ " Test expand(`=...`) i.e. backticks expression expansion
+ call assert_equal('5', expand('`=2+3`'))
+ call assert_equal('3.14', expand('`=3.14`'))
+
+ " clean up
+ bw!
+endfunc
+
+" Test for expand() in latin1 encoding
+func Test_normal_expand_latin1()
+ new
+ let save_enc = &encoding
+ set encoding=latin1
+ call setline(1, 'val = item->color;')
+ call cursor(1, 11)
+ call assert_equal('color', expand("<cword>"))
+ call assert_equal('item->color', expand("<cexpr>"))
+ let &encoding = save_enc
+ bw!
+endfunc
+
+func Test_normal11_showcmd()
+ " test for 'showcmd'
+ 10new
+ exe "norm! ofoobar\<esc>"
+ call assert_equal(2, line('$'))
+ set showcmd
+ exe "norm! ofoobar2\<esc>"
+ call assert_equal(3, line('$'))
+ exe "norm! VAfoobar3\<esc>"
+ call assert_equal(3, line('$'))
+ exe "norm! 0d3\<del>2l"
+ call assert_equal('obar2foobar3', getline('.'))
+ " test for the visual block size displayed in the status line
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc'])
+ call feedkeys("ggl\<C-V>lljj", 'xt')
+ redraw!
+ call assert_match('3x3$', Screenline(&lines))
+ call feedkeys("\<C-V>", 'xt')
+ " test for visually selecting a multi-byte character
+ call setline(1, ["\U2206"])
+ call feedkeys("ggv", 'xt')
+ redraw!
+ call assert_match('1-3$', Screenline(&lines))
+ call feedkeys("v", 'xt')
+ " test for visually selecting the end of line
+ call setline(1, ["foobar"])
+ call feedkeys("$vl", 'xt')
+ redraw!
+ call assert_match('2$', Screenline(&lines))
+ call feedkeys("y", 'xt')
+ call assert_equal("r\n", @")
+ bw!
+endfunc
+
+" Test for nv_error and normal command errors
+func Test_normal12_nv_error()
+ 10new
+ call setline(1, range(1,5))
+ " should not do anything, just beep
+ call assert_beeps('exe "norm! <c-k>"')
+ call assert_equal(map(range(1,5), 'string(v:val)'), getline(1,'$'))
+ call assert_beeps('normal! G2dd')
+ call assert_beeps("normal! g\<C-A>")
+ call assert_beeps("normal! g\<C-X>")
+ call assert_beeps("normal! g\<C-B>")
+ call assert_beeps("normal! vQ\<Esc>")
+ call assert_beeps("normal! 2[[")
+ call assert_beeps("normal! 2]]")
+ call assert_beeps("normal! 2[]")
+ call assert_beeps("normal! 2][")
+ call assert_beeps("normal! 4[z")
+ call assert_beeps("normal! 4]z")
+ call assert_beeps("normal! 4[c")
+ call assert_beeps("normal! 4]c")
+ call assert_beeps("normal! 200%")
+ call assert_beeps("normal! %")
+ call assert_beeps("normal! 2{")
+ call assert_beeps("normal! 2}")
+ call assert_beeps("normal! r\<Right>")
+ call assert_beeps("normal! 8ry")
+ call assert_beeps('normal! "@')
+ bw!
+endfunc
+
+func Test_normal13_help()
+ " Test for F1
+ call assert_equal(1, winnr())
+ call feedkeys("\<f1>", 'txi')
+ call assert_match('help\.txt', bufname('%'))
+ call assert_equal(2, winnr('$'))
+ bw!
+endfunc
+
+func Test_normal14_page()
+ " basic test for Ctrl-F and Ctrl-B
+ call Setup_NewWindow()
+ exe "norm! \<c-f>"
+ call assert_equal('9', getline('.'))
+ exe "norm! 2\<c-f>"
+ call assert_equal('25', getline('.'))
+ exe "norm! 2\<c-b>"
+ call assert_equal('18', getline('.'))
+ 1
+ set scrolloff=5
+ exe "norm! 2\<c-f>"
+ call assert_equal('21', getline('.'))
+ exe "norm! \<c-b>"
+ call assert_equal('13', getline('.'))
+ 1
+ set scrolloff=99
+ exe "norm! \<c-f>"
+ call assert_equal('13', getline('.'))
+ set scrolloff=0
+ 100
+ exe "norm! $\<c-b>"
+ call assert_equal('92', getline('.'))
+ call assert_equal([0, 92, 1, 0, 1], getcurpos())
+ 100
+ set nostartofline
+ exe "norm! $\<c-b>"
+ call assert_equal('92', getline('.'))
+ call assert_equal([0, 92, 2, 0, v:maxcol], getcurpos())
+ " cleanup
+ set startofline
+ bw!
+endfunc
+
+func Test_normal14_page_eol()
+ 10new
+ norm oxxxxxxx
+ exe "norm 2\<c-f>"
+ " check with valgrind that cursor is put back in column 1
+ exe "norm 2\<c-b>"
+ bw!
+endfunc
+
+" Test for errors with z command
+func Test_normal_z_error()
+ call assert_beeps('normal! z2p')
+ call assert_beeps('normal! zq')
+ call assert_beeps('normal! cz1')
+endfunc
+
+func Test_normal15_z_scroll_vert()
+ " basic test for z commands that scroll the window
+ call Setup_NewWindow()
+ 100
+ norm! >>
+ " Test for z<cr>
+ exe "norm! z\<cr>"
+ call assert_equal(' 100', getline('.'))
+ call assert_equal(100, winsaveview()['topline'])
+ call assert_equal([0, 100, 2, 0, 9], getcurpos())
+
+ " Test for zt
+ 21
+ norm! >>0zt
+ call assert_equal(' 21', getline('.'))
+ call assert_equal(21, winsaveview()['topline'])
+ call assert_equal([0, 21, 1, 0, 8], getcurpos())
+
+ " Test for zb
+ 30
+ norm! >>$ztzb
+ call assert_equal(' 30', getline('.'))
+ call assert_equal(30, winsaveview()['topline']+winheight(0)-1)
+ call assert_equal([0, 30, 3, 0, v:maxcol], getcurpos())
+
+ " Test for z-
+ 1
+ 30
+ norm! 0z-
+ call assert_equal(' 30', getline('.'))
+ call assert_equal(30, winsaveview()['topline']+winheight(0)-1)
+ call assert_equal([0, 30, 2, 0, 9], getcurpos())
+
+ " Test for z{height}<cr>
+ call assert_equal(10, winheight(0))
+ exe "norm! z12\<cr>"
+ call assert_equal(12, winheight(0))
+ exe "norm! z15\<Del>0\<cr>"
+ call assert_equal(10, winheight(0))
+
+ " Test for z.
+ 1
+ 21
+ norm! 0z.
+ call assert_equal(' 21', getline('.'))
+ call assert_equal(17, winsaveview()['topline'])
+ call assert_equal([0, 21, 2, 0, 9], getcurpos())
+
+ " Test for zz
+ 1
+ 21
+ norm! 0zz
+ call assert_equal(' 21', getline('.'))
+ call assert_equal(17, winsaveview()['topline'])
+ call assert_equal([0, 21, 1, 0, 8], getcurpos())
+
+ " Test for z+
+ 11
+ norm! zt
+ norm! z+
+ call assert_equal(' 21', getline('.'))
+ call assert_equal(21, winsaveview()['topline'])
+ call assert_equal([0, 21, 2, 0, 9], getcurpos())
+
+ " Test for [count]z+
+ 1
+ norm! 21z+
+ call assert_equal(' 21', getline('.'))
+ call assert_equal(21, winsaveview()['topline'])
+ call assert_equal([0, 21, 2, 0, 9], getcurpos())
+
+ " Test for z+ with [count] greater than buffer size
+ 1
+ norm! 1000z+
+ call assert_equal(' 100', getline('.'))
+ call assert_equal(100, winsaveview()['topline'])
+ call assert_equal([0, 100, 2, 0, 9], getcurpos())
+
+ " Test for z+ from the last buffer line
+ norm! Gz.z+
+ call assert_equal(' 100', getline('.'))
+ call assert_equal(100, winsaveview()['topline'])
+ call assert_equal([0, 100, 2, 0, 9], getcurpos())
+
+ " Test for z^
+ norm! 22z+0
+ norm! z^
+ call assert_equal(' 21', getline('.'))
+ call assert_equal(12, winsaveview()['topline'])
+ call assert_equal([0, 21, 2, 0, 9], getcurpos())
+
+ " Test for z^ from first buffer line
+ norm! ggz^
+ call assert_equal('1', getline('.'))
+ call assert_equal(1, winsaveview()['topline'])
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+
+ " Test for [count]z^
+ 1
+ norm! 30z^
+ call assert_equal(' 21', getline('.'))
+ call assert_equal(12, winsaveview()['topline'])
+ call assert_equal([0, 21, 2, 0, 9], getcurpos())
+
+ " cleanup
+ bw!
+endfunc
+
+func Test_normal16_z_scroll_hor()
+ " basic test for z commands that scroll the window
+ 10new
+ 15vsp
+ set nowrap listchars=
+ let lineA='abcdefghijklmnopqrstuvwxyz'
+ let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ $put =lineA
+ $put =lineB
+ 1d
+
+ " Test for zl and zh with a count
+ norm! 0z10l
+ call assert_equal([11, 1], [col('.'), wincol()])
+ norm! z4h
+ call assert_equal([11, 5], [col('.'), wincol()])
+ normal! 2gg
+
+ " Test for zl
+ 1
+ norm! 5zl
+ call assert_equal(lineA, getline('.'))
+ call assert_equal(6, col('.'))
+ call assert_equal(5, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('f', @0)
+
+ " Test for zh
+ norm! 2zh
+ call assert_equal(lineA, getline('.'))
+ call assert_equal(6, col('.'))
+ norm! yl
+ call assert_equal('f', @0)
+ call assert_equal(3, winsaveview()['leftcol'])
+
+ " Test for zL
+ norm! zL
+ call assert_equal(11, col('.'))
+ norm! yl
+ call assert_equal('k', @0)
+ call assert_equal(10, winsaveview()['leftcol'])
+ norm! 2zL
+ call assert_equal(25, col('.'))
+ norm! yl
+ call assert_equal('y', @0)
+ call assert_equal(24, winsaveview()['leftcol'])
+
+ " Test for zH
+ norm! 2zH
+ call assert_equal(25, col('.'))
+ call assert_equal(10, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('y', @0)
+
+ " Test for zs
+ norm! $zs
+ call assert_equal(26, col('.'))
+ call assert_equal(25, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+
+ " Test for ze
+ norm! ze
+ call assert_equal(26, col('.'))
+ call assert_equal(11, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+
+ " Test for zs and ze with folds
+ %fold
+ norm! $zs
+ call assert_equal(26, col('.'))
+ call assert_equal(0, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+ norm! ze
+ call assert_equal(26, col('.'))
+ call assert_equal(0, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+
+ " cleanup
+ set wrap listchars=eol:$
+ bw!
+endfunc
+
+func Test_normal17_z_scroll_hor2()
+ " basic test for z commands that scroll the window
+ " using 'sidescrolloff' setting
+ 10new
+ 20vsp
+ set nowrap listchars= sidescrolloff=5
+ let lineA='abcdefghijklmnopqrstuvwxyz'
+ let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ $put =lineA
+ $put =lineB
+ 1d
+
+ " Test for zl
+ 1
+ norm! 5zl
+ call assert_equal(lineA, getline('.'))
+ call assert_equal(11, col('.'))
+ call assert_equal(5, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('k', @0)
+
+ " Test for zh
+ norm! 2zh
+ call assert_equal(lineA, getline('.'))
+ call assert_equal(11, col('.'))
+ norm! yl
+ call assert_equal('k', @0)
+ call assert_equal(3, winsaveview()['leftcol'])
+
+ " Test for zL
+ norm! 0zL
+ call assert_equal(16, col('.'))
+ norm! yl
+ call assert_equal('p', @0)
+ call assert_equal(10, winsaveview()['leftcol'])
+ norm! 2zL
+ call assert_equal(26, col('.'))
+ norm! yl
+ call assert_equal('z', @0)
+ call assert_equal(15, winsaveview()['leftcol'])
+
+ " Test for zH
+ norm! 2zH
+ call assert_equal(15, col('.'))
+ call assert_equal(0, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('o', @0)
+
+ " Test for zs
+ norm! $zs
+ call assert_equal(26, col('.'))
+ call assert_equal(20, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+
+ " Test for ze
+ norm! ze
+ call assert_equal(26, col('.'))
+ call assert_equal(11, winsaveview()['leftcol'])
+ norm! yl
+ call assert_equal('z', @0)
+
+ " cleanup
+ set wrap listchars=eol:$ sidescrolloff=0
+ bw!
+endfunc
+
+" Test for commands that scroll the window horizontally. Test with folds.
+" H, M, L, CTRL-E, CTRL-Y, CTRL-U, CTRL-D, PageUp, PageDown commands
+func Test_vert_scroll_cmds()
+ 15new
+ call setline(1, range(1, 100))
+ exe "normal! 30ggz\<CR>"
+ set foldenable
+ 33,36fold
+ 40,43fold
+ 46,49fold
+ let h = winheight(0)
+
+ " Test for H, M and L commands
+ " Top of the screen = 30
+ " Folded lines = 9
+ " Bottom of the screen = 30 + h + 9 - 1
+ normal! 4L
+ call assert_equal(35 + h, line('.'))
+ normal! 4H
+ call assert_equal(33, line('.'))
+
+ " Test for using a large count value
+ %d
+ call setline(1, range(1, 4))
+ norm! 6H
+ call assert_equal(4, line('.'))
+
+ " Test for 'M' with folded lines
+ %d
+ call setline(1, range(1, 20))
+ 1,5fold
+ norm! LM
+ call assert_equal(12, line('.'))
+
+ " Test for the CTRL-E and CTRL-Y commands with folds
+ %d
+ call setline(1, range(1, 10))
+ 3,5fold
+ exe "normal 6G3\<C-E>"
+ call assert_equal(6, line('w0'))
+ exe "normal 2\<C-Y>"
+ call assert_equal(2, line('w0'))
+
+ " Test for CTRL-Y on a folded line
+ %d
+ call setline(1, range(1, 100))
+ exe (h + 2) .. "," .. (h + 4) .. "fold"
+ exe h + 5
+ normal z-
+ exe "normal \<C-Y>\<C-Y>"
+ call assert_equal(h + 1, line('w$'))
+
+ " Test for CTRL-Y from the first line and CTRL-E from the last line
+ %d
+ set scrolloff=2
+ call setline(1, range(1, 4))
+ exe "normal gg\<C-Y>"
+ call assert_equal(1, line('w0'))
+ call assert_equal(1, line('.'))
+ exe "normal G4\<C-E>\<C-E>"
+ call assert_equal(4, line('w$'))
+ call assert_equal(4, line('.'))
+ set scrolloff&
+
+ " Using <PageUp> and <PageDown> in an empty buffer should beep
+ %d
+ call assert_beeps('exe "normal \<PageUp>"')
+ call assert_beeps('exe "normal \<C-B>"')
+ call assert_beeps('exe "normal \<PageDown>"')
+ call assert_beeps('exe "normal \<C-F>"')
+
+ " Test for <C-U> and <C-D> with fold
+ %d
+ call setline(1, range(1, 100))
+ 10,35fold
+ set scroll=10
+ exe "normal \<C-D>"
+ call assert_equal(36, line('.'))
+ exe "normal \<C-D>"
+ call assert_equal(46, line('.'))
+ exe "normal \<C-U>"
+ call assert_equal(36, line('.'))
+ exe "normal \<C-U>"
+ call assert_equal(10, line('.'))
+ exe "normal \<C-U>"
+ call assert_equal(1, line('.'))
+ set scroll&
+
+ " Test for scrolling to the top of the file with <C-U> and a fold
+ 10
+ normal ztL
+ exe "normal \<C-U>\<C-U>"
+ call assert_equal(1, line('w0'))
+
+ " Test for CTRL-D on a folded line
+ %d
+ call setline(1, range(1, 100))
+ 50,100fold
+ 75
+ normal z-
+ exe "normal \<C-D>"
+ call assert_equal(50, line('.'))
+ call assert_equal(100, line('w$'))
+ normal z.
+ let lnum = winline()
+ exe "normal \<C-D>"
+ call assert_equal(lnum, winline())
+ call assert_equal(50, line('.'))
+ normal zt
+ exe "normal \<C-D>"
+ call assert_equal(50, line('w0'))
+
+ " Test for <S-CR>. Page down.
+ %d
+ call setline(1, range(1, 100))
+ call feedkeys("\<S-CR>", 'xt')
+ call assert_equal(14, line('w0'))
+ call assert_equal(28, line('w$'))
+
+ " Test for <S-->. Page up.
+ call feedkeys("\<S-->", 'xt')
+ call assert_equal(1, line('w0'))
+ call assert_equal(15, line('w$'))
+
+ set foldenable&
+ bwipe!
+endfunc
+
+func Test_scroll_in_ex_mode()
+ " This was using invalid memory because w_botline was invalid.
+ let lines =<< trim END
+ diffsplit
+ norm os00(
+ call writefile(['done'], 'Xdone')
+ qa!
+ END
+ call writefile(lines, 'Xscript', 'D')
+ call assert_equal(1, RunVim([], [], '--clean -X -Z -e -s -S Xscript'))
+ call assert_equal(['done'], readfile('Xdone'))
+
+ call delete('Xdone')
+endfunc
+
+" Test for the 'sidescroll' option
+func Test_sidescroll_opt()
+ new
+ 20vnew
+
+ " scroll by 2 characters horizontally
+ set sidescroll=2 nowrap
+ call setline(1, repeat('a', 40))
+ normal g$l
+ call assert_equal(19, screenpos(0, 1, 21).col)
+ normal l
+ call assert_equal(20, screenpos(0, 1, 22).col)
+ normal g0h
+ call assert_equal(2, screenpos(0, 1, 2).col)
+ call assert_equal(20, screenpos(0, 1, 20).col)
+
+ " when 'sidescroll' is 0, cursor positioned at the center
+ set sidescroll=0
+ normal g$l
+ call assert_equal(11, screenpos(0, 1, 21).col)
+ normal g0h
+ call assert_equal(10, screenpos(0, 1, 10).col)
+
+ %bw!
+ set wrap& sidescroll&
+endfunc
+
+" basic tests for foldopen/folddelete
+func Test_normal18_z_fold()
+ CheckFeature folding
+ call Setup_NewWindow()
+ 50
+ setl foldenable fdm=marker foldlevel=5
+
+ call assert_beeps('normal! zj')
+ call assert_beeps('normal! zk')
+
+ " Test for zF
+ " First fold
+ norm! 4zF
+ " check that folds have been created
+ call assert_equal(['50/*{{{*/', '51', '52', '53/*}}}*/'], getline(50,53))
+
+ " Test for zd
+ 51
+ norm! 2zF
+ call assert_equal(2, foldlevel('.'))
+ norm! kzd
+ call assert_equal(['50', '51/*{{{*/', '52/*}}}*/', '53'], getline(50,53))
+ norm! j
+ call assert_equal(1, foldlevel('.'))
+
+ " Test for zD
+ " also deletes partially selected folds recursively
+ 51
+ norm! zF
+ call assert_equal(2, foldlevel('.'))
+ norm! kV2jzD
+ call assert_equal(['50', '51', '52', '53'], getline(50,53))
+
+ " Test for zE
+ 85
+ norm! 4zF
+ 86
+ norm! 2zF
+ 90
+ norm! 4zF
+ call assert_equal(['85/*{{{*/', '86/*{{{*/', '87/*}}}*/', '88/*}}}*/', '89', '90/*{{{*/', '91', '92', '93/*}}}*/'], getline(85,93))
+ norm! zE
+ call assert_equal(['85', '86', '87', '88', '89', '90', '91', '92', '93'], getline(85,93))
+
+ " Test for zn
+ 50
+ set foldlevel=0
+ norm! 2zF
+ norm! zn
+ norm! k
+ call assert_equal('49', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ call assert_equal(0, &foldenable)
+
+ " Test for zN
+ 49
+ norm! zN
+ call assert_equal('49', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ call assert_equal(1, &foldenable)
+
+ " Test for zi
+ norm! zi
+ call assert_equal(0, &foldenable)
+ norm! zi
+ call assert_equal(1, &foldenable)
+ norm! zi
+ call assert_equal(0, &foldenable)
+ norm! zi
+ call assert_equal(1, &foldenable)
+
+ " Test for za
+ 50
+ norm! za
+ norm! k
+ call assert_equal('49', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ 50
+ norm! za
+ norm! k
+ call assert_equal('49', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+
+ 49
+ norm! 5zF
+ norm! k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+ 49
+ norm! za
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ set nofoldenable
+ " close fold and set foldenable
+ norm! za
+ call assert_equal(1, &foldenable)
+
+ 50
+ " have to use {count}za to open all folds and make the cursor visible
+ norm! 2za
+ norm! 2k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+
+ " Test for zA
+ 49
+ set foldlevel=0
+ 50
+ norm! zA
+ norm! 2k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+
+ " zA on an opened fold when foldenable is not set
+ 50
+ set nofoldenable
+ norm! zA
+ call assert_equal(1, &foldenable)
+ norm! k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " Test for zc
+ norm! zE
+ 50
+ norm! 2zF
+ 49
+ norm! 5zF
+ set nofoldenable
+ 50
+ " There most likely is a bug somewhere:
+ " https://groups.google.com/d/msg/vim_dev/v2EkfJ_KQjI/u-Cvv94uCAAJ
+ " TODO: Should this only close the inner most fold or both folds?
+ norm! zc
+ call assert_equal(1, &foldenable)
+ norm! k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+ set nofoldenable
+ 50
+ norm! Vjzc
+ norm! k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " Test for zC
+ set nofoldenable
+ 50
+ norm! zCk
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " Test for zx
+ " 1) close folds at line 49-54
+ set nofoldenable
+ 48
+ norm! zx
+ call assert_equal(1, &foldenable)
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " 2) do not close fold under cursor
+ 51
+ set nofoldenable
+ norm! zx
+ call assert_equal(1, &foldenable)
+ norm! 3k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ norm! j
+ call assert_equal('53', getline('.'))
+ norm! j
+ call assert_equal('54/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " 3) close one level of folds
+ 48
+ set nofoldenable
+ set foldlevel=1
+ norm! zx
+ call assert_equal(1, &foldenable)
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ norm! j
+ call assert_equal('53', getline('.'))
+ norm! j
+ call assert_equal('54/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " Test for zX
+ " Close all folds
+ set foldlevel=0 nofoldenable
+ 50
+ norm! zX
+ call assert_equal(1, &foldenable)
+ norm! k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " Test for zm
+ 50
+ set nofoldenable foldlevel=2
+ norm! zm
+ call assert_equal(1, &foldenable)
+ call assert_equal(1, &foldlevel)
+ norm! zm
+ call assert_equal(0, &foldlevel)
+ norm! zm
+ call assert_equal(0, &foldlevel)
+ norm! k
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " Test for zm with a count
+ 50
+ set foldlevel=2
+ norm! 3zm
+ call assert_equal(0, &foldlevel)
+ call assert_equal(49, foldclosed(line('.')))
+
+ " Test for zM
+ 48
+ set nofoldenable foldlevel=99
+ norm! zM
+ call assert_equal(1, &foldenable)
+ call assert_equal(0, &foldlevel)
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('55', getline('.'))
+
+ " Test for zr
+ 48
+ set nofoldenable foldlevel=0
+ norm! zr
+ call assert_equal(0, &foldenable)
+ call assert_equal(1, &foldlevel)
+ set foldlevel=0 foldenable
+ norm! zr
+ call assert_equal(1, &foldenable)
+ call assert_equal(1, &foldlevel)
+ norm! zr
+ call assert_equal(2, &foldlevel)
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+
+ " Test for zR
+ 48
+ set nofoldenable foldlevel=0
+ norm! zR
+ call assert_equal(0, &foldenable)
+ call assert_equal(2, &foldlevel)
+ set foldenable foldlevel=0
+ norm! zR
+ call assert_equal(1, &foldenable)
+ call assert_equal(2, &foldlevel)
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ call append(50, ['a /*{{{*/', 'b /*}}}*/'])
+ 48
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('a /*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+ 48
+ norm! zR
+ call assert_equal(1, &foldenable)
+ call assert_equal(3, &foldlevel)
+ call assert_equal('48', getline('.'))
+ norm! j
+ call assert_equal('49/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('50/*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('a /*{{{*/', getline('.'))
+ norm! j
+ call assert_equal('b /*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('51/*}}}*/', getline('.'))
+ norm! j
+ call assert_equal('52', getline('.'))
+
+ " clean up
+ setl nofoldenable fdm=marker foldlevel=0
+ bw!
+endfunc
+
+func Test_normal20_exmode()
+ " Reading from redirected file doesn't work on MS-Windows
+ CheckNotMSWindows
+ call writefile(['1a', 'foo', 'bar', '.', 'w! Xn20file2', 'q!'], 'Xn20script', 'D')
+ call writefile(['1', '2'], 'Xn20file', 'D')
+ call system(GetVimCommand() .. ' -e -s < Xn20script Xn20file')
+ let a = readfile('Xn20file2')
+ call assert_equal(['1', 'foo', 'bar', '2'], a)
+
+ " clean up
+ call delete('Xn20file2')
+ bw!
+endfunc
+
+func Test_normal21_nv_hat()
+
+ " Edit a fresh file and wipe the buffer list so that there is no alternate
+ " file present. Next, check for the expected command failures.
+ edit Xfoo | %bw
+ call assert_fails(':buffer #', 'E86:')
+ call assert_fails(':execute "normal! \<C-^>"', 'E23:')
+ call assert_fails("normal i\<C-R>#", 'E23:')
+
+ " Test for the expected behavior when switching between two named buffers.
+ edit Xfoo | edit Xbar
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+
+ " Test for the expected behavior when only one buffer is named.
+ enew | let l:nr = bufnr('%')
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+ call feedkeys("\<C-^>", 'tx')
+ call assert_equal('', bufname('%'))
+ call assert_equal(l:nr, bufnr('%'))
+
+ " Test that no action is taken by "<C-^>" when an operator is pending.
+ edit Xfoo
+ call feedkeys("ci\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+
+ %bw!
+endfunc
+
+func Test_normal22_zet()
+ " Test for ZZ
+ " let shell = &shell
+ " let &shell = 'sh'
+ call writefile(['1', '2'], 'Xn22file', 'D')
+ let args = ' -N -i NONE --noplugins -X --not-a-term'
+ call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xn22file')
+ let a = readfile('Xn22file')
+ call assert_equal([], a)
+ " Test for ZQ
+ call writefile(['1', '2'], 'Xn22file')
+ call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xn22file')
+ let a = readfile('Xn22file')
+ call assert_equal(['1', '2'], a)
+
+ " Unsupported Z command
+ call assert_beeps('normal! ZW')
+
+ " clean up
+ " let &shell = shell
+endfunc
+
+func Test_normal23_K()
+ " Test for K command
+ new
+ call append(0, ['version8.txt', 'man', 'aa%bb', 'cc|dd'])
+ let k = &keywordprg
+ set keywordprg=:help
+ 1
+ norm! VK
+ call assert_equal('version8.txt', fnamemodify(bufname('%'), ':t'))
+ call assert_equal('help', &ft)
+ call assert_match('\*version8.txt\*', getline('.'))
+ helpclose
+ norm! 0K
+ call assert_equal('version8.txt', fnamemodify(bufname('%'), ':t'))
+ call assert_equal('help', &ft)
+ call assert_match('\*version8\.\d\*', getline('.'))
+ helpclose
+
+ set keywordprg=:new
+ set iskeyword+=%
+ set iskeyword+=\|
+ 2
+ norm! K
+ call assert_equal('man', fnamemodify(bufname('%'), ':t'))
+ bwipe!
+ 3
+ norm! K
+ call assert_equal('aa%bb', fnamemodify(bufname('%'), ':t'))
+ bwipe!
+ if !has('win32')
+ 4
+ norm! K
+ call assert_equal('cc|dd', fnamemodify(bufname('%'), ':t'))
+ bwipe!
+ endif
+ set iskeyword-=%
+ set iskeyword-=\|
+
+ " Test for specifying a count to K
+ 1
+ com! -nargs=* Kprog let g:Kprog_Args = <q-args>
+ set keywordprg=:Kprog
+ norm! 3K
+ call assert_equal('3 version8', g:Kprog_Args)
+ delcom Kprog
+
+ " Only expect "man" to work on Unix
+ if !has("unix")
+ let &keywordprg = k
+ bw!
+ return
+ endif
+
+ let not_gnu_man = has('mac') || has('bsd')
+ if not_gnu_man
+ " In macOS and BSD, the option for specifying a pager is different
+ set keywordprg=man\ -P\ cat
+ else
+ set keywordprg=man\ --pager=cat
+ endif
+ " Test for using man
+ 2
+ let a = execute('unsilent norm! K')
+ if not_gnu_man
+ call assert_match("man -P cat 'man'", a)
+ else
+ call assert_match("man --pager=cat 'man'", a)
+ endif
+
+ " Error cases
+ call setline(1, '#$#')
+ call assert_fails('normal! ggK', 'E349:')
+ call setline(1, '---')
+ call assert_fails('normal! ggv2lK', 'E349:')
+ call setline(1, ['abc', 'xyz'])
+ call assert_fails("normal! gg2lv2h\<C-]>", 'E433:')
+ call assert_beeps("normal! ggVjK")
+ norm! V
+ call assert_beeps("norm! cK")
+
+ " clean up
+ let &keywordprg = k
+ bw!
+endfunc
+
+func Test_normal24_rot13()
+ " Testing for g?? g?g?
+ new
+ call append(0, 'abcdefghijklmnopqrstuvwxyzäüö')
+ 1
+ norm! g??
+ call assert_equal('nopqrstuvwxyzabcdefghijklmäüö', getline('.'))
+ norm! g?g?
+ call assert_equal('abcdefghijklmnopqrstuvwxyzäüö', getline('.'))
+
+ " clean up
+ bw!
+endfunc
+
+func Test_normal25_tag()
+ CheckFeature quickfix
+
+ " Testing for CTRL-] g CTRL-] g]
+ " CTRL-W g] CTRL-W CTRL-] CTRL-W g CTRL-]
+ h
+ " Test for CTRL-]
+ call search('\<x\>$')
+ exe "norm! \<c-]>"
+ call assert_equal("change.txt", fnamemodify(bufname('%'), ':t'))
+ norm! yiW
+ call assert_equal("*x*", @0)
+ exe ":norm \<c-o>"
+
+ " Test for g_CTRL-]
+ call search('\<v_u\>$')
+ exe "norm! g\<c-]>"
+ call assert_equal("change.txt", fnamemodify(bufname('%'), ':t'))
+ norm! yiW
+ call assert_equal("*v_u*", @0)
+ exe ":norm \<c-o>"
+
+ " Test for g]
+ call search('\<i_<Esc>$')
+ let a = execute(":norm! g]")
+ call assert_match('i_<Esc>.*insert.txt', a)
+
+ if !empty(exepath('cscope')) && has('cscope')
+ " setting cscopetag changes how g] works
+ set cst
+ exe "norm! g]"
+ call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
+ norm! yiW
+ call assert_equal("*i_<Esc>*", @0)
+ exe ":norm \<c-o>"
+ " Test for CTRL-W g]
+ exe "norm! \<C-W>g]"
+ call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
+ norm! yiW
+ call assert_equal("*i_<Esc>*", @0)
+ call assert_equal(3, winnr('$'))
+ helpclose
+ set nocst
+ endif
+
+ " Test for CTRL-W g]
+ let a = execute("norm! \<C-W>g]")
+ call assert_match('i_<Esc>.*insert.txt', a)
+
+ " Test for CTRL-W CTRL-]
+ exe "norm! \<C-W>\<C-]>"
+ call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
+ norm! yiW
+ call assert_equal("*i_<Esc>*", @0)
+ call assert_equal(3, winnr('$'))
+ helpclose
+
+ " Test for CTRL-W g CTRL-]
+ exe "norm! \<C-W>g\<C-]>"
+ call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t'))
+ norm! yiW
+ call assert_equal("*i_<Esc>*", @0)
+ call assert_equal(3, winnr('$'))
+ helpclose
+
+ " clean up
+ helpclose
+endfunc
+
+func Test_normal26_put()
+ " Test for ]p ]P [p and [P
+ new
+ call append(0, ['while read LINE', 'do', ' ((count++))', ' if [ $? -ne 0 ]; then', " echo 'Error writing file'", ' fi', 'done'])
+ 1
+ /Error/y a
+ 2
+ norm! "a]pj"a[p
+ call assert_equal(['do', "echo 'Error writing file'", " echo 'Error writing file'", ' ((count++))'], getline(2,5))
+ 1
+ /^\s\{4}/
+ exe "norm! \"a]P3Eldt'"
+ exe "norm! j\"a[P2Eldt'"
+ call assert_equal([' if [ $? -ne 0 ]; then', " echo 'Error writing'", " echo 'Error'", " echo 'Error writing file'", ' fi'], getline(6,10))
+
+ " clean up
+ bw!
+endfunc
+
+func Test_normal27_bracket()
+ " Test for [' [` ]' ]`
+ call Setup_NewWindow()
+ 1,21s/.\+/ & b/
+ 1
+ norm! $ma
+ 5
+ norm! $mb
+ 10
+ norm! $mc
+ 15
+ norm! $md
+ 20
+ norm! $me
+
+ " Test for ['
+ 9
+ norm! 2['
+ call assert_equal(' 1 b', getline('.'))
+ call assert_equal(1, line('.'))
+ call assert_equal(3, col('.'))
+
+ " Test for ]'
+ norm! ]'
+ call assert_equal(' 5 b', getline('.'))
+ call assert_equal(5, line('.'))
+ call assert_equal(3, col('.'))
+
+ " No mark before line 1, cursor moves to first non-blank on current line
+ 1
+ norm! 5|['
+ call assert_equal(' 1 b', getline('.'))
+ call assert_equal(1, line('.'))
+ call assert_equal(3, col('.'))
+
+ " No mark after line 21, cursor moves to first non-blank on current line
+ 21
+ norm! 5|]'
+ call assert_equal(' 21 b', getline('.'))
+ call assert_equal(21, line('.'))
+ call assert_equal(3, col('.'))
+
+ " Test for [`
+ norm! 2[`
+ call assert_equal(' 15 b', getline('.'))
+ call assert_equal(15, line('.'))
+ call assert_equal(8, col('.'))
+
+ " Test for ]`
+ norm! ]`
+ call assert_equal(' 20 b', getline('.'))
+ call assert_equal(20, line('.'))
+ call assert_equal(8, col('.'))
+
+ " No mark before line 1, cursor does not move
+ 1
+ norm! 5|[`
+ call assert_equal(' 1 b', getline('.'))
+ call assert_equal(1, line('.'))
+ call assert_equal(5, col('.'))
+
+ " No mark after line 21, cursor does not move
+ 21
+ norm! 5|]`
+ call assert_equal(' 21 b', getline('.'))
+ call assert_equal(21, line('.'))
+ call assert_equal(5, col('.'))
+
+ " Count too large for [`
+ " cursor moves to first lowercase mark
+ norm! 99[`
+ call assert_equal(' 1 b', getline('.'))
+ call assert_equal(1, line('.'))
+ call assert_equal(7, col('.'))
+
+ " Count too large for ]`
+ " cursor moves to last lowercase mark
+ norm! 99]`
+ call assert_equal(' 20 b', getline('.'))
+ call assert_equal(20, line('.'))
+ call assert_equal(8, col('.'))
+
+ " clean up
+ bw!
+endfunc
+
+" Test for ( and ) sentence movements
+func Test_normal28_parenthesis()
+ new
+ call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here'])
+
+ $
+ norm! d(
+ call assert_equal(['This is a test. With some sentences!', '', 'Even with a question? And one more. ', ''], getline(1, '$'))
+ norm! 2d(
+ call assert_equal(['This is a test. With some sentences!', '', ' ', ''], getline(1, '$'))
+ 1
+ norm! 0d)
+ call assert_equal(['With some sentences!', '', ' ', ''], getline(1, '$'))
+
+ call append('$', ['This is a long sentence', '', 'spanning', 'over several lines. '])
+ $
+ norm! $d(
+ call assert_equal(['With some sentences!', '', ' ', '', 'This is a long sentence', ''], getline(1, '$'))
+
+ " Move to the next sentence from a paragraph macro
+ %d
+ call setline(1, ['.LP', 'blue sky!. blue sky.', 'blue sky. blue sky.'])
+ call cursor(1, 1)
+ normal )
+ call assert_equal([2, 1], [line('.'), col('.')])
+ normal )
+ call assert_equal([2, 12], [line('.'), col('.')])
+ normal ((
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ " It is an error if a next sentence is not found
+ %d
+ call setline(1, '.SH')
+ call assert_beeps('normal )')
+
+ " If only dot is present, don't treat that as a sentence
+ call setline(1, '. This is a sentence.')
+ normal $((
+ call assert_equal(3, col('.'))
+
+ " Jumping to a fold should open the fold
+ call setline(1, ['', '', 'one', 'two', 'three'])
+ set foldenable
+ 2,$fold
+ call feedkeys(')', 'xt')
+ call assert_equal(3, line('.'))
+ call assert_equal(1, foldlevel('.'))
+ call assert_equal(-1, foldclosed('.'))
+ set foldenable&
+
+ " clean up
+ bw!
+endfunc
+
+" Test for { and } paragraph movements
+func Test_normal29_brace()
+ let text =<< trim [DATA]
+ A paragraph begins after each empty line, and also at each of a set of
+ paragraph macros, specified by the pairs of characters in the 'paragraphs'
+ option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to
+ the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in
+ the first column). A section boundary is also a paragraph boundary.
+ Note that a blank line (only containing white space) is NOT a paragraph
+ boundary.
+
+
+ Also note that this does not include a '{' or '}' in the first column. When
+ the '{' flag is in 'cpoptions' then '{' in the first column is used as a
+ paragraph boundary |posix|.
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+ [DATA]
+
+ new
+ call append(0, text)
+ 1
+ norm! 0d2}
+
+ let expected =<< trim [DATA]
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
+ norm! 0d}
+
+ let expected =<< trim [DATA]
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
+ $
+ norm! d{
+
+ let expected =<< trim [DATA]
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
+ norm! d{
+
+ let expected =<< trim [DATA]
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test with { in cpooptions
+ %d
+ call append(0, text)
+ set cpo+={
+ 1
+ norm! 0d2}
+
+ let expected =<< trim [DATA]
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
+ $
+ norm! d}
+
+ let expected =<< trim [DATA]
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
+ norm! gg}
+ norm! d5}
+
+ let expected =<< trim [DATA]
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+
+ [DATA]
+ call assert_equal(expected, getline(1, '$'))
+
+ " Jumping to a fold should open the fold
+ %d
+ call setline(1, ['', 'one', 'two', ''])
+ set foldenable
+ 2,$fold
+ call feedkeys('}', 'xt')
+ call assert_equal(4, line('.'))
+ call assert_equal(1, foldlevel('.'))
+ call assert_equal(-1, foldclosed('.'))
+ set foldenable&
+
+ " clean up
+ set cpo-={
+ bw!
+endfunc
+
+" Test for section movements
+func Test_normal_section()
+ new
+ let lines =<< trim [END]
+ int foo()
+ {
+ if (1)
+ {
+ a = 1;
+ }
+ }
+ [END]
+ call setline(1, lines)
+
+ " jumping to a folded line using [[ should open the fold
+ 2,3fold
+ call cursor(5, 1)
+ call feedkeys("[[", 'xt')
+ call assert_equal(2, line('.'))
+ call assert_equal(-1, foldclosedend(line('.')))
+
+ bwipe!
+endfunc
+
+" Test for changing case using u, U, gu, gU and ~ (tilde) commands
+func Test_normal30_changecase()
+ new
+ call append(0, 'This is a simple test: äüöß')
+ norm! 1ggVu
+ call assert_equal('this is a simple test: äüöß', getline('.'))
+ norm! VU
+ call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.'))
+ norm! guu
+ call assert_equal('this is a simple test: äüöss', getline('.'))
+ norm! gUgU
+ call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.'))
+ norm! gugu
+ call assert_equal('this is a simple test: äüöss', getline('.'))
+ norm! gUU
+ call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.'))
+ norm! 010~
+ call assert_equal('this is a SIMPLE TEST: ÄÜÖSS', getline('.'))
+ norm! V~
+ call assert_equal('THIS IS A simple test: äüöss', getline('.'))
+ call assert_beeps('norm! c~')
+ %d
+ call assert_beeps('norm! ~')
+
+ " Test for changing case across lines using 'whichwrap'
+ call setline(1, ['aaaaaa', 'aaaaaa'])
+ normal! gg10~
+ call assert_equal(['AAAAAA', 'aaaaaa'], getline(1, 2))
+ set whichwrap+=~
+ normal! gg10~
+ call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2))
+ set whichwrap&
+
+ " try changing the case with a double byte encoding (DBCS)
+ %bw!
+ let enc = &enc
+ set encoding=cp932
+ call setline(1, "\u8470")
+ normal ~
+ normal gU$gu$gUgUg~g~gugu
+ call assert_equal("\u8470", getline(1))
+ let &encoding = enc
+
+ " clean up
+ bw!
+endfunc
+
+" Turkish ASCII turns to multi-byte. On some systems Turkish locale
+" is available but toupper()/tolower() don't do the right thing.
+func Test_normal_changecase_turkish()
+ new
+ try
+ lang tr_TR.UTF-8
+ set casemap=
+ let iupper = toupper('i')
+ if iupper == "\u0130"
+ call setline(1, 'iI')
+ 1normal gUU
+ call assert_equal("\u0130I", getline(1))
+ call assert_equal("\u0130I", toupper("iI"))
+
+ call setline(1, 'iI')
+ 1normal guu
+ call assert_equal("i\u0131", getline(1))
+ call assert_equal("i\u0131", tolower("iI"))
+ elseif iupper == "I"
+ call setline(1, 'iI')
+ 1normal gUU
+ call assert_equal("II", getline(1))
+ call assert_equal("II", toupper("iI"))
+
+ call setline(1, 'iI')
+ 1normal guu
+ call assert_equal("ii", getline(1))
+ call assert_equal("ii", tolower("iI"))
+ else
+ call assert_true(false, "expected toupper('i') to be either 'I' or '\u0130'")
+ endif
+ set casemap&
+ call setline(1, 'iI')
+ 1normal gUU
+ call assert_equal("II", getline(1))
+ call assert_equal("II", toupper("iI"))
+
+ call setline(1, 'iI')
+ 1normal guu
+ call assert_equal("ii", getline(1))
+ call assert_equal("ii", tolower("iI"))
+
+ lang en_US.UTF-8
+ catch /E197:/
+ " can't use Turkish locale
+ throw 'Skipped: Turkish locale not available'
+ endtry
+
+ bwipe!
+endfunc
+
+" Test for r (replace) command
+func Test_normal31_r_cmd()
+ new
+ call append(0, 'This is a simple test: abcd')
+ exe "norm! 1gg$r\<cr>"
+ call assert_equal(['This is a simple test: abc', '', ''], getline(1,'$'))
+ exe "norm! 1gg2wlr\<cr>"
+ call assert_equal(['This is a', 'simple test: abc', '', ''], getline(1,'$'))
+ exe "norm! 2gg0W5r\<cr>"
+ call assert_equal(['This is a', 'simple ', ' abc', '', ''], getline('1', '$'))
+ set autoindent
+ call setline(2, ['simple test: abc', ''])
+ exe "norm! 2gg0W5r\<cr>"
+ call assert_equal(['This is a', 'simple ', 'abc', '', '', ''], getline('1', '$'))
+ exe "norm! 1ggVr\<cr>"
+ call assert_equal('^M^M^M^M^M^M^M^M^M', strtrans(getline(1)))
+ call setline(1, 'This is a')
+ exe "norm! 1gg05rf"
+ call assert_equal('fffffis a', getline(1))
+
+ " When replacing characters, copy characters from above and below lines
+ " using CTRL-Y and CTRL-E.
+ " Different code paths are used for utf-8 and latin1 encodings
+ set showmatch
+ for enc in ['latin1', 'utf-8']
+ enew!
+ let &encoding = enc
+ call setline(1, [' {a}', 'xxxxxxxxxx', ' [b]'])
+ exe "norm! 2gg5r\<C-Y>l5r\<C-E>"
+ call assert_equal(' {a}x [b]x', getline(2))
+ endfor
+ set showmatch&
+
+ " r command should fail in operator pending mode
+ call assert_beeps('normal! cr')
+
+ " replace a tab character in visual mode
+ %d
+ call setline(1, ["a\tb", "c\td", "e\tf"])
+ normal gglvjjrx
+ call assert_equal(['axx', 'xxx', 'xxf'], getline(1, '$'))
+
+ " replace with a multibyte character (with multiple composing characters)
+ %d
+ new
+ call setline(1, 'aaa')
+ exe "normal $ra\u0328\u0301"
+ call assert_equal("aaa\u0328\u0301", getline(1))
+
+ " clean up
+ set noautoindent
+ bw!
+endfunc
+
+" Test for g*, g#
+func Test_normal32_g_cmd1()
+ new
+ call append(0, ['abc.x_foo', 'x_foobar.abc'])
+ 1
+ norm! $g*
+ call assert_equal('x_foo', @/)
+ call assert_equal('x_foobar.abc', getline('.'))
+ norm! $g#
+ call assert_equal('abc', @/)
+ call assert_equal('abc.x_foo', getline('.'))
+
+ " clean up
+ bw!
+endfunc
+
+" Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G,
+" gi and gI commands
+func Test_normal33_g_cmd2()
+ call Setup_NewWindow()
+ " Test for g`
+ clearjumps
+ norm! ma10j
+ let a=execute(':jumps')
+ " empty jumplist
+ call assert_equal('>', a[-1:])
+ norm! g`a
+ call assert_equal('>', a[-1:])
+ call assert_equal(1, line('.'))
+ call assert_equal('1', getline('.'))
+ call cursor(10, 1)
+ norm! g'a
+ call assert_equal('>', a[-1:])
+ call assert_equal(1, line('.'))
+
+ " Test for g; and g,
+ norm! g;
+ " there is only one change in the changelist
+ " currently, when we setup the window
+ call assert_equal(2, line('.'))
+ call assert_fails(':norm! g;', 'E662:')
+ call assert_fails(':norm! g,', 'E663:')
+ let &ul = &ul
+ call append('$', ['a', 'b', 'c', 'd'])
+ let &ul = &ul
+ call append('$', ['Z', 'Y', 'X', 'W'])
+ let a = execute(':changes')
+ call assert_match('2\s\+0\s\+2', a)
+ call assert_match('101\s\+0\s\+a', a)
+ call assert_match('105\s\+0\s\+Z', a)
+ norm! 3g;
+ call assert_equal(2, line('.'))
+ norm! 2g,
+ call assert_equal(105, line('.'))
+
+ " Test for g& - global substitute
+ %d
+ call setline(1, range(1,10))
+ call append('$', ['a', 'b', 'c', 'd'])
+ $s/\w/&&/g
+ exe "norm! /[1-8]\<cr>"
+ norm! g&
+ call assert_equal(['11', '22', '33', '44', '55', '66', '77', '88', '9', '110', 'a', 'b', 'c', 'dd'], getline(1, '$'))
+
+ " Jumping to a fold using gg should open the fold
+ set foldenable
+ set foldopen+=jump
+ 5,8fold
+ call feedkeys('6gg', 'xt')
+ call assert_equal(1, foldlevel('.'))
+ call assert_equal(-1, foldclosed('.'))
+ set foldopen-=jump
+ set foldenable&
+
+ " Test for gv
+ %d
+ call append('$', repeat(['abcdefgh'], 8))
+ exe "norm! 2gg02l\<c-v>2j2ly"
+ call assert_equal(['cde', 'cde', 'cde'], getreg(0, 1, 1))
+ " in visual mode, gv swaps current and last selected region
+ exe "norm! G0\<c-v>4k4lgvd"
+ call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'abcdefgh', 'abcdefgh', 'abcdefgh', 'abcdefgh', 'abcdefgh'], getline(1,'$'))
+ exe "norm! G0\<c-v>4k4ly"
+ exe "norm! gvood"
+ call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$'))
+ " gv cannot be used in operator pending mode
+ call assert_beeps('normal! cgv')
+ " gv should beep without a previously selected visual area
+ new
+ call assert_beeps('normal! gv')
+ close
+
+ " Test for gk/gj
+ %d
+ 15vsp
+ set wrap listchars= sbr=
+ let lineA = 'abcdefghijklmnopqrstuvwxyz'
+ let lineB = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ let lineC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ $put =lineA
+ $put =lineB
+
+ norm! 3gg0dgk
+ call assert_equal(['', 'abcdefghijklmno', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'], getline(1, '$'))
+ set nu
+ norm! 3gg0gjdgj
+ call assert_equal(['', 'abcdefghijklmno', '0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
+
+ " Test for gJ
+ norm! 2gggJ
+ call assert_equal(['', 'abcdefghijklmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
+ call assert_equal(16, col('.'))
+ " shouldn't do anything
+ norm! 10gJ
+ call assert_equal(1, col('.'))
+
+ " Test for g0 g^ gm g$
+ exe "norm! 2gg0gji "
+ call assert_equal(['', 'abcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$'))
+ norm! g0yl
+ call assert_equal(12, col('.'))
+ call assert_equal(' ', getreg(0))
+ norm! g$yl
+ call assert_equal(22, col('.'))
+ call assert_equal('3', getreg(0))
+ norm! gmyl
+ call assert_equal(17, col('.'))
+ call assert_equal('n', getreg(0))
+ norm! g^yl
+ call assert_equal(15, col('.'))
+ call assert_equal('l', getreg(0))
+ call assert_beeps('normal 5g$')
+
+ " Test for g$ with double-width character half displayed
+ vsplit
+ 9wincmd |
+ setlocal nowrap nonumber
+ call setline(2, 'asdfasdfヨ')
+ 2
+ normal 0g$
+ call assert_equal(8, col('.'))
+ 10wincmd |
+ normal 0g$
+ call assert_equal(9, col('.'))
+
+ setlocal signcolumn=yes
+ 11wincmd |
+ normal 0g$
+ call assert_equal(8, col('.'))
+ 12wincmd |
+ normal 0g$
+ call assert_equal(9, col('.'))
+
+ close
+
+ " Test for g_
+ call assert_beeps('normal! 100g_')
+ call setline(2, [' foo ', ' foobar '])
+ normal! 2ggg_
+ call assert_equal(5, col('.'))
+ normal! 2g_
+ call assert_equal(8, col('.'))
+
+ norm! 2ggdG
+ $put =lineC
+
+ " Test for gM
+ norm! gMyl
+ call assert_equal(73, col('.'))
+ call assert_equal('0', getreg(0))
+ " Test for 20gM
+ norm! 20gMyl
+ call assert_equal(29, col('.'))
+ call assert_equal('S', getreg(0))
+ " Test for 60gM
+ norm! 60gMyl
+ call assert_equal(87, col('.'))
+ call assert_equal('E', getreg(0))
+
+ " Test for gM with Tab characters
+ call setline('.', "\ta\tb\tc\td\te\tf")
+ norm! gMyl
+ call assert_equal(6, col('.'))
+ call assert_equal("c", getreg(0))
+
+ " Test for g Ctrl-G
+ call setline('.', lineC)
+ norm! 60gMyl
+ set ff=unix
+ let a=execute(":norm! g\<c-g>")
+ call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a)
+
+ " Test for gI
+ norm! gIfoo
+ call assert_equal(['', 'foo0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'], getline(1,'$'))
+
+ " Test for gi
+ wincmd c
+ %d
+ set tw=0
+ call setline(1, ['foobar', 'new line'])
+ norm! A next word
+ $put ='third line'
+ norm! gi another word
+ call assert_equal(['foobar next word another word', 'new line', 'third line'], getline(1,'$'))
+ call setline(1, 'foobar')
+ normal! Ggifirst line
+ call assert_equal('foobarfirst line', getline(1))
+ " Test gi in 'virtualedit' mode with cursor after the end of the line
+ set virtualedit=all
+ call setline(1, 'foo')
+ exe "normal! Abar\<Right>\<Right>\<Right>\<Right>"
+ call setline(1, 'foo')
+ normal! Ggifirst line
+ call assert_equal('foo first line', getline(1))
+ set virtualedit&
+
+ " Test for aborting a g command using CTRL-\ CTRL-G
+ exe "normal! g\<C-\>\<C-G>"
+ call assert_equal('foo first line', getline('.'))
+
+ " clean up
+ bw!
+endfunc
+
+func Test_normal_ex_substitute()
+ " This was hanging on the substitute prompt.
+ new
+ call setline(1, 'a')
+ exe "normal! gggQs/a/b/c\<CR>"
+ call assert_equal('a', getline(1))
+ bwipe!
+endfunc
+
+" Test for g CTRL-G
+func Test_g_ctrl_g()
+ new
+
+ let a = execute(":norm! g\<c-g>")
+ call assert_equal("\n--No lines in buffer--", a)
+
+ " Test for CTRL-G (same as :file)
+ let a = execute(":norm! \<c-g>")
+ call assert_equal("\n\n\"[No Name]\" --No lines in buffer--", a)
+
+ call setline(1, ['first line', 'second line'])
+
+ " Test g CTRL-g with dos, mac and unix file type.
+ norm! gojll
+ set ff=dos
+ let a = execute(":norm! g\<c-g>")
+ call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 15 of 25", a)
+
+ set ff=mac
+ let a = execute(":norm! g\<c-g>")
+ call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 14 of 23", a)
+
+ set ff=unix
+ let a = execute(":norm! g\<c-g>")
+ call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 14 of 23", a)
+
+ " Test g CTRL-g in visual mode (v)
+ let a = execute(":norm! gojllvlg\<c-g>")
+ call assert_equal("\nSelected 1 of 2 Lines; 1 of 4 Words; 2 of 23 Bytes", a)
+
+ " Test g CTRL-g in visual mode (CTRL-V) with end col > start col
+ let a = execute(":norm! \<Esc>gojll\<C-V>kllg\<c-g>")
+ call assert_equal("\nSelected 3 Cols; 2 of 2 Lines; 2 of 4 Words; 6 of 23 Bytes", a)
+
+ " Test g_CTRL-g in visual mode (CTRL-V) with end col < start col
+ let a = execute(":norm! \<Esc>goll\<C-V>jhhg\<c-g>")
+ call assert_equal("\nSelected 3 Cols; 2 of 2 Lines; 2 of 4 Words; 6 of 23 Bytes", a)
+
+ " Test g CTRL-g in visual mode (CTRL-V) with end_vcol being MAXCOL
+ let a = execute(":norm! \<Esc>gojll\<C-V>k$g\<c-g>")
+ call assert_equal("\nSelected 2 of 2 Lines; 4 of 4 Words; 17 of 23 Bytes", a)
+
+ " There should be one byte less with noeol
+ set bin noeol
+ let a = execute(":norm! \<Esc>gog\<c-g>")
+ call assert_equal("\nCol 1 of 10; Line 1 of 2; Word 1 of 4; Char 1 of 23; Byte 1 of 22", a)
+ set bin & eol&
+
+ call setline(1, ['Français', '日本語'])
+
+ let a = execute(":norm! \<Esc>gojlg\<c-g>")
+ call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20", a)
+
+ let a = execute(":norm! \<Esc>gojvlg\<c-g>")
+ call assert_equal("\nSelected 1 of 2 Lines; 1 of 2 Words; 2 of 13 Chars; 6 of 20 Bytes", a)
+
+ let a = execute(":norm! \<Esc>goll\<c-v>jlg\<c-g>")
+ call assert_equal("\nSelected 4 Cols; 2 of 2 Lines; 2 of 2 Words; 6 of 13 Chars; 11 of 20 Bytes", a)
+
+ set fenc=utf8 bomb
+ let a = execute(":norm! \<Esc>gojlg\<c-g>")
+ call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+3 for BOM)", a)
+
+ set fenc=utf16 bomb
+ let a = execute(":norm! g\<c-g>")
+ call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+2 for BOM)", a)
+
+ set fenc=utf32 bomb
+ let a = execute(":norm! g\<c-g>")
+ call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+4 for BOM)", a)
+
+ set fenc& bomb&
+
+ set ff&
+ bwipe!
+endfunc
+
+" Test for g8
+func Test_normal34_g_cmd3()
+ new
+ let a=execute(':norm! 1G0g8')
+ call assert_equal("\nNUL", a)
+
+ call setline(1, 'abcdefghijklmnopqrstuvwxyzäüö')
+ let a=execute(':norm! 1G$g8')
+ call assert_equal("\nc3 b6 ", a)
+
+ call setline(1, "a\u0302")
+ let a=execute(':norm! 1G0g8')
+ call assert_equal("\n61 + cc 82 ", a)
+
+ " clean up
+ bw!
+endfunc
+
+" Test 8g8 which finds invalid utf8 at or after the cursor.
+func Test_normal_8g8()
+ new
+
+ " With invalid byte.
+ call setline(1, "___\xff___")
+ norm! 1G08g8g
+ call assert_equal([0, 1, 4, 0, 1], getcurpos())
+
+ " With invalid byte before the cursor.
+ call setline(1, "___\xff___")
+ norm! 1G$h8g8g
+ call assert_equal([0, 1, 6, 0, 9], getcurpos())
+
+ " With truncated sequence.
+ call setline(1, "___\xE2\x82___")
+ norm! 1G08g8g
+ call assert_equal([0, 1, 4, 0, 1], getcurpos())
+
+ " With overlong sequence.
+ call setline(1, "___\xF0\x82\x82\xAC___")
+ norm! 1G08g8g
+ call assert_equal([0, 1, 4, 0, 1], getcurpos())
+
+ " With valid utf8.
+ call setline(1, "café")
+ norm! 1G08g8
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+
+ bw!
+endfunc
+
+" Test for g<
+func Test_normal35_g_cmd4()
+ " Cannot capture its output,
+ " probably a bug, therefore, test disabled:
+ throw "Skipped: output of g< can't be tested currently"
+ echo "a\nb\nc\nd"
+ let b=execute(':norm! g<')
+ call assert_true(!empty(b), 'failed `execute(g<)`')
+endfunc
+
+" Test for gp gP go
+func Test_normal36_g_cmd5()
+ new
+ call append(0, 'abcdefghijklmnopqrstuvwxyz')
+ set ff=unix
+ " Test for gp gP
+ call append(1, range(1,10))
+ 1
+ norm! 1yy
+ 3
+ norm! gp
+ call assert_equal([0, 5, 1, 0, 1], getcurpos())
+ $
+ norm! gP
+ call assert_equal([0, 14, 1, 0, 1], getcurpos())
+
+ " Test for go
+ norm! 26go
+ call assert_equal([0, 1, 26, 0, 26], getcurpos())
+ norm! 27go
+ call assert_equal([0, 1, 26, 0, 26], getcurpos())
+ norm! 28go
+ call assert_equal([0, 2, 1, 0, 1], getcurpos())
+ set ff=dos
+ norm! 29go
+ call assert_equal([0, 2, 1, 0, 1], getcurpos())
+ set ff=unix
+ norm! gg0
+ norm! 101go
+ call assert_equal([0, 13, 26, 0, 26], getcurpos())
+ norm! 103go
+ call assert_equal([0, 14, 1, 0, 1], getcurpos())
+ " count > buffer content
+ norm! 120go
+ call assert_equal([0, 14, 1, 0, v:maxcol], getcurpos())
+ " clean up
+ bw!
+endfunc
+
+" Test for gt and gT
+func Test_normal37_g_cmd6()
+ tabnew 1.txt
+ tabnew 2.txt
+ tabnew 3.txt
+ norm! 1gt
+ call assert_equal(1, tabpagenr())
+ norm! 3gt
+ call assert_equal(3, tabpagenr())
+ norm! 1gT
+ " count gT goes not to the absolute tabpagenumber
+ " but, but goes to the count previous tabpagenumber
+ call assert_equal(2, tabpagenr())
+ " wrap around
+ norm! 3gT
+ call assert_equal(3, tabpagenr())
+ " gt does not wrap around
+ norm! 5gt
+ call assert_equal(3, tabpagenr())
+
+ for i in range(3)
+ tabclose
+ endfor
+ " clean up
+ call assert_fails(':tabclose', 'E784:')
+endfunc
+
+" Test for <Home> and <C-Home> key
+func Test_normal38_nvhome()
+ new
+ call setline(1, range(10))
+ $
+ setl et sw=2
+ norm! V10>$
+ " count is ignored
+ exe "norm! 10\<home>"
+ call assert_equal(1, col('.'))
+ exe "norm! \<home>"
+ call assert_equal([0, 10, 1, 0, 1], getcurpos())
+ exe "norm! 5\<c-home>"
+ call assert_equal([0, 5, 1, 0, 1], getcurpos())
+ exe "norm! \<c-home>"
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+ exe "norm! G\<c-kHome>"
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+
+ " clean up
+ bw!
+endfunc
+
+" Test for <End> and <C-End> keys
+func Test_normal_nvend()
+ new
+ call setline(1, map(range(1, 10), '"line" .. v:val'))
+ exe "normal! \<End>"
+ call assert_equal(5, col('.'))
+ exe "normal! 4\<End>"
+ call assert_equal([4, 5], [line('.'), col('.')])
+ exe "normal! \<C-End>"
+ call assert_equal([10, 6], [line('.'), col('.')])
+
+ bwipe!
+endfunc
+
+" Test for cw cW ce
+func Test_normal39_cw()
+ " Test for cw and cW on whitespace
+ new
+ set tw=0
+ call append(0, 'here are some words')
+ norm! 1gg0elcwZZZ
+ call assert_equal('hereZZZare some words', getline('.'))
+ norm! 1gg0elcWYYY
+ call assert_equal('hereZZZareYYYsome words', getline('.'))
+ norm! 2gg0cwfoo
+ call assert_equal('foo', getline('.'))
+
+ call setline(1, 'one; two')
+ call cursor(1, 1)
+ call feedkeys('cwvim', 'xt')
+ call assert_equal('vim; two', getline(1))
+ call feedkeys('0cWone', 'xt')
+ call assert_equal('one two', getline(1))
+ "When cursor is at the end of a word 'ce' will change until the end of the
+ "next word, but 'cw' will change only one character
+ call setline(1, 'one two')
+ call feedkeys('0ecwce', 'xt')
+ call assert_equal('once two', getline(1))
+ call setline(1, 'one two')
+ call feedkeys('0ecely', 'xt')
+ call assert_equal('only', getline(1))
+
+ " clean up
+ bw!
+endfunc
+
+" Test for CTRL-\ commands
+func Test_normal40_ctrl_bsl()
+ new
+ call append(0, 'here are some words')
+ exe "norm! 1gg0a\<C-\>\<C-N>"
+ call assert_equal('n', mode())
+ call assert_equal(1, col('.'))
+ call assert_equal('', visualmode())
+ exe "norm! 1gg0viw\<C-\>\<C-N>"
+ call assert_equal('n', mode())
+ call assert_equal(4, col('.'))
+ exe "norm! 1gg0a\<C-\>\<C-G>"
+ call assert_equal('n', mode())
+ call assert_equal(1, col('.'))
+ "imap <buffer> , <c-\><c-n>
+ set im
+ exe ":norm! \<c-\>\<c-n>dw"
+ set noim
+ call assert_equal('are some words', getline(1))
+ call assert_false(&insertmode)
+ call assert_beeps("normal! \<C-\>\<C-A>")
+
+ " clean up
+ bw!
+endfunc
+
+" Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= in insert mode
+func Test_normal41_insert_reg()
+ new
+ set sts=2 sw=2 ts=8 tw=0
+ call append(0, ["aaa\tbbb\tccc", '', '', ''])
+ let a=getline(1)
+ norm! 2gg0
+ exe "norm! a\<c-r>=a\<cr>"
+ norm! 3gg0
+ exe "norm! a\<c-r>\<c-r>=a\<cr>"
+ norm! 4gg0
+ exe "norm! a\<c-r>\<c-o>=a\<cr>"
+ call assert_equal(['aaa bbb ccc', 'aaa bbb ccc', 'aaa bbb ccc', 'aaa bbb ccc', ''], getline(1, '$'))
+
+ " clean up
+ set sts=0 sw=8 ts=8
+ bw!
+endfunc
+
+" Test for Ctrl-D and Ctrl-U
+func Test_normal42_halfpage()
+ call Setup_NewWindow()
+ call assert_equal(5, &scroll)
+ exe "norm! \<c-d>"
+ call assert_equal('6', getline('.'))
+ exe "norm! 2\<c-d>"
+ call assert_equal('8', getline('.'))
+ call assert_equal(2, &scroll)
+ set scroll=5
+ exe "norm! \<c-u>"
+ call assert_equal('3', getline('.'))
+ 1
+ set scrolloff=5
+ exe "norm! \<c-d>"
+ call assert_equal('10', getline('.'))
+ exe "norm! \<c-u>"
+ call assert_equal('5', getline('.'))
+ 1
+ set scrolloff=99
+ exe "norm! \<c-d>"
+ call assert_equal('10', getline('.'))
+ set scrolloff=0
+ 100
+ exe "norm! $\<c-u>"
+ call assert_equal('95', getline('.'))
+ call assert_equal([0, 95, 1, 0, 1], getcurpos())
+ 100
+ set nostartofline
+ exe "norm! $\<c-u>"
+ call assert_equal('95', getline('.'))
+ call assert_equal([0, 95, 2, 0, v:maxcol], getcurpos())
+ " cleanup
+ set startofline
+ bw!
+endfunc
+
+func Test_normal45_drop()
+ if !has('dnd')
+ " The ~ register does not exist
+ call assert_beeps('norm! "~')
+ return
+ endif
+
+ " basic test for drag-n-drop
+ " unfortunately, without a gui, we can't really test much here,
+ " so simply test that ~p fails (which uses the drop register)
+ new
+ call assert_fails(':norm! "~p', 'E353:')
+ call assert_equal([], getreg('~', 1, 1))
+ " the ~ register is read only
+ call assert_fails(':let @~="1"', 'E354:')
+ bw!
+endfunc
+
+func Test_normal46_ignore()
+ new
+ " How to test this?
+ " let's just for now test, that the buffer
+ " does not change
+ call feedkeys("\<c-s>", 't')
+ call assert_equal([''], getline(1,'$'))
+
+ " no valid commands
+ exe "norm! \<char-0x100>"
+ call assert_equal([''], getline(1,'$'))
+
+ exe "norm! ä"
+ call assert_equal([''], getline(1,'$'))
+
+ " clean up
+ bw!
+endfunc
+
+func Test_normal47_visual_buf_wipe()
+ " This was causing a crash or ml_get error.
+ enew!
+ call setline(1,'xxx')
+ normal $
+ new
+ call setline(1, range(1,2))
+ 2
+ exe "norm \<C-V>$"
+ bw!
+ norm yp
+ set nomodified
+endfunc
+
+func Test_normal48_wincmd()
+ new
+ exe "norm! \<c-w>c"
+ call assert_equal(1, winnr('$'))
+ call assert_fails(":norm! \<c-w>c", 'E444:')
+endfunc
+
+func Test_normal49_counts()
+ new
+ call setline(1, 'one two three four five six seven eight nine ten')
+ 1
+ norm! 3d2w
+ call assert_equal('seven eight nine ten', getline(1))
+ bw!
+endfunc
+
+func Test_normal50_commandline()
+ CheckFeature timers
+ CheckFeature cmdline_hist
+
+ func! DoTimerWork(id)
+ call assert_equal('[Command Line]', bufname(''))
+
+ " should fail, with E11, but does fail with E23?
+ "call feedkeys("\<c-^>", 'tm')
+
+ " should fail with E11 - "Invalid in command-line window"
+ call assert_fails(":wincmd p", 'E11:')
+
+ " Return from commandline window.
+ call feedkeys("\<CR>", 't')
+ endfunc
+
+ let oldlang=v:lang
+ lang C
+ set updatetime=20
+ call timer_start(100, 'DoTimerWork')
+ try
+ " throws E23, for whatever reason...
+ call feedkeys('q:', 'x!')
+ catch /E23/
+ " no-op
+ endtry
+
+ " clean up
+ delfunc DoTimerWork
+ set updatetime=4000
+ exe "lang" oldlang
+ bw!
+endfunc
+
+func Test_normal51_FileChangedRO()
+ CheckFeature autocmd
+ " Don't sleep after the warning message.
+ call test_settime(1)
+ call writefile(['foo'], 'Xreadonly.log', 'D')
+ new Xreadonly.log
+ setl ro
+ au FileChangedRO <buffer> :call feedkeys("\<c-^>", 'tix')
+ call assert_fails(":norm! Af", 'E788:')
+ call assert_equal(['foo'], getline(1,'$'))
+ call assert_equal('Xreadonly.log', bufname(''))
+
+ " cleanup
+ call test_settime(0)
+ bw!
+endfunc
+
+func Test_normal52_rl()
+ CheckFeature rightleft
+ new
+ call setline(1, 'abcde fghij klmnopq')
+ norm! 1gg$
+ set rl
+ call assert_equal(19, col('.'))
+ call feedkeys('l', 'tx')
+ call assert_equal(18, col('.'))
+ call feedkeys('h', 'tx')
+ call assert_equal(19, col('.'))
+ call feedkeys("\<right>", 'tx')
+ call assert_equal(18, col('.'))
+ call feedkeys("\<left>", 'tx')
+ call assert_equal(19, col('.'))
+ call feedkeys("\<s-right>", 'tx')
+ call assert_equal(13, col('.'))
+ call feedkeys("\<c-right>", 'tx')
+ call assert_equal(7, col('.'))
+ call feedkeys("\<c-left>", 'tx')
+ call assert_equal(13, col('.'))
+ call feedkeys("\<s-left>", 'tx')
+ call assert_equal(19, col('.'))
+ call feedkeys("<<", 'tx')
+ call assert_equal(' abcde fghij klmnopq',getline(1))
+ call feedkeys(">>", 'tx')
+ call assert_equal('abcde fghij klmnopq',getline(1))
+
+ " cleanup
+ set norl
+ bw!
+endfunc
+
+func Test_normal54_Ctrl_bsl()
+ new
+ call setline(1, 'abcdefghijklmn')
+ exe "norm! df\<c-\>\<c-n>"
+ call assert_equal(['abcdefghijklmn'], getline(1,'$'))
+ exe "norm! df\<c-\>\<c-g>"
+ call assert_equal(['abcdefghijklmn'], getline(1,'$'))
+ exe "norm! df\<c-\>m"
+ call assert_equal(['abcdefghijklmn'], getline(1,'$'))
+
+ call setline(2, 'abcdefghijklmnÄf')
+ norm! 2gg0
+ exe "norm! df\<Char-0x101>"
+ call assert_equal(['abcdefghijklmn', 'f'], getline(1,'$'))
+ norm! 1gg0
+ exe "norm! df\<esc>"
+ call assert_equal(['abcdefghijklmn', 'f'], getline(1,'$'))
+
+ " clean up
+ bw!
+endfunc
+
+func Test_normal_large_count()
+ " This may fail with 32bit long, how do we detect that?
+ new
+ normal o
+ normal 6666666666dL
+ bwipe!
+endfunc
+
+func Test_delete_until_paragraph()
+ new
+ normal grádv}
+ call assert_equal('á', getline(1))
+ normal grád}
+ call assert_equal('', getline(1))
+ bwipe!
+endfunc
+
+" Test for the gr (virtual replace) command
+func Test_gr_command()
+ enew!
+ " Test for the bug fixed by 7.4.387
+ let save_cpo = &cpo
+ call append(0, ['First line', 'Second line', 'Third line'])
+ exe "normal i\<C-G>u"
+ call cursor(2, 1)
+ set cpo-=X
+ normal 4gro
+ call assert_equal('oooond line', getline(2))
+ undo
+ set cpo+=X
+ normal 4gro
+ call assert_equal('ooooecond line', getline(2))
+ let &cpo = save_cpo
+
+ normal! ggvegrx
+ call assert_equal('xxxxx line', getline(1))
+ exe "normal! gggr\<C-V>122"
+ call assert_equal('zxxxx line', getline(1))
+
+ set virtualedit=all
+ normal! 15|grl
+ call assert_equal('zxxxx line l', getline(1))
+ set virtualedit&
+ set nomodifiable
+ call assert_fails('normal! grx', 'E21:')
+ call assert_fails('normal! gRx', 'E21:')
+ call assert_nobeep("normal! gr\<Esc>")
+ set modifiable&
+
+ call assert_nobeep("normal! gr\<Esc>")
+ call assert_beeps("normal! cgr\<Esc>")
+
+ call assert_equal('zxxxx line l', getline(1))
+ exe "normal! 2|gr\<C-V>\<Esc>"
+ call assert_equal("z\<Esc>xx line l", getline(1))
+
+ call setline(1, 'abcdef')
+ exe "normal! 0gr\<C-O>lx"
+ call assert_equal("\<C-O>def", getline(1))
+
+ call setline(1, 'abcdef')
+ exe "normal! 0gr\<C-G>lx"
+ call assert_equal("\<C-G>def", getline(1))
+
+ bwipe!
+endfunc
+
+func Test_nv_hat_count()
+ %bwipeout!
+ let l:nr = bufnr('%') + 1
+ call assert_fails(':execute "normal! ' . l:nr . '\<C-^>"', 'E92:')
+
+ edit Xfoo
+ let l:foo_nr = bufnr('Xfoo')
+
+ edit Xbar
+ let l:bar_nr = bufnr('Xbar')
+
+ " Make sure we are not just using the alternate file.
+ edit Xbaz
+
+ call feedkeys(l:foo_nr . "\<C-^>", 'tx')
+ call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t'))
+
+ call feedkeys(l:bar_nr . "\<C-^>", 'tx')
+ call assert_equal('Xbar', fnamemodify(bufname('%'), ':t'))
+
+ %bwipeout!
+endfunc
+
+func Test_message_when_using_ctrl_c()
+ " Make sure no buffers are changed.
+ %bwipe!
+
+ exe "normal \<C-C>"
+ call assert_match("Type :qa and press <Enter> to exit Vim", Screenline(&lines))
+
+ new
+ cal setline(1, 'hi!')
+ exe "normal \<C-C>"
+ call assert_match("Type :qa! and press <Enter> to abandon all changes and exit Vim", Screenline(&lines))
+
+ bwipe!
+endfunc
+
+func Test_mode_updated_after_ctrl_c()
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', {'rows': 5})
+ call term_sendkeys(buf, "i")
+ call term_sendkeys(buf, "\<C-O>")
+ " wait a moment so that the "-- (insert) --" message is displayed
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<C-C>")
+ call VerifyScreenDump(buf, 'Test_mode_updated_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for '[m', ']m', '[M' and ']M'
+" Jumping to beginning and end of methods in Java-like languages
+func Test_java_motion()
+ new
+ call assert_beeps('normal! [m')
+ call assert_beeps('normal! ]m')
+ call assert_beeps('normal! [M')
+ call assert_beeps('normal! ]M')
+ let lines =<< trim [CODE]
+ Piece of Java
+ {
+ tt m1 {
+ t1;
+ } e1
+
+ tt m2 {
+ t2;
+ } e2
+
+ tt m3 {
+ if (x)
+ {
+ t3;
+ }
+ } e3
+ }
+ [CODE]
+ call setline(1, lines)
+
+ normal gg
+
+ normal 2]maA
+ call assert_equal("\ttt m1 {A", getline('.'))
+ call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal j]maB
+ call assert_equal("\ttt m2 {B", getline('.'))
+ call assert_equal([7, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal ]maC
+ call assert_equal("\ttt m3 {C", getline('.'))
+ call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal [maD
+ call assert_equal("\ttt m3 {DC", getline('.'))
+ call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal k2[maE
+ call assert_equal("\ttt m1 {EA", getline('.'))
+ call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal 3[maF
+ call assert_equal("{F", getline('.'))
+ call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ normal ]MaG
+ call assert_equal("\t}G e1", getline('.'))
+ call assert_equal([5, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal j2]MaH
+ call assert_equal("\t}H e3", getline('.'))
+ call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal ]M]M
+ normal aI
+ call assert_equal("}I", getline('.'))
+ call assert_equal([17, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ normal 2[MaJ
+ call assert_equal("\t}JH e3", getline('.'))
+ call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal k[MaK
+ call assert_equal("\t}K e2", getline('.'))
+ call assert_equal([9, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal 3[MaL
+ call assert_equal("{LF", getline('.'))
+ call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ call cursor(2, 1)
+ call assert_beeps('norm! 5]m')
+
+ " jumping to a method in a fold should open the fold
+ 6,10fold
+ call feedkeys("gg3]m", 'xt')
+ call assert_equal([7, 8, 15], [line('.'), col('.'), virtcol('.')])
+ call assert_equal(-1, foldclosedend(7))
+
+ bwipe!
+endfunc
+
+" Tests for g cmds
+func Test_normal_gdollar_cmd()
+ call Setup_NewWindow()
+ " Make long lines that will wrap
+ %s/$/\=repeat(' foobar', 10)/
+ 20vsp
+ set wrap
+ " Test for g$ with count
+ norm! gg
+ norm! 0vg$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v4g$y
+ call assert_equal(72, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.."\n", getreg(0))
+ norm! gg
+ norm! 0v6g$y
+ call assert_equal(40, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foo', getreg(0))
+ set nowrap
+ " clean up
+ norm! gg
+ norm! 0vg$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v4g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '4 foobar foobar foob', getreg(0))
+ norm! gg
+ norm! 0v6g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '4 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '5 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n"..
+ \ '6 foobar foobar foob', getreg(0))
+ " Move to last line, also down movement is not possible, should still move
+ " the cursor to the last visible char
+ norm! G
+ norm! 0v6g$y
+ call assert_equal(20, col("'>"))
+ call assert_equal('100 foobar foobar fo', getreg(0))
+ bw!
+endfunc
+
+func Test_normal_gk_gj()
+ " needs 80 column new window
+ new
+ vert 80new
+ call assert_beeps('normal gk')
+ put =[repeat('x',90)..' {{{1', 'x {{{1']
+ norm! gk
+ " In a 80 column wide terminal the window will be only 78 char
+ " (because Vim will leave space for the other window),
+ " but if the terminal is larger, it will be 80 chars, so verify the
+ " cursor column correctly.
+ call assert_equal(winwidth(0)+1, col('.'))
+ call assert_equal(winwidth(0)+1, virtcol('.'))
+ norm! j
+ call assert_equal(6, col('.'))
+ call assert_equal(6, virtcol('.'))
+ norm! gk
+ call assert_equal(95, col('.'))
+ call assert_equal(95, virtcol('.'))
+ %bw!
+
+ " needs 80 column new window
+ new
+ vert 80new
+ call assert_beeps('normal gj')
+ set number
+ set numberwidth=10
+ set cpoptions+=n
+ put =[repeat('0',90), repeat('1',90)]
+ norm! 075l
+ call assert_equal(76, col('.'))
+ norm! gk
+ call assert_equal(1, col('.'))
+ norm! gk
+ call assert_equal(76, col('.'))
+ norm! gk
+ call assert_equal(1, col('.'))
+ norm! gj
+ call assert_equal(76, col('.'))
+ norm! gj
+ call assert_equal(1, col('.'))
+ norm! gj
+ call assert_equal(76, col('.'))
+ " When 'nowrap' is set, gk and gj behave like k and j
+ set nowrap
+ normal! gk
+ call assert_equal([2, 76], [line('.'), col('.')])
+ normal! gj
+ call assert_equal([3, 76], [line('.'), col('.')])
+ %bw!
+ set cpoptions& number& numberwidth& wrap&
+endfunc
+
+" Test for using : to run a multi-line Ex command in operator pending mode
+func Test_normal_yank_with_excmd()
+ new
+ call setline(1, ['foo', 'bar', 'baz'])
+ let @a = ''
+ call feedkeys("\"ay:if v:true\<CR>normal l\<CR>endif\<CR>", 'xt')
+ call assert_equal('f', @a)
+
+ bwipe!
+endfunc
+
+" Test for supplying a count to a normal-mode command across a cursorhold call
+func Test_normal_cursorhold_with_count()
+ func s:cHold()
+ let g:cHold_Called += 1
+ endfunc
+ new
+ augroup normalcHoldTest
+ au!
+ au CursorHold <buffer> call s:cHold()
+ augroup END
+ let g:cHold_Called = 0
+ call feedkeys("3\<CursorHold>2ix", 'xt')
+ call assert_equal(1, g:cHold_Called)
+ call assert_equal(repeat('x', 32), getline(1))
+ augroup normalcHoldTest
+ au!
+ augroup END
+ au! normalcHoldTest
+
+ bwipe!
+ delfunc s:cHold
+endfunc
+
+" Test for using a count and a command with CTRL-W
+func Test_wincmd_with_count()
+ call feedkeys("\<C-W>12n", 'xt')
+ call assert_equal(12, winheight(0))
+endfunc
+
+" Test for 'b', 'B' 'ge' and 'gE' commands
+func Test_horiz_motion()
+ new
+ normal! gg
+ call assert_beeps('normal! b')
+ call assert_beeps('normal! B')
+ call assert_beeps('normal! gE')
+ call assert_beeps('normal! ge')
+ " <S-Backspace> moves one word left and <C-Backspace> moves one WORD left
+ call setline(1, 'one ,two ,three')
+ exe "normal! $\<S-BS>"
+ call assert_equal(11, col('.'))
+ exe "normal! $\<C-BS>"
+ call assert_equal(10, col('.'))
+
+ bwipe!
+endfunc
+
+" Test for using a : command in operator pending mode
+func Test_normal_colon_op()
+ new
+ call setline(1, ['one', 'two'])
+ call assert_beeps("normal! Gc:d\<CR>")
+ bwipe!
+endfunc
+
+" Test for d and D commands
+func Test_normal_delete_cmd()
+ new
+ " D in an empty line
+ call setline(1, '')
+ normal D
+ call assert_equal('', getline(1))
+ " D in an empty line in virtualedit mode
+ set virtualedit=all
+ normal D
+ call assert_equal('', getline(1))
+ set virtualedit&
+ " delete to a readonly register
+ call setline(1, ['abcd'])
+ call assert_beeps('normal ":d2l')
+
+ " D and d with 'nomodifiable'
+ call setline(1, ['abcd'])
+ setlocal nomodifiable
+ call assert_fails('normal D', 'E21:')
+ call assert_fails('normal d$', 'E21:')
+
+ bwipe!
+endfunc
+
+" Test for deleting or changing characters across lines with 'whichwrap'
+" containing 's'. Should count <EOL> as one character.
+func Test_normal_op_across_lines()
+ new
+ set whichwrap&
+ call setline(1, ['one two', 'three four'])
+ exe "norm! $3d\<Space>"
+ call assert_equal(['one twhree four'], getline(1, '$'))
+
+ call setline(1, ['one two', 'three four'])
+ exe "norm! $3c\<Space>x"
+ call assert_equal(['one twxhree four'], getline(1, '$'))
+
+ set whichwrap+=l
+ call setline(1, ['one two', 'three four'])
+ exe "norm! $3x"
+ call assert_equal(['one twhree four'], getline(1, '$'))
+
+ bwipe!
+ set whichwrap&
+endfunc
+
+" Test for 'w' and 'b' commands
+func Test_normal_word_move()
+ new
+ call setline(1, ['foo bar a', '', 'foo bar b'])
+ " copy a single character word at the end of a line
+ normal 1G$yw
+ call assert_equal('a', @")
+ " copy a single character word at the end of a file
+ normal G$yw
+ call assert_equal('b', @")
+ " check for a word movement handling an empty line properly
+ normal 1G$vwy
+ call assert_equal("a\n\n", @")
+
+ " copy using 'b' command
+ %d
+ " non-empty blank line at the start of file
+ call setline(1, [' ', 'foo bar'])
+ normal 2Gyb
+ call assert_equal(" \n", @")
+ " try to copy backwards from the start of the file
+ call setline(1, ['one two', 'foo bar'])
+ call assert_beeps('normal ggyb')
+ " 'b' command should stop at an empty line
+ call setline(1, ['one two', '', 'foo bar'])
+ normal 3Gyb
+ call assert_equal("\n", @")
+ normal 3Gy2b
+ call assert_equal("two\n", @")
+ " 'b' command should not stop at a non-empty blank line
+ call setline(1, ['one two', ' ', 'foo bar'])
+ normal 3Gyb
+ call assert_equal("two\n ", @")
+
+ bwipe!
+endfunc
+
+" Test for 'scrolloff' with a long line that doesn't fit in the screen
+func Test_normal_scrolloff()
+ 10new
+ 60vnew
+ call setline(1, ' 1 ' .. repeat('a', 57)
+ \ .. ' 2 ' .. repeat('b', 57)
+ \ .. ' 3 ' .. repeat('c', 57)
+ \ .. ' 4 ' .. repeat('d', 57)
+ \ .. ' 5 ' .. repeat('e', 57)
+ \ .. ' 6 ' .. repeat('f', 57)
+ \ .. ' 7 ' .. repeat('g', 57)
+ \ .. ' 8 ' .. repeat('h', 57)
+ \ .. ' 9 ' .. repeat('i', 57)
+ \ .. '10 ' .. repeat('j', 57)
+ \ .. '11 ' .. repeat('k', 57)
+ \ .. '12 ' .. repeat('l', 57)
+ \ .. '13 ' .. repeat('m', 57)
+ \ .. '14 ' .. repeat('n', 57)
+ \ .. '15 ' .. repeat('o', 57)
+ \ .. '16 ' .. repeat('p', 57)
+ \ .. '17 ' .. repeat('q', 57)
+ \ .. '18 ' .. repeat('r', 57)
+ \ .. '19 ' .. repeat('s', 57)
+ \ .. '20 ' .. repeat('t', 57)
+ \ .. '21 ' .. repeat('u', 57)
+ \ .. '22 ' .. repeat('v', 57)
+ \ .. '23 ' .. repeat('w', 57)
+ \ .. '24 ' .. repeat('x', 57)
+ \ .. '25 ' .. repeat('y', 57)
+ \ .. '26 ' .. repeat('z', 57)
+ \ )
+ set scrolloff=10
+ normal gg10gj
+ call assert_equal(6, winline())
+ normal 10gj
+ call assert_equal(6, winline())
+ normal 10gk
+ call assert_equal(6, winline())
+ normal 0
+ call assert_equal(1, winline())
+ normal $
+ call assert_equal(10, winline())
+
+ set scrolloff&
+ bwipe!
+endfunc
+
+" Test for vertical scrolling with CTRL-F and CTRL-B with a long line
+func Test_normal_vert_scroll_longline()
+ 10new
+ 80vnew
+ call setline(1, range(1, 10))
+ call append(5, repeat('a', 1000))
+ exe "normal gg\<C-F>"
+ call assert_equal(6, line('.'))
+ exe "normal \<C-F>\<C-F>"
+ call assert_equal(11, line('.'))
+ call assert_equal(1, winline())
+ exe "normal \<C-B>"
+ call assert_equal(10, line('.'))
+ call assert_equal(3, winline())
+ exe "normal \<C-B>\<C-B>"
+ call assert_equal(5, line('.'))
+ call assert_equal(5, winline())
+
+ bwipe!
+endfunc
+
+" Test for jumping in a file using %
+func Test_normal_percent_jump()
+ new
+ call setline(1, range(1, 100))
+
+ " jumping to a folded line should open the fold
+ 25,75fold
+ call feedkeys('50%', 'xt')
+ call assert_equal(50, line('.'))
+ call assert_equal(-1, foldclosedend(50))
+
+ bwipe!
+endfunc
+
+" Test for << and >> commands to shift text by 'shiftwidth'
+func Test_normal_shift_rightleft()
+ new
+ call setline(1, ['one', '', "\t", ' two', "\tthree", ' four'])
+ set shiftwidth=2 tabstop=8
+ normal gg6>>
+ call assert_equal([' one', '', "\t ", ' two', "\t three", "\tfour"],
+ \ getline(1, '$'))
+ normal ggVG2>>
+ call assert_equal([' one', '', "\t ", "\ttwo",
+ \ "\t three", "\t four"], getline(1, '$'))
+ normal gg6<<
+ call assert_equal([' one', '', "\t ", ' two', "\t three",
+ \ "\t four"], getline(1, '$'))
+ normal ggVG2<<
+ call assert_equal(['one', '', "\t", ' two', "\tthree", ' four'],
+ \ getline(1, '$'))
+ set shiftwidth& tabstop&
+ bw!
+endfunc
+
+" Some commands like yy, cc, dd, >>, << and !! accept a count after
+" typing the first letter of the command.
+func Test_normal_count_after_operator()
+ new
+ setlocal shiftwidth=4 tabstop=8 autoindent
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ let @a = ''
+ normal! j"ay4y
+ call assert_equal("two\nthree\nfour\nfive\n", @a)
+ normal! 3G>2>
+ call assert_equal(['one', 'two', ' three', ' four', 'five'],
+ \ getline(1, '$'))
+ exe "normal! 3G0c2cred\nblue"
+ call assert_equal(['one', 'two', ' red', ' blue', 'five'],
+ \ getline(1, '$'))
+ exe "normal! gg<8<"
+ call assert_equal(['one', 'two', 'red', 'blue', 'five'],
+ \ getline(1, '$'))
+ exe "normal! ggd3d"
+ call assert_equal(['blue', 'five'], getline(1, '$'))
+ call setline(1, range(1, 4))
+ call feedkeys("gg!3!\<C-B>\"\<CR>", 'xt')
+ call assert_equal('".,.+2!', @:)
+ call feedkeys("gg!1!\<C-B>\"\<CR>", 'xt')
+ call assert_equal('".!', @:)
+ call feedkeys("gg!9!\<C-B>\"\<CR>", 'xt')
+ call assert_equal('".,$!', @:)
+ bw!
+endfunc
+
+func Test_normal_gj_on_extra_wide_char()
+ new | 25vsp
+ let text='1 foooooooo ar e insâ€zwe1 foooooooo insâ€zwei' .
+ \ ' i drei vier fünf sechs sieben acht un zehn elf zwöfl' .
+ \ ' dreizehn v ierzehn fünfzehn'
+ put =text
+ call cursor(2,1)
+ norm! gj
+ call assert_equal([0,2,25,0], getpos('.'))
+ bw!
+endfunc
+
+func Test_normal_count_out_of_range()
+ new
+ call setline(1, 'text')
+ normal 44444444444|
+ call assert_equal(999999999, v:count)
+ normal 444444444444|
+ call assert_equal(999999999, v:count)
+ normal 4444444444444|
+ call assert_equal(999999999, v:count)
+ normal 4444444444444444444|
+ call assert_equal(999999999, v:count)
+
+ normal 9y99999999|
+ call assert_equal(899999991, v:count)
+ normal 10y99999999|
+ call assert_equal(999999999, v:count)
+ normal 44444444444y44444444444|
+ call assert_equal(999999999, v:count)
+ bwipe!
+endfunc
+
+" Test that mouse shape is restored to Normal mode after failed "c" operation.
+func Test_mouse_shape_after_failed_change()
+ CheckFeature mouseshape
+ CheckCanRunGui
+
+ let lines =<< trim END
+ vim9script
+ set mouseshape+=o:busy
+ setlocal nomodifiable
+ var mouse_shapes = []
+
+ feedkeys('c')
+ timer_start(50, (_) => {
+ mouse_shapes += [getmouseshape()]
+ timer_start(50, (_) => {
+ feedkeys('c')
+ timer_start(50, (_) => {
+ mouse_shapes += [getmouseshape()]
+ timer_start(50, (_) => {
+ writefile(mouse_shapes, 'Xmouseshapes')
+ quit
+ })
+ })
+ })
+ })
+ END
+ call writefile(lines, 'Xmouseshape.vim', 'D')
+ call RunVim([], [], "-g -S Xmouseshape.vim")
+ sleep 300m
+ call assert_equal(['busy', 'arrow'], readfile('Xmouseshapes'))
+
+ call delete('Xmouseshapes')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_number.vim b/src/testdir/test_number.vim
new file mode 100644
index 0000000..81f8f73
--- /dev/null
+++ b/src/testdir/test_number.vim
@@ -0,0 +1,357 @@
+" Test for 'number' and 'relativenumber'
+
+source check.vim
+source view_util.vim
+
+source screendump.vim
+
+func s:screen_lines(start, end) abort
+ return ScreenLines([a:start, a:end], 8)
+endfunc
+
+func s:compare_lines(expect, actual)
+ call assert_equal(a:expect, a:actual)
+endfunc
+
+func s:test_windows(h, w) abort
+ call NewWindow(a:h, a:w)
+endfunc
+
+func s:close_windows() abort
+ call CloseWindow()
+endfunc
+
+func s:validate_cursor() abort
+ " update skipcol.
+ " wincol():
+ " f_wincol
+ " -> validate_cursor
+ " -> curs_columns
+ call wincol()
+endfunc
+
+func Test_set_options()
+ set nu rnu
+ call assert_equal(1, &nu)
+ call assert_equal(1, &rnu)
+
+ call s:test_windows(10, 20)
+ call assert_equal(1, &nu)
+ call assert_equal(1, &rnu)
+ call s:close_windows()
+
+ set nu& rnu&
+endfunc
+
+func Test_set_global_and_local()
+ " setlocal must NOT reset the other global value
+ set nonu nornu
+ setglobal nu
+ setlocal rnu
+ call assert_equal(1, &g:nu)
+
+ set nonu nornu
+ setglobal rnu
+ setlocal nu
+ call assert_equal(1, &g:rnu)
+
+ " setglobal MUST reset the other global value
+ set nonu nornu
+ setglobal nu
+ setglobal rnu
+ call assert_equal(1, &g:nu)
+
+ set nonu nornu
+ setglobal rnu
+ setglobal nu
+ call assert_equal(1, &g:rnu)
+
+ " set MUST reset the other global value
+ set nonu nornu
+ set nu
+ set rnu
+ call assert_equal(1, &g:nu)
+
+ set nonu nornu
+ set rnu
+ set nu
+ call assert_equal(1, &g:rnu)
+
+ set nu& rnu&
+endfunc
+
+func Test_number()
+ call s:test_windows(10, 20)
+ call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"])
+ setl number
+ let lines = s:screen_lines(1, 4)
+ let expect = [
+\ " 1 abcd",
+\ " 2 klmn",
+\ " 3 uvwx",
+\ " 4 EFGH",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_relativenumber()
+ call s:test_windows(10, 20)
+ call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"])
+ 3
+ setl relativenumber
+ let lines = s:screen_lines(1, 6)
+ let expect = [
+\ " 2 abcd",
+\ " 1 klmn",
+\ " 0 uvwx",
+\ " 1 EFGH",
+\ " 2 OPQR",
+\ " 3 YZ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_number_with_relativenumber()
+ call s:test_windows(10, 20)
+ call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"])
+ 4
+ setl number relativenumber
+ let lines = s:screen_lines(1, 6)
+ let expect = [
+\ " 3 abcd",
+\ " 2 klmn",
+\ " 1 uvwx",
+\ "4 EFGH",
+\ " 1 OPQR",
+\ " 2 YZ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_number_with_linewrap1()
+ call s:test_windows(3, 20)
+ normal! 61ia
+ setl number wrap
+ call s:validate_cursor()
+ let lines = s:screen_lines(1, 3)
+ let expect = [
+\ "<<< aaaa",
+\ " aaaa",
+\ " aaaa",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI
+func XTest_number_with_linewrap2()
+ call s:test_windows(3, 20)
+ normal! 61ia
+ setl number wrap
+ call s:validate_cursor()
+ 0
+ call s:validate_cursor()
+ let lines = s:screen_lines(1, 3)
+ let expect = [
+\ " 1 aaaa",
+\ " aaaa",
+\ " aaaa",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI
+func XTest_number_with_linewrap3()
+ call s:test_windows(4, 20)
+ normal! 81ia
+ setl number wrap
+ call s:validate_cursor()
+ setl nonumber
+ call s:validate_cursor()
+ let lines = s:screen_lines(1, 4)
+ let expect = [
+\ "aaaaaaaa",
+\ "aaaaaaaa",
+\ "aaaaaaaa",
+\ "a ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_numberwidth()
+ call s:test_windows(10, 20)
+ call setline(1, repeat(['aaaa'], 10))
+ setl number numberwidth=6
+ let lines = s:screen_lines(1, 3)
+ let expect = [
+\ " 1 aa",
+\ " 2 aa",
+\ " 3 aa",
+\ ]
+ call s:compare_lines(expect, lines)
+
+ set relativenumber
+ let lines = s:screen_lines(1, 3)
+ let expect = [
+\ "1 aa",
+\ " 1 aa",
+\ " 2 aa",
+\ ]
+ call s:compare_lines(expect, lines)
+
+ set nonumber
+ let lines = s:screen_lines(1, 3)
+ let expect = [
+\ " 0 aa",
+\ " 1 aa",
+\ " 2 aa",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+func Test_numberwidth_adjusted()
+ call s:test_windows(10, 20)
+ call setline(1, repeat(['aaaa'], 10000))
+ setl number numberwidth=4
+ let lines = s:screen_lines(1, 3)
+ let expect = [
+\ " 1 aa",
+\ " 2 aa",
+\ " 3 aa",
+\ ]
+ call s:compare_lines(expect, lines)
+
+ $
+ let lines = s:screen_lines(8, 10)
+ let expect = [
+\ " 9998 aa",
+\ " 9999 aa",
+\ "10000 aa",
+\ ]
+ call s:compare_lines(expect, lines)
+
+ setl relativenumber
+ let lines = s:screen_lines(8, 10)
+ let expect = [
+\ " 2 aa",
+\ " 1 aa",
+\ "10000 aa",
+\ ]
+ call s:compare_lines(expect, lines)
+
+ setl nonumber
+ let lines = s:screen_lines(8, 10)
+ let expect = [
+\ " 2 aaaa",
+\ " 1 aaaa",
+\ " 0 aaaa",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+" This was causing a memcheck error
+func Test_relativenumber_uninitialised()
+ new
+ set rnu
+ call setline(1, ["a", "b"])
+ redraw
+ call feedkeys("j", 'xt')
+ redraw
+ bwipe!
+endfunc
+
+func Test_relativenumber_colors()
+ CheckScreendump
+
+ let lines =<< trim [CODE]
+ call setline(1, range(200))
+ 111
+ set number relativenumber
+ hi LineNr ctermfg=red
+ [CODE]
+ call writefile(lines, 'XTest_relnr', 'D')
+
+ " Check that the balloon shows up after a mouse move
+ let buf = RunVimInTerminal('-S XTest_relnr', {'rows': 10, 'cols': 50})
+ call TermWait(buf, 50)
+ " Default colors
+ call VerifyScreenDump(buf, 'Test_relnr_colors_1', {})
+
+ call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>:\<CR>")
+ call VerifyScreenDump(buf, 'Test_relnr_colors_2', {})
+
+ call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>:\<CR>")
+ call VerifyScreenDump(buf, 'Test_relnr_colors_3', {})
+
+ call term_sendkeys(buf, ":hi clear LineNrAbove\<CR>")
+ call VerifyScreenDump(buf, 'Test_relnr_colors_4', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_relativenumber_callback()
+ CheckScreendump
+ CheckFeature timers
+
+ let lines =<< trim END
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set relativenumber
+ call cursor(4, 1)
+
+ func Func(timer)
+ call cursor(1, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ END
+ call writefile(lines, 'Xrnu_timer', 'D')
+
+ let buf = RunVimInTerminal('-S Xrnu_timer', #{rows: 8})
+ call TermWait(buf, 310)
+ call VerifyScreenDump(buf, 'Test_relativenumber_callback_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for displaying line numbers with 'rightleft'
+func Test_number_rightleft()
+ CheckFeature rightleft
+ new
+ setlocal number
+ setlocal rightleft
+ call setline(1, range(1, 1000))
+ normal! 9Gzt
+ redraw!
+ call assert_match('^\s\+9 9$', Screenline(1))
+ normal! 10Gzt
+ redraw!
+ call assert_match('^\s\+01 10$', Screenline(1))
+ normal! 100Gzt
+ redraw!
+ call assert_match('^\s\+001 100$', Screenline(1))
+ normal! 1000Gzt
+ redraw!
+ call assert_match('^\s\+0001 1000$', Screenline(1))
+ bw!
+endfunc
+
+" This used to cause a divide by zero
+func Test_number_no_text_virtual_edit()
+ vnew
+ call setline(1, ['line one', 'line two'])
+ set number virtualedit=all
+ normal w
+ 4wincmd |
+ normal j
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
new file mode 100644
index 0000000..b3b90d1
--- /dev/null
+++ b/src/testdir/test_options.vim
@@ -0,0 +1,1673 @@
+" Test for options
+
+source shared.vim
+source check.vim
+source view_util.vim
+
+func Test_whichwrap()
+ set whichwrap=b,s
+ call assert_equal('b,s', &whichwrap)
+
+ set whichwrap+=h,l
+ call assert_equal('b,s,h,l', &whichwrap)
+
+ set whichwrap+=h,l
+ call assert_equal('b,s,h,l', &whichwrap)
+
+ set whichwrap+=h,l
+ call assert_equal('b,s,h,l', &whichwrap)
+
+ set whichwrap=h,h
+ call assert_equal('h', &whichwrap)
+
+ set whichwrap=h,h,h
+ call assert_equal('h', &whichwrap)
+
+ " For compatibility with Vim 3.0 and before, number values are also
+ " supported for 'whichwrap'
+ set whichwrap=1
+ call assert_equal('b', &whichwrap)
+ set whichwrap=2
+ call assert_equal('s', &whichwrap)
+ set whichwrap=4
+ call assert_equal('h,l', &whichwrap)
+ set whichwrap=8
+ call assert_equal('<,>', &whichwrap)
+ set whichwrap=16
+ call assert_equal('[,]', &whichwrap)
+ set whichwrap=31
+ call assert_equal('b,s,h,l,<,>,[,]', &whichwrap)
+
+ set whichwrap&
+endfunc
+
+func Test_isfname()
+ " This used to cause Vim to access uninitialized memory.
+ set isfname=
+ call assert_equal("~X", expand("~X"))
+ set isfname&
+ " Test for setting 'isfname' to an unsupported character
+ let save_isfname = &isfname
+ call assert_fails('exe $"set isfname+={"\u1234"}"', 'E474:')
+ call assert_equal(save_isfname, &isfname)
+endfunc
+
+" Test for getting the value of 'pastetoggle'
+func Test_pastetoggle()
+ " character with K_SPECIAL byte
+ let &pastetoggle = '…'
+ call assert_equal('…', &pastetoggle)
+ call assert_equal("\n pastetoggle=…", execute('set pastetoggle?'))
+
+ " modified character with K_SPECIAL byte
+ let &pastetoggle = '<M-…>'
+ call assert_equal('<M-…>', &pastetoggle)
+ call assert_equal("\n pastetoggle=<M-…>", execute('set pastetoggle?'))
+
+ " illegal bytes
+ let str = ":\x7f:\x80:\x90:\xd0:"
+ let &pastetoggle = str
+ call assert_equal(str, &pastetoggle)
+ call assert_equal("\n pastetoggle=" .. strtrans(str), execute('set pastetoggle?'))
+
+ unlet str
+ set pastetoggle&
+endfunc
+
+func Test_wildchar()
+ " Empty 'wildchar' used to access invalid memory.
+ call assert_fails('set wildchar=', 'E521:')
+ call assert_fails('set wildchar=abc', 'E521:')
+ set wildchar=<Esc>
+ let a=execute('set wildchar?')
+ call assert_equal("\n wildchar=<Esc>", a)
+ set wildchar=27
+ let a=execute('set wildchar?')
+ call assert_equal("\n wildchar=<Esc>", a)
+ set wildchar&
+endfunc
+
+func Test_wildoptions()
+ set wildoptions=
+ set wildoptions+=tagfile
+ set wildoptions+=tagfile
+ call assert_equal('tagfile', &wildoptions)
+endfunc
+
+func Test_options_command()
+ let caught = 'ok'
+ try
+ options
+ catch
+ let caught = v:throwpoint . "\n" . v:exception
+ endtry
+ call assert_equal('ok', caught)
+
+ " Check if the option-window is opened horizontally.
+ wincmd j
+ call assert_notequal('option-window', bufname(''))
+ wincmd k
+ call assert_equal('option-window', bufname(''))
+ " close option-window
+ close
+
+ " Open the option-window vertically.
+ vert options
+ " Check if the option-window is opened vertically.
+ wincmd l
+ call assert_notequal('option-window', bufname(''))
+ wincmd h
+ call assert_equal('option-window', bufname(''))
+ " close option-window
+ close
+
+ " Open the option-window at the top.
+ set splitbelow
+ topleft options
+ call assert_equal(1, winnr())
+ close
+
+ " Open the option-window at the bottom.
+ set nosplitbelow
+ botright options
+ call assert_equal(winnr('$'), winnr())
+ close
+ set splitbelow&
+
+ " Open the option-window in a new tab.
+ tab options
+ " Check if the option-window is opened in a tab.
+ normal gT
+ call assert_notequal('option-window', bufname(''))
+ normal gt
+ call assert_equal('option-window', bufname(''))
+ " close option-window
+ close
+
+ " Open the options window browse
+ if has('browse')
+ browse set
+ call assert_equal('option-window', bufname(''))
+ close
+ endif
+endfunc
+
+func Test_path_keep_commas()
+ " Test that changing 'path' keeps two commas.
+ set path=foo,,bar
+ set path-=bar
+ set path+=bar
+ call assert_equal('foo,,bar', &path)
+
+ set path&
+endfunc
+
+func Test_path_too_long()
+ exe 'set path=' .. repeat('x', 10000)
+ call assert_fails('find x', 'E854:')
+ set path&
+endfunc
+
+func Test_signcolumn()
+ CheckFeature signs
+ call assert_equal("auto", &signcolumn)
+ set signcolumn=yes
+ set signcolumn=no
+ call assert_fails('set signcolumn=nope')
+endfunc
+
+func Test_filetype_valid()
+ set ft=valid_name
+ call assert_equal("valid_name", &filetype)
+ set ft=valid-name
+ call assert_equal("valid-name", &filetype)
+
+ call assert_fails(":set ft=wrong;name", "E474:")
+ call assert_fails(":set ft=wrong\\\\name", "E474:")
+ call assert_fails(":set ft=wrong\\|name", "E474:")
+ call assert_fails(":set ft=wrong/name", "E474:")
+ call assert_fails(":set ft=wrong\\\nname", "E474:")
+ call assert_equal("valid-name", &filetype)
+
+ exe "set ft=trunc\x00name"
+ call assert_equal("trunc", &filetype)
+endfunc
+
+func Test_syntax_valid()
+ CheckFeature syntax
+ set syn=valid_name
+ call assert_equal("valid_name", &syntax)
+ set syn=valid-name
+ call assert_equal("valid-name", &syntax)
+
+ call assert_fails(":set syn=wrong;name", "E474:")
+ call assert_fails(":set syn=wrong\\\\name", "E474:")
+ call assert_fails(":set syn=wrong\\|name", "E474:")
+ call assert_fails(":set syn=wrong/name", "E474:")
+ call assert_fails(":set syn=wrong\\\nname", "E474:")
+ call assert_equal("valid-name", &syntax)
+
+ exe "set syn=trunc\x00name"
+ call assert_equal("trunc", &syntax)
+endfunc
+
+func Test_keymap_valid()
+ CheckFeature keymap
+ call assert_fails(":set kmp=valid_name", "E544:")
+ call assert_fails(":set kmp=valid_name", "valid_name")
+ call assert_fails(":set kmp=valid-name", "E544:")
+ call assert_fails(":set kmp=valid-name", "valid-name")
+
+ call assert_fails(":set kmp=wrong;name", "E474:")
+ call assert_fails(":set kmp=wrong\\\\name", "E474:")
+ call assert_fails(":set kmp=wrong\\|name", "E474:")
+ call assert_fails(":set kmp=wrong/name", "E474:")
+ call assert_fails(":set kmp=wrong\\\nname", "E474:")
+
+ call assert_fails(":set kmp=trunc\x00name", "E544:")
+ call assert_fails(":set kmp=trunc\x00name", "trunc")
+endfunc
+
+func Check_dir_option(name)
+ " Check that it's possible to set the option.
+ exe 'set ' . a:name . '=/usr/share/dict/words'
+ call assert_equal('/usr/share/dict/words', eval('&' . a:name))
+ exe 'set ' . a:name . '=/usr/share/dict/words,/and/there'
+ call assert_equal('/usr/share/dict/words,/and/there', eval('&' . a:name))
+ exe 'set ' . a:name . '=/usr/share/dict\ words'
+ call assert_equal('/usr/share/dict words', eval('&' . a:name))
+
+ " Check rejecting weird characters.
+ call assert_fails("set " . a:name . "=/not&there", "E474:")
+ call assert_fails("set " . a:name . "=/not>there", "E474:")
+ call assert_fails("set " . a:name . "=/not.*there", "E474:")
+endfunc
+
+func Test_cinkeys()
+ " This used to cause invalid memory access
+ set cindent cinkeys=0
+ norm a
+ set cindent& cinkeys&
+endfunc
+
+func Test_dictionary()
+ call Check_dir_option('dictionary')
+endfunc
+
+func Test_thesaurus()
+ call Check_dir_option('thesaurus')
+endfun
+
+func Test_complete()
+ " Trailing single backslash used to cause invalid memory access.
+ set complete=s\
+ new
+ call feedkeys("i\<C-N>\<Esc>", 'xt')
+ bwipe!
+ call assert_fails('set complete=ix', 'E535:')
+ set complete&
+endfun
+
+func Test_set_completion()
+ call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:)
+
+ call feedkeys(":setlocal di\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"setlocal dictionary diff diffexpr diffopt digraph directory display', @:)
+
+ call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:)
+
+ " Expand boolean options. When doing :set no<Tab>
+ " vim displays the options names without "no" but completion uses "no...".
+ call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set nodiff digraph', @:)
+
+ call feedkeys(":set invdi\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set invdiff digraph', @:)
+
+ " Expand abbreviation of options.
+ call feedkeys(":set ts\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tabstop thesaurus thesaurusfunc ttyscroll', @:)
+
+ " Expand current value
+ call feedkeys(":set fileencodings=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set fileencodings=ucs-bom,utf-8,default,latin1', @:)
+
+ call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:)
+
+ " Expand key codes.
+ call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set <Help> <Home>', @:)
+
+ " Expand terminal options.
+ call feedkeys(":set t_A\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set t_AB t_AF t_AU t_AL', @:)
+ call assert_fails('call feedkeys(":set <t_afoo>=\<C-A>\<CR>", "xt")', 'E474:')
+
+ " Expand directories.
+ call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match(' ./samples/ ', @:)
+ call assert_notmatch(' ./summarize.vim ', @:)
+
+ " Expand files and directories.
+ call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match(' ./samples/.* ./summarize.vim', @:)
+
+ call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:)
+ set tags&
+
+ " Expanding the option names
+ call feedkeys(":set \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set all', @:)
+
+ " Expanding a second set of option names
+ call feedkeys(":set wrapscan \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set wrapscan all', @:)
+
+ " Expanding a special keycode
+ call feedkeys(":set <Home>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set <Home>', @:)
+
+ " Expanding an invalid special keycode
+ call feedkeys(":set <abcd>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set <abcd>\<Tab>", @:)
+
+ " Expanding a terminal keycode
+ call feedkeys(":set t_AB\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set t_AB", @:)
+
+ " Expanding an invalid option name
+ call feedkeys(":set abcde=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set abcde=\<Tab>", @:)
+
+ " Expanding after a = for a boolean option
+ call feedkeys(":set wrapscan=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set wrapscan=\<Tab>", @:)
+
+ " Expanding a numeric option
+ call feedkeys(":set tabstop+=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set tabstop+=" .. &tabstop, @:)
+
+ " Expanding a non-boolean option
+ call feedkeys(":set invtabstop=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set invtabstop=", @:)
+
+ " Expand options for 'spellsuggest'
+ call feedkeys(":set spellsuggest=best,file:xyz\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set spellsuggest=best,file:xyz", @:)
+
+ " Expand value for 'key'
+ set key=abcd
+ call feedkeys(":set key=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set key=*****', @:)
+ set key=
+
+ " Expand values for 'filetype'
+ call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set filetype=sshdconfig', @:)
+ call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:)
+endfunc
+
+func Test_set_errors()
+ call assert_fails('set scroll=-1', 'E49:')
+ call assert_fails('set backupcopy=', 'E474:')
+ call assert_fails('set regexpengine=3', 'E474:')
+ call assert_fails('set history=10001', 'E474:')
+ call assert_fails('set numberwidth=21', 'E474:')
+ call assert_fails('set colorcolumn=-a', 'E474:')
+ call assert_fails('set colorcolumn=a', 'E474:')
+ call assert_fails('set colorcolumn=1,', 'E474:')
+ call assert_fails('set colorcolumn=1;', 'E474:')
+ call assert_fails('set cmdheight=-1', 'E487:')
+ call assert_fails('set cmdwinheight=-1', 'E487:')
+ if has('conceal')
+ call assert_fails('set conceallevel=-1', 'E487:')
+ call assert_fails('set conceallevel=4', 'E474:')
+ endif
+ call assert_fails('set helpheight=-1', 'E487:')
+ call assert_fails('set history=-1', 'E487:')
+ call assert_fails('set report=-1', 'E487:')
+ call assert_fails('set shiftwidth=-1', 'E487:')
+ call assert_fails('set sidescroll=-1', 'E487:')
+ call assert_fails('set tabstop=-1', 'E487:')
+ call assert_fails('set tabstop=10000', 'E474:')
+ call assert_fails('let &tabstop = 10000', 'E474:')
+ call assert_fails('set tabstop=5500000000', 'E474:')
+ call assert_fails('set textwidth=-1', 'E487:')
+ call assert_fails('set timeoutlen=-1', 'E487:')
+ call assert_fails('set updatecount=-1', 'E487:')
+ call assert_fails('set updatetime=-1', 'E487:')
+ call assert_fails('set winheight=-1', 'E487:')
+ call assert_fails('set tabstop!', 'E488:')
+ call assert_fails('set xxx', 'E518:')
+ call assert_fails('set beautify?', 'E519:')
+ call assert_fails('set undolevels=x', 'E521:')
+ call assert_fails('set tabstop=', 'E521:')
+ call assert_fails('set comments=-', 'E524:')
+ call assert_fails('set comments=a', 'E525:')
+ call assert_fails('set foldmarker=x', 'E536:')
+ call assert_fails('set commentstring=x', 'E537:')
+ call assert_fails('let &commentstring = "x"', 'E537:')
+ call assert_fails('set complete=x', 'E539:')
+ call assert_fails('set rulerformat=%-', 'E539:')
+ call assert_fails('set rulerformat=%(', 'E542:')
+ call assert_fails('set rulerformat=%15(%%', 'E542:')
+ call assert_fails('set statusline=%$', 'E539:')
+ call assert_fails('set statusline=%{', 'E540:')
+ call assert_fails('set statusline=%{%', 'E540:')
+ call assert_fails('set statusline=%{%}', 'E539:')
+ call assert_fails('set statusline=%(', 'E542:')
+ call assert_fails('set statusline=%)', 'E542:')
+ call assert_fails('set tabline=%$', 'E539:')
+ call assert_fails('set tabline=%{', 'E540:')
+ call assert_fails('set tabline=%{%', 'E540:')
+ call assert_fails('set tabline=%{%}', 'E539:')
+ call assert_fails('set tabline=%(', 'E542:')
+ call assert_fails('set tabline=%)', 'E542:')
+
+ if has('cursorshape')
+ " This invalid value for 'guicursor' used to cause Vim to crash.
+ call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:')
+ call assert_fails('set guicursor=i-ci', 'E545:')
+ call assert_fails('set guicursor=x', 'E545:')
+ call assert_fails('set guicursor=x:', 'E546:')
+ call assert_fails('set guicursor=r-cr:horx', 'E548:')
+ call assert_fails('set guicursor=r-cr:hor0', 'E549:')
+ endif
+ if has('mouseshape')
+ call assert_fails('se mouseshape=i-r:x', 'E547:')
+ endif
+
+ " Test for 'backupext' and 'patchmode' set to the same value
+ set backupext=.bak
+ set patchmode=.patch
+ call assert_fails('set patchmode=.bak', 'E589:')
+ call assert_equal('.patch', &patchmode)
+ call assert_fails('set backupext=.patch', 'E589:')
+ call assert_equal('.bak', &backupext)
+ set backupext& patchmode&
+
+ call assert_fails('set winminheight=10 winheight=9', 'E591:')
+ set winminheight& winheight&
+ set winheight=10 winminheight=10
+ call assert_fails('set winheight=9', 'E591:')
+ set winminheight& winheight&
+ call assert_fails('set winminwidth=10 winwidth=9', 'E592:')
+ set winminwidth& winwidth&
+ call assert_fails('set winwidth=9 winminwidth=10', 'E592:')
+ set winwidth& winminwidth&
+ call assert_fails("set showbreak=\x01", 'E595:')
+ call assert_fails('set t_foo=', 'E846:')
+ call assert_fails('set tabstop??', 'E488:')
+ call assert_fails('set wrapscan!!', 'E488:')
+ call assert_fails('set tabstop&&', 'E488:')
+ call assert_fails('set wrapscan<<', 'E488:')
+ call assert_fails('set wrapscan=1', 'E474:')
+ call assert_fails('set autoindent@', 'E488:')
+ call assert_fails('set wildchar=<abc>', 'E474:')
+ call assert_fails('set cmdheight=1a', 'E521:')
+ call assert_fails('set invcmdheight', 'E474:')
+ if has('python') || has('python3')
+ call assert_fails('set pyxversion=6', 'E474:')
+ endif
+ call assert_fails("let &tabstop='ab'", 'E521:')
+ call assert_fails('set spellcapcheck=%\\(', 'E54:')
+ call assert_fails('set sessionoptions=curdir,sesdir', 'E474:')
+ call assert_fails('set foldmarker={{{,', 'E474:')
+ call assert_fails('set sessionoptions=sesdir,curdir', 'E474:')
+ setlocal listchars=trail:·
+ call assert_fails('set ambiwidth=double', 'E834:')
+ setlocal listchars=trail:-
+ setglobal listchars=trail:·
+ call assert_fails('set ambiwidth=double', 'E834:')
+ set listchars&
+ setlocal fillchars=stl:·
+ call assert_fails('set ambiwidth=double', 'E835:')
+ setlocal fillchars=stl:-
+ setglobal fillchars=stl:·
+ call assert_fails('set ambiwidth=double', 'E835:')
+ set fillchars&
+ call assert_fails('set fileencoding=latin1,utf-8', 'E474:')
+ set nomodifiable
+ call assert_fails('set fileencoding=latin1', 'E21:')
+ set modifiable&
+ call assert_fails('set t_#-&', 'E522:')
+ call assert_fails('let &formatoptions = "?"', 'E539:')
+ call assert_fails('call setbufvar("", "&formatoptions", "?")', 'E539:')
+endfunc
+
+func Test_set_encoding()
+ let save_encoding = &encoding
+
+ set enc=iso8859-1
+ call assert_equal('latin1', &enc)
+ set enc=iso8859_1
+ call assert_equal('latin1', &enc)
+ set enc=iso-8859-1
+ call assert_equal('latin1', &enc)
+ set enc=iso_8859_1
+ call assert_equal('latin1', &enc)
+ set enc=iso88591
+ call assert_equal('latin1', &enc)
+ set enc=iso8859
+ call assert_equal('latin1', &enc)
+ set enc=iso-8859
+ call assert_equal('latin1', &enc)
+ set enc=iso_8859
+ call assert_equal('latin1', &enc)
+ call assert_fails('set enc=iso8858', 'E474:')
+ call assert_equal('latin1', &enc)
+
+ let &encoding = save_encoding
+endfunc
+
+func CheckWasSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_match('Last set from.*test_options.vim', verb_cm)
+endfunc
+func CheckWasNotSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_notmatch('Last set from', verb_cm)
+endfunc
+
+" Must be executed before other tests that set 'term'.
+func Test_000_term_option_verbose()
+ CheckNotGui
+
+ call CheckWasNotSet('t_cm')
+
+ let term_save = &term
+ set term=ansi
+ call CheckWasSet('t_cm')
+ let &term = term_save
+endfunc
+
+func Test_copy_context()
+ setlocal list
+ call CheckWasSet('list')
+ split
+ call CheckWasSet('list')
+ quit
+ setlocal nolist
+
+ set ai
+ call CheckWasSet('ai')
+ set filetype=perl
+ call CheckWasSet('filetype')
+ set fo=tcroq
+ call CheckWasSet('fo')
+
+ split Xsomebuf
+ call CheckWasSet('ai')
+ call CheckWasNotSet('filetype')
+ call CheckWasSet('fo')
+endfunc
+
+func Test_set_ttytype()
+ CheckUnix
+ CheckNotGui
+
+ " Setting 'ttytype' used to cause a double-free when exiting vim and
+ " when vim is compiled with -DEXITFREE.
+ set ttytype=ansi
+ call assert_equal('ansi', &ttytype)
+ call assert_equal(&ttytype, &term)
+ set ttytype=xterm
+ call assert_equal('xterm', &ttytype)
+ call assert_equal(&ttytype, &term)
+ try
+ set ttytype=
+ call assert_report('set ttytype= did not fail')
+ catch /E529/
+ endtry
+
+ " Some systems accept any terminal name and return dumb settings,
+ " check for failure of finding the entry and for missing 'cm' entry.
+ try
+ set ttytype=xxx
+ call assert_report('set ttytype=xxx did not fail')
+ catch /E522\|E437/
+ endtry
+
+ set ttytype&
+ call assert_equal(&ttytype, &term)
+
+ if has('gui') && !has('gui_running')
+ call assert_fails('set term=gui', 'E531:')
+ endif
+endfunc
+
+func Test_set_all()
+ set tw=75
+ set iskeyword=a-z,A-Z
+ set nosplitbelow
+ let out = execute('set all')
+ call assert_match('textwidth=75', out)
+ call assert_match('iskeyword=a-z,A-Z', out)
+ call assert_match('nosplitbelow', out)
+ set tw& iskeyword& splitbelow&
+endfunc
+
+func Test_set_one_column()
+ let out_mult = execute('set all')->split("\n")
+ let out_one = execute('set! all')->split("\n")
+ call assert_true(len(out_mult) < len(out_one))
+endfunc
+
+func Test_set_values()
+ " opt_test.vim is generated from ../optiondefs.h using gen_opt_test.vim
+ if filereadable('opt_test.vim')
+ source opt_test.vim
+ else
+ throw 'Skipped: opt_test.vim does not exist'
+ endif
+endfunc
+
+func Test_renderoptions()
+ " Only do this for Windows Vista and later, fails on Windows XP and earlier.
+ " Doesn't hurt to do this on a non-Windows system.
+ if windowsversion() !~ '^[345]\.'
+ set renderoptions=type:directx
+ set rop=type:directx
+ endif
+endfunc
+
+func ResetIndentexpr()
+ set indentexpr=
+endfunc
+
+func Test_set_indentexpr()
+ " this was causing usage of freed memory
+ set indentexpr=ResetIndentexpr()
+ new
+ call feedkeys("i\<c-f>", 'x')
+ call assert_equal('', &indentexpr)
+ bwipe!
+endfunc
+
+func Test_backupskip()
+ " Option 'backupskip' may contain several comma-separated path
+ " specifications if one or more of the environment variables TMPDIR, TMP,
+ " or TEMP is defined. To simplify testing, convert the string value into a
+ " list.
+ let bsklist = split(&bsk, ',')
+
+ if has("mac")
+ let found = (index(bsklist, '/private/tmp/*') >= 0)
+ call assert_true(found, '/private/tmp not in option bsk: ' . &bsk)
+ elseif has("unix")
+ let found = (index(bsklist, '/tmp/*') >= 0)
+ call assert_true(found, '/tmp not in option bsk: ' . &bsk)
+ endif
+
+ " If our test platform is Windows, the path(s) in option bsk will use
+ " backslash for the path separator and the components could be in short
+ " (8.3) format. As such, we need to replace the backslashes with forward
+ " slashes and convert the path components to long format. The expand()
+ " function will do this but it cannot handle comma-separated paths. This is
+ " why bsk was converted from a string into a list of strings above.
+ "
+ " One final complication is that the wildcard "/*" is at the end of each
+ " path and so expand() might return a list of matching files. To prevent
+ " this, we need to remove the wildcard before calling expand() and then
+ " append it afterwards.
+ if has('win32')
+ let item_nbr = 0
+ while item_nbr < len(bsklist)
+ let path_spec = bsklist[item_nbr]
+ let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2)
+ let path_spec = substitute(expand(path_spec), '\\', '/', 'g')
+ let bsklist[item_nbr] = path_spec . '/*'
+ let item_nbr += 1
+ endwhile
+ endif
+
+ " Option bsk will also include these environment variables if defined.
+ " If they're defined, verify they appear in the option value.
+ for var in ['$TMPDIR', '$TMP', '$TEMP']
+ if exists(var)
+ let varvalue = substitute(expand(var), '\\', '/', 'g')
+ let varvalue = substitute(varvalue, '/$', '', '')
+ let varvalue .= '/*'
+ let found = (index(bsklist, varvalue) >= 0)
+ call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk)
+ endif
+ endfor
+
+ " Duplicates from environment variables should be filtered out (option has
+ " P_NODUP). Run this in a separate instance and write v:errors in a file,
+ " so that we see what happens on startup.
+ let after =<< trim [CODE]
+ let bsklist = split(&backupskip, ',')
+ call assert_equal(uniq(copy(bsklist)), bsklist)
+ call writefile(['errors:'] + v:errors, 'Xtestout')
+ qall
+ [CODE]
+ call writefile(after, 'Xafter', 'D')
+ let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
+
+ let saveenv = {}
+ for var in ['TMPDIR', 'TMP', 'TEMP']
+ let saveenv[var] = getenv(var)
+ call setenv(var, '/duplicate/path')
+ endfor
+
+ exe 'silent !' . cmd
+ call assert_equal(['errors:'], readfile('Xtestout'))
+
+ " restore environment variables
+ for var in ['TMPDIR', 'TMP', 'TEMP']
+ call setenv(var, saveenv[var])
+ endfor
+
+ call delete('Xtestout')
+
+ " Duplicates should be filtered out (option has P_NODUP)
+ let backupskip = &backupskip
+ set backupskip=
+ set backupskip+=/test/dir
+ set backupskip+=/other/dir
+ set backupskip+=/test/dir
+ call assert_equal('/test/dir,/other/dir', &backupskip)
+ let &backupskip = backupskip
+endfunc
+
+func Test_buf_copy_winopt()
+ set hidden
+
+ " Test copy option from current buffer in window
+ split
+ enew
+ setlocal numberwidth=5
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(5,&numberwidth)
+ bw!
+ call assert_equal(4,&numberwidth)
+
+ " Test copy value from window that used to be display the buffer
+ split
+ enew
+ setlocal numberwidth=6
+ bnext
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(6,&numberwidth)
+ bw!
+
+ " Test that if buffer is current, don't use the stale cached value
+ " from the last time the buffer was displayed.
+ split
+ enew
+ setlocal numberwidth=7
+ bnext
+ bnext
+ setlocal numberwidth=8
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(8,&numberwidth)
+ bw!
+
+ " Test value is not copied if window already has seen the buffer
+ enew
+ split
+ setlocal numberwidth=9
+ bnext
+ setlocal numberwidth=10
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(4,&numberwidth)
+ bw!
+
+ set hidden&
+endfunc
+
+def Test_split_copy_options()
+ var values = [
+ ['cursorbind', true, false],
+ ['fillchars', '"vert:-"', '"' .. &fillchars .. '"'],
+ ['list', true, 0],
+ ['listchars', '"space:-"', '"' .. &listchars .. '"'],
+ ['number', true, 0],
+ ['relativenumber', true, false],
+ ['scrollbind', true, false],
+ ['smoothscroll', true, false],
+ ['virtualedit', '"block"', '"' .. &virtualedit .. '"'],
+ ['wincolor', '"Search"', '"' .. &wincolor .. '"'],
+ ['wrap', false, true],
+ ]
+ if has('linebreak')
+ values += [
+ ['breakindent', true, false],
+ ['breakindentopt', '"min:5"', '"' .. &breakindentopt .. '"'],
+ ['linebreak', true, false],
+ ['numberwidth', 7, 4],
+ ['showbreak', '"++"', '"' .. &showbreak .. '"'],
+ ]
+ endif
+ if has('rightleft')
+ values += [
+ ['rightleft', true, false],
+ ['rightleftcmd', '"search"', '"' .. &rightleftcmd .. '"'],
+ ]
+ endif
+ if has('statusline')
+ values += [
+ ['statusline', '"---%f---"', '"' .. &statusline .. '"'],
+ ]
+ endif
+ if has('spell')
+ values += [
+ ['spell', true, false],
+ ]
+ endif
+ if has('syntax')
+ values += [
+ ['cursorcolumn', true, false],
+ ['cursorline', true, false],
+ ['cursorlineopt', '"screenline"', '"' .. &cursorlineopt .. '"'],
+ ['colorcolumn', '"+1"', '"' .. &colorcolumn .. '"'],
+ ]
+ endif
+ if has('diff')
+ values += [
+ ['diff', true, false],
+ ]
+ endif
+ if has('conceal')
+ values += [
+ ['concealcursor', '"nv"', '"' .. &concealcursor .. '"'],
+ ['conceallevel', '3', &conceallevel],
+ ]
+ endif
+ if has('terminal')
+ values += [
+ ['termwinkey', '"<C-X>"', '"' .. &termwinkey .. '"'],
+ ['termwinsize', '"10x20"', '"' .. &termwinsize .. '"'],
+ ]
+ endif
+ if has('folding')
+ values += [
+ ['foldcolumn', 5, &foldcolumn],
+ ['foldenable', false, true],
+ ['foldexpr', '"2 + 3"', '"' .. &foldexpr .. '"'],
+ ['foldignore', '"+="', '"' .. &foldignore .. '"'],
+ ['foldlevel', 4, &foldlevel],
+ ['foldmarker', '">>,<<"', '"' .. &foldmarker .. '"'],
+ ['foldmethod', '"marker"', '"' .. &foldmethod .. '"'],
+ ['foldminlines', 3, &foldminlines],
+ ['foldnestmax', 17, &foldnestmax],
+ ['foldtext', '"closed"', '"' .. &foldtext .. '"'],
+ ]
+ endif
+ if has('signs')
+ values += [
+ ['signcolumn', '"number"', '"' .. &signcolumn .. '"'],
+ ]
+ endif
+
+ # set options to non-default value
+ for item in values
+ exe $'&l:{item[0]} = {item[1]}'
+ endfor
+
+ # check values are set in new window
+ split
+ for item in values
+ exe $'assert_equal({item[1]}, &{item[0]}, "{item[0]}")'
+ endfor
+
+ # restore
+ close
+ for item in values
+ exe $'&l:{item[0]} = {item[2]}'
+ endfor
+enddef
+
+func Test_shortmess_F()
+ new
+ call assert_match('\[No Name\]', execute('file'))
+ set shortmess+=F
+ call assert_match('\[No Name\]', execute('file'))
+ call assert_match('^\s*$', execute('file foo'))
+ call assert_match('foo', execute('file'))
+ set shortmess-=F
+ call assert_match('bar', execute('file bar'))
+ call assert_match('bar', execute('file'))
+ set shortmess&
+ bwipe
+endfunc
+
+func Test_shortmess_F2()
+ e file1
+ e file2
+ call assert_match('file1', execute('bn', ''))
+ call assert_match('file2', execute('bn', ''))
+ set shortmess+=F
+ call assert_true(empty(execute('bn', '')))
+ call assert_false(test_getvalue('need_fileinfo'))
+ call assert_true(empty(execute('bn', '')))
+ call assert_false('need_fileinfo'->test_getvalue())
+ set hidden
+ call assert_true(empty(execute('bn', '')))
+ call assert_false(test_getvalue('need_fileinfo'))
+ call assert_true(empty(execute('bn', '')))
+ call assert_false(test_getvalue('need_fileinfo'))
+ set nohidden
+ call assert_true(empty(execute('bn', '')))
+ call assert_false(test_getvalue('need_fileinfo'))
+ call assert_true(empty(execute('bn', '')))
+ call assert_false(test_getvalue('need_fileinfo'))
+ set shortmess&
+ call assert_match('file1', execute('bn', ''))
+ call assert_match('file2', execute('bn', ''))
+ bwipe
+ bwipe
+ call assert_fails('call test_getvalue("abc")', 'E475:')
+endfunc
+
+func Test_local_scrolloff()
+ set so=5
+ set siso=7
+ split
+ call assert_equal(5, &so)
+ setlocal so=3
+ call assert_equal(3, &so)
+ wincmd w
+ call assert_equal(5, &so)
+ wincmd w
+ setlocal so<
+ call assert_equal(5, &so)
+ setlocal so=0
+ call assert_equal(0, &so)
+ setlocal so=-1
+ call assert_equal(5, &so)
+
+ call assert_equal(7, &siso)
+ setlocal siso=3
+ call assert_equal(3, &siso)
+ wincmd w
+ call assert_equal(7, &siso)
+ wincmd w
+ setlocal siso<
+ call assert_equal(7, &siso)
+ setlocal siso=0
+ call assert_equal(0, &siso)
+ setlocal siso=-1
+ call assert_equal(7, &siso)
+
+ close
+ set so&
+ set siso&
+endfunc
+
+func Test_writedelay()
+ CheckFunction reltimefloat
+
+ new
+ call setline(1, 'empty')
+ redraw
+ set writedelay=10
+ let start = reltime()
+ call setline(1, repeat('x', 70))
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ set writedelay=0
+ " With 'writedelay' set should take at least 30 * 10 msec
+ call assert_inrange(30 * 0.01, 999.0, elapsed)
+
+ bwipe!
+endfunc
+
+func Test_visualbell()
+ set belloff=
+ set visualbell
+ call assert_beeps('normal 0h')
+ set novisualbell
+ set belloff=all
+endfunc
+
+" Test for the 'write' option
+func Test_write()
+ new
+ call setline(1, ['L1'])
+ set nowrite
+ call assert_fails('write Xwrfile', 'E142:')
+ set write
+ close!
+endfunc
+
+" Test for 'buftype' option
+func Test_buftype()
+ new
+ call setline(1, ['L1'])
+ set buftype=nowrite
+ call assert_fails('write', 'E382:')
+
+ for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'terminal', 'prompt', 'popup']
+ exe 'set buftype=' .. val
+ call writefile(['something'], 'XBuftype', 'D')
+ call assert_fails('write XBuftype', 'E13:', 'with buftype=' .. val)
+ endfor
+
+ bwipe!
+endfunc
+
+" Test for the 'rightleftcmd' option
+func Test_rightleftcmd()
+ CheckFeature rightleft
+ set rightleft
+
+ let g:l = []
+ func AddPos()
+ call add(g:l, screencol())
+ return ''
+ endfunc
+ cmap <expr> <F2> AddPos()
+
+ set rightleftcmd=
+ call feedkeys("/\<F2>abc\<Right>\<F2>\<Left>\<Left>\<F2>" ..
+ \ "\<Right>\<F2>\<Esc>", 'xt')
+ call assert_equal([2, 5, 3, 4], g:l)
+
+ let g:l = []
+ set rightleftcmd=search
+ call feedkeys("/\<F2>abc\<Left>\<F2>\<Right>\<Right>\<F2>" ..
+ \ "\<Left>\<F2>\<Esc>", 'xt')
+ call assert_equal([&co - 1, &co - 4, &co - 2, &co - 3], g:l)
+
+ cunmap <F2>
+ unlet g:l
+ set rightleftcmd&
+ set rightleft&
+endfunc
+
+" Test for the 'debug' option
+func Test_debug_option()
+ " redraw to avoid matching previous messages
+ redraw
+ set debug=beep
+ exe "normal \<C-c>"
+ call assert_equal('Beep!', Screenline(&lines))
+ call assert_equal('line 4:', Screenline(&lines - 1))
+ " only match the final colon in the line that shows the source
+ call assert_match(':$', Screenline(&lines - 2))
+ set debug&
+endfunc
+
+" Test for the default CDPATH option
+func Test_opt_default_cdpath()
+ let after =<< trim [CODE]
+ call assert_equal(',/path/to/dir1,/path/to/dir2', &cdpath)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ if has('unix')
+ let $CDPATH='/path/to/dir1:/path/to/dir2'
+ else
+ let $CDPATH='/path/to/dir1;/path/to/dir2'
+ endif
+ if RunVim([], after, '')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for setting keycodes using set
+func Test_opt_set_keycode()
+ call assert_fails('set <t_k1=l', 'E474:')
+ call assert_fails('set <Home=l', 'E474:')
+ set <t_k9>=abcd
+ call assert_equal('abcd', &t_k9)
+ set <t_k9>&
+ set <F9>=xyz
+ call assert_equal('xyz', &t_k9)
+ set <t_k9>&
+
+ " should we test all of them?
+ set t_Ce=testCe
+ set t_Cs=testCs
+ set t_Us=testUs
+ set t_ds=testds
+ set t_Ds=testDs
+ call assert_equal('testCe', &t_Ce)
+ call assert_equal('testCs', &t_Cs)
+ call assert_equal('testUs', &t_Us)
+ call assert_equal('testds', &t_ds)
+ call assert_equal('testDs', &t_Ds)
+endfunc
+
+" Test for changing options in a sandbox
+func Test_opt_sandbox()
+ for opt in ['backupdir', 'cdpath', 'exrc']
+ call assert_fails('sandbox set ' .. opt .. '?', 'E48:')
+ call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:')
+ endfor
+ call assert_fails('sandbox let &modelineexpr = 1', 'E48:')
+endfunc
+
+" Test for setting an option with local value to global value
+func Test_opt_local_to_global()
+ setglobal equalprg=gprg
+ setlocal equalprg=lprg
+ call assert_equal('gprg', &g:equalprg)
+ call assert_equal('lprg', &l:equalprg)
+ call assert_equal('lprg', &equalprg)
+ set equalprg<
+ call assert_equal('', &l:equalprg)
+ call assert_equal('gprg', &equalprg)
+ setglobal equalprg=gnewprg
+ setlocal equalprg=lnewprg
+ setlocal equalprg<
+ call assert_equal('gnewprg', &l:equalprg)
+ call assert_equal('gnewprg', &equalprg)
+ set equalprg&
+
+ " Test for setting the global/local value of a boolean option
+ setglobal autoread
+ setlocal noautoread
+ call assert_false(&autoread)
+ set autoread<
+ call assert_true(&autoread)
+ setglobal noautoread
+ setlocal autoread
+ setlocal autoread<
+ call assert_false(&autoread)
+ set autoread&
+endfunc
+
+func Test_set_in_sandbox()
+ " Some boolean options cannot be set in sandbox, some can.
+ call assert_fails('sandbox set modelineexpr', 'E48:')
+ sandbox set number
+ call assert_true(&number)
+ set number&
+
+ " Some boolean options cannot be set in sandbox, some can.
+ if has('python') || has('python3')
+ call assert_fails('sandbox set pyxversion=3', 'E48:')
+ endif
+ sandbox set tabstop=4
+ call assert_equal(4, &tabstop)
+ set tabstop&
+
+ " Some string options cannot be set in sandbox, some can.
+ call assert_fails('sandbox set backupdir=/tmp', 'E48:')
+ sandbox set filetype=perl
+ call assert_equal('perl', &filetype)
+ set filetype&
+endfunc
+
+" Test for incrementing, decrementing and multiplying a number option value
+func Test_opt_num_op()
+ set shiftwidth=4
+ set sw+=2
+ call assert_equal(6, &sw)
+ set sw-=2
+ call assert_equal(4, &sw)
+ set sw^=2
+ call assert_equal(8, &sw)
+ set shiftwidth&
+endfunc
+
+" Test for setting option values using v:false and v:true
+func Test_opt_boolean()
+ set number&
+ set number
+ call assert_equal(1, &nu)
+ set nonu
+ call assert_equal(0, &nu)
+ let &nu = v:true
+ call assert_equal(1, &nu)
+ let &nu = v:false
+ call assert_equal(0, &nu)
+ set number&
+endfunc
+
+" Test for the 'window' option
+func Test_window_opt()
+ " Needs only one open widow
+ %bw!
+ call setline(1, range(1, 8))
+ set window=5
+ exe "normal \<C-F>"
+ call assert_equal(4, line('w0'))
+ exe "normal \<C-F>"
+ call assert_equal(7, line('w0'))
+ exe "normal \<C-F>"
+ call assert_equal(8, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(5, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(2, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(1, line('w0'))
+ set window=1
+ exe "normal gg\<C-F>"
+ call assert_equal(2, line('w0'))
+ exe "normal \<C-F>"
+ call assert_equal(3, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(2, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(1, line('w0'))
+ enew!
+ set window&
+endfunc
+
+" Test for the 'winminheight' option
+func Test_opt_winminheight()
+ only!
+ let &winheight = &lines + 4
+ call assert_fails('let &winminheight = &lines + 2', 'E36:')
+ call assert_true(&winminheight <= &lines)
+ set winminheight&
+ set winheight&
+endfunc
+
+func Test_opt_winminheight_term()
+ CheckRunVimInTerminal
+
+ " The tabline should be taken into account.
+ let lines =<< trim END
+ set wmh=0 stal=2
+ below sp | wincmd _
+ below sp | wincmd _
+ below sp | wincmd _
+ below sp
+ END
+ call writefile(lines, 'Xwinminheight', 'D')
+ let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
+ call term_sendkeys(buf, ":set wmh=1\n")
+ call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_opt_winminheight_term_tabs()
+ CheckRunVimInTerminal
+
+ " The tabline should be taken into account.
+ let lines =<< trim END
+ set wmh=0 stal=2
+ split
+ split
+ split
+ split
+ tabnew
+ END
+ call writefile(lines, 'Xwinminheight', 'D')
+ let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
+ call term_sendkeys(buf, ":set wmh=1\n")
+ call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for the 'winminwidth' option
+func Test_opt_winminwidth()
+ only!
+ let &winwidth = &columns + 4
+ call assert_fails('let &winminwidth = &columns + 2', 'E36:')
+ call assert_true(&winminwidth <= &columns)
+ set winminwidth&
+ set winwidth&
+endfunc
+
+" Test for setting option value containing spaces with isfname+=32
+func Test_isfname_with_options()
+ set isfname+=32
+ setlocal keywordprg=:term\ help.exe
+ call assert_equal(':term help.exe', &keywordprg)
+ set isfname&
+ setlocal keywordprg&
+endfunc
+
+" Test that resetting laststatus does change scroll option
+func Test_opt_reset_scroll()
+ CheckRunVimInTerminal
+ let vimrc =<< trim [CODE]
+ set scroll=2
+ set laststatus=2
+ [CODE]
+ call writefile(vimrc, 'Xscroll', 'D')
+ let buf = RunVimInTerminal('-S Xscroll', {'rows': 16, 'cols': 45})
+ call term_sendkeys(buf, ":verbose set scroll?\n")
+ call WaitForAssert({-> assert_match('Last set.*window size', term_getline(buf, 15))})
+ call assert_match('^\s*scroll=7$', term_getline(buf, 14))
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Check that VIM_POSIX env variable influences default value of 'cpo' and 'shm'
+func Test_VIM_POSIX()
+ let saved_VIM_POSIX = getenv("VIM_POSIX")
+
+ call setenv('VIM_POSIX', "1")
+ let after =<< trim [CODE]
+ call writefile([&cpo, &shm], 'X_VIM_POSIX')
+ qall
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;',
+ \ 'AS'], readfile('X_VIM_POSIX'))
+ endif
+
+ call setenv('VIM_POSIX', v:null)
+ let after =<< trim [CODE]
+ call writefile([&cpo, &shm], 'X_VIM_POSIX')
+ qall
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;',
+ \ 'S'], readfile('X_VIM_POSIX'))
+ endif
+
+ call delete('X_VIM_POSIX')
+ call setenv('VIM_POSIX', saved_VIM_POSIX)
+endfunc
+
+" Test for setting an option to a Vi or Vim default
+func Test_opt_default()
+ set formatoptions&vi
+ call assert_equal('vt', &formatoptions)
+ set formatoptions&vim
+ call assert_equal('tcq', &formatoptions)
+
+ call assert_equal('ucs-bom,utf-8,default,latin1', &fencs)
+ set fencs=latin1
+ set fencs&
+ call assert_equal('ucs-bom,utf-8,default,latin1', &fencs)
+ set fencs=latin1
+ set all&
+ call assert_equal('ucs-bom,utf-8,default,latin1', &fencs)
+endfunc
+
+" Test for the 'cmdheight' option
+func Test_cmdheight()
+ %bw!
+ let ht = &lines
+ set cmdheight=9999
+ call assert_equal(1, winheight(0))
+ call assert_equal(ht - 1, &cmdheight)
+ set cmdheight&
+endfunc
+
+" To specify a control character as an option value, '^' can be used
+func Test_opt_control_char()
+ set wildchar=^v
+ call assert_equal("\<C-V>", nr2char(&wildchar))
+ set wildcharm=^r
+ call assert_equal("\<C-R>", nr2char(&wildcharm))
+ " Bug: This doesn't work for the 'cedit' and 'termwinkey' options
+ set wildchar& wildcharm&
+endfunc
+
+" Test for the 'errorbells' option
+func Test_opt_errorbells()
+ set errorbells
+ call assert_beeps('s/a1b2/x1y2/')
+ set noerrorbells
+endfunc
+
+func Test_opt_scrolljump()
+ help
+ resize 10
+
+ " Test with positive 'scrolljump'.
+ set scrolljump=2
+ norm! Lj
+ call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
+ \ 'topline':3, 'coladd':0, 'skipcol':0, 'curswant':0},
+ \ winsaveview())
+
+ " Test with negative 'scrolljump' (percentage of window height).
+ set scrolljump=-40
+ norm! ggLj
+ call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
+ \ 'topline':5, 'coladd':0, 'skipcol':0, 'curswant':0},
+ \ winsaveview())
+
+ set scrolljump&
+ bw
+endfunc
+
+" Test for the 'cdhome' option
+func Test_opt_cdhome()
+ if has('unix') || has('vms')
+ throw 'Skipped: only works on non-Unix'
+ endif
+
+ set cdhome&
+ call assert_equal(0, &cdhome)
+ set cdhome
+
+ " This paragraph is copied from Test_cd_no_arg().
+ let path = getcwd()
+ cd
+ call assert_equal($HOME, getcwd())
+ call assert_notequal(path, getcwd())
+ exe 'cd ' .. fnameescape(path)
+ call assert_equal(path, getcwd())
+
+ set cdhome&
+endfunc
+
+func Test_set_completion_2()
+ CheckOption termguicolors
+
+ " Test default option completion
+ set wildoptions=
+ call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set termguicolors', @:)
+
+ call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set notermguicolors', @:)
+
+ " Test fuzzy option completion
+ set wildoptions=fuzzy
+ call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set termguicolors termencoding', @:)
+
+ call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set notermguicolors', @:)
+
+ set wildoptions=
+endfunc
+
+func Test_switchbuf_reset()
+ set switchbuf=useopen
+ sblast
+ call assert_equal(1, winnr('$'))
+ set all&
+ call assert_equal('', &switchbuf)
+ sblast
+ call assert_equal(2, winnr('$'))
+ only!
+endfunc
+
+" :set empty string for global 'keywordprg' falls back to ":help"
+func Test_keywordprg_empty()
+ let k = &keywordprg
+ set keywordprg=man
+ call assert_equal('man', &keywordprg)
+ set keywordprg=
+ call assert_equal(':help', &keywordprg)
+ set keywordprg=man
+ call assert_equal('man', &keywordprg)
+ call assert_equal("\n keywordprg=:help", execute('set kp= kp?'))
+ let &keywordprg = k
+endfunc
+
+" check that the very first buffer created does not have 'endoffile' set
+func Test_endoffile_default()
+ let after =<< trim [CODE]
+ call writefile([execute('set eof?')], 'Xtestout')
+ qall!
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal(["\nnoendoffile"], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfunc
+
+" Test for setting the 'lines' and 'columns' options to a minimum value
+func Test_set_min_lines_columns()
+ let save_lines = &lines
+ let save_columns = &columns
+
+ let after =<< trim END
+ set nomore
+ let msg = []
+ let v:errmsg = ''
+ silent! let &columns=0
+ call add(msg, v:errmsg)
+ silent! set columns=0
+ call add(msg, v:errmsg)
+ silent! call setbufvar('', '&columns', 0)
+ call add(msg, v:errmsg)
+ "call writefile(msg, 'XResultsetminlines')
+ silent! let &lines=0
+ call add(msg, v:errmsg)
+ silent! set lines=0
+ call add(msg, v:errmsg)
+ silent! call setbufvar('', '&lines', 0)
+ call add(msg, v:errmsg)
+ call writefile(msg, 'XResultsetminlines')
+ qall!
+ END
+ if RunVim([], after, '')
+ call assert_equal(['E594: Need at least 12 columns',
+ \ 'E594: Need at least 12 columns: columns=0',
+ \ 'E594: Need at least 12 columns',
+ \ 'E593: Need at least 2 lines',
+ \ 'E593: Need at least 2 lines: lines=0',
+ \ 'E593: Need at least 2 lines',], readfile('XResultsetminlines'))
+ endif
+
+ call delete('XResultsetminlines')
+ let &lines = save_lines
+ let &columns = save_columns
+endfunc
+
+" Test for reverting a string option value if the new value is invalid.
+func Test_string_option_revert_on_failure()
+ new
+ let optlist = [
+ \ ['ambiwidth', 'double', 'a123'],
+ \ ['background', 'dark', 'a123'],
+ \ ['backspace', 'eol', 'a123'],
+ \ ['backupcopy', 'no', 'a123'],
+ \ ['belloff', 'showmatch', 'a123'],
+ \ ['breakindentopt', 'min:10', 'list'],
+ \ ['bufhidden', 'wipe', 'a123'],
+ \ ['buftype', 'nowrite', 'a123'],
+ \ ['casemap', 'keepascii', 'a123'],
+ \ ['cedit', "\<C-Y>", 'z'],
+ \ ['colorcolumn', '10', 'z'],
+ \ ['commentstring', '#%s', 'a123'],
+ \ ['complete', '.,t', 'a'],
+ \ ['completefunc', 'MyCmplFunc', '1a-'],
+ \ ['completeopt', 'popup', 'a123'],
+ \ ['completepopup', 'width:20', 'border'],
+ \ ['concealcursor', 'v', 'xyz'],
+ \ ['cpoptions', 'HJ', '~'],
+ \ ['cryptmethod', 'zip', 'a123'],
+ \ ['cursorlineopt', 'screenline', 'a123'],
+ \ ['debug', 'throw', 'a123'],
+ \ ['diffopt', 'iwhite', 'a123'],
+ \ ['display', 'uhex', 'a123'],
+ \ ['eadirection', 'hor', 'a123'],
+ \ ['encoding', 'utf-8', 'a123'],
+ \ ['eventignore', 'TextYankPost', 'a123'],
+ \ ['fileencoding', 'utf-8', 'a123,'],
+ \ ['fileformat', 'mac', 'a123'],
+ \ ['fileformats', 'mac', 'a123'],
+ \ ['filetype', 'abc', 'a^b'],
+ \ ['fillchars', 'diff:~', 'a123'],
+ \ ['foldclose', 'all', 'a123'],
+ \ ['foldmarker', '[[[,]]]', '[[['],
+ \ ['foldmethod', 'marker', 'a123'],
+ \ ['foldopen', 'percent', 'a123'],
+ \ ['formatoptions', 'an', '*'],
+ \ ['guicursor', 'n-v-c:block-Cursor/lCursor', 'n-v-c'],
+ \ ['helplang', 'en', 'a'],
+ \ ['highlight', '!:CursorColumn', '8:'],
+ \ ['keymodel', 'stopsel', 'a123'],
+ \ ['keyprotocol', 'kitty:kitty', 'kitty:'],
+ \ ['lispoptions', 'expr:1', 'a123'],
+ \ ['listchars', 'tab:->', 'tab:'],
+ \ ['matchpairs', '<:>', '<:'],
+ \ ['mkspellmem', '100000,1000,100', '100000'],
+ \ ['mouse', 'nvi', 'z'],
+ \ ['mousemodel', 'extend', 'a123'],
+ \ ['nrformats', 'alpha', 'a123'],
+ \ ['omnifunc', 'MyOmniFunc', '1a-'],
+ \ ['operatorfunc', 'MyOpFunc', '1a-'],
+ \ ['previewpopup', 'width:20', 'a123'],
+ \ ['printoptions', 'paper:A4', 'a123:'],
+ \ ['quickfixtextfunc', 'MyQfFunc', '1a-'],
+ \ ['rulerformat', '%l', '%['],
+ \ ['scrollopt', 'hor,jump', 'a123'],
+ \ ['selection', 'exclusive', 'a123'],
+ \ ['selectmode', 'cmd', 'a123'],
+ \ ['sessionoptions', 'options', 'a123'],
+ \ ['shortmess', 'w', '2'],
+ \ ['showbreak', '>>', "\x01"],
+ \ ['showcmdloc', 'statusline', 'a123'],
+ \ ['signcolumn', 'no', 'a123'],
+ \ ['spellcapcheck', '[.?!]\+', '%\{'],
+ \ ['spellfile', 'MySpell.en.add', "\x01"],
+ \ ['spelllang', 'en', "#"],
+ \ ['spelloptions', 'camel', 'a123'],
+ \ ['spellsuggest', 'double', 'a123'],
+ \ ['splitkeep', 'topline', 'a123'],
+ \ ['statusline', '%f', '%['],
+ \ ['swapsync', 'sync', 'a123'],
+ \ ['switchbuf', 'usetab', 'a123'],
+ \ ['syntax', 'abc', 'a^b'],
+ \ ['tabline', '%f', '%['],
+ \ ['tagcase', 'ignore', 'a123'],
+ \ ['tagfunc', 'MyTagFunc', '1a-'],
+ \ ['thesaurusfunc', 'MyThesaurusFunc', '1a-'],
+ \ ['viewoptions', 'options', 'a123'],
+ \ ['virtualedit', 'onemore', 'a123'],
+ \ ['whichwrap', '<,>', '{,}'],
+ \ ['wildmode', 'list', 'a123'],
+ \ ['wildoptions', 'pum', 'a123']
+ \ ]
+ if has('gui')
+ call add(optlist, ['browsedir', 'buffer', 'a123'])
+ endif
+ if has('clipboard_working')
+ call add(optlist, ['clipboard', 'unnamed', 'a123'])
+ endif
+ if has('win32')
+ call add(optlist, ['completeslash', 'slash', 'a123'])
+ endif
+ if has('cscope')
+ call add(optlist, ['cscopequickfix', 't-', 'z-'])
+ endif
+ if !has('win32')
+ call add(optlist, ['imactivatefunc', 'MyActFunc', '1a-'])
+ call add(optlist, ['imstatusfunc', 'MyStatusFunc', '1a-'])
+ endif
+ if has('keymap')
+ call add(optlist, ['keymap', 'greek', '[]'])
+ endif
+ if has('mouseshape')
+ call add(optlist, ['mouseshape', 'm:no', 'a123:'])
+ endif
+ if has('win32') && has('gui')
+ call add(optlist, ['renderoptions', 'type:directx', 'type:directx,a123'])
+ endif
+ if has('rightleft')
+ call add(optlist, ['rightleftcmd', 'search', 'a123'])
+ endif
+ if has('terminal')
+ call add(optlist, ['termwinkey', '<C-L>', '<C'])
+ call add(optlist, ['termwinsize', '24x80', '100'])
+ endif
+ if has('win32') && has('terminal')
+ call add(optlist, ['termwintype', 'winpty', 'a123'])
+ endif
+ if exists('+toolbar')
+ call add(optlist, ['toolbar', 'text', 'a123'])
+ endif
+ if exists('+toolbariconsize')
+ call add(optlist, ['toolbariconsize', 'medium', 'a123'])
+ endif
+ if exists('+ttymouse') && !has('gui')
+ call add(optlist, ['ttymouse', 'xterm', 'a123'])
+ endif
+ if exists('+vartabs')
+ call add(optlist, ['varsofttabstop', '12', 'a123'])
+ call add(optlist, ['vartabstop', '4,20', '4,'])
+ endif
+ if exists('+winaltkeys')
+ call add(optlist, ['winaltkeys', 'no', 'a123'])
+ endif
+ for opt in optlist
+ exe $"let save_opt = &{opt[0]}"
+ try
+ exe $"let &{opt[0]} = '{opt[1]}'"
+ catch
+ call assert_report($"Caught {v:exception} with {opt->string()}")
+ endtry
+ call assert_fails($"let &{opt[0]} = '{opt[2]}'", '', opt[0])
+ call assert_equal(opt[1], eval($"&{opt[0]}"), opt[0])
+ exe $"let &{opt[0]} = save_opt"
+ endfor
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_packadd.vim b/src/testdir/test_packadd.vim
new file mode 100644
index 0000000..89b7817
--- /dev/null
+++ b/src/testdir/test_packadd.vim
@@ -0,0 +1,446 @@
+" Tests for 'packpath' and :packadd
+
+source check.vim
+
+func SetUp()
+ let s:topdir = getcwd() . '/Xppdir'
+ exe 'set packpath=' . s:topdir
+ let s:plugdir = s:topdir . '/pack/mine/opt/mytest'
+endfunc
+
+func TearDown()
+ call delete(s:topdir, 'rf')
+endfunc
+
+func Test_packadd()
+ if !exists('s:plugdir')
+ echomsg 'when running this test manually, call SetUp() first'
+ return
+ endif
+
+ call mkdir(s:plugdir . '/plugin/also', 'p')
+ call mkdir(s:plugdir . '/ftdetect', 'p')
+ call mkdir(s:plugdir . '/after', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ let rtp_entries = split(rtp, ',')
+ for entry in rtp_entries
+ if entry =~? '\<after\>'
+ let first_after_entry = entry
+ break
+ endif
+ endfor
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+
+ exe 'split ' . s:plugdir . '/plugin/also/loaded.vim'
+ call setline(1, 'let g:plugin_also_works = 77')
+ wq
+
+ exe 'split ' . s:plugdir . '/ftdetect/test.vim'
+ call setline(1, 'let g:ftdetect_works = 17')
+ wq
+
+ packadd mytest
+
+ call assert_equal(42, g:plugin_works)
+ call assert_equal(77, g:plugin_also_works)
+ call assert_equal(17, g:ftdetect_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xppdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+
+ let new_after = match(&rtp, '/testdir/Xppdir/pack/mine/opt/mytest/after,')
+ let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g')
+ let old_after = match(&rtp, ',' . forwarded . '\>')
+ call assert_true(new_after > 0, 'rtp is ' . &rtp)
+ call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp)
+ call assert_true(new_after < old_after, 'rtp is ' . &rtp)
+
+ " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest'
+ call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p')
+ let rtp = &rtp
+ packadd myte
+
+ " Check the path of 'myte' is added
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xppdir/pack/mine/opt/myte\($\|,\)', &rtp)
+
+ " Check exception
+ call assert_fails("packadd directorynotfound", 'E919:')
+ call assert_fails("packadd", 'E471:')
+endfunc
+
+func Test_packadd_start()
+ let plugdir = s:topdir . '/pack/mine/start/other'
+ call mkdir(plugdir . '/plugin', 'p')
+ set rtp&
+ let rtp = &rtp
+ filetype on
+
+ exe 'split ' . plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 24')
+ wq
+
+ packadd other
+
+ call assert_equal(24, g:plugin_works)
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('/testdir/Xppdir/pack/mine/start/other\($\|,\)', &rtp)
+endfunc
+
+func Test_packadd_noload()
+ call mkdir(s:plugdir . '/plugin', 'p')
+ call mkdir(s:plugdir . '/syntax', 'p')
+ set rtp&
+ let rtp = &rtp
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 42')
+ wq
+ let g:plugin_works = 0
+
+ packadd! mytest
+
+ call assert_true(len(&rtp) > len(rtp))
+ call assert_match('testdir/Xppdir/pack/mine/opt/mytest\($\|,\)', &rtp)
+ call assert_equal(0, g:plugin_works)
+
+ " check the path is not added twice
+ let new_rtp = &rtp
+ packadd! mytest
+ call assert_equal(new_rtp, &rtp)
+endfunc
+
+func Test_packadd_symlink_dir()
+ CheckUnix
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym'
+ call mkdir(real_dir, 'p')
+ exec "silent !ln -s Xsym" top2_dir
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 44')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(44, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir
+endfunc
+
+func Test_packadd_symlink_dir2()
+ CheckUnix
+ let top2_dir = s:topdir . '/Xdir2'
+ let real_dir = s:topdir . '/Xsym/pack'
+ call mkdir(top2_dir, 'p')
+ call mkdir(real_dir, 'p')
+ let &rtp = top2_dir . ',' . top2_dir . '/after'
+ let &packpath = &rtp
+
+ exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack'
+ let s:plugdir = top2_dir . '/pack/mine/opt/mytest'
+ call mkdir(s:plugdir . '/plugin', 'p')
+
+ exe 'split ' . s:plugdir . '/plugin/test.vim'
+ call setline(1, 'let g:plugin_works = 48')
+ wq
+ let g:plugin_works = 0
+
+ packadd mytest
+
+ " Must have been inserted in the middle, not at the end
+ call assert_match('/Xdir2/pack/mine/opt/mytest,', &rtp)
+ call assert_equal(48, g:plugin_works)
+
+ " No change when doing it again.
+ let rtp_before = &rtp
+ packadd mytest
+ call assert_equal(rtp_before, &rtp)
+
+ set rtp&
+ let rtp = &rtp
+ exec "silent !rm" top2_dir . '/pack'
+ exec "silent !rmdir" top2_dir
+endfunc
+
+" Check command-line completion for :packadd
+func Test_packadd_completion()
+ let optdir1 = &packpath . '/pack/mine/opt'
+ let optdir2 = &packpath . '/pack/candidate/opt'
+
+ call mkdir(optdir1 . '/pluginA', 'p')
+ call mkdir(optdir1 . '/pluginC', 'p')
+ call writefile([], optdir1 . '/unrelated')
+ call mkdir(optdir2 . '/pluginB', 'p')
+ call mkdir(optdir2 . '/pluginC', 'p')
+ call writefile([], optdir2 . '/unrelated')
+
+ let li = []
+ call feedkeys(":packadd \<Tab>')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":packadd " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("packadd pluginA", li[0])
+ call assert_equal("packadd pluginB", li[1])
+ call assert_equal("packadd pluginC", li[2])
+ call assert_equal("packadd ", li[3])
+endfunc
+
+func Test_packloadall()
+ " plugin foo with an autoload directory
+ let fooplugindir = &packpath . '/pack/mine/start/foo/plugin'
+ call mkdir(fooplugindir, 'p')
+ call writefile(['let g:plugin_foo_number = 1234',
+ \ 'let g:plugin_foo_auto = bbb#value',
+ \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim')
+ let fooautodir = &packpath . '/pack/mine/start/foo/autoload'
+ call mkdir(fooautodir, 'p')
+ call writefile(['let bar#value = 77'], fooautodir . '/bar.vim')
+
+ " plugin aaa with an autoload directory
+ let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin'
+ call mkdir(aaaplugindir, 'p')
+ call writefile(['let g:plugin_aaa_number = 333',
+ \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim')
+ let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload'
+ call mkdir(aaaautodir, 'p')
+ call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim')
+
+ " plugin extra with only an autoload directory
+ let extraautodir = &packpath . '/pack/mine/start/extra/autoload'
+ call mkdir(extraautodir, 'p')
+ call writefile(['let extra#value = 99'], extraautodir . '/extra.vim')
+
+ packloadall
+ call assert_equal(1234, g:plugin_foo_number)
+ call assert_equal(55, g:plugin_foo_auto)
+ call assert_equal(99, g:plugin_extra_auto)
+ call assert_equal(333, g:plugin_aaa_number)
+ call assert_equal(77, g:plugin_aaa_auto)
+
+ " only works once
+ call writefile(['let g:plugin_bar_number = 4321'], fooplugindir . '/bar2.vim')
+ packloadall
+ call assert_false(exists('g:plugin_bar_number'))
+
+ " works when ! used
+ packloadall!
+ call assert_equal(4321, g:plugin_bar_number)
+endfunc
+
+func Test_start_autoload()
+ " plugin foo with an autoload directory
+ let autodir = &packpath .. '/pack/mine/start/foo/autoload'
+ call mkdir(autodir, 'p')
+ let fname = autodir .. '/foobar.vim'
+ call writefile(['func foobar#test()',
+ \ ' return 1666',
+ \ 'endfunc'], fname)
+
+ call assert_equal(1666, foobar#test())
+endfunc
+
+func Test_helptags()
+ let docdir1 = &packpath . '/pack/mine/start/foo/doc'
+ let docdir2 = &packpath . '/pack/mine/start/bar/doc'
+ call mkdir(docdir1, 'p')
+ call mkdir(docdir2, 'p')
+ call writefile(['look here: *look-here*'], docdir1 . '/bar.txt')
+ call writefile(['look away: *look-away*'], docdir2 . '/foo.txt')
+ exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar'
+
+ helptags ALL
+
+ let tags1 = readfile(docdir1 . '/tags')
+ call assert_match('look-here', tags1[0])
+ let tags2 = readfile(docdir2 . '/tags')
+ call assert_match('look-away', tags2[0])
+
+ call assert_fails('helptags abcxyz', 'E150:')
+endfunc
+
+func Test_colorscheme()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ colorscheme one
+ call assert_equal(1, g:found_one)
+ colorscheme two
+ call assert_equal(1, g:found_two)
+ colorscheme three
+ call assert_equal(1, g:found_three)
+endfunc
+
+func Test_colorscheme_completion()
+ let colordirrun = &packpath . '/runtime/colors'
+ let colordirstart = &packpath . '/pack/mine/start/foo/colors'
+ let colordiropt = &packpath . '/pack/mine/opt/bar/colors'
+ call mkdir(colordirrun, 'p')
+ call mkdir(colordirstart, 'p')
+ call mkdir(colordiropt, 'p')
+ call writefile(['let g:found_one = 1'], colordirrun . '/one.vim')
+ call writefile(['let g:found_two = 1'], colordirstart . '/two.vim')
+ call writefile(['let g:found_three = 1'], colordiropt . '/three.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let li=[]
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 1) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't')
+ call feedkeys(":colorscheme " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx')
+ call assert_equal("colorscheme one", li[0])
+ call assert_equal("colorscheme three", li[1])
+ call assert_equal("colorscheme two", li[2])
+ call assert_equal("colorscheme ", li[3])
+endfunc
+
+func Test_runtime()
+ let rundir = &packpath . '/runtime/extra'
+ let startdir = &packpath . '/pack/mine/start/foo/extra'
+ let optdir = &packpath . '/pack/mine/opt/bar/extra'
+ call mkdir(rundir, 'p')
+ call mkdir(startdir, 'p')
+ call mkdir(optdir, 'p')
+ call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim')
+ call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim')
+ call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim')
+ call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim')
+ call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ let g:sequence = ''
+ runtime extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime START extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime OPT extra/bar.vim
+ call assert_equal('opt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/bar.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime! PACK extra/bar.vim
+ call assert_equal('startopt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+
+ let g:sequence = ''
+ runtime ALL extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime ALL extra/foo.vim
+ call assert_equal('foostart', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/xxx.vim
+ call assert_equal('xxxopt', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/bar.vim
+ call assert_equal('runstartopt', g:sequence)
+endfunc
+
+func Test_runtime_completion()
+ let rundir = &packpath . '/runtime/Aextra'
+ let startdir = &packpath . '/pack/mine/start/foo/Aextra'
+ let optdir = &packpath . '/pack/mine/opt/bar/Aextra'
+ call mkdir(rundir . '/Arunbaz', 'p')
+ call mkdir(startdir . '/Astartbaz', 'p')
+ call mkdir(optdir . '/Aoptbaz', 'p')
+ call writefile([], rundir . '/../Arunfoo.vim')
+ call writefile([], rundir . '/Arunbar.vim')
+ call writefile([], rundir . '/Aunrelated')
+ call writefile([], rundir . '/../Aunrelated')
+ call writefile([], startdir . '/../Astartfoo.vim')
+ call writefile([], startdir . '/Astartbar.vim')
+ call writefile([], startdir . '/Aunrelated')
+ call writefile([], startdir . '/../Aunrelated')
+ call writefile([], optdir . '/../Aoptfoo.vim')
+ call writefile([], optdir . '/Aoptbar.vim')
+ call writefile([], optdir . '/Aunrelated')
+ call writefile([], optdir . '/../Aunrelated')
+ exe 'set rtp=' . &packpath . '/runtime'
+
+ func Check_runtime_completion(arg, arg1, res)
+ call feedkeys(':runtime ' .. a:arg .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"runtime ' .. a:arg1 .. join(a:res), @:)
+ call assert_equal(a:res, getcompletion(a:arg, 'runtime'))
+ endfunc
+
+ call Check_runtime_completion('', '',
+ \ ['Aextra/', 'Arunfoo.vim', 'START', 'OPT', 'PACK', 'ALL'])
+ call Check_runtime_completion('S', '',
+ \ ['START'])
+ call Check_runtime_completion('O', '',
+ \ ['OPT'])
+ call Check_runtime_completion('P', '',
+ \ ['PACK'])
+ call Check_runtime_completion('A', '',
+ \ ['Aextra/', 'Arunfoo.vim', 'ALL'])
+ call Check_runtime_completion('Aextra/', '',
+ \ ['Aextra/Arunbar.vim', 'Aextra/Arunbaz/'])
+
+ call Check_runtime_completion('START ', 'START ',
+ \ ['Aextra/', 'Astartfoo.vim'])
+ call Check_runtime_completion('START A', 'START ',
+ \ ['Aextra/', 'Astartfoo.vim'])
+ call Check_runtime_completion('START Aextra/', 'START ',
+ \ ['Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
+
+ call Check_runtime_completion('OPT ', 'OPT ',
+ \ ['Aextra/', 'Aoptfoo.vim'])
+ call Check_runtime_completion('OPT A', 'OPT ',
+ \ ['Aextra/', 'Aoptfoo.vim'])
+ call Check_runtime_completion('OPT Aextra/', 'OPT ',
+ \ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/'])
+
+ call Check_runtime_completion('PACK ', 'PACK ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('PACK A', 'PACK ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('PACK Aextra/', 'PACK ',
+ \ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/',
+ \ 'Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
+
+ call Check_runtime_completion('ALL ', 'ALL ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Arunfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('ALL A', 'ALL ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Arunfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('ALL Aextra/', 'ALL ',
+ \ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/',
+ \ 'Aextra/Arunbar.vim', 'Aextra/Arunbaz/',
+ \ 'Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
+
+ delfunc Check_runtime_completion
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_partial.vim b/src/testdir/test_partial.vim
new file mode 100644
index 0000000..4b054b5
--- /dev/null
+++ b/src/testdir/test_partial.vim
@@ -0,0 +1,409 @@
+" Test binding arguments to a Funcref.
+
+source check.vim
+
+func MyFunc(arg1, arg2, arg3)
+ return a:arg1 . '/' . a:arg2 . '/' . a:arg3
+endfunc
+
+func MySort(up, one, two)
+ if a:one == a:two
+ return 0
+ endif
+ if a:up
+ return a:one > a:two ? 1 : -1
+ endif
+ return a:one < a:two ? 1 : -1
+endfunc
+
+func MyMap(sub, index, val)
+ return a:val - a:sub
+endfunc
+
+func MyFilter(threshold, index, val)
+ return a:val > a:threshold
+endfunc
+
+func Test_partial_args()
+ let Cb = function('MyFunc', ["foo", "bar"])
+
+ call Cb("zzz")
+ call assert_equal("foo/bar/xxx", Cb("xxx"))
+ call assert_equal("foo/bar/yyy", call(Cb, ["yyy"]))
+ let Cb2 = function(Cb)
+ call assert_equal("foo/bar/zzz", Cb2("zzz"))
+ let Cb3 = function(Cb, ["www"])
+ call assert_equal("foo/bar/www", Cb3())
+
+ let Cb = function('MyFunc', [])
+ call assert_equal("a/b/c", Cb("a", "b", "c"))
+ let Cb2 = function(Cb, [])
+ call assert_equal("a/b/d", Cb2("a", "b", "d"))
+ let Cb3 = function(Cb, ["a", "b"])
+ call assert_equal("a/b/e", Cb3("e"))
+
+ let Sort = function('MySort', [1])
+ call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
+ let Sort = function('MySort', [0])
+ call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
+
+ let Map = function('MyMap', [2])
+ call assert_equal([-1, 0, 1], map([1, 2, 3], Map))
+ let Map = function('MyMap', [3])
+ call assert_equal([-2, -1, 0], map([1, 2, 3], Map))
+
+ let Filter = function('MyFilter', [1])
+ call assert_equal([2, 3], filter([1, 2, 3], Filter))
+ let Filter = function('MyFilter', [2])
+ call assert_equal([3], filter([1, 2, 3], Filter))
+endfunc
+
+func MyDictFunc(arg1, arg2) dict
+ return self.name . '/' . a:arg1 . '/' . a:arg2
+endfunc
+
+func Test_partial_dict()
+ let dict = {'name': 'hello'}
+ let Cb = function('MyDictFunc', ["foo", "bar"], dict)
+ call test_garbagecollect_now()
+ call assert_equal("hello/foo/bar", Cb())
+ call assert_fails('Cb("xxx")', 'E492:')
+
+ let Cb = function('MyDictFunc', ["foo"], dict)
+ call assert_equal("hello/foo/xxx", Cb("xxx"))
+ call assert_fails('Cb()', 'E492:')
+
+ let Cb = function('MyDictFunc', [], dict)
+ call assert_equal("hello/ttt/xxx", Cb("ttt", "xxx"))
+ call assert_fails('Cb("yyy")', 'E492:')
+
+ let Cb = function('MyDictFunc', dict)
+ call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
+ call assert_fails('Cb("fff")', 'E492:')
+
+ let Cb = function('MyDictFunc', dict)
+ call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb))
+
+ let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
+ call assert_equal("Hello", dict.tr())
+
+ call assert_fails("let F=function('setloclist', 10)", "E923:")
+ call assert_fails("let F=function('setloclist', [], [])", "E1206:")
+endfunc
+
+func Test_partial_implicit()
+ let dict = {'name': 'foo'}
+ func dict.MyFunc(arg) dict
+ return self.name . '/' . a:arg
+ endfunc
+
+ call assert_equal('foo/bar', dict.MyFunc('bar'))
+
+ call assert_fails('let func = dict.MyFunc', 'E704:')
+ let Func = dict.MyFunc
+ call assert_equal('foo/aaa', Func('aaa'))
+
+ let Func = function(dict.MyFunc, ['bbb'])
+ call assert_equal('foo/bbb', Func())
+endfunc
+
+fun InnerCall(funcref)
+ return a:funcref
+endfu
+
+fun OuterCall()
+ let opt = { 'func' : function('max') }
+ call InnerCall(opt.func)
+endfu
+
+func Test_function_in_dict()
+ call OuterCall()
+endfunc
+
+func s:cache_clear() dict
+ return self.name
+endfunc
+
+func Test_script_function_in_dict()
+ let s:obj = {'name': 'foo'}
+ let s:obj2 = {'name': 'bar'}
+
+ let s:obj['clear'] = function('s:cache_clear')
+
+ call assert_equal('foo', s:obj.clear())
+ let F = s:obj.clear
+ call assert_equal('foo', F())
+ call assert_equal('foo', call(s:obj.clear, [], s:obj))
+ call assert_equal('bar', call(s:obj.clear, [], s:obj2))
+
+ let s:obj2['clear'] = function('s:cache_clear')
+ call assert_equal('bar', s:obj2.clear())
+ let B = s:obj2.clear
+ call assert_equal('bar', B())
+endfunc
+
+func s:cache_arg(arg) dict
+ let s:result = self.name . '/' . a:arg
+ return s:result
+endfunc
+
+func Test_script_function_in_dict_arg()
+ let s:obj = {'name': 'foo'}
+ let s:obj['clear'] = function('s:cache_arg')
+
+ call assert_equal('foo/bar', s:obj.clear('bar'))
+ let F = s:obj.clear
+ let s:result = ''
+ call assert_equal('foo/bar', F('bar'))
+ call assert_equal('foo/bar', s:result)
+
+ let s:obj['clear'] = function('s:cache_arg', ['bar'])
+ call assert_equal('foo/bar', s:obj.clear())
+ let s:result = ''
+ call s:obj.clear()
+ call assert_equal('foo/bar', s:result)
+
+ let F = s:obj.clear
+ call assert_equal('foo/bar', F())
+ let s:result = ''
+ call F()
+ call assert_equal('foo/bar', s:result)
+
+ call assert_equal('foo/bar', call(s:obj.clear, [], s:obj))
+endfunc
+
+func Test_partial_exists()
+ let F = function('MyFunc')
+ call assert_true(exists('*F'))
+ let lF = [F]
+ call assert_true(exists('*lF[0]'))
+
+ let F = function('MyFunc', ['arg'])
+ call assert_true(exists('*F'))
+ let lF = [F]
+ call assert_true(exists('*lF[0]'))
+endfunc
+
+func Test_partial_string()
+ let F = function('MyFunc')
+ call assert_equal("function('MyFunc')", string(F))
+ let F = function('MyFunc', ['foo'])
+ call assert_equal("function('MyFunc', ['foo'])", string(F))
+ let F = function('MyFunc', ['foo', 'bar'])
+ call assert_equal("function('MyFunc', ['foo', 'bar'])", string(F))
+ let d = {'one': 1}
+ let F = function('MyFunc', d)
+ call assert_equal("function('MyFunc', {'one': 1})", string(F))
+ let F = function('MyFunc', ['foo'], d)
+ call assert_equal("function('MyFunc', ['foo'], {'one': 1})", string(F))
+ call assert_equal("function('')", string(test_null_function()))
+ call assert_equal("function('')", string(test_null_partial()))
+endfunc
+
+func Test_func_unref()
+ let obj = {}
+ function! obj.func() abort
+ endfunction
+ let funcnumber = matchstr(string(obj.func), '^function(''\zs.\{-}\ze''')
+ call assert_true(exists('*{' . funcnumber . '}'))
+ unlet obj
+ call assert_false(exists('*{' . funcnumber . '}'))
+endfunc
+
+func Test_tostring()
+ let d = {}
+ let d.d = d
+ function d.test3()
+ echo 42
+ endfunction
+ try
+ call string(d.test3)
+ catch
+ call assert_true(v:false, v:exception)
+ endtry
+endfunc
+
+func Test_redefine_dict_func()
+ let d = {}
+ function d.test4()
+ endfunction
+ let d.test4 = d.test4
+ try
+ function! d.test4(name)
+ endfunction
+ catch
+ call assert_true(v:errmsg, v:exception)
+ endtry
+endfunc
+
+func Test_bind_in_python()
+ CheckFeature python
+ let g:d = {}
+ function g:d.test2()
+ endfunction
+ python import vim
+ try
+ call assert_equal(pyeval('vim.bindeval("g:d.test2")'), g:d.test2)
+ catch
+ call assert_true(v:false, v:exception)
+ endtry
+endfunc
+
+" This caused double free on exit if EXITFREE is defined.
+func Test_cyclic_list_arg()
+ let l = []
+ let Pt = function('string', [l])
+ call add(l, Pt)
+ unlet l
+ unlet Pt
+endfunc
+
+" This caused double free on exit if EXITFREE is defined.
+func Test_cyclic_dict_arg()
+ let d = {}
+ let Pt = function('string', [d])
+ let d.Pt = Pt
+ unlet d
+ unlet Pt
+endfunc
+
+func Ignored3(job1, job2, status)
+endfunc
+
+func Test_cycle_partial_job()
+ CheckFeature job
+ let job = job_start('echo')
+ call job_setoptions(job, {'exit_cb': function('Ignored3', [job])})
+ unlet job
+endfunc
+
+func Ignored2(job, status)
+endfunc
+
+func Test_ref_job_partial_dict()
+ CheckFeature job
+ let g:ref_job = job_start('echo')
+ let d = {'a': 'b'}
+ call job_setoptions(g:ref_job, {'exit_cb': function('Ignored2', [], d)})
+ call test_garbagecollect_now()
+endfunc
+
+func Test_auto_partial_rebind()
+ let dict1 = {'name': 'dict1'}
+ func! dict1.f1()
+ return self.name
+ endfunc
+ let dict1.f2 = function(dict1.f1, dict1)
+
+ call assert_equal('dict1', dict1.f1())
+ call assert_equal('dict1', dict1['f1']())
+ call assert_equal('dict1', dict1.f2())
+ call assert_equal('dict1', dict1['f2']())
+
+ let dict2 = {'name': 'dict2'}
+ let dict2.f1 = dict1.f1
+ let dict2.f2 = dict1.f2
+
+ call assert_equal('dict2', dict2.f1())
+ call assert_equal('dict2', dict2['f1']())
+ call assert_equal('dict1', dict2.f2())
+ call assert_equal('dict1', dict2['f2']())
+endfunc
+
+func Test_get_partial_items()
+ let dict = {'name': 'hello'}
+ let args = ["foo", "bar"]
+ let Func = function('MyDictFunc')
+ let Cb = function('MyDictFunc', args, dict)
+
+ call assert_equal(Func, get(Cb, 'func'))
+ call assert_equal('MyDictFunc', get(Cb, 'name'))
+ call assert_equal(args, get(Cb, 'args'))
+ call assert_equal(dict, get(Cb, 'dict'))
+ call assert_fails('call get(Cb, "xxx")', 'E475:')
+
+ call assert_equal(Func, get(Func, 'func'))
+ call assert_equal('MyDictFunc', get(Func, 'name'))
+ call assert_equal([], get(Func, 'args'))
+ call assert_true(empty( get(Func, 'dict')))
+
+ let P = function('substitute', ['hello there', 'there'])
+ let dict = {'partial has': 'no dict'}
+ call assert_equal(dict, get(P, 'dict', dict))
+ call assert_equal(0, get(l:P, 'dict'))
+endfunc
+
+func Test_compare_partials()
+ let d1 = {}
+ let d2 = {}
+
+ function d1.f1() dict
+ endfunction
+
+ function d1.f2() dict
+ endfunction
+
+ let F1 = get(d1, 'f1')
+ let F2 = get(d1, 'f2')
+
+ let F1d1 = function(F1, d1)
+ let F2d1 = function(F2, d2)
+ let F1d1a1 = function(F1d1, [1])
+ let F1d1a12 = function(F1d1, [1, 2])
+ let F1a1 = function(F1, [1])
+ let F1a2 = function(F1, [2])
+ let F1d2 = function(F1, d2)
+ let d3 = {'f1': F1, 'f2': F2}
+ let F1d3 = function(F1, d3)
+ let F1ad1 = function(F1, [d1])
+ let F1ad3 = function(F1, [d3])
+
+ call assert_match('^function(''\d\+'')$', string(F1)) " Not a partial
+ call assert_match('^function(''\d\+'')$', string(F2)) " Not a partial
+ call assert_match('^function(''\d\+'', {.*})$', string(F1d1)) " A partial
+ call assert_match('^function(''\d\+'', {.*})$', string(F2d1)) " A partial
+ call assert_match('^function(''\d\+'', \[.*\])$', string(F1a1)) " No dict
+
+ " !=
+ let X = F1
+ call assert_false(F1 != X) " same function
+ let X = F1d1
+ call assert_false(F1d1 != X) " same partial
+ let X = F1d1a1
+ call assert_false(F1d1a1 != X) " same partial
+ let X = F1a1
+ call assert_false(F1a1 != X) " same partial
+
+ call assert_true(F1 != F2) " Different functions
+ call assert_true(F1 != F1d1) " Partial /= non-partial
+ call assert_true(F1d1a1 != F1d1a12) " Different number of arguments
+ call assert_true(F1a1 != F1d1a12) " One has no dict
+ call assert_true(F1a1 != F1a2) " Different arguments
+ call assert_true(F1d2 != F1d1) " Different dictionaries
+ call assert_false(F1d1 != F1d3) " Equal dictionaries, even though d1 isnot d3
+
+ " isnot, option 1
+ call assert_true(F1 isnot# F2) " Different functions
+ call assert_true(F1 isnot# F1d1) " Partial /= non-partial
+ call assert_true(F1d1 isnot# F1d3) " d1 isnot d3, even though d1 == d3
+ call assert_true(F1a1 isnot# F1d1a12) " One has no dict
+ call assert_true(F1a1 isnot# F1a2) " Different number of arguments
+ call assert_true(F1ad1 isnot# F1ad3) " In arguments d1 isnot d3
+
+ " isnot, option 2
+ call assert_true(F1 isnot# F2) " Different functions
+ call assert_true(F1 isnot# F1d1) " Partial /= non-partial
+ call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time
+
+ " compare two null partials
+ let N1 = test_null_partial()
+ let N2 = N1
+ call assert_true(N1 is N2)
+ call assert_true(N1 == N2)
+
+ " compare a partial and a null partial
+ call assert_false(N1 == F1)
+ call assert_false(F1 is N1)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_paste.vim b/src/testdir/test_paste.vim
new file mode 100644
index 0000000..d079f48
--- /dev/null
+++ b/src/testdir/test_paste.vim
@@ -0,0 +1,338 @@
+" Tests for bracketed paste and other forms of pasting.
+
+" Bracketed paste only works with "xterm". Not in GUI or Windows console.
+source check.vim
+source term_util.vim
+CheckNotMSWindows
+CheckNotGui
+
+set term=xterm
+
+source shared.vim
+
+func Test_paste_normal_mode()
+ new
+ " In first column text is inserted
+ call setline(1, ['a', 'b', 'c'])
+ call cursor(2, 1)
+ call feedkeys("\<Esc>[200~foo\<CR>bar\<Esc>[201~", 'xt')
+ call assert_equal('foo', getline(2))
+ call assert_equal('barb', getline(3))
+ call assert_equal('c', getline(4))
+
+ " When repeating text is appended
+ normal .
+ call assert_equal('barfoo', getline(3))
+ call assert_equal('barb', getline(4))
+ call assert_equal('c', getline(5))
+ bwipe!
+
+ " In second column text is appended
+ call setline(1, ['a', 'bbb', 'c'])
+ call cursor(2, 2)
+ call feedkeys("\<Esc>[200~foo\<CR>bar\<Esc>[201~", 'xt')
+ call assert_equal('bbfoo', getline(2))
+ call assert_equal('barb', getline(3))
+ call assert_equal('c', getline(4))
+
+ " In last column text is appended
+ call setline(1, ['a', 'bbb', 'c'])
+ call cursor(2, 3)
+ call feedkeys("\<Esc>[200~foo\<CR>bar\<Esc>[201~", 'xt')
+ call assert_equal('bbbfoo', getline(2))
+ call assert_equal('bar', getline(3))
+ call assert_equal('c', getline(4))
+endfunc
+
+func Test_paste_insert_mode()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ 2
+ call feedkeys("i\<Esc>[200~foo\<CR>bar\<Esc>[201~ done\<Esc>", 'xt')
+ call assert_equal('foo', getline(2))
+ call assert_equal('bar doneb', getline(3))
+ call assert_equal('c', getline(4))
+
+ normal .
+ call assert_equal('bar donfoo', getline(3))
+ call assert_equal('bar doneeb', getline(4))
+ call assert_equal('c', getline(5))
+
+ set ai et tw=10
+ call setline(1, ['a', ' b', 'c'])
+ 2
+ call feedkeys("A\<Esc>[200~foo\<CR> bar bar bar\<Esc>[201~\<Esc>", 'xt')
+ call assert_equal(' bfoo', getline(2))
+ call assert_equal(' bar bar bar', getline(3))
+ call assert_equal('c', getline(4))
+
+ set ai& et& tw=0
+ bwipe!
+endfunc
+
+func Test_paste_clipboard()
+ CheckFeature clipboard_working
+
+ let @+ = "nasty\<Esc>:!ls\<CR>command"
+ new
+ exe "normal i\<C-R>+\<Esc>"
+ call assert_equal("nasty\<Esc>:!ls\<CR>command", getline(1))
+ bwipe!
+endfunc
+
+" bracketed paste in command line
+func Test_paste_cmdline()
+ call feedkeys(":a\<Esc>[200~foo\<CR>bar\<Esc>[201~b\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"afoo\<CR>barb", getreg(':'))
+endfunc
+
+" bracketed paste in Ex-mode
+func Test_paste_ex_mode()
+ unlet! foo
+ call feedkeys("Qlet foo=\"\<Esc>[200~foo\<CR>bar\<Esc>[201~\"\<CR>vi\<CR>", 'xt')
+ call assert_equal("foo\rbar", foo)
+
+ " pasting more than 40 bytes
+ exe "norm Q\<PasteStart>0000000000000000000000000000000000000000000000000000000000000000000000\<C-C>"
+endfunc
+
+func Test_paste_onechar()
+ new
+ let @f='abc'
+ call feedkeys("i\<C-R>\<Esc>[200~foo\<CR>bar\<Esc>[201~", 'xt')
+ call assert_equal("abc", getline(1))
+ close!
+endfunc
+
+func Test_paste_visual_mode()
+ new
+ call setline(1, 'here are some words')
+ call feedkeys("0fsve\<Esc>[200~more\<Esc>[201~", 'xt')
+ call assert_equal('here are more words', getline(1))
+ call assert_equal('some', getreg('-'))
+ normal! u
+ call assert_equal('here are some words', getline(1))
+ exe "normal! \<C-R>"
+ call assert_equal('here are more words', getline(1))
+
+ " include last char in the line
+ call feedkeys("0fwve\<Esc>[200~noises\<Esc>[201~", 'xt')
+ call assert_equal('here are more noises', getline(1))
+ call assert_equal('words', getreg('-'))
+ normal! u
+ call assert_equal('here are more words', getline(1))
+ exe "normal! \<C-R>"
+ call assert_equal('here are more noises', getline(1))
+
+ " exclude last char in the line
+ call setline(1, 'some words!')
+ call feedkeys("0fwve\<Esc>[200~noises\<Esc>[201~", 'xt')
+ call assert_equal('some noises!', getline(1))
+ call assert_equal('words', getreg('-'))
+ normal! u
+ call assert_equal('some words!', getline(1))
+ exe "normal! \<C-R>"
+ call assert_equal('some noises!', getline(1))
+
+ " multi-line selection
+ call setline(1, ['some words', 'and more'])
+ call feedkeys("0fwvj0fd\<Esc>[200~letters\<Esc>[201~", 'xt')
+ call assert_equal('some letters more', getline(1))
+ call assert_equal("words\nand", getreg('1'))
+ normal! u
+ call assert_equal(['some words', 'and more'], getline(1, 2))
+ exe "normal! \<C-R>"
+ call assert_equal('some letters more', getline(1))
+
+ " linewise non-last line, cursor at start of line
+ call setline(1, ['some words', 'and more'])
+ call feedkeys("0V\<Esc>[200~letters\<Esc>[201~", 'xt')
+ call assert_equal('lettersand more', getline(1))
+ call assert_equal("some words\n", getreg('1'))
+ normal! u
+ call assert_equal(['some words', 'and more'], getline(1, 2))
+ exe "normal! \<C-R>"
+ call assert_equal('lettersand more', getline(1))
+
+ " linewise non-last line, cursor in the middle of line
+ call setline(1, ['some words', 'and more'])
+ call feedkeys("0fwV\<Esc>[200~letters\<Esc>[201~", 'xt')
+ call assert_equal('lettersand more', getline(1))
+ call assert_equal("some words\n", getreg('1'))
+ normal! u
+ call assert_equal(['some words', 'and more'], getline(1, 2))
+ exe "normal! \<C-R>"
+ call assert_equal('lettersand more', getline(1))
+
+ " linewise last line
+ call setline(1, ['some words', 'and more'])
+ call feedkeys("j0V\<Esc>[200~letters\<Esc>[201~", 'xt')
+ call assert_equal(['some words', 'letters'], getline(1, 2))
+ call assert_equal("and more\n", getreg('1'))
+ normal! u
+ call assert_equal(['some words', 'and more'], getline(1, 2))
+ exe "normal! \<C-R>"
+ call assert_equal(['some words', 'letters'], getline(1, 2))
+
+ bwipe!
+endfunc
+
+func CheckCopyPaste()
+ call setline(1, ['copy this', ''])
+ normal 1G0"*y$
+ normal j"*p
+ call assert_equal('copy this', getline(2))
+endfunc
+
+func Test_xrestore()
+ CheckFeature xterm_clipboard
+ let g:test_is_flaky = 1
+
+ let display = $DISPLAY
+ new
+ call CheckCopyPaste()
+
+ xrestore
+ call CheckCopyPaste()
+
+ exe "xrestore " .. display
+ call CheckCopyPaste()
+
+ bwipe!
+endfunc
+
+" Test for 'pastetoggle'
+func Test_pastetoggle()
+ new
+ set pastetoggle=<F4>
+ set nopaste
+ call feedkeys("iHello\<F4>", 'xt')
+ call assert_true(&paste)
+ call feedkeys("i\<F4>", 'xt')
+ call assert_false(&paste)
+ call assert_equal('Hello', getline(1))
+ " command-line completion for 'pastetoggle' value
+ call feedkeys(":set pastetoggle=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set pastetoggle=<F4>', @:)
+ set pastetoggle&
+ bwipe!
+endfunc
+
+func Test_pastetoggle_timeout_no_typed_after_mapped()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set pastetoggle=abc
+ set ttimeoutlen=10000
+ imap d a
+ END
+ call writefile(lines, 'Xpastetoggle_no_typed_after_mapped.vim', 'D')
+ let buf = RunVimInTerminal('-S Xpastetoggle_no_typed_after_mapped.vim', #{rows: 8})
+ call TermWait(buf)
+ call term_sendkeys(buf, ":call feedkeys('id', 't')\<CR>")
+ call term_wait(buf, 200)
+ call term_sendkeys(buf, 'bc')
+ " 'ttimeoutlen' should NOT apply
+ call WaitForAssert({-> assert_match('^-- INSERT --', term_getline(buf, 8))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_pastetoggle_timeout_typed_after_mapped()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set pastetoggle=abc
+ set ttimeoutlen=10000
+ imap d a
+ END
+ call writefile(lines, 'Xpastetoggle_typed_after_mapped.vim', 'D')
+ let buf = RunVimInTerminal('-S Xpastetoggle_typed_after_mapped.vim', #{rows: 8})
+ call TermWait(buf)
+ call term_sendkeys(buf, ":call feedkeys('idb', 't')\<CR>")
+ call term_wait(buf, 200)
+ call term_sendkeys(buf, 'c')
+ " 'ttimeoutlen' should apply
+ call WaitForAssert({-> assert_match('^-- INSERT (paste) --', term_getline(buf, 8))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_pastetoggle_timeout_typed_after_noremap()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set pastetoggle=abc
+ set ttimeoutlen=10000
+ inoremap d a
+ END
+ call writefile(lines, 'Xpastetoggle_typed_after_noremap.vim', 'D')
+ let buf = RunVimInTerminal('-S Xpastetoggle_typed_after_noremap.vim', #{rows: 8})
+ call TermWait(buf)
+ call term_sendkeys(buf, ":call feedkeys('idb', 't')\<CR>")
+ call term_wait(buf, 200)
+ call term_sendkeys(buf, 'c')
+ " 'ttimeoutlen' should apply
+ call WaitForAssert({-> assert_match('^-- INSERT (paste) --', term_getline(buf, 8))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for restoring option values when 'paste' is disabled
+func Test_paste_opt_restore()
+ set autoindent expandtab ruler showmatch
+ if has('rightleft')
+ set revins hkmap
+ endif
+ set smarttab softtabstop=3 textwidth=27 wrapmargin=12
+ if has('vartabs')
+ set varsofttabstop=10,20
+ endif
+
+ " enabling 'paste' should reset the above options
+ set paste
+ call assert_false(&autoindent)
+ call assert_false(&expandtab)
+ if has('rightleft')
+ call assert_false(&revins)
+ call assert_false(&hkmap)
+ endif
+ call assert_false(&ruler)
+ call assert_false(&showmatch)
+ call assert_false(&smarttab)
+ call assert_equal(0, &softtabstop)
+ call assert_equal(0, &textwidth)
+ call assert_equal(0, &wrapmargin)
+ if has('vartabs')
+ call assert_equal('', &varsofttabstop)
+ endif
+
+ " disabling 'paste' should restore the option values
+ set nopaste
+ call assert_true(&autoindent)
+ call assert_true(&expandtab)
+ if has('rightleft')
+ call assert_true(&revins)
+ call assert_true(&hkmap)
+ endif
+ call assert_true(&ruler)
+ call assert_true(&showmatch)
+ call assert_true(&smarttab)
+ call assert_equal(3, &softtabstop)
+ call assert_equal(27, &textwidth)
+ call assert_equal(12, &wrapmargin)
+ if has('vartabs')
+ call assert_equal('10,20', &varsofttabstop)
+ endif
+
+ set autoindent& expandtab& ruler& showmatch&
+ if has('rightleft')
+ set revins& hkmap&
+ endif
+ set smarttab& softtabstop& textwidth& wrapmargin&
+ if has('vartabs')
+ set varsofttabstop&
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_perl.vim b/src/testdir/test_perl.vim
new file mode 100644
index 0000000..681aaae
--- /dev/null
+++ b/src/testdir/test_perl.vim
@@ -0,0 +1,356 @@
+" Tests for Perl interface
+
+source check.vim
+source shared.vim
+CheckFeature perl
+
+" FIXME: RunTest don't see any error when Perl abort...
+perl $SIG{__WARN__} = sub { die "Unexpected warnings from perl: @_" };
+
+func Test_change_buffer()
+ call setline(line('$'), ['1 line 1'])
+ perl VIM::DoCommand("normal /^1\n")
+ perl $curline = VIM::Eval("line('.')")
+ perl $curbuf->Set($curline, "1 changed line 1")
+ call assert_equal('1 changed line 1', getline('$'))
+endfunc
+
+func Test_evaluate_list()
+ call setline(line('$'), ['2 line 2'])
+ perl VIM::DoCommand("normal /^2\n")
+ perl $curline = VIM::Eval("line('.')")
+ let l = ["abc", "def"]
+ perl << EOF
+ $l = VIM::Eval("l");
+ $curbuf->Append($curline, $l);
+EOF
+ normal j
+ .perldo s|\n|/|g
+ call assert_equal('abc/def/', getline('$'))
+endfunc
+
+funct Test_VIM_Blob()
+ call assert_equal('0z', perleval('VIM::Blob("")'))
+ call assert_equal('0z31326162', 'VIM::Blob("12ab")'->perleval())
+ call assert_equal('0z00010203', perleval('VIM::Blob("\x00\x01\x02\x03")'))
+ call assert_equal('0z8081FEFF', perleval('VIM::Blob("\x80\x81\xfe\xff")'))
+endfunc
+
+func Test_buffer_Delete()
+ new
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
+ perl $curbuf->Delete(7)
+ perl $curbuf->Delete(2, 5)
+ perl $curbuf->Delete(10)
+ call assert_equal(['a', 'f', 'h'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Append()
+ new
+ perl $curbuf->Append(1, '1')
+ perl $curbuf->Append(2, '2', '3', '4')
+ perl @l = ('5' ..'7')
+ perl $curbuf->Append(0, @l)
+ call assert_equal(['5', '6', '7', '', '1', '2', '3', '4'], getline(1, '$'))
+
+ perl $curbuf->Append(0)
+ call assert_match('^Usage: VIBUF::Append(vimbuf, lnum, @lines) at .* line 1\.$',
+ \ GetMessages()[-1])
+
+ bwipe!
+endfunc
+
+func Test_buffer_Set()
+ new
+ call setline(1, ['1', '2', '3', '4', '5'])
+ perl $curbuf->Set(2, 'a', 'b', 'c')
+ perl $curbuf->Set(4, 'A', 'B', 'C')
+ call assert_equal(['1', 'a', 'b', 'A', 'B'], getline(1, '$'))
+
+ perl $curbuf->Set(0)
+ call assert_match('^Usage: VIBUF::Set(vimbuf, lnum, @lines) at .* line 1\.$',
+ \ GetMessages()[-1])
+
+ bwipe!
+endfunc
+
+func Test_buffer_Get()
+ new
+ call setline(1, ['1', '2', '3', '4'])
+ call assert_equal('2:3', perleval('join(":", $curbuf->Get(2, 3))'))
+ bwipe!
+endfunc
+
+func Test_buffer_Count()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ call assert_equal(3, perleval('$curbuf->Count()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Name()
+ new
+ call assert_equal('', perleval('$curbuf->Name()'))
+ bwipe!
+ new Xfoo
+ call assert_equal('Xfoo', perleval('$curbuf->Name()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Number()
+ call assert_equal(bufnr('%'), perleval('$curbuf->Number()'))
+endfunc
+
+func Test_window_Cursor()
+ new
+ call setline(1, ['line1', 'line2'])
+ perl $curwin->Cursor(2, 3)
+ call assert_equal('2:3', perleval('join(":", $curwin->Cursor())'))
+ " Col is numbered from 0 in Perl, and from 1 in Vim script.
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ bwipe!
+endfunc
+
+func Test_window_SetHeight()
+ new
+ perl $curwin->SetHeight(2)
+ call assert_equal(2, winheight(0))
+ bwipe!
+endfunc
+
+func Test_VIM_Windows()
+ new
+ " VIM::Windows() without argument in scalar and list context.
+ perl $winnr = VIM::Windows()
+ perl @winlist = VIM::Windows()
+ perl $curbuf->Append(0, $winnr, scalar(@winlist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+
+ " VIM::Windows() with window number argument.
+ perl VIM::Windows(VIM::Eval('winnr()'))->Buffer()->Set(1, 'bar')
+ call assert_equal('bar', getline(1))
+ bwipe!
+endfunc
+
+func Test_VIM_Buffers()
+ new Xbar
+ " VIM::Buffers() without argument in scalar and list context.
+ perl $nbuf = VIM::Buffers()
+ perl @buflist = VIM::Buffers()
+
+ " VIM::Buffers() with argument.
+ perl $mybuf = (VIM::Buffers('Xbar'))[0]
+ perl $mybuf->Append(0, $nbuf, scalar(@buflist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+func <SID>catch_peval(expr)
+ try
+ call perleval(a:expr)
+ catch
+ return v:exception
+ endtry
+ call assert_report('no exception for `perleval("'.a:expr.'")`')
+ return ''
+endfunc
+
+func Test_perleval()
+ call assert_false(perleval('undef'))
+
+ " scalar
+ call assert_equal(0, perleval('0'))
+ call assert_equal(2, perleval('2'))
+ call assert_equal(-2, perleval('-2'))
+ call assert_equal(2.5, perleval('2.5'))
+
+ sandbox call assert_equal(2, perleval('2'))
+
+ call assert_equal('abc', perleval('"abc"'))
+ call assert_equal("abc\ndef", perleval('"abc\0def"'))
+
+ " ref
+ call assert_equal([], perleval('[]'))
+ call assert_equal(['word', 42, [42],{}], perleval('["word", 42, [42], {}]'))
+
+ call assert_equal({}, perleval('{}'))
+ call assert_equal({'foo': 'bar'}, perleval('{foo => "bar"}'))
+
+ perl our %h; our @a;
+ let a = perleval('[\(%h, %h, @a, @a)]')
+ call assert_true((a[0] is a[1]))
+ call assert_true((a[2] is a[3]))
+ perl undef %h; undef @a;
+
+ call assert_true(<SID>catch_peval('{"" , 0}') =~ 'Malformed key Dictionary')
+ call assert_true(<SID>catch_peval('{"\0" , 0}') =~ 'Malformed key Dictionary')
+ call assert_true(<SID>catch_peval('{"foo\0bar" , 0}') =~ 'Malformed key Dictionary')
+
+ call assert_equal('*VIM', perleval('"*VIM"'))
+ call assert_true(perleval('\\0') =~ 'SCALAR(0x\x\+)')
+
+ " typeglob
+ call assert_equal('*main::STDOUT', perleval('*STDOUT'))
+'
+ call perleval("++-$foo")
+ let messages = split(execute('message'), "\n")
+ call assert_match("Can't modify negation", messages[-1])
+endfunc
+
+func Test_perldo()
+ new
+ " :perldo in empty buffer does nothing.
+ perldo ++$counter
+ call assert_equal(0, perleval("$counter"))
+
+ sp __TEST__
+ exe 'read ' g:testname
+ perldo s/perl/vieux_chameau/g
+ 1
+ call assert_false(search('\Cperl'))
+ bw!
+
+ " Check deleting lines does not trigger ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("%d_")
+ bwipe!
+
+ " Check a Perl expression which gives an error.
+ new
+ call setline(1, 'one')
+ perldo 1/0
+ call assert_match('^Illegal division by zero at .* line 1\.$', GetMessages()[-1])
+ bwipe!
+
+ " Check switching to another buffer does not trigger ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ %bwipe!
+endfunc
+
+func Test_VIM_package()
+ perl VIM::DoCommand('let l:var = "foo"')
+ call assert_equal(l:var, 'foo')
+
+ set noet
+ perl VIM::SetOption('et')
+ call assert_true(&et)
+endfunc
+
+func Test_stdio()
+ redir =>l:out
+ perl << trim EOF
+ VIM::Msg("VIM::Msg");
+ VIM::Msg("VIM::Msg Error", "Error");
+ print "STDOUT";
+ print STDERR "STDERR";
+ EOF
+ redir END
+ call assert_equal(['VIM::Msg', 'VIM::Msg Error', 'STDOUT', 'STDERR'], split(l:out, "\n"))
+endfunc
+
+" Run first to get a clean namespace
+func Test_000_SvREFCNT()
+ for i in range(8)
+ exec 'new X'.i
+ endfor
+ new t
+ perl <<--perl
+#line 5 "Test_000_SvREFCNT()"
+ my ($b, $w);
+
+ my $num = 0;
+ for ( 0 .. 100 ) {
+ if ( ++$num >= 8 ) { $num = 0 }
+ VIM::DoCommand("buffer X$num");
+ $b = $curbuf;
+ }
+
+ VIM::DoCommand("buffer t");
+
+ $b = $curbuf for 0 .. 100;
+ $w = $curwin for 0 .. 100;
+ () = VIM::Buffers for 0 .. 100;
+ () = VIM::Windows for 0 .. 100;
+
+ VIM::DoCommand('bw! t');
+ if (exists &Internals::SvREFCNT) {
+ my $cb = Internals::SvREFCNT($$b);
+ my $cw = Internals::SvREFCNT($$w);
+ VIM::Eval("assert_equal(2, $cb, 'T1')");
+ VIM::Eval("assert_equal(2, $cw, 'T2')");
+ my $strongref;
+ foreach ( VIM::Buffers, VIM::Windows ) {
+ VIM::DoCommand("%bw!");
+ my $c = Internals::SvREFCNT($_);
+ VIM::Eval("assert_equal(2, $c, 'T3')");
+ $c = Internals::SvREFCNT($$_);
+ next if $c == 2 && !$strongref++;
+ VIM::Eval("assert_equal(1, $c, 'T4')");
+ }
+ $cb = Internals::SvREFCNT($$curbuf);
+ $cw = Internals::SvREFCNT($$curwin);
+ VIM::Eval("assert_equal(3, $cb, 'T5')");
+ VIM::Eval("assert_equal(3, $cw, 'T6')");
+ }
+ VIM::Eval("assert_false($$b)");
+ VIM::Eval("assert_false($$w)");
+--perl
+ %bw!
+endfunc
+
+" This caused a memory error before issue #10386 was fixed
+func Test_stack_usage_fix()
+ let script =<< CODE
+ " This will grow Perl's stack in first invocation
+ eval [0, 0]->map({ -> perleval("push@_,0..4096;0") })
+ q!
+CODE
+ call RunVim([], script, '')
+endfunc
+
+func Test_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ perldo $curwin->Cursor(1, 5)
+ call assert_equal([1, 6], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 6], [line('.'), col('.')])
+endfunc
+
+" Test for various heredoc syntax
+func Test_perl_heredoc()
+ perl << END
+VIM::DoCommand('let s = "A"')
+END
+ perl <<
+VIM::DoCommand('let s ..= "B"')
+.
+ perl << trim END
+ VIM::DoCommand('let s ..= "C"')
+ END
+ perl << trim
+ VIM::DoCommand('let s ..= "D"')
+ .
+ perl << trim eof
+ VIM::DoCommand('let s ..= "E"')
+ eof
+ call assert_equal('ABCDE', s)
+endfunc
+
+func Test_perl_in_sandbox()
+ sandbox perl print 'test'
+ let messages = split(execute('message'), "\n")
+ call assert_match("'print' trapped by operation mask", messages[-1])
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_plus_arg_edit.vim b/src/testdir/test_plus_arg_edit.vim
new file mode 100644
index 0000000..5fdf1be
--- /dev/null
+++ b/src/testdir/test_plus_arg_edit.vim
@@ -0,0 +1,45 @@
+" Tests for complicated + argument to :edit command
+
+function Test_edit()
+ call writefile(["foo|bar"], "Xfile1", 'D')
+ call writefile(["foo/bar"], "Xfile2", 'D')
+ edit +1|s/|/PIPE/|w Xfile1| e Xfile2|1 | s/\//SLASH/|w
+ call assert_equal(["fooPIPEbar"], readfile("Xfile1"))
+ call assert_equal(["fooSLASHbar"], readfile("Xfile2"))
+endfunction
+
+func Test_edit_bad()
+ " Test loading a utf8 file with bad utf8 sequences.
+ call writefile(["[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]"], "Xbadfile", 'D')
+ new
+
+ " Without ++bad=..., the default behavior is like ++bad=?
+ e! ++enc=utf8 Xbadfile
+ call assert_equal('[?][?][???][??]', getline(1))
+
+ e! ++encoding=utf8 ++bad=_ Xbadfile
+ call assert_equal('[_][_][___][__]', getline(1))
+
+ e! ++enc=utf8 ++bad=drop Xbadfile
+ call assert_equal('[][][][]', getline(1))
+
+ e! ++enc=utf8 ++bad=keep Xbadfile
+ call assert_equal("[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]", getline(1))
+
+ call assert_fails('e! ++enc=utf8 ++bad=foo Xbadfile', 'E474:')
+
+ bw!
+endfunc
+
+" Test for ++bin and ++nobin arguments
+func Test_binary_arg()
+ new
+ edit ++bin Xfile1
+ call assert_equal(1, &binary)
+ edit ++nobin Xfile2
+ call assert_equal(0, &binary)
+ call assert_fails('edit ++binabc Xfile3', 'E474:')
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim
new file mode 100644
index 0000000..1401e55
--- /dev/null
+++ b/src/testdir/test_popup.vim
@@ -0,0 +1,1253 @@
+" Test for completion menu
+
+source shared.vim
+source screendump.vim
+source check.vim
+
+let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
+let g:setting = ''
+
+func ListMonths()
+ if g:setting != ''
+ exe ":set" g:setting
+ endif
+ let mth = copy(g:months)
+ let entered = strcharpart(getline('.'),0,col('.'))
+ if !empty(entered)
+ let mth = filter(mth, 'v:val=~"^".entered')
+ endif
+ call complete(1, mth)
+ return ''
+endfunc
+
+func Test_popup_complete2()
+ " Although the popupmenu is not visible, this does not mean completion mode
+ " has ended. After pressing <f5> to complete the currently typed char, Vim
+ " still stays in the first state of the completion (:h ins-completion-menu),
+ " although the popupmenu wasn't shown <c-e> will remove the inserted
+ " completed text (:h complete_CTRL-E), while the following <c-e> will behave
+ " like expected (:h i_CTRL-E)
+ new
+ inoremap <f5> <c-r>=ListMonths()<cr>
+ call append(1, ["December2015"])
+ :1
+ call feedkeys("aD\<f5>\<C-E>\<C-E>\<C-E>\<C-E>\<enter>\<esc>", 'tx')
+ call assert_equal(["Dece", "", "December2015"], getline(1,3))
+ %d
+ bw!
+endfunc
+
+func Test_popup_complete()
+ new
+ inoremap <f5> <c-r>=ListMonths()<cr>
+
+ " <C-E> - select original typed text before the completion started
+ call feedkeys("aJu\<f5>\<down>\<c-e>\<esc>", 'tx')
+ call assert_equal(["Ju"], getline(1,2))
+ %d
+
+ " <C-Y> - accept current match
+ call feedkeys("a\<f5>". repeat("\<down>",7). "\<c-y>\<esc>", 'tx')
+ call assert_equal(["August"], getline(1,2))
+ %d
+
+ " <BS> - Delete one character from the inserted text (state: 1)
+ " TODO: This should not end the completion, but it does.
+ " This should according to the documentation:
+ " January
+ " but instead, this does
+ " Januar
+ " (idea is, C-L inserts the match from the popup menu
+ " but if the menu is closed, it will insert the character <c-l>
+ call feedkeys("aJ\<f5>\<bs>\<c-l>\<esc>", 'tx')
+ call assert_equal(["Januar "], getline(1,2))
+ %d
+
+ " any-non special character: Stop completion without changing the match
+ " and insert the typed character
+ call feedkeys("a\<f5>20", 'tx')
+ call assert_equal(["January20"], getline(1,2))
+ %d
+
+ " any-non printable, non-white character: Add this character and
+ " reduce number of matches
+ call feedkeys("aJu\<f5>\<c-p>l\<c-y>", 'tx')
+ call assert_equal(["Jul"], getline(1,2))
+ %d
+
+ " any-non printable, non-white character: Add this character and
+ " reduce number of matches
+ call feedkeys("aJu\<f5>\<c-p>l\<c-n>\<c-y>", 'tx')
+ call assert_equal(["July"], getline(1,2))
+ %d
+
+ " any-non printable, non-white character: Add this character and
+ " reduce number of matches
+ call feedkeys("aJu\<f5>\<c-p>l\<c-e>", 'tx')
+ call assert_equal(["Jul"], getline(1,2))
+ %d
+
+ " <BS> - Delete one character from the inserted text (state: 2)
+ call feedkeys("a\<f5>\<c-n>\<bs>", 'tx')
+ call assert_equal(["Februar"], getline(1,2))
+ %d
+
+ " <c-l> - Insert one character from the current match
+ call feedkeys("aJ\<f5>".repeat("\<c-n>",3)."\<c-l>\<esc>", 'tx')
+ call assert_equal(["J "], getline(1,2))
+ %d
+
+ " <c-l> - Insert one character from the current match
+ call feedkeys("aJ\<f5>".repeat("\<c-n>",4)."\<c-l>\<esc>", 'tx')
+ call assert_equal(["January "], getline(1,2))
+ %d
+
+ " <c-y> - Accept current selected match
+ call feedkeys("aJ\<f5>\<c-y>\<esc>", 'tx')
+ call assert_equal(["January"], getline(1,2))
+ %d
+
+ " <c-e> - End completion, go back to what was there before selecting a match
+ call feedkeys("aJu\<f5>\<c-e>\<esc>", 'tx')
+ call assert_equal(["Ju"], getline(1,2))
+ %d
+
+ " <PageUp> - Select a match several entries back
+ call feedkeys("a\<f5>\<PageUp>\<c-y>\<esc>", 'tx')
+ call assert_equal([""], getline(1,2))
+ %d
+
+ " <PageUp><PageUp> - Select a match several entries back
+ call feedkeys("a\<f5>\<PageUp>\<PageUp>\<c-y>\<esc>", 'tx')
+ call assert_equal(["December"], getline(1,2))
+ %d
+
+ " <PageUp><PageUp><PageUp> - Select a match several entries back
+ call feedkeys("a\<f5>\<PageUp>\<PageUp>\<PageUp>\<c-y>\<esc>", 'tx')
+ call assert_equal(["February"], getline(1,2))
+ %d
+
+ " <PageDown> - Select a match several entries further
+ call feedkeys("a\<f5>\<PageDown>\<c-y>\<esc>", 'tx')
+ call assert_equal(["November"], getline(1,2))
+ %d
+
+ " <PageDown><PageDown> - Select a match several entries further
+ call feedkeys("a\<f5>\<PageDown>\<PageDown>\<c-y>\<esc>", 'tx')
+ call assert_equal(["December"], getline(1,2))
+ %d
+
+ " <PageDown><PageDown><PageDown> - Select a match several entries further
+ call feedkeys("a\<f5>\<PageDown>\<PageDown>\<PageDown>\<c-y>\<esc>", 'tx')
+ call assert_equal([""], getline(1,2))
+ %d
+
+ " <PageDown><PageDown><PageDown><PageDown> - Select a match several entries further
+ call feedkeys("a\<f5>".repeat("\<PageDown>",4)."\<c-y>\<esc>", 'tx')
+ call assert_equal(["October"], getline(1,2))
+ %d
+
+ " <Up> - Select a match don't insert yet
+ call feedkeys("a\<f5>\<Up>\<c-y>\<esc>", 'tx')
+ call assert_equal([""], getline(1,2))
+ %d
+
+ " <Up><Up> - Select a match don't insert yet
+ call feedkeys("a\<f5>\<Up>\<Up>\<c-y>\<esc>", 'tx')
+ call assert_equal(["December"], getline(1,2))
+ %d
+
+ " <Up><Up><Up> - Select a match don't insert yet
+ call feedkeys("a\<f5>\<Up>\<Up>\<Up>\<c-y>\<esc>", 'tx')
+ call assert_equal(["November"], getline(1,2))
+ %d
+
+ " <Tab> - Stop completion and insert the match
+ call feedkeys("a\<f5>\<Tab>\<c-y>\<esc>", 'tx')
+ call assert_equal(["January "], getline(1,2))
+ %d
+
+ " <Space> - Stop completion and insert the match
+ call feedkeys("a\<f5>".repeat("\<c-p>",5)." \<esc>", 'tx')
+ call assert_equal(["September "], getline(1,2))
+ %d
+
+ " <Enter> - Use the text and insert line break (state: 1)
+ call feedkeys("a\<f5>\<enter>\<esc>", 'tx')
+ call assert_equal(["January", ''], getline(1,2))
+ %d
+
+ " <Enter> - Insert the current selected text (state: 2)
+ call feedkeys("a\<f5>".repeat("\<Up>",5)."\<enter>\<esc>", 'tx')
+ call assert_equal(["September"], getline(1,2))
+ %d
+
+ " Insert match immediately, if there is only one match
+ " <c-y> selects a character from the line above
+ call append(0, ["December2015"])
+ call feedkeys("aD\<f5>\<C-Y>\<C-Y>\<C-Y>\<C-Y>\<enter>\<esc>", 'tx')
+ call assert_equal(["December2015", "December2015", ""], getline(1,3))
+ %d
+
+ " use menuone for 'completeopt'
+ " Since for the first <c-y> the menu is still shown, will only select
+ " three letters from the line above
+ set completeopt&vim
+ set completeopt+=menuone
+ call append(0, ["December2015"])
+ call feedkeys("aD\<f5>\<C-Y>\<C-Y>\<C-Y>\<C-Y>\<enter>\<esc>", 'tx')
+ call assert_equal(["December2015", "December201", ""], getline(1,3))
+ %d
+
+ " use longest for 'completeopt'
+ set completeopt&vim
+ call feedkeys("aM\<f5>\<C-N>\<C-P>\<c-e>\<enter>\<esc>", 'tx')
+ set completeopt+=longest
+ call feedkeys("aM\<f5>\<C-N>\<C-P>\<c-e>\<enter>\<esc>", 'tx')
+ call assert_equal(["M", "Ma", ""], getline(1,3))
+ %d
+
+ " use noselect/noinsert for 'completeopt'
+ set completeopt&vim
+ call feedkeys("aM\<f5>\<enter>\<esc>", 'tx')
+ set completeopt+=noselect
+ call feedkeys("aM\<f5>\<enter>\<esc>", 'tx')
+ set completeopt-=noselect completeopt+=noinsert
+ call feedkeys("aM\<f5>\<enter>\<esc>", 'tx')
+ call assert_equal(["March", "M", "March"], getline(1,4))
+ %d
+endfunc
+
+
+func Test_popup_completion_insertmode()
+ new
+ inoremap <F5> <C-R>=ListMonths()<CR>
+
+ call feedkeys("a\<f5>\<down>\<enter>\<esc>", 'tx')
+ call assert_equal('February', getline(1))
+ %d
+ " Set noinsertmode
+ let g:setting = 'noinsertmode'
+ call feedkeys("a\<f5>\<down>\<enter>\<esc>", 'tx')
+ call assert_equal('February', getline(1))
+ call assert_false(pumvisible())
+ %d
+ " Go through all matches, until none is selected
+ let g:setting = ''
+ call feedkeys("a\<f5>". repeat("\<c-n>",12)."\<enter>\<esc>", 'tx')
+ call assert_equal('', getline(1))
+ %d
+ " select previous entry
+ call feedkeys("a\<f5>\<c-p>\<enter>\<esc>", 'tx')
+ call assert_equal('', getline(1))
+ %d
+ " select last entry
+ call feedkeys("a\<f5>\<c-p>\<c-p>\<enter>\<esc>", 'tx')
+ call assert_equal('December', getline(1))
+
+ iunmap <F5>
+endfunc
+
+func Test_noinsert_complete()
+ func! s:complTest1() abort
+ eval ['source', 'soundfold']->complete(1)
+ return ''
+ endfunc
+
+ func! s:complTest2() abort
+ call complete(1, ['source', 'soundfold'])
+ return ''
+ endfunc
+
+ new
+ set completeopt+=noinsert
+ inoremap <F5> <C-R>=s:complTest1()<CR>
+ call feedkeys("i\<F5>soun\<CR>\<CR>\<ESC>.", 'tx')
+ call assert_equal('soundfold', getline(1))
+ call assert_equal('soundfold', getline(2))
+ bwipe!
+
+ new
+ inoremap <F5> <C-R>=s:complTest2()<CR>
+ call feedkeys("i\<F5>\<CR>\<ESC>", 'tx')
+ call assert_equal('source', getline(1))
+ bwipe!
+
+ set completeopt-=noinsert
+ iunmap <F5>
+endfunc
+
+func Test_complete_no_filter()
+ func! s:complTest1() abort
+ call complete(1, [{'word': 'foobar'}])
+ return ''
+ endfunc
+ func! s:complTest2() abort
+ call complete(1, [{'word': 'foobar', 'equal': 1}])
+ return ''
+ endfunc
+
+ let completeopt = &completeopt
+
+ " without equal=1
+ new
+ set completeopt=menuone,noinsert,menu
+ inoremap <F5> <C-R>=s:complTest1()<CR>
+ call feedkeys("i\<F5>z\<CR>\<CR>\<ESC>.", 'tx')
+ call assert_equal('z', getline(1))
+ bwipe!
+
+ " with equal=1
+ new
+ set completeopt=menuone,noinsert,menu
+ inoremap <F5> <C-R>=s:complTest2()<CR>
+ call feedkeys("i\<F5>z\<CR>\<CR>\<ESC>.", 'tx')
+ call assert_equal('foobar', getline(1))
+ bwipe!
+
+ let &completeopt = completeopt
+ iunmap <F5>
+endfunc
+
+func Test_compl_vim_cmds_after_register_expr()
+ func! s:test_func()
+ return 'autocmd '
+ endfunc
+ augroup AAAAA_Group
+ au!
+ augroup END
+
+ new
+ call feedkeys("i\<c-r>=s:test_func()\<CR>\<C-x>\<C-v>\<Esc>", 'tx')
+ call assert_equal('autocmd AAAAA_Group', getline(1))
+ autocmd! AAAAA_Group
+ augroup! AAAAA_Group
+ bwipe!
+endfunc
+
+func Test_compl_ignore_mappings()
+ call setline(1, ['foo', 'bar', 'baz', 'foobar'])
+ inoremap <C-P> (C-P)
+ inoremap <C-N> (C-N)
+ normal! G
+ call feedkeys("o\<C-X>\<C-N>\<C-N>\<C-N>\<C-P>\<C-N>\<C-Y>", 'tx')
+ call assert_equal('baz', getline('.'))
+ " Also test with unsimplified keys
+ call feedkeys("o\<C-X>\<*C-N>\<*C-N>\<*C-N>\<*C-P>\<*C-N>\<C-Y>", 'tx')
+ call assert_equal('baz', getline('.'))
+ iunmap <C-P>
+ iunmap <C-N>
+ bwipe!
+endfunc
+
+func DummyCompleteOne(findstart, base)
+ if a:findstart
+ return 0
+ else
+ wincmd n
+ return ['onedef', 'oneDEF']
+ endif
+endfunc
+
+" Test that nothing happens if the 'completefunc' tries to open
+" a new window (fails to open window, continues)
+func Test_completefunc_opens_new_window_one()
+ new
+ let winid = win_getid()
+ setlocal completefunc=DummyCompleteOne
+ call setline(1, 'one')
+ /^one
+ call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:')
+ call assert_equal(winid, win_getid())
+ call assert_equal('onedef', getline(1))
+ q!
+endfunc
+
+" Test that nothing happens if the 'completefunc' opens
+" a new window (no completion, no crash)
+func DummyCompleteTwo(findstart, base)
+ if a:findstart
+ wincmd n
+ return 0
+ else
+ return ['twodef', 'twoDEF']
+ endif
+endfunc
+
+" Test that nothing happens if the 'completefunc' opens
+" a new window (no completion, no crash)
+func Test_completefunc_opens_new_window_two()
+ new
+ let winid = win_getid()
+ setlocal completefunc=DummyCompleteTwo
+ call setline(1, 'two')
+ /^two
+ call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:')
+ call assert_equal(winid, win_getid())
+ call assert_equal('twodef', getline(1))
+ q!
+endfunc
+
+func DummyCompleteThree(findstart, base)
+ if a:findstart
+ return 0
+ else
+ return ['threedef', 'threeDEF']
+ endif
+endfunc
+
+:"Test that 'completefunc' works when it's OK.
+func Test_completefunc_works()
+ new
+ let winid = win_getid()
+ setlocal completefunc=DummyCompleteThree
+ call setline(1, 'three')
+ /^three
+ call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")
+ call assert_equal(winid, win_getid())
+ call assert_equal('threeDEF', getline(1))
+ q!
+endfunc
+
+func DummyCompleteFour(findstart, base)
+ if a:findstart
+ return 0
+ else
+ call complete_add('four1')
+ eval 'four2'->complete_add()
+ call complete_check()
+ call complete_add('four3')
+ call complete_add('four4')
+ call complete_check()
+ call complete_add('four5')
+ call complete_add('four6')
+ return []
+ endif
+endfunc
+
+" Test that 'omnifunc' works when it's OK.
+func Test_omnifunc_with_check()
+ new
+ setlocal omnifunc=DummyCompleteFour
+ call setline(1, 'four')
+ /^four
+ call feedkeys("A\<C-X>\<C-O>\<C-N>\<Esc>", "x")
+ call assert_equal('four2', getline(1))
+
+ call setline(1, 'four')
+ /^four
+ call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<Esc>", "x")
+ call assert_equal('four3', getline(1))
+
+ call setline(1, 'four')
+ /^four
+ call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<C-N>\<C-N>\<Esc>", "x")
+ call assert_equal('four5', getline(1))
+
+ q!
+endfunc
+
+func UndoComplete()
+ call complete(1, ['January', 'February', 'March',
+ \ 'April', 'May', 'June', 'July', 'August', 'September',
+ \ 'October', 'November', 'December'])
+ return ''
+endfunc
+
+" Test that no undo item is created when no completion is inserted
+func Test_complete_no_undo()
+ set completeopt=menu,preview,noinsert,noselect
+ inoremap <Right> <C-R>=UndoComplete()<CR>
+ new
+ call feedkeys("ixxx\<CR>\<CR>yyy\<Esc>k", 'xt')
+ call feedkeys("iaaa\<Esc>0", 'xt')
+ call assert_equal('aaa', getline(2))
+ call feedkeys("i\<Right>\<Esc>", 'xt')
+ call assert_equal('aaa', getline(2))
+ call feedkeys("u", 'xt')
+ call assert_equal('', getline(2))
+
+ call feedkeys("ibbb\<Esc>0", 'xt')
+ call assert_equal('bbb', getline(2))
+ call feedkeys("A\<Right>\<Down>\<CR>\<Esc>", 'xt')
+ call assert_equal('January', getline(2))
+ call feedkeys("u", 'xt')
+ call assert_equal('bbb', getline(2))
+
+ call feedkeys("A\<Right>\<C-N>\<Esc>", 'xt')
+ call assert_equal('January', getline(2))
+ call feedkeys("u", 'xt')
+ call assert_equal('bbb', getline(2))
+
+ iunmap <Right>
+ set completeopt&
+ q!
+endfunc
+
+func DummyCompleteFive(findstart, base)
+ if a:findstart
+ return 0
+ else
+ return [
+ \ { 'word': 'January', 'info': "info1-1\n1-2\n1-3" },
+ \ { 'word': 'February', 'info': "info2-1\n2-2\n2-3" },
+ \ { 'word': 'March', 'info': "info3-1\n3-2\n3-3" },
+ \ { 'word': 'April', 'info': "info4-1\n4-2\n4-3" },
+ \ { 'word': 'May', 'info': "info5-1\n5-2\n5-3" },
+ \ ]
+ endif
+endfunc
+
+" Test that 'completefunc' on Scratch buffer with preview window works when
+" it's OK.
+func Test_completefunc_with_scratch_buffer()
+ CheckFeature quickfix
+
+ new +setlocal\ buftype=nofile\ bufhidden=wipe\ noswapfile
+ set completeopt+=preview
+ setlocal completefunc=DummyCompleteFive
+ call feedkeys("A\<C-X>\<C-U>\<C-N>\<C-N>\<C-N>\<Esc>", "x")
+ call assert_equal(['April'], getline(1, '$'))
+ pclose
+ q!
+ set completeopt&
+endfunc
+
+" <C-E> - select original typed text before the completion started without
+" auto-wrap text.
+func Test_completion_ctrl_e_without_autowrap()
+ new
+ let tw_save = &tw
+ set tw=78
+ let li = [
+ \ '" zzz',
+ \ '" zzzyyyyyyyyyyyyyyyyyyy']
+ call setline(1, li)
+ 0
+ call feedkeys("A\<C-X>\<C-N>\<C-E>\<Esc>", "tx")
+ call assert_equal(li, getline(1, '$'))
+
+ let &tw = tw_save
+ q!
+endfunc
+
+func DummyCompleteSix()
+ call complete(1, ['Hello', 'World'])
+ return ''
+endfunction
+
+" complete() correctly clears the list of autocomplete candidates
+" See #1411
+func Test_completion_clear_candidate_list()
+ new
+ %d
+ " select first entry from the completion popup
+ call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>", "tx")
+ call assert_equal('Hello', getline(1))
+ %d
+ " select second entry from the completion popup
+ call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>", "tx")
+ call assert_equal('World', getline(1))
+ %d
+ " select original text
+ call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>", "tx")
+ call assert_equal(' xxx', getline(1))
+ %d
+ " back at first entry from completion list
+ call feedkeys("a xxx\<C-N>\<C-R>=DummyCompleteSix()\<CR>\<C-N>\<C-N>\<C-N>", "tx")
+ call assert_equal('Hello', getline(1))
+
+ bw!
+endfunc
+
+func Test_completion_respect_bs_option()
+ new
+ let li = ["aaa", "aaa12345", "aaaabcdef", "aaaABC"]
+
+ set bs=indent,eol
+ call setline(1, li)
+ 1
+ call feedkeys("A\<C-X>\<C-N>\<C-P>\<BS>\<BS>\<BS>\<Esc>", "tx")
+ call assert_equal('aaa', getline(1))
+
+ %d
+ set bs=indent,eol,start
+ call setline(1, li)
+ 1
+ call feedkeys("A\<C-X>\<C-N>\<C-P>\<BS>\<BS>\<BS>\<Esc>", "tx")
+ call assert_equal('', getline(1))
+
+ bw!
+endfunc
+
+func CompleteUndo() abort
+ call complete(1, g:months)
+ return ''
+endfunc
+
+func Test_completion_can_undo()
+ inoremap <Right> <c-r>=CompleteUndo()<cr>
+ set completeopt+=noinsert,noselect
+
+ new
+ call feedkeys("a\<Right>a\<Esc>", 'xt')
+ call assert_equal('a', getline(1))
+ undo
+ call assert_equal('', getline(1))
+
+ bwipe!
+ set completeopt&
+ iunmap <Right>
+endfunc
+
+func Test_completion_comment_formatting()
+ new
+ setl formatoptions=tcqro
+ call feedkeys("o/*\<cr>\<cr>/\<esc>", 'tx')
+ call assert_equal(['', '/*', ' *', ' */'], getline(1,4))
+ %d
+ call feedkeys("o/*\<cr>foobar\<cr>/\<esc>", 'tx')
+ call assert_equal(['', '/*', ' * foobar', ' */'], getline(1,4))
+ %d
+ try
+ call feedkeys("o/*\<cr>\<cr>\<c-x>\<c-u>/\<esc>", 'tx')
+ call assert_report('completefunc not set, should have failed')
+ catch
+ call assert_exception('E764:')
+ endtry
+ call assert_equal(['', '/*', ' *', ' */'], getline(1,4))
+ bwipe!
+endfunc
+
+func MessCompleteMonths()
+ for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep")
+ call complete_add(m)
+ if complete_check()
+ break
+ endif
+ endfor
+ return []
+endfunc
+
+func MessCompleteMore()
+ call complete(1, split("Oct Nov Dec"))
+ return []
+endfunc
+
+func MessComplete(findstart, base)
+ if a:findstart
+ let line = getline('.')
+ let start = col('.') - 1
+ while start > 0 && line[start - 1] =~ '\a'
+ let start -= 1
+ endwhile
+ return start
+ else
+ call MessCompleteMonths()
+ call MessCompleteMore()
+ return []
+ endif
+endfunc
+
+func Test_complete_func_mess()
+ " Calling complete() after complete_add() in 'completefunc' is wrong, but it
+ " should not crash.
+ set completefunc=MessComplete
+ new
+ call setline(1, 'Ju')
+ call assert_fails('call feedkeys("A\<c-x>\<c-u>/\<esc>", "tx")', 'E565:')
+ call assert_equal('Jan/', getline(1))
+ bwipe!
+ set completefunc=
+endfunc
+
+func Test_complete_CTRLN_startofbuffer()
+ new
+ call setline(1, [ 'organize(cupboard, 3, 2);',
+ \ 'prioritize(bureau, 8, 7);',
+ \ 'realize(bannister, 4, 4);',
+ \ 'moralize(railing, 3,9);'])
+ let expected=['cupboard.organize(3, 2);',
+ \ 'bureau.prioritize(8, 7);',
+ \ 'bannister.realize(4, 4);',
+ \ 'railing.moralize(3,9);']
+ call feedkeys("qai\<c-n>\<c-n>.\<esc>3wdW\<cr>q3@a", 'tx')
+ call assert_equal(expected, getline(1,'$'))
+ bwipe!
+endfunc
+
+func Test_popup_and_window_resize()
+ CheckFeature terminal
+ CheckFeature quickfix
+ CheckNotGui
+ let g:test_is_flaky = 1
+
+ let h = winheight(0)
+ if h < 15
+ return
+ endif
+ let rows = h / 3
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': rows})
+ call term_sendkeys(buf, (h / 3 - 1) . "o\<esc>")
+ " Wait for the nested Vim to exit insert mode, where it will show the ruler.
+ " Need to trigger a redraw.
+ call WaitFor({-> execute("redraw") == "" && term_getline(buf, rows) =~ '\<' . rows . ',.*Bot'})
+
+ call term_sendkeys(buf, "Gi\<c-x>")
+ call term_sendkeys(buf, "\<c-v>")
+ call TermWait(buf, 50)
+ " popup first entry "!" must be at the top
+ call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, 1))})
+ exe 'resize +' . (h - 1)
+ call TermWait(buf, 50)
+ redraw!
+ " popup shifted down, first line is now empty
+ call WaitForAssert({-> assert_equal('', term_getline(buf, 1))})
+ sleep 100m
+ " popup is below cursor line and shows first match "!"
+ call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0] + 1))})
+ " cursor line also shows !
+ call assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0]))
+ bwipe!
+endfunc
+
+func Test_popup_and_preview_autocommand()
+ CheckFeature python
+ CheckFeature quickfix
+ if winheight(0) < 15
+ throw 'Skipped: window height insufficient'
+ endif
+
+ " This used to crash Vim
+ new
+ augroup MyBufAdd
+ au!
+ au BufAdd * nested tab sball
+ augroup END
+ set omnifunc=pythoncomplete#Complete
+ call setline(1, 'import os')
+ " make the line long
+ call setline(2, ' os.')
+ $
+ call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<C-N>\<enter>\<esc>", 'tx')
+ call assert_equal("import os", getline(1))
+ call assert_match(' os.\(EX_IOERR\|O_CREAT\)$', getline(2))
+ call assert_equal(1, winnr('$'))
+ " previewwindow option is not set
+ call assert_equal(0, &previewwindow)
+ norm! gt
+ call assert_equal(0, &previewwindow)
+ norm! gT
+ call assert_equal(10, tabpagenr('$'))
+ tabonly
+ pclose
+ augroup MyBufAdd
+ au!
+ augroup END
+ augroup! MyBufAdd
+ bw!
+endfunc
+
+func Test_popup_and_previewwindow_dump()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ set previewheight=9
+ silent! pedit
+ call setline(1, map(repeat(["ab"], 10), "v:val .. v:key"))
+ exec "norm! G\<C-E>\<C-E>"
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {})
+
+ " wait for the script to finish
+ call TermWait(buf)
+
+ " Test that popup and previewwindow do not overlap.
+ call term_sendkeys(buf, "o")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<C-X>\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {})
+
+ call term_sendkeys(buf, "\<Esc>u")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_balloon_split()
+ CheckFunction balloon_split
+
+ call assert_equal([
+ \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"',
+ \ ], balloon_split(
+ \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"'))
+ call assert_equal([
+ \ 'one two three four one two three four one two thre',
+ \ 'e four',
+ \ ], balloon_split(
+ \ 'one two three four one two three four one two three four'))
+
+ eval 'struct = {one = 1, two = 2, three = 3}'
+ \ ->balloon_split()
+ \ ->assert_equal([
+ \ 'struct = {',
+ \ ' one = 1,',
+ \ ' two = 2,',
+ \ ' three = 3}',
+ \ ])
+
+ call assert_equal([
+ \ 'struct = {',
+ \ ' one = 1,',
+ \ ' nested = {',
+ \ ' n1 = "yes",',
+ \ ' n2 = "no"}',
+ \ ' two = 2}',
+ \ ], balloon_split(
+ \ 'struct = {one = 1, nested = {n1 = "yes", n2 = "no"} two = 2}'))
+ call assert_equal([
+ \ 'struct = 0x234 {',
+ \ ' long = 2343 "\\"some long string that will be wr',
+ \ 'apped in two\\"",',
+ \ ' next = 123}',
+ \ ], balloon_split(
+ \ 'struct = 0x234 {long = 2343 "\\"some long string that will be wrapped in two\\"", next = 123}'))
+ call assert_equal([
+ \ 'Some comment',
+ \ '',
+ \ 'typedef this that;',
+ \ ], balloon_split(
+ \ "Some comment\n\ntypedef this that;"))
+endfunc
+
+func Test_popup_position()
+ CheckScreendump
+
+ let lines =<< trim END
+ 123456789_123456789_123456789_a
+ 123456789_123456789_123456789_b
+ 123
+ END
+ call writefile(lines, 'Xtest', 'D')
+ let buf = RunVimInTerminal('Xtest', {})
+ call term_sendkeys(buf, ":vsplit\<CR>")
+
+ " default pumwidth in left window: overlap in right window
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_01', {'rows': 8})
+ call term_sendkeys(buf, "\<Esc>u")
+
+ " default pumwidth: fill until right of window
+ call term_sendkeys(buf, "\<C-W>l")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_02', {'rows': 8})
+
+ " larger pumwidth: used as minimum width
+ call term_sendkeys(buf, "\<Esc>u")
+ call term_sendkeys(buf, ":set pumwidth=30\<CR>")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_03', {'rows': 8})
+
+ " completed text wider than the window and 'pumwidth' smaller than available
+ " space
+ call term_sendkeys(buf, "\<Esc>u")
+ call term_sendkeys(buf, ":set pumwidth=20\<CR>")
+ call term_sendkeys(buf, "ggI123456789_\<Esc>")
+ call term_sendkeys(buf, "jI123456789_\<Esc>")
+ call term_sendkeys(buf, "GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popup_position_04', {'rows': 10})
+
+ call term_sendkeys(buf, "\<Esc>u")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_command()
+ CheckFeature menu
+
+ menu Test.Foo Foo
+ call assert_fails('popup Test.Foo', 'E336:')
+ call assert_fails('popup Test.Foo.X', 'E327:')
+ call assert_fails('popup Foo', 'E337:')
+ unmenu Test.Foo
+endfunc
+
+func Test_popup_command_dump()
+ CheckFeature menu
+ CheckScreendump
+
+ let script =<< trim END
+ func StartTimer()
+ call timer_start(100, {-> ChangeMenu()})
+ endfunc
+ func ChangeMenu()
+ aunmenu PopUp.&Paste
+ nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR>
+ echomsg 'changed'
+ endfunc
+ END
+ call writefile(script, 'XtimerScript', 'D')
+
+ let lines =<< trim END
+ one two three four five
+ and one two Xthree four five
+ one more two three four five
+ END
+ call writefile(lines, 'Xtest', 'D')
+ let buf = RunVimInTerminal('-S XtimerScript Xtest', {})
+ call term_sendkeys(buf, ":source $VIMRUNTIME/menu.vim\<CR>")
+ call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_command_01', {})
+
+ " go to the Paste entry in the menu
+ call term_sendkeys(buf, "jj")
+ call VerifyScreenDump(buf, 'Test_popup_command_02', {})
+
+ " Select a word
+ call term_sendkeys(buf, "j\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_command_03', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Set a timer to change a menu entry while it's displayed. The text should
+ " not change but the command does. Making the screendump also verifies that
+ " "changed" shows up, which means the timer triggered
+ call term_sendkeys(buf, "/X\<CR>:call StartTimer() | popup PopUp\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_command_04', {})
+
+ " Select the Paste entry, executes the changed menu item.
+ call term_sendkeys(buf, "jj\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_command_05', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_complete_backwards()
+ new
+ call setline(1, ['Post', 'Port', 'Po'])
+ let expected=['Post', 'Port', 'Port']
+ call cursor(3,2)
+ call feedkeys("A\<C-X>". repeat("\<C-P>", 3). "rt\<cr>", 'tx')
+ call assert_equal(expected, getline(1,'$'))
+ bwipe!
+endfunc
+
+func Test_popup_complete_backwards_ctrl_p()
+ new
+ call setline(1, ['Post', 'Port', 'Po'])
+ let expected=['Post', 'Port', 'Port']
+ call cursor(3,2)
+ call feedkeys("A\<C-P>\<C-N>rt\<cr>", 'tx')
+ call assert_equal(expected, getline(1,'$'))
+ bwipe!
+endfunc
+
+func Test_complete_o_tab()
+ let s:o_char_pressed = 0
+
+ fun! s:act_on_text_changed()
+ if s:o_char_pressed
+ let s:o_char_pressed = 0
+ call feedkeys("\<c-x>\<c-n>", 'i')
+ endif
+ endfunc
+
+ set completeopt=menu,noselect
+ new
+ imap <expr> <buffer> <tab> pumvisible() ? "\<c-p>" : "X"
+ autocmd! InsertCharPre <buffer> let s:o_char_pressed = (v:char ==# 'o')
+ autocmd! TextChangedI <buffer> call <sid>act_on_text_changed()
+ call setline(1, ['hoard', 'hoax', 'hoarse', ''])
+ let l:expected = ['hoard', 'hoax', 'hoarse', 'hoax', 'hoax']
+ call cursor(4,1)
+ call test_override("char_avail", 1)
+ call feedkeys("Ahoa\<tab>\<tab>\<c-y>\<esc>", 'tx')
+ call feedkeys("oho\<tab>\<tab>\<c-y>\<esc>", 'tx')
+ call assert_equal(l:expected, getline(1,'$'))
+
+ call test_override("char_avail", 0)
+ bwipe!
+ set completeopt&
+ delfunc s:act_on_text_changed
+endfunc
+
+func Test_menu_only_exists_in_terminal()
+ CheckCommand tlmenu
+ CheckNotGui
+
+ tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+
+ aunmenu *
+ try
+ popup Edit
+ call assert_false(1, 'command should have failed')
+ catch
+ call assert_exception('E328:')
+ endtry
+endfunc
+
+" This used to crash before patch 8.1.1424
+func Test_popup_delete_when_shown()
+ CheckFeature menu
+ CheckNotGui
+
+ func Func()
+ popup Foo
+ return "\<Ignore>"
+ endfunc
+
+ nmenu Foo.Bar :
+ nnoremap <expr> <F2> Func()
+ call feedkeys("\<F2>\<F2>\<Esc>", 'xt')
+
+ delfunc Func
+ nunmenu Foo.Bar
+ nunmap <F2>
+endfunc
+
+func Test_popup_complete_info_01()
+ new
+ inoremap <buffer><F5> <C-R>=complete_info().mode<CR>
+ func s:complTestEval() abort
+ call complete(1, ['aa', 'ab'])
+ return ''
+ endfunc
+ inoremap <buffer><F6> <C-R>=s:complTestEval()<CR>
+ call writefile([
+ \ 'dummy dummy.txt 1',
+ \], 'Xdummy.txt', 'D')
+ setlocal tags=Xdummy.txt
+ setlocal dictionary=Xdummy.txt
+ setlocal thesaurus=Xdummy.txt
+ setlocal omnifunc=syntaxcomplete#Complete
+ setlocal completefunc=syntaxcomplete#Complete
+ setlocal spell
+ for [keys, mode_name] in [
+ \ ["", ''],
+ \ ["\<C-X>", 'ctrl_x'],
+ \ ["\<C-X>\<C-N>", 'keyword'],
+ \ ["\<C-X>\<C-P>", 'keyword'],
+ \ ["\<C-X>\<C-E>", 'scroll'],
+ \ ["\<C-X>\<C-Y>", 'scroll'],
+ \ ["\<C-X>\<C-E>\<C-E>\<C-Y>", 'scroll'],
+ \ ["\<C-X>\<C-Y>\<C-E>\<C-Y>", 'scroll'],
+ \ ["\<C-X>\<C-L>", 'whole_line'],
+ \ ["\<C-X>\<C-F>", 'files'],
+ \ ["\<C-X>\<C-]>", 'tags'],
+ \ ["\<C-X>\<C-D>", 'path_defines'],
+ \ ["\<C-X>\<C-I>", 'path_patterns'],
+ \ ["\<C-X>\<C-K>", 'dictionary'],
+ \ ["\<C-X>\<C-T>", 'thesaurus'],
+ \ ["\<C-X>\<C-V>", 'cmdline'],
+ \ ["\<C-X>\<C-U>", 'function'],
+ \ ["\<C-X>\<C-O>", 'omni'],
+ \ ["\<C-X>s", 'spell'],
+ \ ["\<F6>", 'eval'],
+ \]
+ call feedkeys("i" . keys . "\<F5>\<Esc>", 'tx')
+ call assert_equal(mode_name, getline('.'))
+ %d
+ endfor
+
+ bwipe!
+endfunc
+
+func UserDefinedComplete(findstart, base)
+ if a:findstart
+ return 0
+ else
+ return [
+ \ { 'word': 'Jan', 'menu': 'January' },
+ \ { 'word': 'Feb', 'menu': 'February' },
+ \ { 'word': 'Mar', 'menu': 'March' },
+ \ { 'word': 'Apr', 'menu': 'April' },
+ \ { 'word': 'May', 'menu': 'May' },
+ \ ]
+ endif
+endfunc
+
+func GetCompleteInfo()
+ if empty(g:compl_what)
+ let g:compl_info = complete_info()
+ else
+ let g:compl_info = g:compl_what->complete_info()
+ endif
+ return ''
+endfunc
+
+func Test_popup_complete_info_02()
+ new
+ inoremap <buffer><F5> <C-R>=GetCompleteInfo()<CR>
+ setlocal completefunc=UserDefinedComplete
+
+ let d = {
+ \ 'mode': 'function',
+ \ 'pum_visible': 1,
+ \ 'items': [
+ \ {'word': 'Jan', 'menu': 'January', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+ \ {'word': 'Feb', 'menu': 'February', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+ \ {'word': 'Mar', 'menu': 'March', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+ \ {'word': 'Apr', 'menu': 'April', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''},
+ \ {'word': 'May', 'menu': 'May', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}
+ \ ],
+ \ 'selected': 0,
+ \ }
+
+ let g:compl_what = []
+ call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
+ call assert_equal(d, g:compl_info)
+
+ let g:compl_what = ['mode', 'pum_visible', 'selected']
+ call remove(d, 'items')
+ call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
+ call assert_equal(d, g:compl_info)
+
+ let g:compl_what = ['mode']
+ call remove(d, 'selected')
+ call remove(d, 'pum_visible')
+ call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
+ call assert_equal(d, g:compl_info)
+ bwipe!
+endfunc
+
+func Test_popup_complete_info_no_pum()
+ new
+ call assert_false( pumvisible() )
+ let no_pum_info = complete_info()
+ let d = {
+ \ 'mode': '',
+ \ 'pum_visible': 0,
+ \ 'items': [],
+ \ 'selected': -1,
+ \ }
+ call assert_equal( d, complete_info() )
+ bwipe!
+endfunc
+
+func Test_CompleteChanged()
+ new
+ call setline(1, ['foo', 'bar', 'foobar', ''])
+ set complete=. completeopt=noinsert,noselect,menuone
+ function! OnPumChange()
+ let g:event = copy(v:event)
+ let g:item = get(v:event, 'completed_item', {})
+ let g:word = get(g:item, 'word', v:null)
+ endfunction
+ augroup AAAAA_Group
+ au!
+ autocmd CompleteChanged * :call OnPumChange()
+ augroup END
+ call cursor(4, 1)
+
+ call feedkeys("Sf\<C-N>", 'tx')
+ call assert_equal({'completed_item': {}, 'width': 15,
+ \ 'height': 2, 'size': 2,
+ \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event)
+ call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx')
+ call assert_equal('foo', g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
+ call assert_equal('foobar', g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
+ call assert_equal(v:null, g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'tx')
+ call assert_equal('foobar', g:word)
+
+ autocmd! AAAAA_Group
+ set complete& completeopt&
+ delfunc! OnPumChange
+ bw!
+endfunc
+
+func GetPumPosition()
+ call assert_true( pumvisible() )
+ let g:pum_pos = pum_getpos()
+ return ''
+endfunc
+
+func Test_pum_getpos()
+ new
+ inoremap <buffer><F5> <C-R>=GetPumPosition()<CR>
+ setlocal completefunc=UserDefinedComplete
+
+ let d = {
+ \ 'height': 5,
+ \ 'width': 15,
+ \ 'row': 1,
+ \ 'col': 0,
+ \ 'size': 5,
+ \ 'scrollbar': v:false,
+ \ }
+ call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
+ call assert_equal(d, g:pum_pos)
+
+ call assert_false( pumvisible() )
+ call assert_equal( {}, pum_getpos() )
+ bw!
+ unlet g:pum_pos
+endfunc
+
+" Test for the popup menu with the 'rightleft' option set
+func Test_pum_rightleft()
+ CheckFeature rightleft
+ CheckScreendump
+
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+ vim
+ victory
+ END
+ call writefile(lines, 'Xtest1', 'D')
+ let buf = RunVimInTerminal('--cmd "set rightleft" Xtest1', {})
+ call term_sendkeys(buf, "Go\<C-P>")
+ call VerifyScreenDump(buf, 'Test_pum_rightleft_01', {'rows': 8})
+ call term_sendkeys(buf, "\<C-P>\<C-Y>")
+ call TermWait(buf, 30)
+ redraw!
+ call WaitForAssert({-> assert_match('\s*miv', Screenline(5))})
+
+ " Test for expanding tabs to spaces in the popup menu
+ let lines =<< trim END
+ one two
+ one three
+ four
+ END
+ call writefile(lines, 'Xtest2', 'D')
+ call term_sendkeys(buf, "\<Esc>:e! Xtest2\<CR>")
+ call TermWait(buf, 30)
+ call term_sendkeys(buf, "Goone\<C-X>\<C-L>")
+ call TermWait(buf, 30)
+ redraw!
+ call VerifyScreenDump(buf, 'Test_pum_rightleft_02', {'rows': 7})
+ call term_sendkeys(buf, "\<C-Y>")
+ call TermWait(buf, 30)
+ redraw!
+ call WaitForAssert({-> assert_match('\s*eerht eno', Screenline(4))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for a popup menu with a scrollbar
+func Test_pum_scrollbar()
+ CheckScreendump
+ let lines =<< trim END
+ one
+ two
+ three
+ END
+ call writefile(lines, 'Xtest1', 'D')
+ let buf = RunVimInTerminal('--cmd "set pumheight=2" Xtest1', {})
+ call TermWait(buf)
+ call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
+ call VerifyScreenDump(buf, 'Test_pum_scrollbar_01', {'rows': 7})
+ call term_sendkeys(buf, "\<C-E>\<Esc>dd")
+ call TermWait(buf)
+
+ if has('rightleft')
+ call term_sendkeys(buf, ":set rightleft\<CR>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
+ call VerifyScreenDump(buf, 'Test_pum_scrollbar_02', {'rows': 7})
+ endif
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
new file mode 100644
index 0000000..7e1a0fc
--- /dev/null
+++ b/src/testdir/test_popupwin.vim
@@ -0,0 +1,4183 @@
+" Tests for popup windows
+
+source check.vim
+CheckFeature popupwin
+
+source screendump.vim
+source term_util.vim
+
+func Test_simple_popup()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ hi PopupColor1 ctermbg=lightblue
+ hi PopupColor2 ctermbg=lightcyan
+ hi EndOfBuffer ctermbg=lightgrey
+ hi Comment ctermfg=red
+ call prop_type_add('comment', #{highlight: 'Comment'})
+ let winid = popup_create('hello there', #{line: 3, col: 11, minwidth: 20, highlight: 'PopupColor1'})
+ let winid2 = popup_create(['another one', 'another two', 'another three'], #{line: 3, col: 25, minwidth: 20})
+ call setwinvar(winid2, '&wincolor', 'PopupColor2')
+ END
+ call writefile(lines, 'XtestPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_01', {})
+
+ " Add a tabpage
+ call term_sendkeys(buf, ":tabnew\<CR>")
+ call term_sendkeys(buf, ":let popupwin = popup_create(["
+ \ .. "#{text: 'other tab'},"
+ \ .. "#{text: 'a comment line', props: [#{"
+ \ .. "col: 3, length: 7, minwidth: 20, type: 'comment'"
+ \ .. "}]},"
+ \ .. "], #{line: 4, col: 9, minwidth: 20})\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_02', {})
+
+ " switch back to first tabpage
+ call term_sendkeys(buf, "gt")
+ call VerifyScreenDump(buf, 'Test_popupwin_03', {})
+
+ " close that tabpage
+ call term_sendkeys(buf, ":quit!\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_04', {})
+
+ " set 'columns' to a small value, size must be recomputed
+ call term_sendkeys(buf, ":let cols = &columns\<CR>")
+ call term_sendkeys(buf, ":set columns=12\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_04a', {})
+ call term_sendkeys(buf, ":let &columns = cols\<CR>")
+
+ " resize popup, show empty line at bottom
+ call term_sendkeys(buf, ":call popup_move(popupwin, #{minwidth: 15, maxwidth: 25, minheight: 3, maxheight: 5})\<CR>")
+ call term_sendkeys(buf, ":redraw\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_05', {})
+
+ " show not fitting line at bottom
+ call term_sendkeys(buf, ":call setbufline(winbufnr(popupwin), 3, 'this line will not fit here')\<CR>")
+ call term_sendkeys(buf, ":redraw\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_06', {})
+
+ " move popup over ruler
+ call term_sendkeys(buf, ":set cmdheight=2\<CR>")
+ call term_sendkeys(buf, ":call popup_move(popupwin, #{line: 7, col: 55})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_07', {})
+
+ " clear all popups after moving the cursor a bit, so that ruler is updated
+ call term_sendkeys(buf, "axxx\<Esc>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "0")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":call popup_clear()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_08', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_with_border_and_padding()
+ CheckScreendump
+
+ for iter in range(0, 1)
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ call popup_create('hello border', #{line: 2, col: 3, border: []})
+ call popup_create('hello padding', #{line: 2, col: 23, padding: []})
+ call popup_create('hello both', #{line: 2, col: 43, border: [], padding: [], highlight: 'Normal'})
+ call popup_create('border TL', #{line: 6, col: 3, border: [1, 0, 0, 4]})
+ call popup_create('paddings', #{line: 6, col: 23, padding: range(1, 4)})
+ call popup_create('wrapped longer text', #{line: 8, col: 55, padding: [0, 3, 0, 3], border: [0, 1, 0, 1]})
+ call popup_create('right aligned text', #{line: 11, col: 56, wrap: 0, padding: [0, 3, 0, 3], border: [0, 1, 0, 1]})
+ call popup_create('X', #{line: 2, col: 73})
+ call popup_create('X', #{line: 3, col: 74})
+ call popup_create('X', #{line: 4, col: 75})
+ call popup_create('X', #{line: 5, col: 76})
+ END
+ call insert(lines, iter == 1 ? '' : 'set enc=latin1')
+ call writefile(lines, 'XtestPopupBorder', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupBorder', #{rows: 15})
+ call VerifyScreenDump(buf, 'Test_popupwin_2' .. iter, {})
+
+ call StopVimInTerminal(buf)
+ endfor
+
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ hi BlueColor ctermbg=lightblue
+ hi TopColor ctermbg=253
+ hi RightColor ctermbg=245
+ hi BottomColor ctermbg=240
+ hi LeftColor ctermbg=248
+ call popup_create('hello border', #{line: 2, col: 3, border: [], borderhighlight: ['BlueColor']})
+ call popup_create(['hello border', 'and more'], #{line: 2, col: 23, border: [], borderhighlight: ['TopColor', 'RightColor', 'BottomColor', 'LeftColor']})
+ call popup_create(['hello border', 'lines only'], #{line: 2, col: 43, border: [], borderhighlight: ['BlueColor'], borderchars: ['x']})
+ call popup_create(['hello border', 'with corners'], #{line: 2, col: 60, border: [], borderhighlight: ['BlueColor'], borderchars: ['x', '#']})
+ let winid = popup_create(['hello border', 'with numbers'], #{line: 6, col: 3, border: [], borderhighlight: ['BlueColor'], borderchars: ['0', '1', '2', '3', '4', '5', '6', '7']})
+ call popup_create(['hello border', 'just blanks'], #{line: 7, col: 23, border: [], borderhighlight: ['BlueColor'], borderchars: [' ']})
+ func MultiByte()
+ call popup_create(['hello'], #{line: 8, col: 43, border: [], borderchars: ['─', '│', '─', '│', '┌', 'â”', '┘', 'â””']})
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupBorder', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupBorder', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_popupwin_22', {})
+
+ " check that changing borderchars triggers a redraw
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{borderchars: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']})\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_23', {})
+
+ " check multi-byte border only with 'ambiwidth' single
+ if &ambiwidth == 'single'
+ call term_sendkeys(buf, ":call MultiByte()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_24', {})
+ endif
+
+ call StopVimInTerminal(buf)
+
+ let with_border_or_padding = #{
+ \ line: 2,
+ \ core_line: 3,
+ \ col: 3,
+ \ core_col: 4,
+ \ width: 14,
+ \ core_width: 12,
+ \ height: 3,
+ \ core_height: 1,
+ \ firstline: 1,
+ \ lastline: 1,
+ \ scrollbar: 0,
+ \ visible: 1}
+ let winid = popup_create('hello border', #{line: 2, col: 3, border: []})",
+ call assert_equal(with_border_or_padding, winid->popup_getpos())
+ let options = popup_getoptions(winid)
+ call assert_equal([], options.border)
+ call assert_false(has_key(options, "padding"))
+
+ let winid = popup_create('hello padding', #{line: 2, col: 3, padding: []})
+ let with_border_or_padding.width = 15
+ let with_border_or_padding.core_width = 13
+ call assert_equal(with_border_or_padding, popup_getpos(winid))
+ let options = popup_getoptions(winid)
+ call assert_false(has_key(options, "border"))
+ call assert_equal([], options.padding)
+
+ call popup_setoptions(winid, #{
+ \ padding: [1, 2, 3, 4],
+ \ border: [4, 0, 7, 8],
+ \ borderhighlight: ['Top', 'Right', 'Bottom', 'Left'],
+ \ borderchars: ['1', '^', '2', '>', '3', 'v', '4', '<'],
+ \ })
+ let options = popup_getoptions(winid)
+ call assert_equal([1, 0, 1, 1], options.border)
+ call assert_equal([1, 2, 3, 4], options.padding)
+ call assert_equal(['Top', 'Right', 'Bottom', 'Left'], options.borderhighlight)
+ call assert_equal(['1', '^', '2', '>', '3', 'v', '4', '<'], options.borderchars)
+
+ " Check that popup_setoptions() takes the output of popup_getoptions()
+ call popup_setoptions(winid, options)
+ call assert_equal(options, popup_getoptions(winid))
+
+ " Check that range() doesn't crash
+ call popup_setoptions(winid, #{
+ \ padding: range(1, 4),
+ \ border: range(5, 8),
+ \ borderhighlight: range(4),
+ \ borderchars: range(8),
+ \ })
+
+ let winid = popup_create('hello both', #{line: 3, col: 8, border: [], padding: []})
+ call assert_equal(#{
+ \ line: 3,
+ \ core_line: 5,
+ \ col: 8,
+ \ core_col: 10,
+ \ width: 14,
+ \ core_width: 10,
+ \ height: 5,
+ \ scrollbar: 0,
+ \ core_height: 1,
+ \ firstline: 1,
+ \ lastline: 1,
+ \ visible: 1}, popup_getpos(winid))
+
+ call popup_clear()
+endfunc
+
+func Test_popup_with_syntax_win_execute()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ hi PopupColor ctermbg=lightblue
+ let winid = popup_create([
+ \ '#include <stdio.h>',
+ \ 'int main(void)',
+ \ '{',
+ \ ' printf(123);',
+ \ '}',
+ \], #{line: 3, col: 25, highlight: 'PopupColor'})
+ call win_execute(winid, 'set syntax=cpp')
+ END
+ call writefile(lines, 'XtestPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_10', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_with_syntax_setbufvar()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ hi PopupColor ctermbg=lightgrey
+ let winid = popup_create([
+ \ '#include <stdio.h>',
+ \ 'int main(void)',
+ \ '{',
+ \ "\tprintf(567);",
+ \ '}',
+ \], #{line: 3, col: 21, highlight: 'PopupColor'})
+ call setbufvar(winbufnr(winid), '&syntax', 'cpp')
+ END
+ call writefile(lines, 'XtestPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_11', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_with_matches()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['111 222 333', '444 555 666'])
+ let winid = popup_create([
+ \ '111 222 333',
+ \ '444 555 666',
+ \], #{line: 3, col: 10, border: []})
+ set hlsearch
+ hi VeryBlue ctermfg=blue guifg=blue
+ /666
+ call matchadd('ErrorMsg', '111')
+ call matchadd('VeryBlue', '444')
+ call win_execute(winid, "call matchadd('ErrorMsg', '111')")
+ call win_execute(winid, "call matchadd('VeryBlue', '555')")
+ END
+ call writefile(lines, 'XtestPopupMatches', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupMatches', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_matches', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_all_corners()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, repeat([repeat('-', 60)], 15))
+ set so=0
+ normal 2G3|r#
+ let winid1 = popup_create(['first', 'second'], #{
+ \ line: 'cursor+1',
+ \ col: 'cursor',
+ \ pos: 'topleft',
+ \ border: [],
+ \ padding: [],
+ \ })
+ normal 24|r@
+ let winid1 = popup_create(['First', 'SeconD'], #{
+ \ line: 'cursor+1',
+ \ col: 'cursor',
+ \ pos: 'topright',
+ \ border: [],
+ \ padding: [],
+ \ })
+ normal 9G27|r%
+ let winid1 = popup_create(['fiRSt', 'seCOnd'], #{
+ \ line: 'cursor-1',
+ \ col: 'cursor',
+ \ pos: 'botleft',
+ \ border: [],
+ \ padding: [],
+ \ })
+ normal 48|r&
+ let winid1 = popup_create(['FIrsT', 'SEcoND'], #{
+ \ line: 'cursor-1',
+ \ col: 'cursor',
+ \ pos: 'botright',
+ \ border: [],
+ \ padding: [],
+ \ })
+ normal 1G51|r*
+ let winid1 = popup_create(['one', 'two'], #{
+ \ line: 'cursor-1',
+ \ col: 'cursor',
+ \ pos: 'botleft',
+ \ border: [],
+ \ padding: [],
+ \ })
+ END
+ call writefile(lines, 'XtestPopupCorners', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupCorners', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_popupwin_corners', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_nospace()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, repeat([repeat('-', 60)], 15))
+ set so=0
+
+ " cursor in a line in top half, using "botleft" with popup that
+ " does fit
+ normal 5G2|r@
+ let winid1 = popup_create(['one', 'two'], #{
+ \ line: 'cursor-1',
+ \ col: 'cursor',
+ \ pos: 'botleft',
+ \ border: [],
+ \ })
+ " cursor in a line in top half, using "botleft" with popup that
+ " doesn't fit: gets truncated
+ normal 5G9|r#
+ let winid1 = popup_create(['one', 'two', 'tee'], #{
+ \ line: 'cursor-1',
+ \ col: 'cursor',
+ \ pos: 'botleft',
+ \ posinvert: 0,
+ \ border: [],
+ \ })
+ " cursor in a line in top half, using "botleft" with popup that
+ " doesn't fit and 'posinvert' set: flips to below.
+ normal 5G16|r%
+ let winid1 = popup_create(['one', 'two', 'tee'], #{
+ \ line: 'cursor-1',
+ \ col: 'cursor',
+ \ pos: 'botleft',
+ \ border: [],
+ \ })
+ " cursor in a line in bottom half, using "botleft" with popup that
+ " doesn't fit: does not flip.
+ normal 8G23|r*
+ let winid1 = popup_create(['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'], #{
+ \ line: 'cursor-1',
+ \ col: 'cursor',
+ \ pos: 'botleft',
+ \ border: [],
+ \ })
+
+ " cursor in a line in bottom half, using "topleft" with popup that
+ " does fit
+ normal 8G30|r@
+ let winid1 = popup_create(['one', 'two'], #{
+ \ line: 'cursor+1',
+ \ col: 'cursor',
+ \ pos: 'topleft',
+ \ border: [],
+ \ })
+ " cursor in a line in top half, using "topleft" with popup that
+ " doesn't fit: truncated
+ normal 8G37|r#
+ let winid1 = popup_create(['one', 'two', 'tee'], #{
+ \ line: 'cursor+1',
+ \ col: 'cursor',
+ \ pos: 'topleft',
+ \ posinvert: 0,
+ \ border: [],
+ \ })
+ " cursor in a line in top half, using "topleft" with popup that
+ " doesn't fit and "posinvert" set: flips to above.
+ normal 8G44|r%
+ let winid1 = popup_create(['one', 'two', 'tee', 'fou', 'fiv'], #{
+ \ line: 'cursor+1',
+ \ col: 'cursor',
+ \ pos: 'topleft',
+ \ border: [],
+ \ })
+ " cursor in a line in top half, using "topleft" with popup that
+ " doesn't fit: does not flip.
+ normal 5G51|r*
+ let winid1 = popup_create(['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'], #{
+ \ line: 'cursor+1',
+ \ col: 'cursor',
+ \ pos: 'topleft',
+ \ border: [],
+ \ })
+ END
+ call writefile(lines, 'XtestPopupNospace', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupNospace', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_popupwin_nospace', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_firstline_dump()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ let winid = popup_create(['1111', '222222', '33333', '44', '5', '666666', '77777', '888', '9999999999999999'], #{
+ \ maxheight: 4,
+ \ firstline: 3,
+ \ })
+ END
+ call writefile(lines, 'XtestPopupFirstline', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupFirstline', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_firstline_1', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: -1})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_firstline_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_firstline()
+ let winid = popup_create(['1111', '222222', '33333', '44444'], #{
+ \ maxheight: 2,
+ \ firstline: 3,
+ \ })
+ call assert_equal(3, popup_getoptions(winid).firstline)
+ call popup_setoptions(winid, #{firstline: 1})
+ call assert_equal(1, popup_getoptions(winid).firstline)
+ eval winid->popup_close()
+
+ let winid = popup_create(['xxx']->repeat(50), #{
+ \ maxheight: 3,
+ \ firstline: 11,
+ \ })
+ redraw
+ call assert_equal(11, popup_getoptions(winid).firstline)
+ call assert_equal(11, popup_getpos(winid).firstline)
+ " check line() works with popup window
+ call assert_equal(11, line('.', winid))
+ call assert_equal(50, line('$', winid))
+ call assert_equal(0, line('$', 123456))
+
+ " Normal command changes what is displayed but not "firstline"
+ call win_execute(winid, "normal! \<c-y>")
+ call assert_equal(11, popup_getoptions(winid).firstline)
+ call assert_equal(10, popup_getpos(winid).firstline)
+
+ " Making some property change applies "firstline" again
+ call popup_setoptions(winid, #{line: 4})
+ call assert_equal(11, popup_getoptions(winid).firstline)
+ call assert_equal(11, popup_getpos(winid).firstline)
+
+ " Remove "firstline" property and scroll
+ call popup_setoptions(winid, #{firstline: 0})
+ call win_execute(winid, "normal! \<c-y>")
+ call assert_equal(0, popup_getoptions(winid).firstline)
+ call assert_equal(10, popup_getpos(winid).firstline)
+
+ " Making some property change has no side effect
+ call popup_setoptions(winid, #{line: 3})
+ call assert_equal(0, popup_getoptions(winid).firstline)
+ call assert_equal(10, popup_getpos(winid).firstline)
+ call popup_close(winid)
+
+ " CTRL-D scrolls down half a page
+ let winid = popup_create(['xxx']->repeat(50), #{
+ \ maxheight: 8,
+ \ })
+ redraw
+ call assert_equal(1, popup_getpos(winid).firstline)
+ call win_execute(winid, "normal! \<C-D>")
+ call assert_equal(5, popup_getpos(winid).firstline)
+ call win_execute(winid, "normal! \<C-D>")
+ call assert_equal(9, popup_getpos(winid).firstline)
+ call win_execute(winid, "normal! \<C-U>")
+ call assert_equal(5, popup_getpos(winid).firstline)
+
+ call win_execute(winid, "normal! \<C-F>")
+ call assert_equal(11, popup_getpos(winid).firstline)
+ call win_execute(winid, "normal! \<C-B>")
+ call assert_equal(5, popup_getpos(winid).firstline)
+
+ call popup_close(winid)
+
+ " Popup with less elements than the maximum height and negative firstline:
+ " check that the popup height is correctly computed.
+ let winid = popup_create(['xxx']->repeat(4), #{
+ \ firstline: -1,
+ \ maxheight: 6,
+ \ })
+
+ let pos = popup_getpos(winid)
+ call assert_equal(3, pos.width)
+ call assert_equal(4, pos.height)
+
+ call popup_close(winid)
+endfunc
+
+func Test_popup_firstline_cursorline()
+ let winid = popup_create(['1111', '222222', '33333', '44444'], #{
+ \ maxheight: 2,
+ \ firstline: 3,
+ \ cursorline: 1,
+ \ })
+ call assert_equal(3, popup_getoptions(winid).firstline)
+ call assert_equal(3, getwininfo(winid)[0].topline)
+ call assert_equal(3, getcurpos(winid)[1])
+
+ call popup_close(winid)
+endfunc
+
+func Test_popup_noscrolloff()
+ set scrolloff=5
+ let winid = popup_create(['xxx']->repeat(50), #{
+ \ maxheight: 5,
+ \ firstline: 11,
+ \ })
+ redraw
+ call assert_equal(11, popup_getoptions(winid).firstline)
+ call assert_equal(11, popup_getpos(winid).firstline)
+
+ call popup_setoptions(winid, #{firstline: 0})
+ call win_execute(winid, "normal! \<c-y>")
+ call assert_equal(0, popup_getoptions(winid).firstline)
+ call assert_equal(10, popup_getpos(winid).firstline)
+
+ call popup_close(winid)
+endfunc
+
+func Test_popup_drag()
+ CheckScreendump
+
+ " create a popup that covers the command line
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ split
+ vsplit
+ $wincmd w
+ vsplit
+ 1wincmd w
+ let winid = popup_create(['1111', '222222', '33333'], #{
+ \ drag: 1,
+ \ resize: 1,
+ \ border: [],
+ \ line: &lines - 4,
+ \ })
+ func Dragit()
+ map <silent> <F3> :call test_setmouse(&lines - 4, &columns / 2)<CR>
+ map <silent> <F4> :call test_setmouse(&lines - 8, &columns / 2 - 20)<CR>
+ call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ func Resize()
+ map <silent> <F5> :call test_setmouse(6, 21)<CR>
+ map <silent> <F6> :call test_setmouse(7, 25)<CR>
+ call feedkeys("\<F5>\<LeftMouse>\<F6>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ func ClickAndDrag()
+ map <silent> <F3> :call test_setmouse(5, 2)<CR>
+ map <silent> <F4> :call test_setmouse(3, 14)<CR>
+ map <silent> <F5> :call test_setmouse(3, 18)<CR>
+ call feedkeys("\<F3>\<LeftMouse>\<LeftRelease>", "xt")
+ call feedkeys("\<F4>\<LeftMouse>\<F5>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ func DragAllStart()
+ call popup_clear()
+ call popup_create('hello', #{line: 3, col: 5, dragall: 1})
+ endfunc
+ func DragAllDrag()
+ map <silent> <F3> :call test_setmouse(3, 5)<CR>
+ map <silent> <F4> :call test_setmouse(5, 36)<CR>
+ call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupDrag', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupDrag', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_01', {})
+
+ call term_sendkeys(buf, ":call Dragit()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_02', {})
+
+ call term_sendkeys(buf, ":call Resize()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_03', {})
+
+ " dragging works after click on a status line
+ call term_sendkeys(buf, ":call ClickAndDrag()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_04', {})
+
+ " dragging without border
+ call term_sendkeys(buf, ":call DragAllStart()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_05', {})
+ call term_sendkeys(buf, ":call DragAllDrag()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_06', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_drag_minwidth()
+ CheckScreendump
+
+ " create a popup that does not fit
+ let lines =<< trim END
+ call range(40)
+ \ ->map({_,i -> string(i)})
+ \ ->popup_create({
+ \ 'drag': 1,
+ \ 'wrap': 0,
+ \ 'border': [],
+ \ 'scrollbar': 1,
+ \ 'minwidth': 100,
+ \ 'filter': {w, k -> k ==# 'q' ? len([popup_close(w)]) : 0},
+ \ })
+ func DragitDown()
+ map <silent> <F3> :call test_setmouse(1, 10)<CR>
+ map <silent> <F4> :call test_setmouse(5, 40)<CR>
+ call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ func DragitUp()
+ map <silent> <F3> :call test_setmouse(5, 40)<CR>
+ map <silent> <F4> :call test_setmouse(4, 40)<CR>
+ map <silent> <F5> :call test_setmouse(3, 40)<CR>
+ call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<F5>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupDrag', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupDrag', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_minwidth_1', {})
+
+ call term_sendkeys(buf, ":call DragitDown()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_minwidth_2', {})
+
+ call term_sendkeys(buf, ":call DragitUp()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_drag_minwidth_3', {})
+
+ call term_sendkeys(buf, 'q')
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_drag_termwin()
+ CheckUnix
+ CheckScreendump
+ CheckFeature terminal
+
+ " create a popup that covers the terminal window
+ let lines =<< trim END
+ set foldmethod=marker
+ call setline(1, range(100))
+ for nr in range(7)
+ call setline(nr * 12 + 1, "fold {{{")
+ call setline(nr * 12 + 11, "end }}}")
+ endfor
+ %foldclose
+ set shell=/bin/sh noruler
+ unlet $PROMPT_COMMAND
+ let $PS1 = 'vim> '
+ terminal ++rows=4
+ $wincmd w
+ let winid = popup_create(['1111', '2222'], #{
+ \ drag: 1,
+ \ resize: 1,
+ \ border: [],
+ \ line: 3,
+ \ })
+ func DragitLeft()
+ call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ func DragitDown()
+ call feedkeys("\<F4>\<LeftMouse>\<F5>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ func DragitDownLeft()
+ call feedkeys("\<F5>\<LeftMouse>\<F6>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ map <silent> <F3> :call test_setmouse(3, &columns / 2)<CR>
+ map <silent> <F4> :call test_setmouse(3, &columns / 2 - 20)<CR>
+ map <silent> <F5> :call test_setmouse(12, &columns / 2)<CR>
+ map <silent> <F6> :call test_setmouse(12, &columns / 2 - 20)<CR>
+ END
+ call writefile(lines, 'XtestPopupTerm', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupTerm', #{rows: 16})
+ call VerifyScreenDump(buf, 'Test_popupwin_term_01', {})
+
+ call term_sendkeys(buf, ":call DragitLeft()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_term_02', {})
+
+ call term_sendkeys(buf, ":call DragitDown()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_term_03', {})
+
+ call term_sendkeys(buf, ":call DragitDownLeft()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_term_04', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_close_with_mouse()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ " With border, can click on X
+ let winid = popup_create('foobar', #{
+ \ close: 'button',
+ \ border: [],
+ \ line: 1,
+ \ col: 1,
+ \ })
+ func CloseMsg(id, result)
+ echomsg 'Popup closed with ' .. a:result
+ endfunc
+ let winid = popup_create('notification', #{
+ \ close: 'click',
+ \ line: 3,
+ \ col: 15,
+ \ callback: 'CloseMsg',
+ \ })
+ let winid = popup_create('no border here', #{
+ \ close: 'button',
+ \ line: 5,
+ \ col: 3,
+ \ })
+ let winid = popup_create('only padding', #{
+ \ close: 'button',
+ \ padding: [],
+ \ line: 5,
+ \ col: 23,
+ \ })
+ func CloseWithX()
+ call feedkeys("\<F3>\<LeftMouse>\<LeftRelease>", "xt")
+ endfunc
+ map <silent> <F3> :call test_setmouse(1, len('foobar') + 2)<CR>
+ func CloseWithClick()
+ call feedkeys("\<F4>\<LeftMouse>\<LeftRelease>", "xt")
+ endfunc
+ map <silent> <F4> :call test_setmouse(3, 17)<CR>
+ func CreateWithMenuFilter()
+ let winid = popup_create('barfoo', #{
+ \ close: 'button',
+ \ filter: 'popup_filter_menu',
+ \ border: [],
+ \ line: 1,
+ \ col: 40,
+ \ })
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupClose', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupClose', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_close_01', {})
+
+ call term_sendkeys(buf, ":call CloseWithX()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_close_02', {})
+
+ call term_sendkeys(buf, ":call CloseWithClick()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_close_03', {})
+
+ call term_sendkeys(buf, ":call CreateWithMenuFilter()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_close_04', {})
+
+ " We have to send the actual mouse code, feedkeys() would be caught the
+ " filter.
+ call term_sendkeys(buf, "\<Esc>[<0;47;1M")
+ call VerifyScreenDump(buf, 'Test_popupwin_close_05', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunction
+
+func Test_popup_menu_wrap()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ call popup_create([
+ \ 'one',
+ \ 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfas',
+ \ 'three',
+ \ 'four',
+ \ ], #{
+ \ pos: "botleft",
+ \ border: [],
+ \ padding: [0,1,0,1],
+ \ maxheight: 3,
+ \ cursorline: 1,
+ \ filter: 'popup_filter_menu',
+ \ })
+ END
+ call writefile(lines, 'XtestPopupWrap', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupWrap', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_wrap_1', {})
+
+ call term_sendkeys(buf, "jj")
+ call VerifyScreenDump(buf, 'Test_popupwin_wrap_2', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunction
+
+func Test_popup_with_mask()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, repeat([join(range(1, 42), '')], 13))
+ hi PopupColor ctermbg=lightgrey
+ let winid = popup_create([
+ \ 'some text',
+ \ 'another line',
+ \], #{
+ \ line: 1,
+ \ col: 10,
+ \ posinvert: 0,
+ \ wrap: 0,
+ \ fixed: 1,
+ \ scrollbar: v:false,
+ \ zindex: 90,
+ \ padding: [],
+ \ highlight: 'PopupColor',
+ \ mask: [[1,1,1,1], [-5,-1,4,4], [7,9,2,3], [2,4,3,3]]})
+ call popup_create([
+ \ 'xxxxxxxxx',
+ \ 'yyyyyyyyy',
+ \], #{
+ \ line: 3,
+ \ col: 18,
+ \ zindex: 20})
+ let winidb = popup_create([
+ \ 'just one line',
+ \], #{
+ \ line: 7,
+ \ col: 10,
+ \ posinvert: 0,
+ \ wrap: 0,
+ \ fixed: 1,
+ \ scrollbar: v:false,
+ \ close: 'button',
+ \ zindex: 90,
+ \ padding: [],
+ \ border: [],
+ \ mask: [[1,2,1,1], [-5,-1,4,4], [7,9,2,3], [3,5,5,5],[-7,-4,5,5]]})
+ END
+ call writefile(lines, 'XtestPopupMask', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupMask', #{rows: 13})
+ call VerifyScreenDump(buf, 'Test_popupwin_mask_1', {})
+
+ call term_sendkeys(buf, ":call popup_move(winid, #{col: 11, line: 2})\<CR>")
+ call term_sendkeys(buf, ":call popup_move(winidb, #{col: 12})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_mask_2', {})
+
+ call term_sendkeys(buf, ":call popup_move(winid, #{col: 65, line: 2})\<CR>")
+ call term_sendkeys(buf, ":call popup_move(winidb, #{col: 63})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_mask_3', {})
+
+ call term_sendkeys(buf, ":call popup_move(winid, #{pos: 'topright', col: 12, line: 2})\<CR>")
+ call term_sendkeys(buf, ":call popup_move(winidb, #{pos: 'topright', col: 12})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_mask_4', {})
+
+ call term_sendkeys(buf, ":call popup_move(winid, #{pos: 'topright', col: 12, line: 11})\<CR>")
+ call term_sendkeys(buf, ":call popup_move(winidb, #{pos: 'topleft', col: 42, line: 11})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_mask_5', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+
+ " this was causing a crash
+ call popup_create('test', #{mask: [[0, 0, 0, 0]]})
+ call popup_clear()
+
+ " this was causing an internal error
+ enew
+ set nowrap
+ call repeat('x', &columns)->setline(1)
+ call prop_type_add('textprop', {})
+ call prop_add(1, 1, #{length: &columns, type: 'textprop'})
+ vsplit
+ let opts = popup_create('', #{textprop: 'textprop'})
+ \ ->popup_getoptions()
+ \ ->extend(#{mask: [[1, 1, 1, 1]]})
+ call popup_create('', opts)
+ redraw
+
+ close!
+ bwipe!
+ call prop_type_delete('textprop')
+ call popup_clear()
+ set wrap&
+endfunc
+
+func Test_popup_select()
+ CheckScreendump
+ CheckFeature clipboard_working
+
+ " create a popup with some text to be selected
+ let lines =<< trim END
+ set clipboard=autoselect
+ call setline(1, range(1, 20))
+ let winid = popup_create(['the word', 'some more', 'several words here', 'invisible', '5', '6', '7'], #{
+ \ drag: 1,
+ \ border: [],
+ \ line: 3,
+ \ col: 10,
+ \ maxheight: 3,
+ \ })
+ func Select1()
+ call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
+ endfunc
+ map <silent> <F3> :call test_setmouse(4, 15)<CR>
+ map <silent> <F4> :call test_setmouse(6, 23)<CR>
+ END
+ call writefile(lines, 'XtestPopupSelect', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupSelect', #{rows: 10})
+ call term_sendkeys(buf, ":call Select1()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_select_01', {})
+
+ call term_sendkeys(buf, ":call popup_close(winid)\<CR>")
+ call term_sendkeys(buf, "\"*p")
+ " clean the command line, sometimes it still shows a command
+ call term_sendkeys(buf, ":\<esc>")
+
+ call VerifyScreenDump(buf, 'Test_popupwin_select_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_in_tab()
+ " default popup is local to tab, not visible when in other tab
+ let winid = popup_create("text", {})
+ let bufnr = winbufnr(winid)
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal(0, popup_getoptions(winid).tabpage)
+ tabnew
+ call assert_equal(0, popup_getpos(winid).visible)
+ call assert_equal(1, popup_getoptions(winid).tabpage)
+ quit
+ call assert_equal(1, popup_getpos(winid).visible)
+
+ call assert_equal(1, bufexists(bufnr))
+ call popup_clear()
+ " buffer is gone now
+ call assert_equal(0, bufexists(bufnr))
+
+ " global popup is visible in any tab
+ let winid = popup_create("text", #{tabpage: -1})
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal(-1, popup_getoptions(winid).tabpage)
+ tabnew
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal(-1, popup_getoptions(winid).tabpage)
+ quit
+ call assert_equal(1, popup_getpos(winid).visible)
+ call popup_clear()
+
+ " create popup in other tab
+ tabnew
+ let winid = popup_create("text", #{tabpage: 1})
+ call assert_equal(0, popup_getpos(winid).visible)
+ call assert_equal(1, popup_getoptions(winid).tabpage)
+ quit
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal(0, popup_getoptions(winid).tabpage)
+ call popup_clear()
+endfunc
+
+func Test_popup_valid_arguments()
+ call assert_equal(0, len(popup_list()))
+
+ " Zero value is like the property wasn't there
+ let winid = popup_create("text", #{col: 0})
+ let pos = popup_getpos(winid)
+ call assert_inrange(&columns / 2 - 1, &columns / 2 + 1, pos.col)
+ call assert_equal([winid], popup_list())
+ call popup_clear()
+
+ " using cursor column has minimum value of 1
+ let winid = popup_create("text", #{col: 'cursor-100'})
+ let pos = popup_getpos(winid)
+ call assert_equal(1, pos.col)
+ call popup_clear()
+
+ " center
+ let winid = popup_create("text", #{pos: 'center'})
+ let pos = popup_getpos(winid)
+ let around = (&columns - pos.width) / 2
+ call assert_inrange(around - 1, around + 1, pos.col)
+ let around = (&lines - pos.height) / 2
+ call assert_inrange(around - 1, around + 1, pos.line)
+ call popup_clear()
+endfunc
+
+func Test_popup_invalid_arguments()
+ call assert_fails('call popup_create(666, {})', 'E86:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", "none")', 'E1206:')
+ call popup_clear()
+ call assert_fails('call popup_create(test_null_string(), {})', 'E450:')
+ call assert_fails('call popup_create(test_null_list(), {})', 'E450:')
+ call popup_clear()
+
+ call assert_fails('call popup_create("text", #{col: "xxx"})', 'E475:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{col: "cursor8"})', 'E15:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{col: "cursor+x"})', 'E15:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{col: "cursor+8x"})', 'E15:')
+ call popup_clear()
+
+ call assert_fails('call popup_create("text", #{line: "xxx"})', 'E475:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{line: "cursor8"})', 'E15:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{line: "cursor+x"})', 'E15:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{line: "cursor+8x"})', 'E15:')
+ call popup_clear()
+
+ call assert_fails('call popup_create("text", #{pos: "there"})', 'E475:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{padding: "none"})', 'E714:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{border: "none"})', 'E714:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{borderhighlight: "none"})', 'E714:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{borderhighlight: test_null_list()})', 'E714:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{borderchars: "none"})', 'E714:')
+ call popup_clear()
+
+ call assert_fails('call popup_create([#{text: "text"}, 666], {})', 'E1284: Argument 1, list item 2: Dictionary required')
+ call popup_clear()
+ call assert_fails('call popup_create([#{text: "text", props: "none"}], {})', 'E714:')
+ call popup_clear()
+ call assert_fails('call popup_create([#{text: "text", props: ["none"]}], {})', 'E715:')
+ call popup_clear()
+ call assert_fails('call popup_create([#{text: "text", props: range(3)}], {})', 'E715:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{mask: ["asdf"]})', 'E475:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{mask: range(5)})', 'E475:')
+ call popup_clear()
+ call popup_create("text", #{mask: [range(4)]})
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{mask: test_null_list()})', 'E475:')
+ call assert_fails('call popup_create("text", #{mapping: []})', 'E745:')
+ call popup_clear()
+ call assert_fails('call popup_create("text", #{tabpage : 4})', 'E997:')
+ call popup_clear()
+
+ call assert_fails('call popup_create(range(10), {})', 'E1024:')
+ call popup_clear()
+ call assert_fails('call popup_create([1, 2], {})', 'E1284: Argument 1, list item 1: Dictionary required')
+ call popup_clear()
+endfunc
+
+func Test_win_execute_closing_curwin()
+ split
+ let winid = popup_create('some text', {})
+ call assert_fails('call win_execute(winid, winnr() .. "close")', 'E994:')
+ call popup_clear()
+
+ let winid = popup_create('some text', {})
+ call assert_fails('call win_execute(winid, printf("normal! :\<C-u>call popup_close(%d)\<CR>", winid))', 'E994:')
+ call popup_clear()
+endfunc
+
+func Test_win_execute_not_allowed()
+ let winid = popup_create('some text', {})
+ call assert_fails('call win_execute(winid, "split")', 'E994:')
+ call assert_fails('call win_execute(winid, "vsplit")', 'E994:')
+ call assert_fails('call win_execute(winid, "close")', 'E994:')
+ call assert_fails('call win_execute(winid, "bdelete")', 'E994:')
+ call assert_fails('call win_execute(winid, "bwipe!")', 'E994:')
+ call assert_fails('call win_execute(winid, "tabnew")', 'E994:')
+ call assert_fails('call win_execute(winid, "tabnext")', 'E994:')
+ call assert_fails('call win_execute(winid, "next")', 'E994:')
+ call assert_fails('call win_execute(winid, "rewind")', 'E994:')
+ call assert_fails('call win_execute(winid, "pedit filename")', 'E994:')
+ call assert_fails('call win_execute(winid, "buf")', 'E994:')
+ call assert_fails('call win_execute(winid, "bnext")', 'E994:')
+ call assert_fails('call win_execute(winid, "bprev")', 'E994:')
+ call assert_fails('call win_execute(winid, "bfirst")', 'E994:')
+ call assert_fails('call win_execute(winid, "blast")', 'E994:')
+ call assert_fails('call win_execute(winid, "edit")', 'E994:')
+ call assert_fails('call win_execute(winid, "enew")', 'E994:')
+ call assert_fails('call win_execute(winid, "help")', 'E994:')
+ call assert_fails('call win_execute(winid, "1only")', 'E994:')
+ call assert_fails('call win_execute(winid, "wincmd x")', 'E994:')
+ call assert_fails('call win_execute(winid, "wincmd w")', 'E994:')
+ call assert_fails('call win_execute(winid, "wincmd t")', 'E994:')
+ call assert_fails('call win_execute(winid, "wincmd b")', 'E994:')
+ call popup_clear()
+endfunc
+
+func Test_popup_with_wrap()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ let winid = popup_create(
+ \ 'a long line that wont fit',
+ \ #{line: 3, col: 20, maxwidth: 10, wrap: 1})
+ END
+ call writefile(lines, 'XtestPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_wrap', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_without_wrap()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ let winid = popup_create(
+ \ 'a long line that wont fit',
+ \ #{line: 3, col: 20, maxwidth: 10, wrap: 0})
+ END
+ call writefile(lines, 'XtestPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_nowrap', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_with_showbreak()
+ CheckScreendump
+
+ let lines =<< trim END
+ set showbreak=>>\
+ call setline(1, range(1, 20))
+ let winid = popup_dialog(
+ \ 'a long line here that wraps',
+ \ #{filter: 'popup_filter_yesno',
+ \ maxwidth: 12})
+ END
+ call writefile(lines, 'XtestPopupShowbreak', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupShowbreak', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_showbreak', {})
+
+ " clean up
+ call term_sendkeys(buf, "y")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_time()
+ CheckFeature timers
+
+ topleft vnew
+ call setline(1, 'hello')
+
+ let winid = popup_create('world', #{
+ \ line: 1,
+ \ col: 1,
+ \ minwidth: 20,
+ \ time: 500,
+ \})
+ redraw
+ let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
+ call assert_equal('world', line)
+
+ call assert_equal(winid, popup_locate(1, 1))
+ call assert_equal(winid, popup_locate(1, 20))
+ call assert_equal(0, popup_locate(1, 21))
+ call assert_equal(0, popup_locate(2, 1))
+
+ " Mac is usually a bit slow
+ let delay = has('mac') ? '900m' : '700m'
+ exe 'sleep ' .. delay
+ redraw
+
+ let line = join(map(range(1, 5), '1->screenstring(v:val)'), '')
+ call assert_equal('hello', line)
+
+ call popup_create('on the command line', #{
+ \ line: &lines,
+ \ col: 10,
+ \ minwidth: 20,
+ \ time: 500,
+ \})
+ redraw
+ let line = join(map(range(1, 30), 'screenstring(&lines, v:val)'), '')
+ call assert_match('.*on the command line.*', line)
+
+ exe 'sleep ' .. delay
+ redraw
+ let line = join(map(range(1, 30), 'screenstring(&lines, v:val)'), '')
+ call assert_notmatch('.*on the command line.*', line)
+
+ bwipe!
+endfunc
+
+func Test_popup_hide()
+ topleft vnew
+ call setline(1, 'hello')
+
+ let winid = popup_create('world', #{
+ \ line: 1,
+ \ col: 1,
+ \ minwidth: 20,
+ \})
+ redraw
+ let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
+ call assert_equal('world', line)
+ call assert_equal(1, popup_getpos(winid).visible)
+ " buffer is still listed and active
+ call assert_match(winbufnr(winid) .. 'u a.*\[Popup\]', execute('ls u'))
+
+ call popup_hide(winid)
+ redraw
+ let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
+ call assert_equal('hello', line)
+ call assert_equal(0, popup_getpos(winid).visible)
+ " buffer is still listed but hidden
+ call assert_match(winbufnr(winid) .. 'u a.*\[Popup\]', execute('ls u'))
+
+ eval winid->popup_show()
+ redraw
+ let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
+ call assert_equal('world', line)
+ call assert_equal(1, popup_getpos(winid).visible)
+
+
+ call popup_close(winid)
+ redraw
+ let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
+ call assert_equal('hello', line)
+
+ " error is given for existing non-popup window
+ call assert_fails('call popup_hide(win_getid())', 'E993:')
+
+ " no error non-existing window
+ eval 1234234->popup_hide()
+ call popup_show(41234234)
+
+ bwipe!
+endfunc
+
+func Test_popup_move()
+ topleft vnew
+ call setline(1, 'hello')
+
+ let winid = popup_create('world', #{
+ \ line: 1,
+ \ col: 1,
+ \ minwidth: 20,
+ \})
+ redraw
+ let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
+ call assert_equal('world ', line)
+
+ call popup_move(winid, #{line: 2, col: 2})
+ redraw
+ let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
+ call assert_equal('hello ', line)
+ let line = join(map(range(1, 6), 'screenstring(2, v:val)'), '')
+ call assert_equal('~world', line)
+
+ eval winid->popup_move(#{line: 1})
+ redraw
+ let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
+ call assert_equal('hworld', line)
+
+ call assert_fails('call popup_move(winid, [])', 'E1206:')
+ call assert_fails('call popup_move(winid, test_null_dict())', 'E1297:')
+
+ call popup_close(winid)
+
+ call assert_equal(0, popup_move(-1, {}))
+
+ bwipe!
+endfunc
+
+func Test_popup_getpos()
+ let winid = popup_create('hello', #{
+ \ line: 2,
+ \ col: 3,
+ \ minwidth: 10,
+ \ minheight: 11,
+ \})
+ redraw
+ let res = popup_getpos(winid)
+ call assert_equal(2, res.line)
+ call assert_equal(3, res.col)
+ call assert_equal(10, res.width)
+ call assert_equal(11, res.height)
+ call assert_equal(1, res.visible)
+
+ call popup_close(winid)
+endfunc
+
+func Test_popup_width_longest()
+ let tests = [
+ \ [['hello', 'this', 'window', 'displays', 'all of its text'], 15],
+ \ [['hello', 'this', 'window', 'all of its text', 'displays'], 15],
+ \ [['hello', 'this', 'all of its text', 'window', 'displays'], 15],
+ \ [['hello', 'all of its text', 'this', 'window', 'displays'], 15],
+ \ [['all of its text', 'hello', 'this', 'window', 'displays'], 15],
+ \ ]
+
+ for test in tests
+ let winid = popup_create(test[0], #{line: 2, col: 3})
+ redraw
+ let position = popup_getpos(winid)
+ call assert_equal(test[1], position.width)
+ call popup_close(winid)
+ endfor
+endfunc
+
+func Test_popup_wraps()
+ let tests = [
+ \ ['nowrap', 6, 1],
+ \ ['a line that wraps once', 12, 2],
+ \ ['a line that wraps two times', 12, 3],
+ \ ]
+ for test in tests
+ let winid = popup_create(test[0],
+ \ #{line: 2, col: 3, maxwidth: 12})
+ redraw
+ let position = popup_getpos(winid)
+ call assert_equal(test[1], position.width)
+ call assert_equal(test[2], position.height)
+
+ call popup_close(winid)
+ call assert_equal({}, popup_getpos(winid))
+ endfor
+endfunc
+
+func Test_popup_getoptions()
+ let winid = popup_create('hello', #{
+ \ line: 2,
+ \ col: 3,
+ \ minwidth: 10,
+ \ minheight: 11,
+ \ maxwidth: 20,
+ \ maxheight: 21,
+ \ zindex: 100,
+ \ time: 5000,
+ \ fixed: 1
+ \})
+ redraw
+ let res = popup_getoptions(winid)
+ call assert_equal(2, res.line)
+ call assert_equal(3, res.col)
+ call assert_equal(10, res.minwidth)
+ call assert_equal(11, res.minheight)
+ call assert_equal(20, res.maxwidth)
+ call assert_equal(21, res.maxheight)
+ call assert_equal(100, res.zindex)
+ call assert_equal(1, res.fixed)
+ call assert_equal(1, res.mapping)
+ if has('timers')
+ call assert_equal(5000, res.time)
+ endif
+ call popup_close(winid)
+
+ let winid = popup_create('hello', {})
+ redraw
+ let res = popup_getoptions(winid)
+ call assert_equal(0, res.line)
+ call assert_equal(0, res.col)
+ call assert_equal(0, res.minwidth)
+ call assert_equal(0, res.minheight)
+ call assert_equal(0, res.maxwidth)
+ call assert_equal(0, res.maxheight)
+ call assert_equal(50, res.zindex)
+ call assert_equal(0, res.fixed)
+ if has('timers')
+ call assert_equal(0, res.time)
+ endif
+ call popup_close(winid)
+ call assert_equal({}, popup_getoptions(winid))
+endfunc
+
+func Test_popup_option_values()
+ new
+ " window-local
+ setlocal number
+ setlocal nowrap
+ " buffer-local
+ setlocal omnifunc=Something
+ " global/buffer-local
+ setlocal path=/there
+ " global/window-local
+ setlocal statusline=2
+
+ let winid = popup_create('hello', {})
+ call assert_equal(0, getwinvar(winid, '&number'))
+ call assert_equal(1, getwinvar(winid, '&wrap'))
+ call assert_equal('', getwinvar(winid, '&omnifunc'))
+ call assert_equal(&g:path, getwinvar(winid, '&path'))
+ call assert_equal(&g:statusline, getwinvar(winid, '&statusline'))
+
+ " 'scrolloff' is reset to zero
+ call assert_equal(5, &scrolloff)
+ call assert_equal(0, getwinvar(winid, '&scrolloff'))
+
+ call popup_close(winid)
+ bwipe
+endfunc
+
+func Test_popup_atcursor()
+ topleft vnew
+ call setline(1, [
+ \ 'xxxxxxxxxxxxxxxxx',
+ \ 'xxxxxxxxxxxxxxxxx',
+ \ 'xxxxxxxxxxxxxxxxx',
+ \])
+
+ call cursor(2, 2)
+ redraw
+ let winid = popup_atcursor('vim', {})
+ redraw
+ let line = join(map(range(1, 17), 'screenstring(1, v:val)'), '')
+ call assert_equal('xvimxxxxxxxxxxxxx', line)
+ call popup_close(winid)
+
+ call cursor(3, 4)
+ redraw
+ let winid = 'vim'->popup_atcursor({})
+ redraw
+ let line = join(map(range(1, 17), 'screenstring(2, v:val)'), '')
+ call assert_equal('xxxvimxxxxxxxxxxx', line)
+ call popup_close(winid)
+
+ call cursor(1, 1)
+ redraw
+ let winid = popup_create('vim', #{
+ \ line: 'cursor+2',
+ \ col: 'cursor+1',
+ \})
+ redraw
+ let line = join(map(range(1, 17), 'screenstring(3, v:val)'), '')
+ call assert_equal('xvimxxxxxxxxxxxxx', line)
+ call popup_close(winid)
+
+ call cursor(3, 3)
+ redraw
+ let winid = popup_create('vim', #{
+ \ line: 'cursor-2',
+ \ col: 'cursor-1',
+ \})
+ redraw
+ let line = join(map(range(1, 17), 'screenstring(1, v:val)'), '')
+ call assert_equal('xvimxxxxxxxxxxxxx', line)
+ call popup_close(winid)
+
+ " just enough room above
+ call cursor(3, 3)
+ redraw
+ let winid = popup_atcursor(['vim', 'is great'], {})
+ redraw
+ let pos = popup_getpos(winid)
+ call assert_equal(1, pos.line)
+ call popup_close(winid)
+
+ " not enough room above, popup goes below the cursor
+ call cursor(3, 3)
+ redraw
+ let winid = popup_atcursor(['vim', 'is', 'great'], {})
+ redraw
+ let pos = popup_getpos(winid)
+ call assert_equal(4, pos.line)
+ call popup_close(winid)
+
+ " cursor in first line, popup in line 2
+ call cursor(1, 1)
+ redraw
+ let winid = popup_atcursor(['vim', 'is', 'great'], {})
+ redraw
+ let pos = popup_getpos(winid)
+ call assert_equal(2, pos.line)
+ call popup_close(winid)
+
+ bwipe!
+endfunc
+
+func Test_popup_atcursor_pos()
+ CheckScreendump
+ CheckFeature conceal
+
+ let lines =<< trim END
+ call setline(1, repeat([repeat('-', 60)], 15))
+ set so=0
+
+ normal 9G3|r#
+ let winid1 = popup_atcursor(['first', 'second'], #{
+ \ moved: [0, 0, 0],
+ \ })
+ normal 9G21|r&
+ let winid1 = popup_atcursor(['FIrsT', 'SEcoND'], #{
+ \ pos: 'botright',
+ \ moved: [0, 0, 0],
+ \ })
+ normal 3G27|r%
+ let winid1 = popup_atcursor(['fiRSt', 'seCOnd'], #{
+ \ pos: 'topleft',
+ \ moved: [0, 0, 0],
+ \ })
+ normal 3G45|r@
+ let winid1 = popup_atcursor(['First', 'SeconD'], #{
+ \ pos: 'topright',
+ \ moved: range(3),
+ \ mousemoved: range(3),
+ \ })
+
+ normal 9G27|Rconcealed X
+ syn match Hidden /concealed/ conceal
+ set conceallevel=2 concealcursor=n
+ redraw
+ normal 0fX
+ call popup_atcursor('mark', {})
+ END
+ call writefile(lines, 'XtestPopupAtcursorPos', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupAtcursorPos', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_popupwin_atcursor_pos', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_beval()
+ CheckScreendump
+ CheckFeature balloon_eval_term
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ call setline(5, 'here is some text to hover over')
+ set balloonevalterm
+ set balloonexpr=BalloonExpr()
+ set balloondelay=100
+ func BalloonExpr()
+ let s:winid = [v:beval_text]->popup_beval({})
+ return ''
+ endfunc
+ func Hover()
+ call test_setmouse(5, 15)
+ call feedkeys("\<MouseMove>\<Ignore>", "xt")
+ sleep 100m
+ endfunc
+ func MoveOntoPopup()
+ call test_setmouse(4, 17)
+ call feedkeys("\<F4>\<MouseMove>\<Ignore>", "xt")
+ endfunc
+ func MoveAway()
+ call test_setmouse(5, 13)
+ call feedkeys("\<F5>\<MouseMove>\<Ignore>", "xt")
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupBeval', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupBeval', #{rows: 10})
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, 'j')
+ call term_sendkeys(buf, ":call Hover()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_beval_1', {})
+
+ call term_sendkeys(buf, ":call MoveOntoPopup()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_beval_2', {})
+
+ call term_sendkeys(buf, ":call MoveAway()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_beval_3', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_filter()
+ new
+ call setline(1, 'some text')
+
+ func MyPopupFilter(winid, c)
+ if a:c == 'e' || a:c == "\<F9>"
+ let g:eaten = a:c
+ return 1
+ endif
+ if a:c == '0'
+ let g:ignored = '0'
+ return 0
+ endif
+ if a:c == 'x'
+ call popup_close(a:winid)
+ return 1
+ endif
+ return 0
+ endfunc
+
+ let winid = 'something'->popup_create(#{filter: 'MyPopupFilter'})
+ redraw
+
+ " e is consumed by the filter
+ let g:eaten = ''
+ call feedkeys('e', 'xt')
+ call assert_equal('e', g:eaten)
+ call feedkeys("\<F9>", 'xt')
+ call assert_equal("\<F9>", g:eaten)
+
+ " 0 is ignored by the filter
+ let g:ignored = ''
+ normal $
+ call assert_equal(9, getcurpos()[2])
+ call feedkeys('0', 'xt')
+ call assert_equal('0', g:ignored)
+
+ if has('win32') && has('gui_running')
+ echo "FIXME: this check is very flaky on MS-Windows GUI, the cursor doesn't move"
+ else
+ call assert_equal(1, getcurpos()[2])
+ endif
+
+ " x closes the popup
+ call feedkeys('x', 'xt')
+ call assert_equal("\<F9>", g:eaten)
+ call assert_equal(-1, winbufnr(winid))
+
+ unlet g:eaten
+ unlet g:ignored
+ delfunc MyPopupFilter
+ call popup_clear()
+endfunc
+
+" this tests that the filter is not used for :normal command
+func Test_popup_filter_normal_cmd()
+ CheckScreendump
+
+ let lines =<< trim END
+ let text = range(1, 20)->map({_, v -> string(v)})
+ let g:winid = popup_create(text, #{maxheight: 5, minwidth: 3, filter: 'invalidfilter'})
+ call timer_start(0, {-> win_execute(g:winid, 'norm! 10Gzz')})
+ END
+ call writefile(lines, 'XtestPopupNormal', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupNormal', #{rows: 10})
+ call TermWait(buf, 100)
+ call VerifyScreenDump(buf, 'Test_popupwin_normal_cmd', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" test that cursor line highlight is updated after using win_execute()
+func Test_popup_filter_win_execute()
+ CheckScreendump
+
+ let lines =<< trim END
+ let lines = range(1, &lines * 2)->map({_, v -> string(v)})
+ let g:id = popup_create(lines, #{
+ \ minheight: &lines - 5,
+ \ maxheight: &lines - 5,
+ \ cursorline: 1,
+ \ })
+ redraw
+ END
+ call writefile(lines, 'XtestPopupWinExecute', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupWinExecute', #{rows: 14})
+
+ call term_sendkeys(buf, ":call win_execute(g:id, ['normal 17Gzz'])\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+
+ call VerifyScreenDump(buf, 'Test_popupwin_win_execute_cursorline', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_set_firstline()
+ CheckScreendump
+
+ let lines =<< trim END
+ let lines = range(1, 50)->map({_, v -> string(v)})
+ let g:id = popup_create(lines, #{
+ \ minwidth: 20,
+ \ maxwidth: 20,
+ \ minheight: &lines - 5,
+ \ maxheight: &lines - 5,
+ \ cursorline: 1,
+ \ })
+ call popup_setoptions(g:id, #{firstline: 10})
+ redraw
+ END
+ call writefile(lines, 'XtestPopupWinSetFirstline', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupWinSetFirstline', #{rows: 16})
+
+ call VerifyScreenDump(buf, 'Test_popupwin_set_firstline_1', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(g:id, #{firstline: 5})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_set_firstline_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" this tests that we don't get stuck with an error in "win_execute()"
+func Test_popup_filter_win_execute_error()
+ CheckScreendump
+
+ let lines =<< trim END
+ let g:winid = popup_create('some text', {'filter': 'invalidfilter'})
+ call timer_start(0, {-> win_execute(g:winid, 'invalidCommand')})
+ END
+ call writefile(lines, 'XtestPopupWinExecuteError', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupWinExecuteError', #{rows: 10, wait_for_ruler: 0})
+
+ call WaitFor({-> term_getline(buf, 9) =~ 'Not an editor command: invalidCommand'})
+ call term_sendkeys(buf, "\<CR>")
+ call WaitFor({-> term_getline(buf, 9) =~ 'Unknown function: invalidfilter'})
+ call term_sendkeys(buf, "\<CR>")
+ call WaitFor({-> term_getline(buf, 9) =~ 'Not allowed in a popup window'})
+ call term_sendkeys(buf, "\<CR>")
+ call term_sendkeys(buf, "\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_win_execute', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func ShowDialog(key, result)
+ let s:cb_res = 999
+ let winid = popup_dialog('do you want to quit (Yes/no)?', #{
+ \ filter: 'popup_filter_yesno',
+ \ callback: 'QuitCallback',
+ \ })
+ redraw
+ call feedkeys(a:key, "xt")
+ call assert_equal(winid, s:cb_winid)
+ call assert_equal(a:result, s:cb_res)
+endfunc
+
+func Test_popup_dialog()
+ func QuitCallback(id, res)
+ let s:cb_winid = a:id
+ let s:cb_res = a:res
+ endfunc
+
+ let winid = ShowDialog("y", 1)
+ let winid = ShowDialog("Y", 1)
+ let winid = ShowDialog("n", 0)
+ let winid = ShowDialog("N", 0)
+ let winid = ShowDialog("x", 0)
+ let winid = ShowDialog("X", 0)
+ let winid = ShowDialog("\<Esc>", 0)
+ let winid = ShowDialog("\<C-C>", -1)
+
+ delfunc QuitCallback
+endfunc
+
+func ShowMenu(key, result)
+ let s:cb_res = 999
+ let winid = popup_menu(['one', 'two', 'something else'], #{
+ \ callback: 'QuitCallback',
+ \ })
+ redraw
+ call feedkeys(a:key, "xt")
+ call assert_equal(winid, s:cb_winid)
+ call assert_equal(a:result, s:cb_res)
+endfunc
+
+func Test_popup_menu()
+ func QuitCallback(id, res)
+ let s:cb_winid = a:id
+ let s:cb_res = a:res
+ endfunc
+ " mapping won't be used in popup
+ map j k
+
+ let winid = ShowMenu(" ", 1)
+ let winid = ShowMenu("j \<CR>", 2)
+ let winid = ShowMenu("JjK \<CR>", 2)
+ let winid = ShowMenu("jjjjjj ", 3)
+ let winid = ShowMenu("kkk ", 1)
+ let winid = ShowMenu("x", -1)
+ let winid = ShowMenu("X", -1)
+ let winid = ShowMenu("\<Esc>", -1)
+ let winid = ShowMenu("\<C-C>", -1)
+
+ delfunc QuitCallback
+ unmap j
+endfunc
+
+func Test_popup_menu_screenshot()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ hi PopupSelected ctermbg=lightblue
+ call popup_menu(['one', 'two', 'another'], #{callback: 'MenuDone', title: ' make a choice from the list '})
+ func MenuDone(id, res)
+ echomsg "selected " .. a:res
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupMenu', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupMenu', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_01', {})
+
+ call term_sendkeys(buf, "jj")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_02', {})
+
+ call term_sendkeys(buf, " ")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_menu_narrow()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ hi PopupSelected ctermbg=green
+ call popup_menu(['one', 'two', 'three'], #{callback: 'MenuDone'})
+ func MenuDone(id, res)
+ echomsg "selected " .. a:res
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupNarrowMenu', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupNarrowMenu', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_04', {})
+
+ " clean up
+ call term_sendkeys(buf, "x")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_title()
+ CheckScreendump
+
+ " Create a popup without title or border, a line of padding will be added to
+ " put the title on.
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ let winid = popup_create(['one', 'two', 'another'], #{title: 'Title String'})
+ END
+ call writefile(lines, 'XtestPopupTitle', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupTitle', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_title', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{maxwidth: 20, title: 'a very long title that is not going to fit'})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_longtitle_1', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{border: []})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_longtitle_2', {})
+
+ call term_sendkeys(buf, ":call popup_clear()\<CR>")
+ call term_sendkeys(buf, ":call popup_create(['aaa', 'bbb'], #{title: 'Title', minwidth: 12, padding: [2, 2, 2, 2]})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_longtitle_3', {})
+
+ call term_sendkeys(buf, ":call popup_clear()\<CR>")
+ call term_sendkeys(buf, ":call popup_create(['aaa', 'bbb'], #{title: 'Title', minwidth: 12, border: [], padding: [2, 2, 2, 2]})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_longtitle_4', {})
+
+ call term_sendkeys(buf, ":call popup_clear()\<CR>")
+ call term_sendkeys(buf, ":call popup_menu(['This is a line', 'and another line'], #{title: '▶Äã‚ã„ã†ãˆãŠâ—€', })\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_multibytetitle', {})
+ call term_sendkeys(buf, "x")
+
+ " clean up
+ call StopVimInTerminal(buf)
+
+ let winid = popup_create('something', #{title: 'Some Title'})
+ call assert_equal('Some Title', popup_getoptions(winid).title)
+ call popup_setoptions(winid, #{title: 'Another Title'})
+ call assert_equal('Another Title', popup_getoptions(winid).title)
+
+ call popup_clear()
+endfunc
+
+func Test_popup_close_callback()
+ func PopupDone(id, result)
+ let g:result = a:result
+ endfunc
+ let winid = popup_create('something', #{callback: 'PopupDone'})
+ redraw
+ call popup_close(winid, 'done')
+ call assert_equal('done', g:result)
+endfunc
+
+func Test_popup_empty()
+ let winid = popup_create('', #{padding: [2,2,2,2]})
+ redraw
+ let pos = popup_getpos(winid)
+ call assert_equal(5, pos.width)
+ call assert_equal(5, pos.height)
+ call popup_close(winid)
+
+ let winid = popup_create([], #{border: []})
+ redraw
+ let pos = popup_getpos(winid)
+ call assert_equal(3, pos.width)
+ call assert_equal(3, pos.height)
+ call popup_close(winid)
+endfunc
+
+func Test_popup_never_behind()
+ CheckScreendump
+
+ " +-----------------------------+
+ " | | |
+ " | | |
+ " | | |
+ " | line1 |
+ " |------------line2------------|
+ " | line3 |
+ " | line4 |
+ " | |
+ " | |
+ " +-----------------------------+
+ let lines =<< trim END
+ split
+ vsplit
+ let info_window1 = getwininfo()[0]
+ let line = info_window1['height']
+ let col = info_window1['width']
+ call popup_create(['line1', 'line2', 'line3', 'line4'], #{
+ \ line : line,
+ \ col : col,
+ \ })
+ END
+ call writefile(lines, 'XtestPopupBehind', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupBehind', #{rows: 10})
+ call term_sendkeys(buf, "\<C-W>w")
+ call VerifyScreenDump(buf, 'Test_popupwin_behind', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func s:VerifyPosition(p, msg, line, col, width, height)
+ call assert_equal(a:line, popup_getpos(a:p).line, a:msg . ' (l)')
+ call assert_equal(a:col, popup_getpos(a:p).col, a:msg . ' (c)')
+ call assert_equal(a:width, popup_getpos(a:p).width, a:msg . ' (w)')
+ call assert_equal(a:height, popup_getpos(a:p).height, a:msg . ' (h)')
+endfunc
+
+func Test_popup_position_adjust()
+ " Anything placed past the last cell on the right of the screen is moved to
+ " the left.
+ "
+ " When wrapping is disabled, we also shift to the left to display on the
+ " screen, unless fixed is set.
+
+ " Entries for cases which don't vary based on wrapping.
+ " Format is per tests described below
+ let both_wrap_tests = [
+ \ ['a', 5, &columns, 5, &columns, 1, 1],
+ \ ['b', 5, &columns + 1, 5, &columns, 1, 1],
+ \ ['c', 5, &columns - 1, 5, &columns - 1, 1, 1],
+ \ ['d', 5, &columns - 2, 5, &columns - 2, 1, 1],
+ \ ['e', 5, &columns - 3, 5, &columns - 3, 1, 1]]
+
+ " these test groups are dicts with:
+ " - comment: something to identify the group of tests by
+ " - options: dict of options to merge with the row/col in tests
+ " - tests: list of cases. Each one is a list with elements:
+ " - text
+ " - row
+ " - col
+ " - expected row
+ " - expected col
+ " - expected width
+ " - expected height
+ let tests = [
+ \ #{
+ \ comment: 'left-aligned with wrapping',
+ \ options: #{
+ \ wrap: 1,
+ \ pos: 'botleft',
+ \ },
+ \ tests: both_wrap_tests + [
+ \ ['aa', 5, &columns, 4, &columns, 1, 2],
+ \ ['bb', 5, &columns + 1, 4, &columns, 1, 2],
+ \ ['cc', 5, &columns - 1, 5, &columns - 1, 2, 1],
+ \ ['dd', 5, &columns - 2, 5, &columns - 2, 2, 1],
+ \ ['ee', 5, &columns - 3, 5, &columns - 3, 2, 1],
+ \
+ \ ['aaa', 5, &columns, 3, &columns, 1, 3],
+ \ ['bbb', 5, &columns + 1, 3, &columns, 1, 3],
+ \ ['ccc', 5, &columns - 1, 4, &columns - 1, 2, 2],
+ \ ['ddd', 5, &columns - 2, 5, &columns - 2, 3, 1],
+ \ ['eee', 5, &columns - 3, 5, &columns - 3, 3, 1],
+ \
+ \ ['aaaa', 5, &columns, 2, &columns, 1, 4],
+ \ ['bbbb', 5, &columns + 1, 2, &columns, 1, 4],
+ \ ['cccc', 5, &columns - 1, 4, &columns - 1, 2, 2],
+ \ ['dddd', 5, &columns - 2, 4, &columns - 2, 3, 2],
+ \ ['eeee', 5, &columns - 3, 5, &columns - 3, 4, 1],
+ \ ['eeee', 5, &columns - 4, 5, &columns - 4, 4, 1],
+ \ ],
+ \ },
+ \ #{
+ \ comment: 'left aligned without wrapping',
+ \ options: #{
+ \ wrap: 0,
+ \ pos: 'botleft',
+ \ },
+ \ tests: both_wrap_tests + [
+ \ ['aa', 5, &columns, 5, &columns - 1, 2, 1],
+ \ ['bb', 5, &columns + 1, 5, &columns - 1, 2, 1],
+ \ ['cc', 5, &columns - 1, 5, &columns - 1, 2, 1],
+ \ ['dd', 5, &columns - 2, 5, &columns - 2, 2, 1],
+ \ ['ee', 5, &columns - 3, 5, &columns - 3, 2, 1],
+ \
+ \ ['aaa', 5, &columns, 5, &columns - 2, 3, 1],
+ \ ['bbb', 5, &columns + 1, 5, &columns - 2, 3, 1],
+ \ ['ccc', 5, &columns - 1, 5, &columns - 2, 3, 1],
+ \ ['ddd', 5, &columns - 2, 5, &columns - 2, 3, 1],
+ \ ['eee', 5, &columns - 3, 5, &columns - 3, 3, 1],
+ \
+ \ ['aaaa', 5, &columns, 5, &columns - 3, 4, 1],
+ \ ['bbbb', 5, &columns + 1, 5, &columns - 3, 4, 1],
+ \ ['cccc', 5, &columns - 1, 5, &columns - 3, 4, 1],
+ \ ['dddd', 5, &columns - 2, 5, &columns - 3, 4, 1],
+ \ ['eeee', 5, &columns - 3, 5, &columns - 3, 4, 1],
+ \ ],
+ \ },
+ \ #{
+ \ comment: 'left aligned with fixed position',
+ \ options: #{
+ \ wrap: 0,
+ \ fixed: 1,
+ \ pos: 'botleft',
+ \ },
+ \ tests: both_wrap_tests + [
+ \ ['aa', 5, &columns, 5, &columns, 1, 1],
+ \ ['bb', 5, &columns + 1, 5, &columns, 1, 1],
+ \ ['cc', 5, &columns - 1, 5, &columns - 1, 2, 1],
+ \ ['dd', 5, &columns - 2, 5, &columns - 2, 2, 1],
+ \ ['ee', 5, &columns - 3, 5, &columns - 3, 2, 1],
+ \
+ \ ['aaa', 5, &columns, 5, &columns, 1, 1],
+ \ ['bbb', 5, &columns + 1, 5, &columns, 1, 1],
+ \ ['ccc', 5, &columns - 1, 5, &columns - 1, 2, 1],
+ \ ['ddd', 5, &columns - 2, 5, &columns - 2, 3, 1],
+ \ ['eee', 5, &columns - 3, 5, &columns - 3, 3, 1],
+ \
+ \ ['aaaa', 5, &columns, 5, &columns, 1, 1],
+ \ ['bbbb', 5, &columns + 1, 5, &columns, 1, 1],
+ \ ['cccc', 5, &columns - 1, 5, &columns - 1, 2, 1],
+ \ ['dddd', 5, &columns - 2, 5, &columns - 2, 3, 1],
+ \ ['eeee', 5, &columns - 3, 5, &columns - 3, 4, 1],
+ \ ],
+ \ },
+ \ ]
+
+ for test_group in tests
+ for test in test_group.tests
+ let [ text, line, col, e_line, e_col, e_width, e_height ] = test
+ let options = #{
+ \ line: line,
+ \ col: col,
+ \ }
+ call extend(options, test_group.options)
+
+ let p = popup_create(text, options)
+
+ let msg = string(extend(options, #{text: text}))
+ call s:VerifyPosition(p, msg, e_line, e_col, e_width, e_height)
+ call popup_close(p)
+ endfor
+ endfor
+
+ call popup_clear()
+ %bwipe!
+endfunc
+
+func Test_adjust_left_past_screen_width()
+ " width of screen
+ let X = join(map(range(&columns), {->'X'}), '')
+
+ let p = popup_create(X, #{line: 1, col: 1, wrap: 0})
+ call s:VerifyPosition(p, 'full width topleft', 1, 1, &columns, 1)
+
+ redraw
+ let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
+ call assert_equal(X, line)
+
+ call popup_close(p)
+ redraw
+
+ " Same if placed on the right hand side
+ let p = popup_create(X, #{line: 1, col: &columns, wrap: 0})
+ call s:VerifyPosition(p, 'full width topright', 1, 1, &columns, 1)
+
+ redraw
+ let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
+ call assert_equal(X, line)
+
+ call popup_close(p)
+ redraw
+
+ " Extend so > window width
+ let X .= 'x'
+
+ let p = popup_create(X, #{line: 1, col: 1, wrap: 0})
+ call s:VerifyPosition(p, 'full width + 1 topleft', 1, 1, &columns, 1)
+
+ redraw
+ let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
+ call assert_equal(X[ : -2 ], line)
+
+ call popup_close(p)
+ redraw
+
+ " Shifted then truncated (the x is not visible)
+ let p = popup_create(X, #{line: 1, col: &columns - 3, wrap: 0})
+ call s:VerifyPosition(p, 'full width + 1 topright', 1, 1, &columns, 1)
+
+ redraw
+ let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
+ call assert_equal(X[ : -2 ], line)
+
+ call popup_close(p)
+ redraw
+
+ " Not shifted, just truncated
+ let p = popup_create(X,
+ \ #{line: 1, col: 2, wrap: 0, fixed: 1})
+ call s:VerifyPosition(p, 'full width + 1 fixed', 1, 2, &columns - 1, 1)
+
+ redraw
+ let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
+ let e_line = ' ' . X[ 1 : -2 ]
+ call assert_equal(e_line, line)
+
+ call popup_close(p)
+ redraw
+
+ call popup_clear()
+ %bwipe!
+endfunc
+
+func Test_popup_moved()
+ new
+ call test_override('char_avail', 1)
+ call setline(1, ['one word to move around', 'a WORD.and->some thing'])
+
+ exe "normal gg0/word\<CR>"
+ let winid = popup_atcursor('text', #{moved: 'any'})
+ redraw
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal([1, 4, 4], popup_getoptions(winid).moved)
+ " trigger the check for last_cursormoved by going into insert mode
+ call feedkeys("li\<Esc>", 'xt')
+ call assert_equal({}, popup_getpos(winid))
+ call popup_clear()
+
+ exe "normal gg0/word\<CR>"
+ let winid = popup_atcursor('text', #{moved: 'word'})
+ redraw
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal([1, 4, 7], popup_getoptions(winid).moved)
+ call feedkeys("hi\<Esc>", 'xt')
+ call assert_equal({}, popup_getpos(winid))
+ call popup_clear()
+
+ exe "normal gg0/word\<CR>"
+ let winid = popup_atcursor('text', #{moved: 'word'})
+ redraw
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal([1, 4, 7], popup_getoptions(winid).moved)
+ call feedkeys("li\<Esc>", 'xt')
+ call assert_equal(1, popup_getpos(winid).visible)
+ call feedkeys("ei\<Esc>", 'xt')
+ call assert_equal(1, popup_getpos(winid).visible)
+ call feedkeys("eli\<Esc>", 'xt')
+ call assert_equal({}, popup_getpos(winid))
+ call popup_clear()
+
+ " WORD is the default
+ exe "normal gg0/WORD\<CR>"
+ let winid = popup_atcursor('text', {})
+ redraw
+ call assert_equal(1, popup_getpos(winid).visible)
+ call assert_equal([2, 2, 15], popup_getoptions(winid).moved)
+ call feedkeys("eli\<Esc>", 'xt')
+ call assert_equal(1, popup_getpos(winid).visible)
+ call feedkeys("wi\<Esc>", 'xt')
+ call assert_equal(1, popup_getpos(winid).visible)
+ call feedkeys("Eli\<Esc>", 'xt')
+ call assert_equal({}, popup_getpos(winid))
+ call popup_clear()
+
+ exe "normal gg0/word\<CR>"
+ let winid = popup_atcursor('text', #{moved: [5, 10]})
+ redraw
+ call assert_equal(1, popup_getpos(winid).visible)
+ call feedkeys("eli\<Esc>", 'xt')
+ call feedkeys("ei\<Esc>", 'xt')
+ call assert_equal(1, popup_getpos(winid).visible)
+ call feedkeys("eli\<Esc>", 'xt')
+ call assert_equal({}, popup_getpos(winid))
+ call popup_clear()
+
+ bwipe!
+ call test_override('ALL', 0)
+endfunc
+
+func Test_notifications()
+ CheckFeature timers
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ hi Notification ctermbg=lightblue
+ call popup_notification('first notification', {})
+ END
+ call writefile(lines, 'XtestNotifications', 'D')
+ let buf = RunVimInTerminal('-S XtestNotifications', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_notify_01', {})
+
+ " second one goes below the first one
+ call term_sendkeys(buf, ":hi link PopupNotification Notification\<CR>")
+ call term_sendkeys(buf, ":call popup_notification('another important notification', {})\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_notify_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_scrollbar()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ hi ScrollThumb ctermbg=blue
+ hi ScrollBar ctermbg=red
+ let winid = popup_create(['one', 'two', 'three', 'four', 'five',
+ \ 'six', 'seven', 'eight', 'nine'], #{
+ \ minwidth: 8,
+ \ maxheight: 4,
+ \ })
+ func ScrollUp()
+ call feedkeys("\<F3>\<ScrollWheelUp>", "xt")
+ endfunc
+ func ScrollDown()
+ call feedkeys("\<F3>\<ScrollWheelDown>", "xt")
+ endfunc
+ func ClickTop()
+ call feedkeys("\<F4>\<LeftMouse>", "xt")
+ endfunc
+ func ClickBot()
+ call popup_setoptions(g:winid, #{border: [], close: 'button'})
+ call feedkeys("\<F5>\<LeftMouse>", "xt")
+ endfunc
+ func Popup_filter(winid, key)
+ if a:key == 'j'
+ silent! this_throws_an_error_but_is_ignored
+ let line = popup_getoptions(a:winid).firstline
+ let nlines = line('$', a:winid)
+ let newline = line < nlines ? (line + 1) : nlines
+ call popup_setoptions(a:winid, #{firstline: newline})
+ return v:true
+ elseif a:key == 'x'
+ call popup_close(a:winid)
+ return v:true
+ endif
+ endfunc
+
+ def CreatePopup(text: list<string>): number
+ return popup_create(text, {
+ \ minwidth: 30,
+ \ maxwidth: 30,
+ \ minheight: 4,
+ \ maxheight: 4,
+ \ firstline: 1,
+ \ lastline: 4,
+ \ wrap: true,
+ \ scrollbar: true,
+ \ mapping: false,
+ \ filter: g:Popup_filter,
+ \ })
+ enddef
+
+ func PopupScroll()
+ call popup_clear()
+ let text =<< trim END
+ 1
+ 2
+ 3
+ 4
+ long line long line long line long line long line long line
+ long line long line long line long line long line long line
+ long line long line long line long line long line long line
+ END
+ call CreatePopup(text)
+ endfunc
+ func ScrollBottom()
+ call popup_clear()
+ let id = CreatePopup(range(100)->map({k, v -> string(v)}))
+ call popup_setoptions(id, #{firstline: 100, minheight: 9, maxheight: 9})
+ endfunc
+ map <silent> <F3> :call test_setmouse(5, 36)<CR>
+ map <silent> <F4> :call test_setmouse(4, 42)<CR>
+ map <silent> <F5> :call test_setmouse(7, 42)<CR>
+ END
+ call writefile(lines, 'XtestPopupScroll', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupScroll', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_1', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: 2})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_2', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: 6})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_3', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: 9})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_4', {})
+
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{scrollbarhighlight: 'ScrollBar', thumbhighlight: 'ScrollThumb', firstline: 5})\<CR>")
+ " this scrolls two lines (half the window height)
+ call term_sendkeys(buf, ":call ScrollUp()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_5', {})
+
+ call term_sendkeys(buf, ":call ScrollDown()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_6', {})
+
+ call term_sendkeys(buf, ":call ScrollDown()\<CR>")
+ " wait a bit, otherwise it fails sometimes (double click recognized?)
+ sleep 100m
+ call term_sendkeys(buf, ":call ScrollDown()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_7', {})
+
+ call term_sendkeys(buf, ":call ClickTop()\<CR>")
+ sleep 100m
+ call term_sendkeys(buf, ":call ClickTop()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_8', {})
+
+ call term_sendkeys(buf, ":call ClickBot()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_9', {})
+
+ " remove the minwidth and maxheight
+ call term_sendkeys(buf, ":call popup_setoptions(winid, #{maxheight: 0, minwidth: 0})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_10', {})
+
+ " check size with non-wrapping lines
+ call term_sendkeys(buf, ":call g:PopupScroll()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_11', {})
+
+ " check size with wrapping lines
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_12', {})
+
+ " check thumb when scrolled all the way down
+ call term_sendkeys(buf, ":call ScrollBottom()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_scroll_13', {})
+
+ " clean up
+ call term_sendkeys(buf, "x")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_too_high_scrollbar()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20)->map({i, v -> repeat(v, 10)}))
+ set scrolloff=0
+ func ShowPopup()
+ let winid = popup_atcursor(['one', 'two', 'three', 'four', 'five',
+ \ 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve'], #{
+ \ minwidth: 8,
+ \ border: [],
+ \ })
+ endfunc
+ normal 3G$
+ call ShowPopup()
+ END
+ call writefile(lines, 'XtestPopupToohigh', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupToohigh', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_toohigh_1', {})
+
+ call term_sendkeys(buf, ":call popup_clear()\<CR>")
+ call term_sendkeys(buf, "8G$")
+ call term_sendkeys(buf, ":call ShowPopup()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_toohigh_2', {})
+
+ call term_sendkeys(buf, ":call popup_clear()\<CR>")
+ call term_sendkeys(buf, "gg$")
+ call term_sendkeys(buf, ":call ShowPopup()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_toohigh_3', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_fitting_scrollbar()
+ " this was causing a crash, divide by zero
+ let winid = popup_create([
+ \ 'one', 'two', 'longer line that wraps', 'four', 'five'], #{
+ \ scrollbar: 1,
+ \ maxwidth: 10,
+ \ maxheight: 5,
+ \ firstline: 2})
+ redraw
+ call popup_clear()
+endfunc
+
+func Test_popup_settext()
+ CheckScreendump
+
+ let lines =<< trim END
+ let opts = #{wrap: 0}
+ let p = popup_create('test', opts)
+ eval p->popup_settext('this is a text')
+ END
+
+ call writefile(lines, 'XtestPopupSetText', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupSetText', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popup_settext_01', {})
+
+ " Setting to empty string clears it
+ call term_sendkeys(buf, ":call popup_settext(p, '')\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_settext_02', {})
+
+ " Setting a list
+ call term_sendkeys(buf, ":call popup_settext(p, ['a','b','c'])\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_settext_03', {})
+
+ " Shrinking with a list
+ call term_sendkeys(buf, ":call popup_settext(p, ['a'])\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_settext_04', {})
+
+ " Growing with a list
+ call term_sendkeys(buf, ":call popup_settext(p, ['a','b','c'])\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_settext_03', {})
+
+ " Empty list clears
+ call term_sendkeys(buf, ":call popup_settext(p, [])\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_settext_05', {})
+
+ " Dicts
+ call term_sendkeys(buf, ":call popup_settext(p, [#{text: 'aaaa'}, #{text: 'bbbb'}, #{text: 'cccc'}])\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_settext_06', {})
+
+ " range() (doesn't work)
+ call term_sendkeys(buf, ":call popup_settext(p, range(4, 8))\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_settext_07', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_settext_getline()
+ let id = popup_create('', #{ tabpage: 0 })
+ call popup_settext(id, ['a','b'])
+ call assert_equal(2, line('$', id)) " OK :)
+ call popup_close(id)
+
+ let id = popup_create('', #{ tabpage: -1 })
+ call popup_settext(id, ['a','b'])
+ call assert_equal(2, line('$', id)) " Fails :(
+ call popup_close(id)
+endfunc
+
+func Test_popup_settext_null()
+ let id = popup_create('', #{ tabpage: 0 })
+ call popup_settext(id, test_null_list())
+ call popup_close(id)
+
+ let id = popup_create('', #{ tabpage: 0 })
+ call popup_settext(id, test_null_string())
+ call popup_close(id)
+endfunc
+
+func Test_popup_hidden()
+ new
+
+ let winid = popup_atcursor('text', #{hidden: 1})
+ redraw
+ call assert_equal(0, popup_getpos(winid).visible)
+ call popup_close(winid)
+
+ let winid = popup_create('text', #{hidden: 1})
+ redraw
+ call assert_equal(0, popup_getpos(winid).visible)
+ call popup_close(winid)
+
+ func QuitCallback(id, res)
+ let s:cb_winid = a:id
+ let s:cb_res = a:res
+ endfunc
+ let winid = 'make a choice'->popup_dialog(#{hidden: 1,
+ \ filter: 'popup_filter_yesno',
+ \ callback: 'QuitCallback',
+ \ })
+ redraw
+ call assert_equal(0, popup_getpos(winid).visible)
+ call assert_equal(function('popup_filter_yesno'), popup_getoptions(winid).filter)
+ call assert_equal(function('QuitCallback'), popup_getoptions(winid).callback)
+ exe "normal anot used by filter\<Esc>"
+ call assert_equal('not used by filter', getline(1))
+
+ call popup_show(winid)
+ call feedkeys('y', "xt")
+ call assert_equal(1, s:cb_res)
+
+ bwipe!
+ delfunc QuitCallback
+endfunc
+
+" Test options not checked elsewhere
+func Test_set_get_options()
+ let winid = popup_create('some text', #{highlight: 'Beautiful'})
+ let options = popup_getoptions(winid)
+ call assert_equal(1, options.wrap)
+ call assert_equal(0, options.drag)
+ call assert_equal('Beautiful', options.highlight)
+
+ call popup_setoptions(winid, #{wrap: 0, drag: 1, highlight: 'Another'})
+ let options = popup_getoptions(winid)
+ call assert_equal(0, options.wrap)
+ call assert_equal(1, options.drag)
+ call assert_equal('Another', options.highlight)
+
+ call assert_fails('call popup_setoptions(winid, [])', 'E1206:')
+ call assert_fails('call popup_setoptions(winid, test_null_dict())', 'E1297:')
+
+ call popup_close(winid)
+ call assert_equal(0, popup_setoptions(winid, options.wrap))
+endfunc
+
+func Test_popupwin_garbage_collect()
+ func MyPopupFilter(x, winid, c)
+ " NOP
+ endfunc
+
+ let winid = popup_create('something', #{filter: function('MyPopupFilter', [{}])})
+ call test_garbagecollect_now()
+ redraw
+ " Must not crash caused by invalid memory access
+ call feedkeys('j', 'xt')
+ call assert_true(v:true)
+
+ call popup_close(winid)
+ delfunc MyPopupFilter
+endfunc
+
+func Test_popupwin_filter_mode()
+ func MyPopupFilter(winid, c)
+ let s:typed = a:c
+ if a:c == ':' || a:c == "\r" || a:c == 'v'
+ " can start cmdline mode, get out, and start/stop Visual mode
+ return 0
+ endif
+ return 1
+ endfunc
+
+ " Normal, Visual and Insert mode
+ let winid = popup_create('something', #{filter: 'MyPopupFilter', filtermode: 'nvi'})
+ redraw
+ call feedkeys('x', 'xt')
+ call assert_equal('x', s:typed)
+
+ call feedkeys(":let g:foo = 'foo'\<CR>", 'xt')
+ call assert_equal(':', s:typed)
+ call assert_equal('foo', g:foo)
+
+ let @x = 'something'
+ call feedkeys('v$"xy', 'xt')
+ call assert_equal('y', s:typed)
+ call assert_equal('something', @x) " yank command is filtered out
+ call feedkeys('v', 'xt') " end Visual mode
+
+ call popup_close(winid)
+
+ " only Normal mode
+ let winid = popup_create('something', #{filter: 'MyPopupFilter', filtermode: 'n'})
+ redraw
+ call feedkeys('x', 'xt')
+ call assert_equal('x', s:typed)
+
+ call feedkeys(":let g:foo = 'foo'\<CR>", 'xt')
+ call assert_equal(':', s:typed)
+ call assert_equal('foo', g:foo)
+
+ let @x = 'something'
+ call feedkeys('v$"xy', 'xt')
+ call assert_equal('v', s:typed)
+ call assert_notequal('something', @x)
+
+ call popup_close(winid)
+
+ " default: all modes
+ let winid = popup_create('something', #{filter: 'MyPopupFilter'})
+ redraw
+ call feedkeys('x', 'xt')
+ call assert_equal('x', s:typed)
+
+ let g:foo = 'bar'
+ call feedkeys(":let g:foo = 'foo'\<CR>", 'xt')
+ call assert_equal("\r", s:typed)
+ call assert_equal('bar', g:foo)
+
+ let @x = 'something'
+ call feedkeys('v$"xy', 'xt')
+ call assert_equal('y', s:typed)
+ call assert_equal('something', @x) " yank command is filtered out
+ call feedkeys('v', 'xt') " end Visual mode
+
+ call popup_close(winid)
+ delfunc MyPopupFilter
+endfunc
+
+func Test_popupwin_filter_mouse()
+ func MyPopupFilter(winid, c)
+ let g:got_mousepos = getmousepos()
+ return 0
+ endfunc
+
+ call setline(1, ['.'->repeat(25)]->repeat(10))
+ let winid = popup_create(['short', 'long line that will wrap', 'other'], #{
+ \ line: 2,
+ \ col: 4,
+ \ maxwidth: 12,
+ \ padding: [],
+ \ border: [],
+ \ filter: 'MyPopupFilter',
+ \ })
+ redraw
+ " 123456789012345678901
+ " 1 .....................
+ " 2 ...+--------------+..
+ " 3 ...| |..
+ " 4 ...| short |..
+ " 5 ...| long line th |..
+ " 6 ...| at will wrap |..
+ " 7 ...| other |..
+ " 8 ...| |..
+ " 9 ...+--------------+..
+ " 10 .....................
+ let tests = []
+
+ func AddItemOutsidePopup(tests, row, col)
+ eval a:tests->add(#{clickrow: a:row, clickcol: a:col, result: #{
+ \ screenrow: a:row, screencol: a:col,
+ \ winid: win_getid(), winrow: a:row, wincol: a:col,
+ \ line: a:row, column: a:col,
+ \ }})
+ endfunc
+ func AddItemInPopupBorder(tests, winid, row, col)
+ eval a:tests->add(#{clickrow: a:row, clickcol: a:col, result: #{
+ \ screenrow: a:row, screencol: a:col,
+ \ winid: a:winid, winrow: a:row - 1, wincol: a:col - 3,
+ \ line: 0, column: 0,
+ \ }})
+ endfunc
+ func AddItemInPopupText(tests, winid, row, col, textline, textcol)
+ eval a:tests->add(#{clickrow: a:row, clickcol: a:col, result: #{
+ \ screenrow: a:row, screencol: a:col,
+ \ winid: a:winid, winrow: a:row - 1, wincol: a:col - 3,
+ \ line: a:textline, column: a:textcol,
+ \ }})
+ endfunc
+
+ " above and below popup
+ for c in range(1, 21)
+ call AddItemOutsidePopup(tests, 1, c)
+ call AddItemOutsidePopup(tests, 10, c)
+ endfor
+ " left and right of popup
+ for r in range(1, 10)
+ call AddItemOutsidePopup(tests, r, 3)
+ call AddItemOutsidePopup(tests, r, 20)
+ endfor
+ " top and bottom in popup
+ for c in range(4, 19)
+ call AddItemInPopupBorder(tests, winid, 2, c)
+ call AddItemInPopupBorder(tests, winid, 3, c)
+ call AddItemInPopupBorder(tests, winid, 8, c)
+ call AddItemInPopupBorder(tests, winid, 9, c)
+ endfor
+ " left and right margin in popup
+ for r in range(2, 9)
+ call AddItemInPopupBorder(tests, winid, r, 4)
+ call AddItemInPopupBorder(tests, winid, r, 5)
+ call AddItemInPopupBorder(tests, winid, r, 18)
+ call AddItemInPopupBorder(tests, winid, r, 19)
+ endfor
+ " text "short"
+ call AddItemInPopupText(tests, winid, 4, 6, 1, 1)
+ call AddItemInPopupText(tests, winid, 4, 10, 1, 5)
+ call AddItemInPopupText(tests, winid, 4, 11, 1, 6)
+ call AddItemInPopupText(tests, winid, 4, 17, 1, 6)
+ " text "long line th"
+ call AddItemInPopupText(tests, winid, 5, 6, 2, 1)
+ call AddItemInPopupText(tests, winid, 5, 10, 2, 5)
+ call AddItemInPopupText(tests, winid, 5, 17, 2, 12)
+ " text "at will wrap"
+ call AddItemInPopupText(tests, winid, 6, 6, 2, 13)
+ call AddItemInPopupText(tests, winid, 6, 10, 2, 17)
+ call AddItemInPopupText(tests, winid, 6, 17, 2, 24)
+ " text "other"
+ call AddItemInPopupText(tests, winid, 7, 6, 3, 1)
+ call AddItemInPopupText(tests, winid, 7, 10, 3, 5)
+ call AddItemInPopupText(tests, winid, 7, 11, 3, 6)
+ call AddItemInPopupText(tests, winid, 7, 17, 3, 6)
+
+ for item in tests
+ call test_setmouse(item.clickrow, item.clickcol)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal(item.result, g:got_mousepos)
+ endfor
+
+ call popup_close(winid)
+ enew!
+ delfunc MyPopupFilter
+endfunc
+
+func Test_popupwin_with_buffer()
+ call writefile(['some text', 'in a buffer'], 'XsomeFile', 'D')
+ let buf = bufadd('XsomeFile')
+ call assert_equal(0, bufloaded(buf))
+
+ setlocal number
+ call setbufvar(buf, "&wrapmargin", 13)
+
+ let winid = popup_create(buf, {})
+ call assert_notequal(0, winid)
+ let pos = popup_getpos(winid)
+ call assert_equal(2, pos.height)
+ call assert_equal(1, bufloaded(buf))
+
+ " window-local option is set to default, buffer-local is not
+ call assert_equal(0, getwinvar(winid, '&number'))
+ call assert_equal(13, getbufvar(buf, '&wrapmargin'))
+
+ call popup_close(winid)
+ call assert_equal({}, popup_getpos(winid))
+ call assert_equal(1, bufloaded(buf))
+ exe 'bwipe! ' .. buf
+ setlocal nonumber
+
+ edit test_popupwin.vim
+ let winid = popup_create(bufnr(''), {})
+ redraw
+ call popup_close(winid)
+endfunc
+
+func Test_popupwin_buffer_with_swapfile()
+ call writefile(['some text', 'in a buffer'], 'XopenFile', 'D')
+ call writefile([''], '.XopenFile.swp', 'D')
+ let g:ignoreSwapExists = 1
+
+ let bufnr = bufadd('XopenFile')
+ call assert_equal(0, bufloaded(bufnr))
+ let winid = popup_create(bufnr, {'hidden': 1})
+ call assert_equal(1, bufloaded(bufnr))
+ call popup_close(winid)
+
+ exe 'buffer ' .. bufnr
+ call assert_equal(1, &readonly)
+ bwipe!
+
+ unlet g:ignoreSwapExists
+endfunc
+
+func Test_popupwin_terminal_buffer()
+ CheckFeature terminal
+ CheckUnix
+ " Starting a terminal to run a shell in is considered flaky.
+ let g:test_is_flaky = 1
+
+ let origwin = win_getid()
+
+ " open help window to test that :help below fails
+ help
+
+ let termbuf = term_start(&shell, #{hidden: 1})
+ let winid = popup_create(termbuf, #{minwidth: 40, minheight: 10, border: []})
+ " Wait for shell to start
+ call WaitForAssert({-> assert_equal("run", job_status(term_getjob(termbuf)))})
+ " Wait for a prompt (see border char first, then space after prompt)
+ call WaitForAssert({ -> assert_equal(' ', screenstring(screenrow(), screencol() - 1))})
+
+ " When typing a character, the cursor is after it.
+ call feedkeys("x", 'xt')
+ call term_wait(termbuf)
+ redraw
+ call WaitForAssert({ -> assert_equal('x', screenstring(screenrow(), screencol() - 1))})
+ call feedkeys("\<BS>", 'xt')
+
+ " Check this doesn't crash
+ call assert_equal(winnr(), winnr('j'))
+ call assert_equal(winnr(), winnr('k'))
+ call assert_equal(winnr(), winnr('h'))
+ call assert_equal(winnr(), winnr('l'))
+
+ " Cannot quit while job is running
+ call assert_fails('call feedkeys("\<C-W>:quit\<CR>", "xt")', 'E948:')
+
+ " Cannot enter Terminal-Normal mode. (TODO: but it works...)
+ call feedkeys("xxx\<C-W>N", 'xt')
+ call assert_fails('call feedkeys("gf", "xt")', 'E863:')
+ call feedkeys("a\<C-U>", 'xt')
+
+ " Cannot escape from terminal window
+ call assert_fails('tab drop xxx', 'E863:')
+ call assert_fails('help', 'E994:')
+
+ " Cannot open a second one.
+ let termbuf2 = term_start(&shell, #{hidden: 1})
+ call assert_fails('call popup_create(termbuf2, #{})', 'E861:')
+ call term_sendkeys(termbuf2, "exit\<CR>")
+
+ " Exiting shell puts popup window in Terminal-Normal mode.
+ call feedkeys("exit\<CR>", 'xt')
+ " Wait for shell to exit
+ call WaitForAssert({-> assert_equal("dead", job_status(term_getjob(termbuf)))})
+
+ helpclose
+ call feedkeys(":quit\<CR>", 'xt')
+ call assert_equal(origwin, win_getid())
+endfunc
+
+func Test_popupwin_terminal_buffer_none()
+ CheckFeature terminal
+ CheckUnix
+
+ " Starting a terminal to run a shell in is considered flaky.
+ let g:test_is_flaky = 1
+
+ let origwin = win_getid()
+ call term_start("NONE", {"hidden": 1})->popup_create({"border": []})
+ sleep 50m
+
+ " since no actual job is running can close the window with :quit
+ call feedkeys("\<C-W>:q\<CR>", 'xt')
+ call assert_equal([], popup_list())
+
+ call assert_equal(origwin, win_getid())
+endfunc
+
+func Test_popupwin_terminal_scrollbar()
+ CheckFeature terminal
+ CheckScreendump
+ CheckUnix
+
+ call writefile(range(50), 'Xtestfile', 'D')
+ let lines =<< trim END
+ vim9script
+
+ # testing CTRL-W CTRL-W requires two windows
+ split
+
+ term_start(['cat', 'Xtestfile'], {hidden: true})
+ ->popup_create({
+ minwidth: 40,
+ maxwidth: 40,
+ minheight: 8,
+ maxheight: 8,
+ scrollbar: true,
+ border: []
+ })
+ END
+ call writefile(lines, 'Xpterm', 'D')
+ let buf = RunVimInTerminal('-S Xpterm', #{rows: 15})
+ call VerifyScreenDump(buf, 'Test_popupwin_poptermscroll_1', {})
+
+ " scroll to the middle
+ call term_sendkeys(buf, "50%")
+ call VerifyScreenDump(buf, 'Test_popupwin_poptermscroll_2', {})
+
+ " get error if trying to escape the window
+ call term_sendkeys(buf, "\<C-W>\<C-W>")
+ call VerifyScreenDump(buf, 'Test_popupwin_poptermscroll_3', {})
+
+ " close the popupwin.
+ call term_sendkeys(buf, ":q\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_poptermscroll_4', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_close_prevwin()
+ CheckFeature terminal
+ call Popupwin_close_prevwin()
+endfunc
+
+def Popupwin_close_prevwin()
+ assert_equal(1, winnr('$'))
+ split
+ wincmd b
+ assert_equal(2, winnr())
+ var buf = term_start(&shell, {hidden: 1})
+ popup_create(buf, {})
+ g:TermWait(buf, 100)
+ popup_clear(true)
+ assert_equal(2, winnr())
+
+ quit
+ exe 'bwipe! ' .. buf
+enddef
+
+func Test_popupwin_with_buffer_and_filter()
+ new Xwithfilter
+ call setline(1, range(100))
+ let bufnr = bufnr()
+ hide
+
+ func BufferFilter(win, key)
+ if a:key == 'G'
+ " recursive use of "G" does not cause problems.
+ call win_execute(a:win, 'normal! G')
+ return 1
+ endif
+ return 0
+ endfunc
+
+ let winid = popup_create(bufnr, #{maxheight: 5, filter: 'BufferFilter'})
+ call assert_equal(1, popup_getpos(winid).firstline)
+ redraw
+ call feedkeys("G", 'xt')
+ call assert_equal(99, popup_getpos(winid).firstline)
+
+ call popup_close(winid)
+ exe 'bwipe! ' .. bufnr
+endfunc
+
+func Test_popupwin_width()
+ let winid = popup_create(repeat(['short', 'long long long line', 'medium width'], 50), #{
+ \ maxwidth: 40,
+ \ maxheight: 10,
+ \ })
+ for top in range(1, 20)
+ eval winid->popup_setoptions(#{firstline: top})
+ redraw
+ call assert_equal(19, popup_getpos(winid).width)
+ endfor
+ call popup_clear()
+endfunc
+
+func Test_popupwin_buf_close()
+ let buf = bufadd('Xtestbuf')
+ call bufload(buf)
+ call setbufline(buf, 1, ['just', 'some', 'lines'])
+ let winid = popup_create(buf, {})
+ redraw
+ call assert_equal(3, popup_getpos(winid).height)
+ let bufinfo = getbufinfo(buf)[0]
+ call assert_equal(1, bufinfo.changed)
+ call assert_equal(0, bufinfo.hidden)
+ call assert_equal(0, bufinfo.listed)
+ call assert_equal(1, bufinfo.loaded)
+ call assert_equal([], bufinfo.windows)
+ call assert_equal([winid], bufinfo.popups)
+
+ call popup_close(winid)
+ call assert_equal({}, popup_getpos(winid))
+ let bufinfo = getbufinfo(buf)[0]
+ call assert_equal(1, bufinfo.changed)
+ call assert_equal(1, bufinfo.hidden)
+ call assert_equal(0, bufinfo.listed)
+ call assert_equal(1, bufinfo.loaded)
+ call assert_equal([], bufinfo.windows)
+ call assert_equal([], bufinfo.popups)
+ exe 'bwipe! ' .. buf
+endfunc
+
+func Test_popup_menu_with_maxwidth()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 10))
+ hi ScrollThumb ctermbg=blue
+ hi ScrollBar ctermbg=red
+ func PopupMenu(lines, line, col, scrollbar = 0)
+ return popup_menu(a:lines, #{
+ \ maxwidth: 10,
+ \ maxheight: 3,
+ \ pos : 'topleft',
+ \ col : a:col,
+ \ line : a:line,
+ \ scrollbar : a:scrollbar,
+ \ })
+ endfunc
+ call PopupMenu(['x'], 1, 1)
+ call PopupMenu(['123456789|'], 1, 16)
+ call PopupMenu(['123456789|' .. ' '], 7, 1)
+ call PopupMenu([repeat('123456789|', 100)], 7, 16)
+ call PopupMenu(repeat(['123456789|' .. ' '], 5), 1, 33, 1)
+ END
+ call writefile(lines, 'XtestPopupMenuMaxWidth', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupMenuMaxWidth', #{rows: 13})
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_maxwidth_1', {})
+
+ " close the menu popupwin.
+ call term_sendkeys(buf, " ")
+ call term_sendkeys(buf, " ")
+ call term_sendkeys(buf, " ")
+ call term_sendkeys(buf, " ")
+ call term_sendkeys(buf, " ")
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_menu_with_scrollbar()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ hi ScrollThumb ctermbg=blue
+ hi ScrollBar ctermbg=red
+ eval ['one', 'two', 'three', 'four', 'five',
+ \ 'six', 'seven', 'eight', 'nine']
+ \ ->popup_menu(#{
+ \ minwidth: 8,
+ \ maxheight: 3,
+ \ })
+ END
+ call writefile(lines, 'XtestPopupMenuScroll', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupMenuScroll', #{rows: 10})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_1', {})
+
+ call term_sendkeys(buf, "jjj")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_2', {})
+
+ " if the cursor is the bottom line, it stays at the bottom line.
+ call term_sendkeys(buf, repeat("j", 20))
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_3', {})
+
+ call term_sendkeys(buf, "kk")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_4', {})
+
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_5', {})
+
+ " if the cursor is in the top line, it stays in the top line.
+ call term_sendkeys(buf, repeat("k", 20))
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_6', {})
+
+ " close the menu popupwin.
+ call term_sendkeys(buf, " ")
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_menu_filter()
+ CheckScreendump
+
+ let lines =<< trim END
+ function! MyFilter(winid, key) abort
+ if a:key == "0"
+ call win_execute(a:winid, "call setpos('.', [0, 1, 1, 0])")
+ return 1
+ endif
+ if a:key == "G"
+ call win_execute(a:winid, "call setpos('.', [0, line('$'), 1, 0])")
+ return 1
+ endif
+ if a:key == "j"
+ call win_execute(a:winid, "call setpos('.', [0, line('.') + 1, 1, 0])")
+ return 1
+ endif
+ if a:key == "k"
+ call win_execute(a:winid, "call setpos('.', [0, line('.') - 1, 1, 0])")
+ return 1
+ endif
+ if a:key == ':'
+ call popup_close(a:winid)
+ return 0
+ endif
+ return 0
+ endfunction
+ call popup_menu(['111', '222', '333', '444', '555', '666', '777', '888', '999'], #{
+ \ maxheight : 3,
+ \ filter : 'MyFilter'
+ \ })
+ END
+ call writefile(lines, 'XtestPopupMenuFilter', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupMenuFilter', #{rows: 10})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_1', {})
+
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_2', {})
+
+ call term_sendkeys(buf, "G")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_3', {})
+
+ call term_sendkeys(buf, "0")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_4', {})
+
+ " check that when the popup is closed in the filter the screen is redrawn
+ call term_sendkeys(buf, ":")
+ call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_5', {})
+ call term_sendkeys(buf, "\<CR>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popup_cursorline()
+ CheckScreendump
+
+ let winid = popup_create('some text', {})
+ call assert_equal(0, popup_getoptions(winid).cursorline)
+ call popup_close(winid)
+
+ let winid = popup_create('some text', #{ cursorline: 1, })
+ call assert_equal(1, popup_getoptions(winid).cursorline)
+ call popup_close(winid)
+
+ let winid = popup_create('some text', #{ cursorline: v:true, })
+ call assert_equal(1, popup_getoptions(winid).cursorline)
+ call popup_close(winid)
+
+ let winid = popup_create('some text', #{ cursorline: 0, })
+ call assert_equal(0, popup_getoptions(winid).cursorline)
+ call popup_close(winid)
+
+ let winid = popup_menu('some text', {})
+ call assert_equal(1, popup_getoptions(winid).cursorline)
+ call popup_close(winid)
+
+ let winid = popup_menu('some text', #{ cursorline: 1, })
+ call assert_equal(1, popup_getoptions(winid).cursorline)
+ call popup_close(winid)
+
+ let winid = popup_menu('some text', #{ cursorline: 0, })
+ call assert_equal(0, popup_getoptions(winid).cursorline)
+ call popup_close(winid)
+
+ " ---------
+ " Pattern 1
+ " ---------
+ let lines =<< trim END
+ call popup_create(['111', '222', '333'], #{ cursorline : 0 })
+ END
+ call writefile(lines, 'XtestPopupCursorLine', 'D')
+ let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_1', {})
+ call term_sendkeys(buf, ":call popup_clear()\<cr>")
+ call StopVimInTerminal(buf)
+
+ " ---------
+ " Pattern 2
+ " ---------
+ let lines =<< trim END
+ call popup_create(['111', '222', '333'], #{ cursorline : 1 })
+ END
+ call writefile(lines, 'XtestPopupCursorLine')
+ let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_2', {})
+ call term_sendkeys(buf, ":call popup_clear()\<cr>")
+ call StopVimInTerminal(buf)
+
+ " ---------
+ " Pattern 3
+ " ---------
+ let lines =<< trim END
+ function! MyFilter(winid, key) abort
+ if a:key == "j"
+ call win_execute(a:winid, "call setpos('.', [0, line('.') + 1, 1, 0]) | redraw")
+ return 1
+ endif
+ if a:key == 'x'
+ call popup_close(a:winid)
+ return 1
+ endif
+ return 0
+ endfunction
+ call popup_menu(['111', '222', '333'], #{
+ \ cursorline : 0,
+ \ maxheight : 2,
+ \ filter : 'MyFilter',
+ \ })
+ END
+ call writefile(lines, 'XtestPopupCursorLine')
+ let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_3', {})
+ call term_sendkeys(buf, "j")
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_4', {})
+ call term_sendkeys(buf, "x")
+ call StopVimInTerminal(buf)
+
+ " ---------
+ " Pattern 4
+ " ---------
+ let lines =<< trim END
+ function! MyFilter(winid, key) abort
+ if a:key == "j"
+ call win_execute(a:winid, "call setpos('.', [0, line('.') + 1, 1, 0]) | redraw")
+ return 1
+ endif
+ if a:key == 'x'
+ call popup_close(a:winid)
+ return 1
+ endif
+ return 0
+ endfunction
+ call popup_menu(['111', '222', '333'], #{
+ \ cursorline : 1,
+ \ maxheight : 2,
+ \ filter : 'MyFilter',
+ \ })
+ END
+ call writefile(lines, 'XtestPopupCursorLine')
+ let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_5', {})
+ call term_sendkeys(buf, "j")
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_6', {})
+ call term_sendkeys(buf, "x")
+ call StopVimInTerminal(buf)
+
+ " ---------
+ " Cursor in second line when creating the popup
+ " ---------
+ let lines =<< trim END
+ let winid = popup_create(['111', '222', '333'], #{
+ \ cursorline : 1,
+ \ })
+ call win_execute(winid, "2")
+ END
+ call writefile(lines, 'XtestPopupCursorLine')
+ let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_7', {})
+ call StopVimInTerminal(buf)
+
+ " ---------
+ " Use current buffer for popupmenu
+ " ---------
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three'])
+ let winid = popup_create(bufnr('%'), #{
+ \ cursorline : 1,
+ \ })
+ call win_execute(winid, "2")
+ END
+ call writefile(lines, 'XtestPopupCursorLine')
+ let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_cursorline_8', {})
+ call StopVimInTerminal(buf)
+endfunc
+
+def Test_popup_cursorline_vim9()
+ var winid = popup_create('some text', { cursorline: true, })
+ assert_equal(1, popup_getoptions(winid).cursorline)
+ popup_close(winid)
+
+ assert_fails("popup_create('some text', { cursorline: 2, })", 'E1023:')
+ popup_clear()
+enddef
+
+func Test_previewpopup()
+ CheckScreendump
+ CheckFeature quickfix
+
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "another\tXtagfile\t/^this is another",
+ \ "theword\tXtagfile\t/^theword"],
+ \ 'Xtags', 'D')
+ call writefile(range(1,20)
+ \ + ['theword is here']
+ \ + range(22, 27)
+ \ + ['this is another place']
+ \ + range(29, 40),
+ \ "Xtagfile", 'D')
+ call writefile(range(1,10)
+ \ + ['searched word is here']
+ \ + range(12, 20),
+ \ "Xheader.h", 'D')
+ let lines =<< trim END
+ set tags=Xtags
+ call setline(1, [
+ \ 'one',
+ \ '#include "Xheader.h"',
+ \ 'three',
+ \ 'four',
+ \ 'five',
+ \ 'six',
+ \ 'seven',
+ \ 'find theword somewhere',
+ \ 'nine',
+ \ 'this is another word',
+ \ 'very long line where the word is also another'])
+ set previewpopup=height:4,width:40
+ hi OtherColor ctermbg=lightcyan guibg=lightcyan
+ set path=.
+ END
+ call writefile(lines, 'XtestPreviewPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestPreviewPopup', #{rows: 14})
+
+ call term_sendkeys(buf, "/theword\<CR>\<C-W>}")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_1', {})
+
+ call term_sendkeys(buf, ":set previewpopup+=highlight:OtherColor\<CR>")
+ call term_sendkeys(buf, "/another\<CR>\<C-W>}")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_2', {})
+
+ call term_sendkeys(buf, ":call popup_move(popup_findpreview(), #{col: 15})\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_3', {})
+
+ call term_sendkeys(buf, "/another\<CR>\<C-W>}")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_4', {})
+
+ call term_sendkeys(buf, ":silent cd ..\<CR>:\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_5', {})
+ call term_sendkeys(buf, ":silent cd testdir\<CR>")
+
+ call term_sendkeys(buf, ":set previewpopup-=highlight:OtherColor\<CR>")
+ call term_sendkeys(buf, ":pclose\<CR>")
+ call term_sendkeys(buf, ":\<BS>")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_6', {})
+
+ call term_sendkeys(buf, ":pedit +/theword Xtagfile\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_7', {})
+
+ call term_sendkeys(buf, ":pclose\<CR>")
+ call term_sendkeys(buf, ":psearch searched\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_8', {})
+
+ call term_sendkeys(buf, "\<C-W>p")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_9', {})
+
+ call term_sendkeys(buf, ":call win_execute(popup_findpreview(), 'call popup_clear()')\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_10', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_previewpopup_pum()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ let a = 3
+ let b = 1
+ echo a
+ echo b
+ call system('echo hello')
+ " the end
+ END
+ call writefile(lines, 'XpreviewText.vim', 'D')
+
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three', 'other', 'once', 'only', 'off'])
+ set previewpopup=height:6,width:40
+ pedit XpreviewText.vim
+ END
+ call writefile(lines, 'XtestPreviewPum', 'D')
+ let buf = RunVimInTerminal('-S XtestPreviewPum', #{rows: 12})
+
+ call term_sendkeys(buf, "A o\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_preview_1', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_preview_2', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_preview_3', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_preview_4', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+
+func Get_popupmenu_lines()
+ let lines =<< trim END
+ set completeopt+=preview,popup
+ set completefunc=CompleteFuncDict
+ hi InfoPopup ctermbg=yellow
+
+ func CompleteFuncDict(findstart, base)
+ if a:findstart
+ if col('.') > 10
+ return col('.') - 10
+ endif
+ return 0
+ endif
+
+ return {
+ \ 'words': [
+ \ {
+ \ 'word': 'aword',
+ \ 'abbr': 'wrd',
+ \ 'menu': 'extra text',
+ \ 'info': 'words are cool',
+ \ 'kind': 'W',
+ \ 'user_data': 'test'
+ \ },
+ \ {
+ \ 'word': 'anotherword',
+ \ 'abbr': 'anotwrd',
+ \ 'menu': 'extra text',
+ \ 'info': "other words are\ncooler than this and some more text\nto make wrap",
+ \ 'kind': 'W',
+ \ 'user_data': 'notest'
+ \ },
+ \ {
+ \ 'word': 'noinfo',
+ \ 'abbr': 'noawrd',
+ \ 'menu': 'extra text',
+ \ 'info': "lets\nshow\na\nscrollbar\nhere",
+ \ 'kind': 'W',
+ \ 'user_data': 'notest'
+ \ },
+ \ {
+ \ 'word': 'thatword',
+ \ 'abbr': 'thatwrd',
+ \ 'menu': 'extra text',
+ \ 'info': 'that word is cool',
+ \ 'kind': 'W',
+ \ 'user_data': 'notest'
+ \ },
+ \ ]
+ \ }
+ endfunc
+ call setline(1, 'text text text text text text text ')
+ func ChangeColor()
+ let id = popup_findinfo()
+ if buflisted(winbufnr(id))
+ call setline(1, 'buffer is listed')
+ endif
+ eval id->popup_setoptions(#{highlight: 'InfoPopup'})
+ endfunc
+
+ func InfoHidden()
+ set completepopup=height:4,border:off,align:menu
+ set completeopt-=popup completeopt+=popuphidden
+ au CompleteChanged * call HandleChange()
+ endfunc
+
+ let s:counter = 0
+ func HandleChange()
+ let s:counter += 1
+ let selected = complete_info(['selected']).selected
+ if selected <= 0
+ " First time: do nothing, info remains hidden
+ return
+ endif
+ if selected == 1
+ " Second time: show info right away
+ let id = popup_findinfo()
+ if id
+ call popup_settext(id, 'immediate info ' .. s:counter)
+ call popup_show(id)
+ endif
+ else
+ " Third time: show info after a short delay
+ call timer_start(100, 'ShowInfo')
+ endif
+ endfunc
+
+ func ShowInfo(...)
+ let id = popup_findinfo()
+ if id
+ call popup_settext(id, 'async info ' .. s:counter)
+ call popup_show(id)
+ endif
+ endfunc
+
+ func OpenOtherPopups()
+ call popup_create([
+ \ 'popup below',
+ \ 'popup below',
+ \ 'popup below',
+ \ 'popup below',
+ \ ], #{
+ \ line: 'cursor',
+ \ col: 'cursor+3',
+ \ highlight: 'ErrorMsg',
+ \ minwidth: 17,
+ \ zindex: 50,
+ \ })
+ call popup_create([
+ \ 'popup on top',
+ \ 'popup on top',
+ \ 'popup on top',
+ \ ], #{
+ \ line: 'cursor+3',
+ \ col: 'cursor-10',
+ \ highlight: 'Search',
+ \ minwidth: 10,
+ \ zindex: 200,
+ \ })
+ endfunc
+
+ " Check that no autocommands are triggered for the info popup
+ au WinEnter * if win_gettype() == 'popup' | call setline(2, 'WinEnter') | endif
+ au WinLeave * if win_gettype() == 'popup' | call setline(2, 'WinLeave') | endif
+ END
+ return lines
+endfunc
+
+func Test_popupmenu_info_border()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines = Get_popupmenu_lines()
+ call add(lines, 'set completepopup=height:4,highlight:InfoPopup')
+ call writefile(lines, 'XtestInfoPopup', 'D')
+
+ let buf = RunVimInTerminal('-S XtestInfoPopup', #{rows: 14})
+ call TermWait(buf, 25)
+
+ call term_sendkeys(buf, "A\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_1', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_2', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_3', {})
+
+ call term_sendkeys(buf, "\<C-N>\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_4', {})
+
+ " info on the left with scrollbar
+ call term_sendkeys(buf, "test text test text\<C-X>\<C-U>")
+ call term_sendkeys(buf, "\<C-N>\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_5', {})
+
+ " Test that the popupmenu's scrollbar and infopopup do not overlap
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, ":set pumheight=3\<CR>")
+ call term_sendkeys(buf, "cc\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_6', {})
+
+ " Hide the info popup, cycle through buffers, make sure it didn't get
+ " deleted.
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, ":set hidden\<CR>")
+ call term_sendkeys(buf, ":bn\<CR>")
+ call term_sendkeys(buf, ":bn\<CR>")
+ call term_sendkeys(buf, "otest text test text\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_7', {})
+
+ " Test that when the option is changed the popup changes.
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, ":set completepopup=border:off\<CR>")
+ call term_sendkeys(buf, "a\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_8', {})
+
+ call term_sendkeys(buf, " \<Esc>")
+ call term_sendkeys(buf, ":set completepopup+=width:10\<CR>")
+ call term_sendkeys(buf, "a\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_9', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupmenu_info_noborder()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines = Get_popupmenu_lines()
+ call add(lines, 'set completepopup=height:4,border:off')
+ call writefile(lines, 'XtestInfoPopupNb', 'D')
+
+ let buf = RunVimInTerminal('-S XtestInfoPopupNb', #{rows: 14})
+ call TermWait(buf, 25)
+
+ call term_sendkeys(buf, "A\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_nb_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupmenu_info_align_menu()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines = Get_popupmenu_lines()
+ call add(lines, 'set completepopup=height:4,border:off,align:menu')
+ call writefile(lines, 'XtestInfoPopupNb', 'D')
+
+ let buf = RunVimInTerminal('-S XtestInfoPopupNb', #{rows: 14})
+ call TermWait(buf, 25)
+
+ call term_sendkeys(buf, "A\<C-X>\<C-U>")
+ call term_sendkeys(buf, "\<C-N>")
+ call term_sendkeys(buf, "\<C-N>")
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_align_1', {})
+
+ call term_sendkeys(buf, "test text test text test\<C-X>\<C-U>")
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_align_2', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, ":call ChangeColor()\<CR>")
+ call term_sendkeys(buf, ":call setline(2, ['x']->repeat(10))\<CR>")
+ call term_sendkeys(buf, "Gotest text test text\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_align_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupmenu_info_hidden()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines = Get_popupmenu_lines()
+ call add(lines, 'call InfoHidden()')
+ call writefile(lines, 'XtestInfoPopupHidden', 'D')
+
+ let buf = RunVimInTerminal('-S XtestInfoPopupHidden', #{rows: 14})
+ call TermWait(buf, 25)
+
+ call term_sendkeys(buf, "A\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_hidden_1', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_hidden_2', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_hidden_3', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupmenu_info_too_wide()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ call setline(1, range(10))
+
+ set completeopt+=preview,popup
+ set completepopup=align:menu
+ set omnifunc=OmniFunc
+ hi InfoPopup ctermbg=lightgrey
+
+ func OmniFunc(findstart, base)
+ if a:findstart
+ return 0
+ endif
+
+ let menuText = 'some long text to make sure the menu takes up all of the width of the window'
+ return #{
+ \ words: [
+ \ #{
+ \ word: 'scrap',
+ \ menu: menuText,
+ \ info: "other words are\ncooler than this and some more text\nto make wrap",
+ \ },
+ \ #{
+ \ word: 'scappier',
+ \ menu: menuText,
+ \ info: 'words are cool',
+ \ },
+ \ #{
+ \ word: 'scrappier2',
+ \ menu: menuText,
+ \ info: 'words are cool',
+ \ },
+ \ ]
+ \ }
+ endfunc
+ END
+
+ call writefile(lines, 'XtestInfoPopupWide', 'D')
+ let buf = RunVimInTerminal('-S XtestInfoPopupWide', #{rows: 8})
+ call TermWait(buf, 25)
+
+ call term_sendkeys(buf, "Ascr\<C-X>\<C-O>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_wide_1', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupmenu_masking()
+ " Test that popup windows that are opened while popup menu is open are
+ " properly displayed.
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines = Get_popupmenu_lines()
+ call add(lines, 'inoremap <C-A> <Cmd>call OpenOtherPopups()<CR>')
+ call writefile(lines, 'XtestPopupmenuMasking', 'D')
+
+ let buf = RunVimInTerminal('-S XtestPopupmenuMasking', #{rows: 14})
+ call TermWait(buf, 25)
+
+ call term_sendkeys(buf, "A" .. GetEscCodeWithModifier('C', 'X')
+ \ .. GetEscCodeWithModifier('C', 'U')
+ \ .. GetEscCodeWithModifier('C', 'A'))
+ call VerifyScreenDump(buf, 'Test_popupwin_popupmenu_masking_1', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_popupwin_popupmenu_masking_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_recycle_bnr()
+ let bufnr = popup_notification('nothing wrong', {})->winbufnr()
+ call popup_clear()
+ let winid = 'nothing wrong'->popup_notification({})
+ call assert_equal(bufnr, winbufnr(winid))
+ call popup_clear()
+endfunc
+
+func Test_popupwin_getoptions_tablocal()
+ topleft split
+ let win1 = popup_create('nothing', #{maxheight: 8})
+ let win2 = popup_create('something', #{maxheight: 10})
+ let win3 = popup_create('something', #{maxheight: 15})
+ call assert_equal(8, popup_getoptions(win1).maxheight)
+ call assert_equal(10, popup_getoptions(win2).maxheight)
+ call assert_equal(15, popup_getoptions(win3).maxheight)
+ call popup_clear()
+ quit
+endfunc
+
+func Test_popupwin_cancel()
+ let win1 = popup_create('one', #{line: 5, filter: {... -> 0}})
+ let win2 = popup_create('two', #{line: 10, filter: {... -> 0}})
+ let win3 = popup_create('three', #{line: 15, filter: {... -> 0}})
+ call assert_equal(5, popup_getpos(win1).line)
+ call assert_equal(10, popup_getpos(win2).line)
+ call assert_equal(15, popup_getpos(win3).line)
+ " TODO: this also works without patch 8.1.2110
+ call feedkeys("\<C-C>", 'xt')
+ call assert_equal(5, popup_getpos(win1).line)
+ call assert_equal(10, popup_getpos(win2).line)
+ call assert_equal({}, popup_getpos(win3))
+ call feedkeys("\<C-C>", 'xt')
+ call assert_equal(5, popup_getpos(win1).line)
+ call assert_equal({}, popup_getpos(win2))
+ call assert_equal({}, popup_getpos(win3))
+ call feedkeys("\<C-C>", 'xt')
+ call assert_equal({}, popup_getpos(win1))
+ call assert_equal({}, popup_getpos(win2))
+ call assert_equal({}, popup_getpos(win3))
+endfunc
+
+func Test_popupwin_filter_redraw()
+ " Create two popups with a filter that closes the popup when typing "0".
+ " Both popups should close, even though the redraw also calls
+ " popup_reset_handled()
+
+ func CloseFilter(winid, key)
+ if a:key == '0'
+ call popup_close(a:winid)
+ redraw
+ endif
+ return 0 " pass the key
+ endfunc
+
+ let id1 = popup_create('first one', #{
+ \ line: 1,
+ \ col: 1,
+ \ filter: 'CloseFilter',
+ \ })
+ let id2 = popup_create('second one', #{
+ \ line: 9,
+ \ col: 1,
+ \ filter: 'CloseFilter',
+ \ })
+ call assert_equal(1, popup_getpos(id1).line)
+ call assert_equal(9, popup_getpos(id2).line)
+
+ call feedkeys('0', 'xt')
+ call assert_equal({}, popup_getpos(id1))
+ call assert_equal({}, popup_getpos(id2))
+
+ call popup_clear()
+ delfunc CloseFilter
+endfunc
+
+func Test_popupwin_double_width()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, 'x你好世界你好世你好世界你好')
+ call setline(2, '你好世界你好世你好世界你好')
+ call setline(3, 'x你好世界你好世你好世界你好')
+ call popup_create('你好,世界 - 你好,世界xxxxx', #{line: 1, col: 3, maxwidth: 14})
+ END
+ call writefile(lines, 'XtestPopupWide', 'D')
+
+ let buf = RunVimInTerminal('-S XtestPopupWide', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_doublewidth_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_sign()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(10))
+ call sign_define('Current', {
+ \ 'text': '>>',
+ \ 'texthl': 'WarningMsg',
+ \ 'linehl': 'Error',
+ \ })
+ call sign_define('Other', {
+ \ 'text': '#!',
+ \ 'texthl': 'Error',
+ \ 'linehl': 'Search',
+ \ })
+ let winid = popup_create(['hello', 'bright', 'world'], {
+ \ 'minwidth': 20,
+ \ })
+ call setwinvar(winid, "&signcolumn", "yes")
+ let winbufnr = winbufnr(winid)
+
+ " add sign to current buffer, shows
+ call sign_place(1, 'Selected', 'Current', bufnr('%'), {'lnum': 1})
+ " add sign to current buffer, does not show
+ call sign_place(2, 'PopUpSelected', 'Other', bufnr('%'), {'lnum': 2})
+
+ " add sign to popup buffer, shows
+ call sign_place(3, 'PopUpSelected', 'Other', winbufnr, {'lnum': 1})
+ " add sign to popup buffer, does not show
+ call sign_place(4, 'Selected', 'Current', winbufnr, {'lnum': 2})
+
+ func SetOptions()
+ call setwinvar(g:winid, '&number', 1)
+ call setwinvar(g:winid, '&foldcolumn', 2)
+ call popup_settext(g:winid, 'a longer line to check the width')
+ endfunc
+ END
+ call writefile(lines, 'XtestPopupSign', 'D')
+
+ let buf = RunVimInTerminal('-S XtestPopupSign', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popupwin_sign_1', {})
+
+ " set more options to check the width is adjusted
+ call term_sendkeys(buf, ":call SetOptions()\<CR>")
+ call VerifyScreenDump(buf, 'Test_popupwin_sign_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_bufnr()
+ let popwin = popup_create(['blah'], #{})
+ let popbuf = winbufnr(popwin)
+ split asdfasdf
+ let newbuf = bufnr()
+ call assert_true(newbuf > popbuf, 'New buffer number is higher')
+ call assert_equal(newbuf, bufnr('$'))
+ call popup_clear()
+ let popwin = popup_create(['blah'], #{})
+ " reuses previous buffer number
+ call assert_equal(popbuf, winbufnr(popwin))
+ call assert_equal(newbuf, bufnr('$'))
+
+ call popup_clear()
+ bwipe!
+endfunc
+
+func Test_popupwin_filter_input_multibyte()
+ func MyPopupFilter(winid, c)
+ let g:bytes = range(a:c->strlen())->map({i -> char2nr(a:c[i])})
+ return 0
+ endfunc
+ let winid = popup_create('', #{mapping: 0, filter: 'MyPopupFilter'})
+
+ " UTF-8: E3 80 80, including K_SPECIAL(0x80)
+ call feedkeys("\u3000", 'xt')
+ call assert_equal([0xe3, 0x80, 0x80], g:bytes)
+
+ " UTF-8: E3 80 9B, including CSI(0x9B)
+ call feedkeys("\u301b", 'xt')
+ call assert_equal([0xe3, 0x80, 0x9b], g:bytes)
+
+ if has('unix')
+ " with modifyOtherKeys <M-S-a> does not include a modifier sequence
+ if has('gui_running')
+ call feedkeys("\x9b\xfc\x08A", 'Lx!')
+ else
+ call feedkeys("\<Esc>[27;4;65~", 'Lx!')
+ endif
+ call assert_equal([0xc3, 0x81], g:bytes)
+ endif
+
+ call popup_clear()
+ delfunc MyPopupFilter
+ unlet g:bytes
+endfunc
+
+func Test_popupwin_filter_close_ctrl_c()
+ CheckScreendump
+
+ let lines =<< trim END
+ vsplit
+ set laststatus=2
+ set statusline=%!Statusline()
+
+ function Statusline() abort
+ return '%<%f %h%m%r%=%-14.(%l,%c%V%) %P'
+ endfunction
+
+ call popup_create('test test test test...', {'filter': {-> 0}})
+ END
+ call writefile(lines, 'XtestPopupCtrlC', 'D')
+
+ let buf = RunVimInTerminal('-S XtestPopupCtrlC', #{rows: 10})
+
+ call term_sendkeys(buf, "\<C-C>")
+ call VerifyScreenDump(buf, 'Test_popupwin_ctrl_c', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_filter_close_wrong_name()
+ CheckScreendump
+
+ let lines =<< trim END
+ call popup_create('one two three...', {'filter': 'NoSuchFunc'})
+ END
+ call writefile(lines, 'XtestPopupWrongName', 'D')
+
+ let buf = RunVimInTerminal('-S XtestPopupWrongName', #{rows: 10})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_popupwin_wrong_name', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_filter_close_three_errors()
+ CheckScreendump
+
+ let lines =<< trim END
+ set cmdheight=2
+ call popup_create('one two three...', {'filter': 'filter'})
+ END
+ call writefile(lines, 'XtestPopupThreeErrors', 'D')
+
+ let buf = RunVimInTerminal('-S XtestPopupThreeErrors', #{rows: 10})
+
+ call term_sendkeys(buf, "jj")
+ call VerifyScreenDump(buf, 'Test_popupwin_three_errors_1', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_popupwin_three_errors_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_latin1_encoding()
+ CheckScreendump
+ CheckUnix
+
+ " When 'encoding' is a single-byte encoding a terminal window will mess up
+ " the display. Check that showing a popup on top of that doesn't crash.
+ let lines =<< trim END
+ set encoding=latin1
+ terminal cat Xmultibyte
+ call popup_create(['one', 'two', 'three', 'four'], #{line: 1, col: 10})
+ redraw
+ " wait for "cat" to finish
+ while execute('ls!') !~ 'finished'
+ sleep 10m
+ endwhile
+ echo "Done"
+ END
+ call writefile(lines, 'XtestPopupLatin', 'D')
+ call writefile([repeat("\u3042 ", 120)], 'Xmultibyte', 'D')
+
+ let buf = RunVimInTerminal('-S XtestPopupLatin', #{rows: 10})
+ call WaitForAssert({-> assert_match('Done', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, ":q\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_popupwin_atcursor_far_right()
+ new
+
+ " this was getting stuck
+ set signcolumn=yes
+ call setline(1, repeat('=', &columns))
+ normal! ggg$
+ let winid = popup_atcursor(repeat('x', 500), #{moved: 'any', border: []})
+
+ " 'signcolumn' was getting reset
+ call setwinvar(winid, '&signcolumn', 'yes')
+ call popup_setoptions(winid, {'zindex': 1000})
+ call assert_equal('yes', getwinvar(winid, '&signcolumn'))
+
+ call popup_close(winid)
+ bwipe!
+ set signcolumn&
+endfunc
+
+func Test_popupwin_splitmove()
+ vsplit
+ let win2 = win_getid()
+ let popup_winid = popup_dialog('hello', {})
+ call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:')
+ call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:')
+
+ call popup_clear()
+ bwipe
+endfunc
+
+func Test_popupwin_exiting_terminal()
+ CheckFeature terminal
+
+ " Tests that when creating a popup right after closing a terminal window does
+ " not make the popup the current window.
+ let winid = win_getid()
+ try
+ augroup Test_popupwin_exiting_terminal
+ autocmd!
+ autocmd WinEnter * :call popup_create('test', {})
+ augroup END
+ let bnr = term_start(&shell, #{term_finish: 'close'})
+ call term_sendkeys(bnr, "exit\r\n")
+ call WaitForAssert({-> assert_equal(winid, win_getid())})
+ finally
+ call popup_clear(1)
+ augroup Test_popupwin_exiting_terminal
+ autocmd!
+ augroup END
+ endtry
+endfunc
+
+func Test_popup_filter_menu()
+ let colors = ['red', 'green', 'blue']
+ call popup_menu(colors, #{callback: {_, result -> assert_equal('green', colors[result - 1])}})
+ call feedkeys("\<c-n>\<c-n>\<c-p>\<cr>", 'xt')
+endfunc
+
+func Test_popup_getoptions_other_tab()
+ new
+ call setline(1, 'some text')
+ call prop_type_add('textprop', {})
+ call prop_add(1, 1, #{type: 'textprop', length: 1})
+ let id = popup_create('TEST', #{textprop: 'textprop', highlight: 'ErrorMsg', tabpage: 1})
+ tab sp
+ call assert_equal(['textprop', 'textpropid', 'textpropwin'], popup_getoptions(id)->keys()->filter({_, v -> v =~ 'textprop'}))
+
+ tabclose
+ call popup_close(id)
+ bwipe!
+ call prop_type_delete('textprop')
+endfunc
+
+
+func Test_popup_setoptions_other_tab()
+ new Xpotfile
+ let winid = win_getid()
+ call setline(1, 'some text')
+ call prop_type_add('textprop', {})
+ call prop_add(1, 1, #{type: 'textprop', length: 1})
+ let id = popup_create('TEST', #{textprop: 'textprop'})
+ tab sp
+ call popup_setoptions(id, #{textprop: 'textprop', textpropwin: winid})
+ call assert_equal(winid, popup_getoptions(id).textpropwin)
+
+ tabclose
+ call popup_close(id)
+ bwipe! Xpotfile
+ call prop_type_delete('textprop')
+endfunc
+
+func Test_popup_prop_not_visible()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ set nowrap stal=2
+ rightbelow :31vnew
+ setline(1, ['', 'some text', '', 'other text'])
+ prop_type_add('someprop', {})
+ prop_add(2, 9, {type: 'someprop', length: 5})
+ g:some_id = popup_create('attached to "some"', {
+ textprop: 'someprop',
+ highlight: 'ErrorMsg',
+ line: -1,
+ wrap: false,
+ fixed: true,
+ })
+ prop_type_add('otherprop', {})
+ prop_add(4, 10, {type: 'otherprop', length: 5})
+ popup_create('attached to "other"', {
+ textprop: 'otherprop',
+ highlight: 'ErrorMsg',
+ line: -1,
+ wrap: false,
+ fixed: false,
+ })
+ END
+ call writefile(lines, 'XtestPropNotVisible', 'D')
+ let buf = RunVimInTerminal('-S XtestPropNotVisible', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popup_prop_not_visible_01', {})
+
+ " check that hiding and unhiding the popup works
+ call term_sendkeys(buf, ":call popup_hide(g:some_id)\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_prop_not_visible_01a', {})
+ call term_sendkeys(buf, ":call popup_show(g:some_id)\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_prop_not_visible_01b', {})
+
+ call term_sendkeys(buf, ":vert resize -14\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_prop_not_visible_02', {})
+
+ call term_sendkeys(buf, ":vert resize -8\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_prop_not_visible_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunction
+
+func Test_bufdel_skips_popupwin_buffer()
+ let id = popup_create("Some text", {})
+ %bd
+ call popup_close(id)
+endfunc
+
+func Test_term_popup_bufline()
+ " very specific situation where a non-existing buffer line is used, leading
+ " to an ml_get error
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ &scrolloff = 5
+ term_start('seq 1 5', {term_finish: 'open'})
+ timer_start(50, (_) => {
+ set cpoptions&vim
+ var buf = popup_create([], {})->winbufnr()
+ appendbufline(buf, 0, range(5))
+ })
+ END
+ call writefile(lines, 'XtestTermPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestTermPopup', #{rows: 15})
+ call VerifyScreenDump(buf, 'Test_term_popup_bufline', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2
diff --git a/src/testdir/test_popupwin_textprop.vim b/src/testdir/test_popupwin_textprop.vim
new file mode 100644
index 0000000..80b5a88
--- /dev/null
+++ b/src/testdir/test_popupwin_textprop.vim
@@ -0,0 +1,173 @@
+" Tests for popup windows for text properties
+
+source check.vim
+CheckFeature popupwin
+CheckFeature textprop
+
+source screendump.vim
+CheckScreendump
+
+func Test_textprop_popup()
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ call setline(50, 'some text to work with')
+ 50
+ normal zz
+ set scrolloff=0
+ call prop_type_add('popupMarker', #{highlight: 'DiffAdd', bufnr: bufnr('%')})
+ call prop_add(50, 11, #{
+ \ length: 7,
+ \ type: 'popupMarker',
+ \ bufnr: bufnr('%'),
+ \ })
+ let winid = popup_create('the text', #{
+ \ pos: 'botleft',
+ \ textprop: 'popupMarker',
+ \ border: [],
+ \ padding: [0,1,0,1],
+ \ close: 'click',
+ \ })
+ END
+ call writefile(lines, 'XtestTextpropPopup', 'D')
+ let buf = RunVimInTerminal('-S XtestTextpropPopup', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_popup_textprop_01', {})
+
+ call term_sendkeys(buf, "zt")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_02', {})
+
+ call term_sendkeys(buf, "zzIawe\<Esc>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_03', {})
+
+ call term_sendkeys(buf, "0dw")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_04', {})
+
+ call term_sendkeys(buf, "Oinserted\<Esc>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_05', {})
+
+ call term_sendkeys(buf, "k2dd")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_06', {})
+
+ call term_sendkeys(buf, "4\<C-E>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_07', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_textprop_popup_corners()
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ call setline(50, 'now working with some longer text here')
+ 50
+ normal zz
+ set scrolloff=0
+ call prop_type_add('popupMarker', #{highlight: 'DiffAdd'})
+ call prop_add(50, 23, #{
+ \ length: 6,
+ \ type: 'popupMarker',
+ \ })
+ let winid = popup_create('bottom left', #{
+ \ pos: 'botleft',
+ \ textprop: 'popupMarker',
+ \ textpropwin: win_getid(),
+ \ padding: [0,1,0,1],
+ \ })
+ let winid = popup_create('bottom right', #{
+ \ pos: 'botright',
+ \ textprop: 'popupMarker',
+ \ border: [],
+ \ padding: [0,1,0,1],
+ \ })
+ let winid = popup_create('top left', #{
+ \ pos: 'topleft',
+ \ textprop: 'popupMarker',
+ \ border: [],
+ \ padding: [0,1,0,1],
+ \ })
+ let winid = popup_create('top right', #{
+ \ pos: 'topright',
+ \ textprop: 'popupMarker',
+ \ padding: [0,1,0,1],
+ \ })
+ END
+ call writefile(lines, 'XtestTextpropPopupCorners', 'D')
+ let buf = RunVimInTerminal('-S XtestTextpropPopupCorners', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_popup_textprop_corn_1', {})
+
+ call term_sendkeys(buf, "0dw")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_corn_2', {})
+
+ call term_sendkeys(buf, "46Goextra\<Esc>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_corn_3', {})
+
+ call term_sendkeys(buf, "u")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_corn_4', {})
+
+ call term_sendkeys(buf, ":vsplit foo\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_corn_5', {})
+
+ call term_sendkeys(buf, ":only!\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_corn_6', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_textprop_popup_offsets()
+ let lines =<< trim END
+ call setline(1, range(1, 100))
+ call setline(50, 'now working with some longer text here')
+ 50
+ normal zz
+ set scrolloff=0
+ call prop_type_add('popupMarker', #{highlight: 'DiffAdd'})
+ call prop_add(50, 23, #{
+ \ length: 6,
+ \ type: 'popupMarker',
+ \ })
+ let winid = popup_create('bottom left', #{
+ \ pos: 'botleft',
+ \ line: -1,
+ \ col: 2,
+ \ textprop: 'popupMarker',
+ \ padding: [0,1,0,1],
+ \ })
+ let winid = popup_create('bottom right', #{
+ \ pos: 'botright',
+ \ line: -1,
+ \ col: -2,
+ \ textprop: 'popupMarker',
+ \ border: [],
+ \ padding: [0,1,0,1],
+ \ })
+ let winid = popup_create('top left', #{
+ \ pos: 'topleft',
+ \ line: 1,
+ \ col: 2,
+ \ textprop: 'popupMarker',
+ \ border: [],
+ \ padding: [0,1,0,1],
+ \ })
+ let winid = popup_create('top right', #{
+ \ pos: 'topright',
+ \ line: 1,
+ \ col: -2,
+ \ textprop: 'popupMarker',
+ \ padding: [0,1,0,1],
+ \ })
+ END
+ call writefile(lines, 'XtestTextpropPopupOffset', 'D')
+ let buf = RunVimInTerminal('-S XtestTextpropPopupOffset', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_popup_textprop_off_1', {})
+
+ " test that removing the text property closes the popups
+ call term_sendkeys(buf, ":call prop_clear(50)\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_textprop_off_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2
diff --git a/src/testdir/test_preview.vim b/src/testdir/test_preview.vim
new file mode 100644
index 0000000..3d13d99
--- /dev/null
+++ b/src/testdir/test_preview.vim
@@ -0,0 +1,63 @@
+" Tests for the preview window
+
+source check.vim
+CheckFeature quickfix
+
+func Test_Psearch()
+ " this used to cause ml_get errors
+ help
+ let wincount = winnr('$')
+ 0f
+ ps.
+ call assert_equal(wincount + 1, winnr('$'))
+ pclose
+ call assert_equal(wincount, winnr('$'))
+ bwipe
+endfunc
+
+func Test_window_preview()
+ CheckFeature quickfix
+
+ " Open a preview window
+ pedit Xa
+ call assert_equal(2, winnr('$'))
+ call assert_equal(0, &previewwindow)
+
+ " Go to the preview window
+ wincmd P
+ call assert_equal(1, &previewwindow)
+ call assert_equal('preview', win_gettype())
+
+ " Close preview window
+ wincmd z
+ call assert_equal(1, winnr('$'))
+ call assert_equal(0, &previewwindow)
+
+ call assert_fails('wincmd P', 'E441:')
+endfunc
+
+func Test_window_preview_from_help()
+ CheckFeature quickfix
+
+ filetype on
+ call writefile(['/* some C code */'], 'Xpreview.c', 'D')
+ help
+ pedit Xpreview.c
+ wincmd P
+ call assert_equal(1, &previewwindow)
+ call assert_equal('c', &filetype)
+ wincmd z
+
+ filetype off
+ close
+endfunc
+
+func Test_multiple_preview_windows()
+ new
+ set previewwindow
+ new
+ call assert_fails('set previewwindow', 'E590:')
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_profile.vim b/src/testdir/test_profile.vim
new file mode 100644
index 0000000..d24acd3
--- /dev/null
+++ b/src/testdir/test_profile.vim
@@ -0,0 +1,772 @@
+" Test Vim profiler
+
+source check.vim
+CheckFeature profile
+
+source shared.vim
+source screendump.vim
+
+func Test_profile_func()
+ call RunProfileFunc('func', 'let', 'let')
+ call RunProfileFunc('def', 'var', '')
+endfunc
+
+func RunProfileFunc(command, declare, assign)
+ let lines =<< trim [CODE]
+ profile start Xprofile_func.log
+ profile func Foo*
+ XXX Foo1()
+ endXXX
+ XXX Foo2()
+ DDD counter = 100
+ while counter > 0
+ AAA counter = counter - 1
+ endwhile
+ sleep 1m
+ endXXX
+ XXX Foo3()
+ endXXX
+ XXX Bar()
+ endXXX
+ call Foo1()
+ call Foo1()
+ profile pause
+ call Foo1()
+ profile continue
+ call Foo2()
+ call Foo3()
+ call Bar()
+ if !v:profiling
+ delfunc Foo2
+ endif
+ delfunc Foo3
+ [CODE]
+
+ call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') })
+ call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') })
+ call map(lines, {k, v -> substitute(v, 'AAA', a:assign, '') })
+
+ call writefile(lines, 'Xprofile_func.vim', 'D')
+ call system(GetVimCommand()
+ \ . ' -es --clean'
+ \ . ' -c "so Xprofile_func.vim"'
+ \ . ' -c "qall!"')
+ call assert_equal(0, v:shell_error)
+
+ sleep 50m
+ let lines = readfile('Xprofile_func.log')
+
+ " - Foo1() is called 3 times but should be reported as called twice
+ " since one call is in between "profile pause" .. "profile continue".
+ " - Foo2() should come before Foo1() since Foo1() does much more work.
+ " - Foo3() is not reported because function is deleted.
+ " - Unlike Foo3(), Foo2() should not be deleted since there is a check
+ " for v:profiling.
+ " - Bar() is not reported since it does not match "profile func Foo*".
+ call assert_equal(31, len(lines))
+
+ call assert_equal('FUNCTION Foo1()', lines[0])
+ call assert_match('Defined:.*Xprofile_func.vim:3', lines[1])
+ call assert_equal('Called 2 times', lines[2])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
+ call assert_equal('', lines[5])
+ call assert_equal('count total (s) self (s)', lines[6])
+ call assert_equal('', lines[7])
+ call assert_equal('FUNCTION Foo2()', lines[8])
+ call assert_equal('Called 1 time', lines[10])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[11])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[12])
+ call assert_equal('', lines[13])
+ call assert_equal('count total (s) self (s)', lines[14])
+ call assert_match('^\s*1\s\+.*\s\(let\|var\) counter = 100$', lines[15])
+ call assert_match('^\s*101\s\+.*\swhile counter > 0$', lines[16])
+ call assert_match('^\s*100\s\+.*\s \(let\)\= counter = counter - 1$', lines[17])
+ call assert_match('^\s*10[01]\s\+.*\sendwhile$', lines[18])
+ call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19])
+ call assert_equal('', lines[20])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21])
+ call assert_equal('count total (s) self (s) function', lines[22])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[23])
+ call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[24])
+ call assert_equal('', lines[25])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[26])
+ call assert_equal('count total (s) self (s) function', lines[27])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[28])
+ call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[29])
+ call assert_equal('', lines[30])
+
+ call delete('Xprofile_func.log')
+endfunc
+
+func Test_profile_func_with_ifelse()
+ call Run_profile_func_with_ifelse('func', 'let')
+ call Run_profile_func_with_ifelse('def', 'var')
+endfunc
+
+func Run_profile_func_with_ifelse(command, declare)
+ let lines =<< trim [CODE]
+ XXX Foo1()
+ if 1
+ DDD x = 0
+ elseif 1
+ DDD x = 1
+ else
+ DDD x = 2
+ endif
+ endXXX
+ XXX Foo2()
+ if 0
+ DDD x = 0
+ elseif 1
+ DDD x = 1
+ else
+ DDD x = 2
+ endif
+ endXXX
+ XXX Foo3()
+ if 0
+ DDD x = 0
+ elseif 0
+ DDD x = 1
+ else
+ DDD x = 2
+ endif
+ endXXX
+ call Foo1()
+ call Foo2()
+ call Foo3()
+ [CODE]
+
+ call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') })
+ call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') })
+
+ call writefile(lines, 'Xprofile_func.vim', 'D')
+ call system(GetVimCommand()
+ \ . ' -es -i NONE --noplugin'
+ \ . ' -c "profile start Xprofile_func.log"'
+ \ . ' -c "profile func Foo*"'
+ \ . ' -c "so Xprofile_func.vim"'
+ \ . ' -c "qall!"')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_func.log')
+
+ " - Foo1() should pass 'if' block.
+ " - Foo2() should pass 'elseif' block.
+ " - Foo3() should pass 'else' block.
+ call assert_equal(57, len(lines))
+
+ call assert_equal('FUNCTION Foo1()', lines[0])
+ call assert_match('Defined:.*Xprofile_func.vim', lines[1])
+ call assert_equal('Called 1 time', lines[2])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
+ call assert_equal('', lines[5])
+ call assert_equal('count total (s) self (s)', lines[6])
+ call assert_match('^\s*1\s\+.*\sif 1$', lines[7])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 0$', lines[8])
+ call assert_match( '^\s\+elseif 1$', lines[9])
+ call assert_match( '^\s\+\(let\|var\) x = 1$', lines[10])
+ call assert_match( '^\s\+else$', lines[11])
+ call assert_match( '^\s\+\(let\|var\) x = 2$', lines[12])
+ call assert_match('^\s*1\s\+.*\sendif$', lines[13])
+ call assert_equal('', lines[14])
+ call assert_equal('FUNCTION Foo2()', lines[15])
+ call assert_equal('Called 1 time', lines[17])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[18])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[19])
+ call assert_equal('', lines[20])
+ call assert_equal('count total (s) self (s)', lines[21])
+ call assert_match('^\s*1\s\+.*\sif 0$', lines[22])
+ call assert_match( '^\s\+\(let\|var\) x = 0$', lines[23])
+ call assert_match('^\s*1\s\+.*\selseif 1$', lines[24])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 1$', lines[25])
+ call assert_match( '^\s\+else$', lines[26])
+ call assert_match( '^\s\+\(let\|var\) x = 2$', lines[27])
+ call assert_match('^\s*1\s\+.*\sendif$', lines[28])
+ call assert_equal('', lines[29])
+ call assert_equal('FUNCTION Foo3()', lines[30])
+ call assert_equal('Called 1 time', lines[32])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[33])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[34])
+ call assert_equal('', lines[35])
+ call assert_equal('count total (s) self (s)', lines[36])
+ call assert_match('^\s*1\s\+.*\sif 0$', lines[37])
+ call assert_match( '^\s\+\(let\|var\) x = 0$', lines[38])
+ call assert_match('^\s*1\s\+.*\selseif 0$', lines[39])
+ call assert_match( '^\s\+\(let\|var\) x = 1$', lines[40])
+ call assert_match('^\s*1\s\+.*\selse$', lines[41])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[42])
+ call assert_match('^\s*1\s\+.*\sendif$', lines[43])
+ call assert_equal('', lines[44])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[45])
+ call assert_equal('count total (s) self (s) function', lines[46])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[47])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[48])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[49])
+ call assert_equal('', lines[50])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[51])
+ call assert_equal('count total (s) self (s) function', lines[52])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[53])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[54])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[55])
+ call assert_equal('', lines[56])
+
+ call delete('Xprofile_func.log')
+endfunc
+
+func Test_profile_func_with_trycatch()
+ call Run_profile_func_with_trycatch('func', 'let')
+ call Run_profile_func_with_trycatch('def', 'var')
+endfunc
+
+func Run_profile_func_with_trycatch(command, declare)
+ let lines =<< trim [CODE]
+ XXX Foo1()
+ try
+ DDD x = 0
+ catch
+ DDD x = 1
+ finally
+ DDD x = 2
+ endtry
+ endXXX
+ XXX Foo2()
+ try
+ throw 0
+ catch
+ DDD x = 1
+ finally
+ DDD x = 2
+ endtry
+ endXXX
+ XXX Foo3()
+ try
+ throw 0
+ catch
+ throw 1
+ finally
+ DDD x = 2
+ endtry
+ endXXX
+ call Foo1()
+ call Foo2()
+ let rethrown = 0
+ try
+ call Foo3()
+ catch
+ let rethrown = 1
+ endtry
+ if rethrown != 1
+ " call Foo1 again so that the test fails
+ call Foo1()
+ endif
+ [CODE]
+
+ call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') })
+ call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') })
+
+ call writefile(lines, 'Xprofile_func.vim', 'D')
+ call system(GetVimCommand()
+ \ . ' -es -i NONE --noplugin'
+ \ . ' -c "profile start Xprofile_func.log"'
+ \ . ' -c "profile func Foo*"'
+ \ . ' -c "so Xprofile_func.vim"'
+ \ . ' -c "qall!"')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_func.log')
+
+ " - Foo1() should pass 'try' 'finally' blocks.
+ " - Foo2() should pass 'catch' 'finally' blocks.
+ " - Foo3() should not pass 'endtry'.
+ call assert_equal(57, len(lines))
+
+ call assert_equal('FUNCTION Foo1()', lines[0])
+ call assert_match('Defined:.*Xprofile_func.vim', lines[1])
+ call assert_equal('Called 1 time', lines[2])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
+ call assert_equal('', lines[5])
+ call assert_equal('count total (s) self (s)', lines[6])
+ call assert_match('^\s*1\s\+.*\stry$', lines[7])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 0$', lines[8])
+ call assert_match( '^\s\+catch$', lines[9])
+ call assert_match( '^\s\+\(let\|var\) x = 1$', lines[10])
+ call assert_match('^\s*1\s\+.*\sfinally$', lines[11])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[12])
+ call assert_match('^\s*1\s\+.*\sendtry$', lines[13])
+ call assert_equal('', lines[14])
+ call assert_equal('FUNCTION Foo2()', lines[15])
+ call assert_equal('Called 1 time', lines[17])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[18])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[19])
+ call assert_equal('', lines[20])
+ call assert_equal('count total (s) self (s)', lines[21])
+ call assert_match('^\s*1\s\+.*\stry$', lines[22])
+ call assert_match('^\s*1\s\+.*\s throw 0$', lines[23])
+ call assert_match('^\s*1\s\+.*\scatch$', lines[24])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 1$', lines[25])
+ call assert_match('^\s*1\s\+.*\sfinally$', lines[26])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[27])
+ call assert_match('^\s*1\s\+.*\sendtry$', lines[28])
+ call assert_equal('', lines[29])
+ call assert_equal('FUNCTION Foo3()', lines[30])
+ call assert_equal('Called 1 time', lines[32])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[33])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[34])
+ call assert_equal('', lines[35])
+ call assert_equal('count total (s) self (s)', lines[36])
+ call assert_match('^\s*1\s\+.*\stry$', lines[37])
+ call assert_match('^\s*1\s\+.*\s throw 0$', lines[38])
+ call assert_match('^\s*1\s\+.*\scatch$', lines[39])
+ call assert_match('^\s*1\s\+.*\s throw 1$', lines[40])
+ call assert_match('^\s*1\s\+.*\sfinally$', lines[41])
+ call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[42])
+ call assert_match( '^\s\+endtry$', lines[43])
+ call assert_equal('', lines[44])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[45])
+ call assert_equal('count total (s) self (s) function', lines[46])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[47])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[48])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[49])
+ call assert_equal('', lines[50])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[51])
+ call assert_equal('count total (s) self (s) function', lines[52])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[53])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[54])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo.()$', lines[55])
+ call assert_equal('', lines[56])
+
+ call delete('Xprofile_func.log')
+endfunc
+
+func Test_profile_file()
+ let lines =<< trim [CODE]
+ func! Foo()
+ endfunc
+ for i in range(10)
+ " a comment
+ call Foo()
+ endfor
+ call Foo()
+ [CODE]
+
+ call writefile(lines, 'Xprofile_file.vim', 'D')
+ call system(GetVimCommandClean()
+ \ . ' -es'
+ \ . ' -c "profile start Xprofile_file.log"'
+ \ . ' -c "profile file Xprofile_file.vim"'
+ \ . ' -c "so Xprofile_file.vim"'
+ \ . ' -c "so Xprofile_file.vim"'
+ \ . ' -c "qall!"')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_file.log')
+
+ call assert_equal(14, len(lines))
+
+ call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0])
+ call assert_equal('Sourced 2 times', lines[1])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_equal('', lines[4])
+ call assert_equal('count total (s) self (s)', lines[5])
+ call assert_match(' 2 0.\d\+ func! Foo()', lines[6])
+ call assert_equal(' endfunc', lines[7])
+ " Loop iterates 10 times. Since script runs twice, body executes 20 times.
+ " First line of loop executes one more time than body to detect end of loop.
+ call assert_match('^\s*22\s\+\d\+\.\d\+\s\+for i in range(10)$', lines[8])
+ call assert_equal(' " a comment', lines[9])
+ " if self and total are equal we only get one number
+ call assert_match('^\s*20\s\+\(\d\+\.\d\+\s\+\)\=\d\+\.\d\+\s\+call Foo()$', lines[10])
+ call assert_match('^\s*22\s\+\d\+\.\d\+\s\+endfor$', lines[11])
+ " if self and total are equal we only get one number
+ call assert_match('^\s*2\s\+\(\d\+\.\d\+\s\+\)\=\d\+\.\d\+\s\+call Foo()$', lines[12])
+ call assert_equal('', lines[13])
+
+ call delete('Xprofile_file.log')
+endfunc
+
+func Test_profile_file_with_cont()
+ let lines = [
+ \ 'echo "hello',
+ \ ' \ world"',
+ \ 'echo "foo ',
+ \ ' \bar"',
+ \ ]
+
+ call writefile(lines, 'Xprofile_file.vim', 'D')
+ call system(GetVimCommandClean()
+ \ . ' -es'
+ \ . ' -c "profile start Xprofile_file.log"'
+ \ . ' -c "profile file Xprofile_file.vim"'
+ \ . ' -c "so Xprofile_file.vim"'
+ \ . ' -c "qall!"')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_file.log')
+ call assert_equal(11, len(lines))
+
+ call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0])
+ call assert_equal('Sourced 1 time', lines[1])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_equal('', lines[4])
+ call assert_equal('count total (s) self (s)', lines[5])
+ call assert_match(' 1 0.\d\+ echo "hello', lines[6])
+ call assert_equal(' \ world"', lines[7])
+ call assert_match(' 1 0.\d\+ echo "foo ', lines[8])
+ call assert_equal(' \bar"', lines[9])
+ call assert_equal('', lines[10])
+
+ call delete('Xprofile_file.log')
+endfunc
+
+" Test for ':profile stop' and ':profile dump' commands
+func Test_profile_stop_dump()
+ call delete('Xprof1.out')
+ call delete('Xprof2.out')
+ call delete('Xprof3.out')
+ func Xprof_test1()
+ return "Hello"
+ endfunc
+ func Xprof_test2()
+ return "World"
+ endfunc
+
+ " Test for ':profile stop'
+ profile start Xprof1.out
+ profile func Xprof_test1
+ call Xprof_test1()
+ profile stop
+
+ let lines = readfile('Xprof1.out')
+ call assert_equal(17, len(lines))
+ call assert_equal('FUNCTION Xprof_test1()', lines[0])
+ call assert_match('Defined:.*test_profile.vim:', lines[1])
+ call assert_equal('Called 1 time', lines[2])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
+ call assert_equal('', lines[5])
+ call assert_equal('count total (s) self (s)', lines[6])
+ call assert_match('^\s*1\s\+.*\sreturn "Hello"$', lines[7])
+ call assert_equal('', lines[8])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[9])
+ call assert_equal('count total (s) self (s) function', lines[10])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Xprof_test1()$', lines[11])
+ call assert_equal('', lines[12])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[13])
+ call assert_equal('count total (s) self (s) function', lines[14])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Xprof_test1()$', lines[15])
+ call assert_equal('', lines[16])
+
+ " Test for ':profile stop' for a different function
+ profile start Xprof2.out
+ profile func Xprof_test2
+ call Xprof_test2()
+ profile stop
+ let lines = readfile('Xprof2.out')
+ call assert_equal(17, len(lines))
+ call assert_equal('FUNCTION Xprof_test2()', lines[0])
+ call assert_match('Defined:.*test_profile.vim:', lines[1])
+ call assert_equal('Called 1 time', lines[2])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4])
+ call assert_equal('', lines[5])
+ call assert_equal('count total (s) self (s)', lines[6])
+ call assert_match('^\s*1\s\+.*\sreturn "World"$', lines[7])
+ call assert_equal('', lines[8])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[9])
+ call assert_equal('count total (s) self (s) function', lines[10])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Xprof_test2()$', lines[11])
+ call assert_equal('', lines[12])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[13])
+ call assert_equal('count total (s) self (s) function', lines[14])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Xprof_test2()$', lines[15])
+ call assert_equal('', lines[16])
+
+ " Test for ':profile dump'
+ profile start Xprof3.out
+ profile func Xprof_test1
+ profile func Xprof_test2
+ call Xprof_test1()
+ profile dump
+ " dump the profile once and verify the contents
+ let lines = readfile('Xprof3.out')
+ call assert_equal(17, len(lines))
+ call assert_match('^\s*1\s\+.*\sreturn "Hello"$', lines[7])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Xprof_test1()$', lines[11])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Xprof_test1()$', lines[15])
+ " dump the profile again and verify the contents
+ call Xprof_test2()
+ profile dump
+ profile stop
+ let lines = readfile('Xprof3.out')
+ call assert_equal(28, len(lines))
+ call assert_equal('FUNCTION Xprof_test1()', lines[0])
+ call assert_match('^\s*1\s\+.*\sreturn "Hello"$', lines[7])
+ call assert_equal('FUNCTION Xprof_test2()', lines[9])
+ call assert_match('^\s*1\s\+.*\sreturn "World"$', lines[16])
+
+ delfunc Xprof_test1
+ delfunc Xprof_test2
+ call delete('Xprof1.out')
+ call delete('Xprof2.out')
+ call delete('Xprof3.out')
+endfunc
+
+" Test for :profile sub-command completion
+func Test_profile_completion()
+ call feedkeys(":profile \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profile continue dump file func pause start stop', @:)
+
+ call feedkeys(":profile start test_prof\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('^"profile start.* test_profile\.vim', @:)
+
+ call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"profile file test_profile\.vim', @:)
+ call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"profile file test_profile\.vim', @:)
+ call feedkeys(":profile file test_prof \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"profile file test_prof ', @:)
+ call feedkeys(":profile file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"profile file X1B2C3', @:)
+
+ func Xprof_test()
+ endfunc
+ call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profile func Xprof_test', @:)
+ call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profile func Xprof_test', @:)
+ call feedkeys(":profile func Xprof \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profile func Xprof ', @:)
+ call feedkeys(":profile func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profile func X1B2C3', @:)
+
+ call feedkeys(":profdel \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel file func', @:)
+ call feedkeys(":profdel fu\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel func', @:)
+ call feedkeys(":profdel he\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel he', @:)
+ call feedkeys(":profdel here \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel here ', @:)
+ call feedkeys(":profdel file test_prof\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel file test_profile.vim', @:)
+ call feedkeys(":profdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel file X1B2C3', @:)
+ call feedkeys(":profdel func Xprof\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel func Xprof_test', @:)
+ call feedkeys(":profdel func Xprof_test \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel func Xprof_test ', @:)
+ call feedkeys(":profdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"profdel func X1B2C3', @:)
+
+ delfunc Xprof_test
+endfunc
+
+func Test_profile_errors()
+ call assert_fails("profile func Foo", 'E750:')
+ call assert_fails("profile pause", 'E750:')
+ call assert_fails("profile continue", 'E750:')
+ call assert_fails("profile stop", 'E750:')
+ call assert_fails("profile dump", 'E750:')
+endfunc
+
+func Test_profile_truncate_mbyte()
+ if &enc !=# 'utf-8'
+ return
+ endif
+
+ let lines = [
+ \ 'scriptencoding utf-8',
+ \ 'func! Foo()',
+ \ ' return [',
+ \ ' \ "' . join(map(range(0x4E00, 0x4E00 + 340), 'nr2char(v:val)'), '') . '",',
+ \ ' \ "' . join(map(range(0x4F00, 0x4F00 + 340), 'nr2char(v:val)'), '') . '",',
+ \ ' \ ]',
+ \ 'endfunc',
+ \ 'call Foo()',
+ \ ]
+
+ call writefile(lines, 'Xprofile_file.vim', 'D')
+ call system(GetVimCommandClean()
+ \ . ' -es --cmd "set enc=utf-8"'
+ \ . ' -c "profile start Xprofile_file.log"'
+ \ . ' -c "profile file Xprofile_file.vim"'
+ \ . ' -c "so Xprofile_file.vim"'
+ \ . ' -c "qall!"')
+ call assert_equal(0, v:shell_error)
+
+ split Xprofile_file.log
+ if &fenc != ''
+ call assert_equal('utf-8', &fenc)
+ endif
+ /func! Foo()
+ let lnum = line('.')
+ call assert_match('^\s*return \[$', getline(lnum + 1))
+ call assert_match("\u4F52$", getline(lnum + 2))
+ call assert_match("\u5052$", getline(lnum + 3))
+ call assert_match('^\s*\\ \]$', getline(lnum + 4))
+ bwipe!
+
+ call delete('Xprofile_file.log')
+endfunc
+
+func Test_profdel_func()
+ let lines =<< trim [CODE]
+ profile start Xprofile_file.log
+ func! Foo1()
+ endfunc
+ func! Foo2()
+ endfunc
+ func! Foo3()
+ endfunc
+
+ profile func Foo1
+ profile func Foo2
+ call Foo1()
+ call Foo2()
+
+ profile func Foo3
+ profdel func Foo2
+ profdel func Foo3
+ call Foo1()
+ call Foo2()
+ call Foo3()
+ [CODE]
+ call writefile(lines, 'Xprofile_file.vim', 'D')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_file.log')
+ call assert_equal(26, len(lines))
+
+ " Check that:
+ " - Foo1() is called twice (profdel not invoked)
+ " - Foo2() is called once (profdel invoked after it was called)
+ " - Foo3() is not called (profdel invoked before it was called)
+ call assert_equal('FUNCTION Foo1()', lines[0])
+ call assert_match('Defined:.*Xprofile_file.vim', lines[1])
+ call assert_equal('Called 2 times', lines[2])
+ call assert_equal('FUNCTION Foo2()', lines[8])
+ call assert_equal('Called 1 time', lines[10])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[16])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[21])
+
+ call delete('Xprofile_file.log')
+endfunc
+
+func Test_profdel_star()
+ " Foo() is invoked once before and once after 'profdel *'.
+ " So profiling should report it only once.
+ let lines =<< trim [CODE]
+ profile start Xprofile_file.log
+ func! Foo()
+ endfunc
+ profile func Foo
+ call Foo()
+ profdel *
+ call Foo()
+ [CODE]
+ call writefile(lines, 'Xprofile_file.vim', 'D')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_file.log')
+ call assert_equal(16, len(lines))
+
+ call assert_equal('FUNCTION Foo()', lines[0])
+ call assert_match('Defined:.*Xprofile_file.vim', lines[1])
+ call assert_equal('Called 1 time', lines[2])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[8])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[12])
+
+ call delete('Xprofile_file.log')
+endfunc
+
+" When typing the function it won't have a script ID, test that this works.
+func Test_profile_typed_func()
+ CheckScreendump
+
+ let lines =<< trim END
+ profile start XprofileTypedFunc
+ END
+ call writefile(lines, 'XtestProfile', 'D')
+ let buf = RunVimInTerminal('-S XtestProfile', #{})
+
+ call term_sendkeys(buf, ":func DoSomething()\<CR>"
+ \ .. "echo 'hello'\<CR>"
+ \ .. "endfunc\<CR>")
+ call term_sendkeys(buf, ":profile func DoSomething\<CR>")
+ call term_sendkeys(buf, ":call DoSomething()\<CR>")
+ call TermWait(buf, 100)
+ call StopVimInTerminal(buf)
+ let lines = readfile('XprofileTypedFunc')
+ call assert_equal("FUNCTION DoSomething()", lines[0])
+ call assert_equal("Called 1 time", lines[1])
+
+ " clean up
+ call delete('XprofileTypedFunc')
+endfunc
+
+func Test_vim9_profiling()
+ " only tests that compiling and calling functions doesn't crash
+ let lines =<< trim END
+ vim9script
+ def Func()
+ Crash()
+ enddef
+ def Crash()
+ enddef
+ prof start Xprofile_crash.log
+ prof func Func
+ Func()
+ END
+ call writefile(lines, 'Xprofile_crash.vim', 'D')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_crash.vim" -c q')
+ call assert_equal(0, v:shell_error)
+ call assert_true(readfile('Xprofile_crash.log')->len() > 10)
+
+ call delete('Xprofile_crash.log')
+endfunc
+
+func Test_vim9_nested_call()
+ let lines =<< trim END
+ vim9script
+ var total = 0
+ def One(Ref: func(number))
+ for i in range(3)
+ Ref(i)
+ endfor
+ enddef
+ def Two(nr: number)
+ total += nr
+ enddef
+ prof start Xprofile_nested.log
+ prof func One
+ prof func Two
+ One((nr) => Two(nr))
+ assert_equal(3, total)
+ END
+ call writefile(lines, 'Xprofile_nested.vim', 'D')
+ call system(GetVimCommandClean() . ' -es -c "so Xprofile_nested.vim" -c q')
+ call assert_equal(0, v:shell_error)
+
+ let prof_lines = readfile('Xprofile_nested.log')->join('#')
+ call assert_match('FUNCTION <SNR>\d\+_One().*'
+ \ .. '#Called 1 time.*'
+ \ .. '# 1 \s*[0-9.]\+ for i in range(3)'
+ \ .. '# 3 \s*[0-9.]\+ \s*[0-9.]\+ Ref(i)'
+ \ .. '# 3 \s*[0-9.]\+ endfor', prof_lines)
+ call assert_match('FUNCTION <SNR>\d\+_Two().*'
+ \ .. '#Called 3 times.*'
+ \ .. '# 3 \s*[0-9.]\+ total += nr', prof_lines)
+
+ call delete('Xprofile_nested.log')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_prompt_buffer.vim b/src/testdir/test_prompt_buffer.vim
new file mode 100644
index 0000000..9998cc5
--- /dev/null
+++ b/src/testdir/test_prompt_buffer.vim
@@ -0,0 +1,255 @@
+" Tests for setting 'buftype' to "prompt"
+
+source check.vim
+CheckFeature channel
+
+source shared.vim
+source screendump.vim
+
+func CanTestPromptBuffer()
+ " We need to use a terminal window to be able to feed keys without leaving
+ " Insert mode.
+ CheckFeature terminal
+
+ " TODO: make the tests work on MS-Windows
+ CheckNotMSWindows
+endfunc
+
+func WriteScript(name)
+ call writefile([
+ \ 'func TextEntered(text)',
+ \ ' if a:text == "exit"',
+ \ ' " Reset &modified to allow the buffer to be closed.',
+ \ ' set nomodified',
+ \ ' stopinsert',
+ \ ' close',
+ \ ' else',
+ \ ' " Add the output above the current prompt.',
+ \ ' call append(line("$") - 1, "Command: \"" . a:text . "\"")',
+ \ ' " Reset &modified to allow the buffer to be closed.',
+ \ ' set nomodified',
+ \ ' call timer_start(20, {id -> TimerFunc(a:text)})',
+ \ ' endif',
+ \ 'endfunc',
+ \ '',
+ \ 'func TimerFunc(text)',
+ \ ' " Add the output above the current prompt.',
+ \ ' call append(line("$") - 1, "Result: \"" . a:text . "\"")',
+ \ ' " Reset &modified to allow the buffer to be closed.',
+ \ ' set nomodified',
+ \ 'endfunc',
+ \ '',
+ \ 'func SwitchWindows()',
+ \ ' call timer_start(0, {-> execute("wincmd p|wincmd p", "")})',
+ \ 'endfunc',
+ \ '',
+ \ 'call setline(1, "other buffer")',
+ \ 'set nomodified',
+ \ 'new',
+ \ 'set buftype=prompt',
+ \ 'call prompt_setcallback(bufnr(""), function("TextEntered"))',
+ \ 'eval bufnr("")->prompt_setprompt("cmd: ")',
+ \ 'startinsert',
+ \ ], a:name)
+endfunc
+
+func Test_prompt_basic()
+ call CanTestPromptBuffer()
+ let scriptName = 'XpromptscriptBasic'
+ call WriteScript(scriptName)
+
+ let buf = RunVimInTerminal('-S ' . scriptName, {})
+ call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, "hello\<CR>")
+ call WaitForAssert({-> assert_equal('cmd: hello', term_getline(buf, 1))})
+ call WaitForAssert({-> assert_equal('Command: "hello"', term_getline(buf, 2))})
+ call WaitForAssert({-> assert_equal('Result: "hello"', term_getline(buf, 3))})
+
+ call term_sendkeys(buf, "exit\<CR>")
+ call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+ call delete(scriptName)
+endfunc
+
+func Test_prompt_editing()
+ call CanTestPromptBuffer()
+ let scriptName = 'XpromptscriptEditing'
+ call WriteScript(scriptName)
+
+ let buf = RunVimInTerminal('-S ' . scriptName, {})
+ call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
+
+ let bs = "\<BS>"
+ call term_sendkeys(buf, "hello" . bs . bs)
+ call WaitForAssert({-> assert_equal('cmd: hel', term_getline(buf, 1))})
+
+ let left = "\<Left>"
+ call term_sendkeys(buf, left . left . left . bs . '-')
+ call WaitForAssert({-> assert_equal('cmd: -hel', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, "\<C-O>lz")
+ call WaitForAssert({-> assert_equal('cmd: -hzel', term_getline(buf, 1))})
+
+ let end = "\<End>"
+ call term_sendkeys(buf, end . "x")
+ call WaitForAssert({-> assert_equal('cmd: -hzelx', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, "\<C-U>exit\<CR>")
+ call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+ call delete(scriptName)
+endfunc
+
+func Test_prompt_switch_windows()
+ call CanTestPromptBuffer()
+ let scriptName = 'XpromptSwitchWindows'
+ call WriteScript(scriptName)
+
+ let buf = RunVimInTerminal('-S ' . scriptName, {'rows': 12})
+ call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
+ call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))})
+
+ call term_sendkeys(buf, "\<C-O>:call SwitchWindows()\<CR>")
+ call term_wait(buf, 50)
+ call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call term_wait(buf, 50)
+ call WaitForAssert({-> assert_match('^ *$', term_getline(buf, 12))})
+
+ call StopVimInTerminal(buf)
+ call delete(scriptName)
+endfunc
+
+func Test_prompt_garbage_collect()
+ func MyPromptCallback(x, text)
+ " NOP
+ endfunc
+ func MyPromptInterrupt(x)
+ " NOP
+ endfunc
+
+ new
+ set buftype=prompt
+ eval bufnr('')->prompt_setcallback(function('MyPromptCallback', [{}]))
+ eval bufnr('')->prompt_setinterrupt(function('MyPromptInterrupt', [{}]))
+ call test_garbagecollect_now()
+ " Must not crash
+ call feedkeys("\<CR>\<C-C>", 'xt')
+ call assert_true(v:true)
+
+ call assert_fails("call prompt_setcallback(bufnr(), [])", 'E921:')
+ call assert_equal(0, prompt_setcallback({}, ''))
+ call assert_fails("call prompt_setinterrupt(bufnr(), [])", 'E921:')
+ call assert_equal(0, prompt_setinterrupt({}, ''))
+
+ delfunc MyPromptCallback
+ bwipe!
+endfunc
+
+func Test_prompt_backspace()
+ new
+ set buftype=prompt
+ call feedkeys("A123456\<Left>\<BS>\<Esc>", 'xt')
+ call assert_equal('% 12346', getline(1))
+ bwipe!
+endfunc
+
+" Test for editing the prompt buffer
+func Test_prompt_buffer_edit()
+ new
+ set buftype=prompt
+ normal! i
+ call assert_beeps('normal! dd')
+ call assert_beeps('normal! ~')
+ call assert_beeps('normal! o')
+ call assert_beeps('normal! O')
+ call assert_beeps('normal! p')
+ call assert_beeps('normal! P')
+ call assert_beeps('normal! u')
+ call assert_beeps('normal! ra')
+ call assert_beeps('normal! s')
+ call assert_beeps('normal! S')
+ call assert_beeps("normal! \<C-A>")
+ call assert_beeps("normal! \<C-X>")
+ call assert_beeps("normal! dp")
+ call assert_beeps("normal! do")
+ " pressing CTRL-W in the prompt buffer should trigger the window commands
+ call assert_equal(1, winnr())
+ exe "normal A\<C-W>\<C-W>"
+ call assert_equal(2, winnr())
+ wincmd w
+ close!
+ call assert_equal(0, prompt_setprompt([], ''))
+endfunc
+
+func Test_prompt_buffer_getbufinfo()
+ new
+ call assert_equal('', prompt_getprompt('%'))
+ call assert_equal('', prompt_getprompt(bufnr('%')))
+ let another_buffer = bufnr('%')
+
+ set buftype=prompt
+ call assert_equal('% ', prompt_getprompt('%'))
+ call prompt_setprompt( bufnr( '%' ), 'This is a test: ' )
+ call assert_equal('This is a test: ', prompt_getprompt('%'))
+
+ call prompt_setprompt( bufnr( '%' ), '' )
+ call assert_equal('', '%'->prompt_getprompt())
+
+ call prompt_setprompt( bufnr( '%' ), 'Another: ' )
+ call assert_equal('Another: ', prompt_getprompt('%'))
+ let another = bufnr('%')
+
+ new
+
+ call assert_equal('', prompt_getprompt('%'))
+ call assert_equal('Another: ', prompt_getprompt(another))
+
+ " Doesn't exist
+ let buffers_before = len( getbufinfo() )
+ call assert_equal('', prompt_getprompt( bufnr('$') + 1))
+ call assert_equal(buffers_before, len( getbufinfo()))
+
+ " invalid type
+ call assert_fails('call prompt_getprompt({})', 'E728:')
+
+ %bwipe!
+endfunc
+
+func Test_prompt_while_writing_to_hidden_buffer()
+ call CanTestPromptBuffer()
+ CheckUnix
+
+ " Make a job continuously write to a hidden buffer, check that the prompt
+ " buffer is not affected.
+ let scriptName = 'XpromptscriptHiddenBuf'
+ let script =<< trim END
+ set buftype=prompt
+ call prompt_setprompt( bufnr(), 'cmd:' )
+ let job = job_start(['/bin/sh', '-c',
+ \ 'while true;
+ \ do echo line;
+ \ sleep 0.1;
+ \ done'], #{out_io: 'buffer', out_name: ''})
+ startinsert
+ END
+ eval script->writefile(scriptName, 'D')
+
+ let buf = RunVimInTerminal('-S ' .. scriptName, {})
+ call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, 'test')
+ call WaitForAssert({-> assert_equal('cmd:test', term_getline(buf, 1))})
+ call term_sendkeys(buf, 'test')
+ call WaitForAssert({-> assert_equal('cmd:testtest', term_getline(buf, 1))})
+ call term_sendkeys(buf, 'test')
+ call WaitForAssert({-> assert_equal('cmd:testtesttest', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_put.vim b/src/testdir/test_put.vim
new file mode 100644
index 0000000..a6cea74
--- /dev/null
+++ b/src/testdir/test_put.vim
@@ -0,0 +1,246 @@
+" Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc.
+
+source check.vim
+
+func Test_put_block()
+ new
+ call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x')
+ call feedkeys("\<C-V>y", 'x')
+ call feedkeys("gg0p", 'x')
+ call assert_equal("\u2500x", getline(1))
+ bwipe!
+endfunc
+
+func Test_put_char_block()
+ new
+ call setline(1, ['Line 1', 'Line 2'])
+ f Xfile_put
+ " visually select both lines and put the cursor at the top of the visual
+ " selection and then put the buffer name over it
+ exe "norm! G0\<c-v>ke\"%p"
+ call assert_equal(['Xfile_put 1', 'Xfile_put 2'], getline(1,2))
+ bw!
+endfunc
+
+func Test_put_char_block2()
+ new
+ call setreg('a', ' one ', 'v')
+ call setline(1, ['Line 1', '', 'Line 3', ''])
+ " visually select the first 3 lines and put register a over it
+ exe "norm! ggl\<c-v>2j2l\"ap"
+ call assert_equal(['L one 1', '', 'L one 3', ''], getline(1, 4))
+ " clean up
+ bw!
+endfunc
+
+func Test_put_lines()
+ new
+ let a = [ getreg('a'), getregtype('a') ]
+ call setline(1, ['Line 1', 'Line2', 'Line 3', ''])
+ exe 'norm! gg"add"AddG""p'
+ call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$'))
+ " clean up
+ bw!
+ eval a[0]->setreg('a', a[1])
+endfunc
+
+func Test_put_expr()
+ new
+ call setline(1, repeat(['A'], 6))
+ exec "1norm! \"=line('.')\<cr>p"
+ norm! j0.
+ norm! j0.
+ exec "4norm! \"=\<cr>P"
+ norm! j0.
+ norm! j0.
+ call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_put_fails_when_nomodifiable()
+ new
+ setlocal nomodifiable
+
+ normal! yy
+ call assert_fails(':put', 'E21:')
+ call assert_fails(':put!', 'E21:')
+ call assert_fails(':normal! p', 'E21:')
+ call assert_fails(':normal! gp', 'E21:')
+ call assert_fails(':normal! P', 'E21:')
+ call assert_fails(':normal! gP', 'E21:')
+
+ if has('mouse')
+ set mouse=n
+ call assert_fails('execute "normal! \<MiddleMouse>"', 'E21:')
+ set mouse&
+ endif
+
+ bwipeout!
+endfunc
+
+" A bug was discovered where the Normal mode put commands (e.g., "p") would
+" output duplicate error messages when invoked in a non-modifiable buffer.
+func Test_put_p_errmsg_nodup()
+ new
+ setlocal nomodifiable
+
+ normal! yy
+
+ func Capture_p_error()
+ redir => s:p_err
+ normal! p
+ redir END
+ endfunc
+
+ silent! call Capture_p_error()
+
+ " Error message output within a function should be three lines (the function
+ " name, the line number, and the error message).
+ call assert_equal(3, count(s:p_err, "\n"))
+
+ delfunction Capture_p_error
+ bwipeout!
+endfunc
+
+func Test_put_p_indent_visual()
+ new
+ call setline(1, ['select this text', 'select that text'])
+ " yank "that" from the second line
+ normal 2Gwvey
+ " select "this" in the first line and put
+ normal k0wve[p
+ call assert_equal('select that text', getline(1))
+ call assert_equal('select that text', getline(2))
+ bwipe!
+endfunc
+
+" Test for deleting all the contents of a buffer with a put
+func Test_put_visual_delete_all_lines()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ let @r = ''
+ normal! VG"rgp
+ call assert_equal(1, line('$'))
+ close!
+endfunc
+
+func Test_gp_with_count_leaves_cursor_at_end()
+ new
+ call setline(1, '<---->')
+ call setreg('@', "foo\nbar", 'c')
+ normal 1G3|3gp
+ call assert_equal([0, 4, 4, 0], getpos("."))
+ call assert_equal(['<--foo', 'barfoo', 'barfoo', 'bar-->'], getline(1, '$'))
+ call assert_equal([0, 4, 3, 0], getpos("']"))
+
+ bwipe!
+endfunc
+
+func Test_p_with_count_leaves_mark_at_end()
+ new
+ call setline(1, '<---->')
+ call setreg('@', "start\nend", 'c')
+ normal 1G3|3p
+ call assert_equal([0, 1, 4, 0], getpos("."))
+ call assert_equal(['<--start', 'endstart', 'endstart', 'end-->'], getline(1, '$'))
+ call assert_equal([0, 4, 3, 0], getpos("']"))
+
+ bwipe!
+endfunc
+
+func Test_very_large_count()
+ new
+ " total put-length (21474837 * 100) brings 32 bit int overflow
+ let @" = repeat('x', 100)
+ call assert_fails('norm 21474837p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_64bit()
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+
+ new
+ let @" = repeat('x', 100)
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_block()
+ new
+ " total put-length (21474837 * 100) brings 32 bit int overflow
+ call setline(1, repeat('x', 100))
+ exe "norm \<C-V>99ly"
+ call assert_fails('norm 21474837p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_very_large_count_block_64bit()
+ if v:sizeoflong < 8
+ throw 'Skipped: only works with 64 bit long ints'
+ endif
+
+ new
+ call setline(1, repeat('x', 100))
+ exe "norm \<C-V>$y"
+ call assert_fails('norm 999999999p', 'E1240:')
+ bwipe!
+endfunc
+
+func Test_put_above_first_line()
+ new
+ let @" = 'text'
+ silent! normal 0o00
+ 0put
+ call assert_equal('text', getline(1))
+ bwipe!
+endfunc
+
+func Test_multibyte_op_end_mark()
+ new
+ call setline(1, 'теÑÑ‚')
+ normal viwdp
+ call assert_equal([0, 1, 7, 0], getpos("'>"))
+ call assert_equal([0, 1, 7, 0], getpos("']"))
+
+ normal Vyp
+ call assert_equal([0, 1, v:maxcol, 0], getpos("'>"))
+ call assert_equal([0, 2, 7, 0], getpos("']"))
+ bwipe!
+endfunc
+
+" this was putting a mark before the start of a line
+func Test_put_empty_register()
+ new
+ norm yy
+ norm [Pi00ggv)s0
+ sil! norm [P
+ bwipe!
+endfunc
+
+" this was putting the end mark after the end of the line
+func Test_put_visual_mode()
+ edit! SomeNewBuffer
+ set selection=exclusive
+ exe "norm o\t"
+ m0
+ sil! norm  p p
+
+ bwipe!
+ set selection&
+endfunc
+
+func Test_put_visual_block_mode()
+ enew
+ exe "norm 0R\<CR>\<C-C>V"
+ sil exe "norm \<C-V>c \<MiddleDrag>"
+ set ve=all
+ sil norm vz=p
+
+ bwipe!
+ set ve=
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_python2.vim b/src/testdir/test_python2.vim
new file mode 100644
index 0000000..066b4bd
--- /dev/null
+++ b/src/testdir/test_python2.vim
@@ -0,0 +1,3788 @@
+" Test for python 2 commands.
+
+source check.vim
+CheckFeature python
+CheckFeature quickfix
+source shared.vim
+
+" NOTE: This will cause errors when run under valgrind.
+" This would require recompiling Python with:
+" ./configure --without-pymalloc
+" See http://svn.python.org/view/python/trunk/Misc/README.valgrind?view=markup
+"
+
+" This function should be called first. This sets up python functions used by
+" the other tests.
+func Test_AAA_python_setup()
+ py << trim EOF
+ import vim
+ import sys
+
+ def emsg(ei):
+ return ei[0].__name__ + ':' + repr(ei[1].args)
+
+ def ee(expr, g=globals(), l=locals()):
+ try:
+ exec(expr, g, l)
+ except:
+ ei = sys.exc_info()
+ msg = emsg(ei)
+ msg = msg.replace('TypeError:(\'argument 1 ', 'TypeError:(\'')
+ if expr.find('None') > -1:
+ msg = msg.replace('TypeError:(\'iteration over non-sequence\',)',
+ 'TypeError:("\'NoneType\' object is not iterable",)')
+ if expr.find('FailingNumber') > -1:
+ msg = msg.replace(', not \'FailingNumber\'', '').replace('"', '\'')
+ msg = msg.replace('TypeError:(\'iteration over non-sequence\',)',
+ 'TypeError:("\'FailingNumber\' object is not iterable",)')
+ if msg.find('(\'\'') > -1 or msg.find('(\'can\'t') > -1:
+ msg = msg.replace('(\'', '("').replace('\',)', '",)')
+ # Some Python versions say can't, others cannot.
+ if msg.find('can\'t') > -1:
+ msg = msg.replace('can\'t', 'cannot')
+ # Some Python versions use single quote, some double quote
+ if msg.find('"cannot ') > -1:
+ msg = msg.replace('"cannot ', '\'cannot ')
+ if msg.find(' attributes"') > -1:
+ msg = msg.replace(' attributes"', ' attributes\'')
+ if expr == 'fd(self=[])':
+ # HACK: PyMapping_Check changed meaning
+ msg = msg.replace('AttributeError:(\'keys\',)',
+ 'TypeError:(\'unable to convert list to vim dictionary\',)')
+ vim.current.buffer.append(expr + ':' + msg)
+ else:
+ vim.current.buffer.append(expr + ':NOT FAILED')
+ EOF
+endfunc
+
+func Test_pydo()
+ " Check deleting lines does not trigger an ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ pydo vim.command("%d_")
+ bwipe!
+
+ " Check switching to another buffer does not trigger an ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ pydo vim.command("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ bwipe!
+ bwipe!
+
+ " Try modifying a buffer with 'nomodifiable' set
+ set nomodifiable
+ call assert_fails('pydo toupper(line)', 'E21:')
+ set modifiable
+
+ " Invalid command
+ call AssertException(['pydo non_existing_cmd'],
+ \ "Vim(pydo):NameError: global name 'non_existing_cmd' is not defined")
+ call AssertException(["pydo raise Exception('test')"],
+ \ 'Vim(pydo):Exception: test')
+ call AssertException(["pydo {lambda}"],
+ \ 'Vim(pydo):SyntaxError: invalid syntax')
+endfunc
+
+func Test_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ pydo vim.current.window.cursor = (1, 5)
+ call assert_equal([1, 6], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 6], [line('.'), col('.')])
+endfunc
+
+func Test_vim_function()
+ " Check creating vim.Function object
+
+ func s:foo()
+ return matchstr(expand('<sfile>'), '<SNR>\zs\d\+_foo$')
+ endfunc
+ let name = '<SNR>' . s:foo()
+
+ try
+ py f = vim.bindeval('function("s:foo")')
+ call assert_equal(name, pyeval('f.name'))
+ catch
+ call assert_false(v:exception)
+ endtry
+
+ try
+ py f = vim.Function('\x80\xfdR' + vim.eval('s:foo()'))
+ call assert_equal(name, 'f.name'->pyeval())
+ catch
+ call assert_false(v:exception)
+ endtry
+
+ " Non-existing function attribute
+ call AssertException(["let x = pyeval('f.abc')"],
+ \ 'Vim(let):AttributeError: abc')
+
+ py del f
+ delfunc s:foo
+endfunc
+
+func Test_skipped_python_command_does_not_affect_pyxversion()
+ set pyxversion=0
+ if 0
+ python import vim
+ endif
+ call assert_equal(0, &pyxversion) " This assertion would have failed with Vim 8.0.0251. (pyxversion was introduced in 8.0.0251.)
+endfunc
+
+func _SetUpHiddenBuffer()
+ new
+ edit hidden
+ setlocal bufhidden=hide
+
+ enew
+ let lnum = 0
+ while lnum < 10
+ call append( 1, string( lnum ) )
+ let lnum = lnum + 1
+ endwhile
+ normal G
+
+ call assert_equal( line( '.' ), 11 )
+endfunc
+
+func _CleanUpHiddenBuffer()
+ bwipe! hidden
+ bwipe!
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Clear()
+ call _SetUpHiddenBuffer()
+ py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = None
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_List()
+ call _SetUpHiddenBuffer()
+ py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = [ 'test' ]
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Str()
+ call _SetUpHiddenBuffer()
+ py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = 'test'
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_ClearLine()
+ call _SetUpHiddenBuffer()
+ py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = None
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func _SetUpVisibleBuffer()
+ new
+ let lnum = 0
+ while lnum < 10
+ call append( 1, string( lnum ) )
+ let lnum = lnum + 1
+ endwhile
+ normal G
+ call assert_equal( line( '.' ), 11 )
+endfunc
+
+func Test_Write_To_Current_Buffer_Fixes_Cursor_Clear()
+ call _SetUpVisibleBuffer()
+
+ py vim.current.buffer[:] = None
+ call assert_equal( line( '.' ), 1 )
+
+ bwipe!
+endfunc
+
+func Test_Write_To_Current_Buffer_Fixes_Cursor_List()
+ call _SetUpVisibleBuffer()
+
+ py vim.current.buffer[:] = [ 'test' ]
+ call assert_equal( line( '.' ), 1 )
+
+ bwipe!
+endfunc
+
+func Test_Write_To_Current_Buffer_Fixes_Cursor_Str()
+ call _SetUpVisibleBuffer()
+
+ py vim.current.buffer[-1] = None
+ call assert_equal( line( '.' ), 10 )
+
+ bwipe!
+endfunc
+
+func Test_Catch_Exception_Message()
+ try
+ py raise RuntimeError( 'TEST' )
+ catch /.*/
+ call assert_match( '^Vim(.*):RuntimeError: TEST$', v:exception )
+ endtry
+endfunc
+
+" Test for various heredoc syntax
+func Test_python_heredoc()
+ python << END
+s='A'
+END
+ python <<
+s+='B'
+.
+ python << trim END
+ s+='C'
+ END
+ python << trim
+ s+='D'
+ .
+ python << trim eof
+ s+='E'
+ eof
+ call assert_equal('ABCDE', pyxeval('s'))
+endfunc
+
+" Test for the buffer range object
+func Test_python_range()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ py b = vim.current.buffer
+ py r = b.range(1, 3)
+ call assert_equal(0, pyeval('r.start'))
+ call assert_equal(2, pyeval('r.end'))
+ call assert_equal('one', pyeval('r[0]'))
+ call assert_equal('one', pyeval('r[-3]'))
+ call assert_equal('three', pyeval('r[-4]'))
+ call assert_equal(['two', 'three'], pyeval('r[1:]'))
+ py r[0] = 'green'
+ call assert_equal(['green', 'two', 'three'], getline(1, '$'))
+ py r[0:2] = ['red', 'blue']
+ call assert_equal(['red', 'blue', 'three'], getline(1, '$'))
+ call assert_equal(['start', 'end', '__members__'], pyeval('r.__members__'))
+
+ " try different invalid start/end index for the range slice
+ %d
+ call setline(1, ['one', 'two', 'three'])
+ py r[-10:1] = ["a"]
+ py r[10:12] = ["b"]
+ py r[-10:-9] = ["c"]
+ py r[1:0] = ["d"]
+ call assert_equal(['c', 'd', 'a', 'two', 'three', 'b'], getline(1, '$'))
+
+ " The following code used to trigger an ml_get error
+ %d
+ let x = pyeval('r[:]')
+
+ " Non-existing range attribute
+ call AssertException(["let x = pyeval('r.abc')"],
+ \ 'Vim(let):AttributeError: abc')
+
+ close!
+endfunc
+
+" Test for the python tabpage object
+func Test_python_tabpage()
+ tabnew
+ py t = vim.tabpages[1]
+ py wl = t.windows
+ tabclose
+ " Accessing a closed tabpage
+ call AssertException(["let n = pyeval('t.number')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted tab page')
+ call AssertException(["let n = pyeval('len(wl)')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted tab page')
+ call AssertException(["py w = wl[0]"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted tab page')
+ call AssertException(["py vim.current.tabpage = t"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted tab page')
+ call assert_match('<tabpage object (deleted)', pyeval('repr(t)'))
+ %bw!
+endfunc
+
+" Test for the python window object
+func Test_python_window()
+ " Test for setting the window height
+ 10new
+ py vim.current.window.height = 5
+ call assert_equal(5, winheight(0))
+ py vim.current.window.height = 3.2
+ call assert_equal(3, winheight(0))
+
+ " Test for setting the window width
+ 10vnew
+ py vim.current.window.width = 6
+ call assert_equal(6, winwidth(0))
+
+ " Try accessing a closed window
+ py w = vim.current.window
+ py wopts = w.options
+ close
+ " Access the attributes of a closed window
+ call AssertException(["let n = pyeval('w.number')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted window')
+ call AssertException(["py w.height = 5"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted window')
+ call AssertException(["py vim.current.window = w"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted window')
+ " Try to set one of the options of the closed window
+ " The following caused an ASAN failure
+ call AssertException(["py wopts['list'] = False"],
+ \ 'vim.error: attempt to refer to deleted window')
+ call assert_match('<window object (deleted)', pyeval("repr(w)"))
+ %bw!
+endfunc
+
+" Test for the python List object
+func Test_python_list()
+ let l = [1, 2]
+ py pl = vim.bindeval('l')
+ call assert_equal(['locked', '__members__'], pyeval('pl.__members__'))
+
+ " Try to convert a null List
+ call AssertException(["py t = vim.eval('test_null_list()')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a List with a null List item
+ call AssertException(["py t = vim.eval('[test_null_list()]')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to bind a null List variable (works because an empty list is used)
+ let cmds =<< trim END
+ let l = test_null_list()
+ py ll = vim.bindeval('l')
+ END
+ call AssertException(cmds, '')
+
+ let l = []
+ py l = vim.bindeval('l')
+ py f = vim.bindeval('function("strlen")')
+ " Extending List directly with different types
+ py l.extend([1, "as'd", [1, 2, f, {'a': 1}]])
+ call assert_equal([1, "as'd", [1, 2, function("strlen"), {'a': 1}]], l)
+ call assert_equal([1, 2, function("strlen"), {'a': 1}], l[-1])
+ call assert_fails('echo l[-4]', 'E684:')
+
+ " List assignment
+ py l[0] = 0
+ call assert_equal([0, "as'd", [1, 2, function("strlen"), {'a': 1}]], l)
+ py l[-2] = f
+ call assert_equal([0, function("strlen"), [1, 2, function("strlen"), {'a': 1}]], l)
+
+ " appending to a list
+ let l = [1, 2]
+ py ll = vim.bindeval('l')
+ py ll[2] = 8
+ call assert_equal([1, 2, 8], l)
+
+ " Using dict as an index
+ call AssertException(['py ll[{}] = 10'],
+ \ 'Vim(python):TypeError: index must be int or slice, not dict')
+endfunc
+
+" Test for the python Dict object
+func Test_python_dict()
+ let d = {}
+ py pd = vim.bindeval('d')
+ call assert_equal(['locked', 'scope', '__members__'],
+ \ pyeval('pd.__members__'))
+
+ " Try to convert a null Dict
+ call AssertException(["py t = vim.eval('test_null_dict()')"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a Dict with a null List value
+ call AssertException(["py t = vim.eval(\"{'a' : test_null_list()}\")"],
+ \ 'Vim(python):SystemError: error return without exception set')
+
+ " Try to convert a Dict with a null string key
+ py t = vim.eval("{test_null_string() : 10}")
+ call assert_fails("let d = pyeval('t')", 'E859:')
+
+ " Dict length
+ let d = {'a' : 10, 'b' : 20}
+ py d = vim.bindeval('d')
+ call assert_equal(2, pyeval('len(d)'))
+
+ " Deleting a non-existing key
+ call AssertException(["py del d['c']"], "Vim(python):KeyError: 'c'")
+endfunc
+
+" Extending Dictionary directly with different types
+func Test_python_dict_extend()
+ let d = {}
+ func d.f()
+ return 1
+ endfunc
+
+ py f = vim.bindeval('function("strlen")')
+ py << trim EOF
+ d = vim.bindeval('d')
+ d['1'] = 'asd'
+ d.update() # Must not do anything, including throwing errors
+ d.update(b = [1, 2, f])
+ d.update((('-1', {'a': 1}),))
+ d.update({'0': -1})
+ dk = d.keys()
+ dv = d.values()
+ di = d.items()
+ cmpfun = lambda a, b: cmp(repr(a), repr(b))
+ dk.sort(cmpfun)
+ dv.sort(cmpfun)
+ di.sort(cmpfun)
+ EOF
+
+ " Try extending a locked dictionary
+ lockvar d
+ call AssertException(["py d.update({'b' : 20})"],
+ \ 'Vim(python):vim.error: dictionary is locked')
+ unlockvar d
+
+ call assert_equal(1, pyeval("d['f'](self={})"))
+ call assert_equal("['-1', '0', '1', 'b', 'f']", pyeval('repr(dk)'))
+ call assert_equal("['asd', -1L, <vim.Function '1'>, <vim.dictionary object at >, <vim.list object at >]", substitute(pyeval('repr(dv)'),'0x\x\+','','g'))
+ call assert_equal("[('-1', <vim.dictionary object at >), ('0', -1L), ('1', 'asd'), ('b', <vim.list object at >), ('f', <vim.Function '1'>)]", substitute(pyeval('repr(di)'),'0x\x\+','','g'))
+ call assert_equal(['0', '1', 'b', 'f', '-1'], keys(d))
+ call assert_equal("[-1, 'asd', [1, 2, function('strlen')], function('1'), {'a': 1}]", string(values(d)))
+ py del dk
+ py del di
+ py del dv
+endfunc
+
+func Test_python_list_del_items()
+ " removing items with del
+ let l = [0, function("strlen"), [1, 2, function("strlen"), {'a': 1}]]
+ py l = vim.bindeval('l')
+ py del l[2]
+ call assert_equal("[0, function('strlen')]", string(l))
+
+ let l = range(8)
+ py l = vim.bindeval('l')
+ py del l[:3]
+ py del l[1:]
+ call assert_equal([3], l)
+
+ " removing items out of range: silently skip items that don't exist
+
+ " The following two ranges delete nothing as they match empty list:
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[2:1]
+ call assert_equal([0, 1, 2, 3], l)
+ py del l[2:2]
+ call assert_equal([0, 1, 2, 3], l)
+ py del l[2:3]
+ call assert_equal([0, 1, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[2:4]
+ call assert_equal([0, 1], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[2:5]
+ call assert_equal([0, 1], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[2:6]
+ call assert_equal([0, 1], l)
+
+ " The following two ranges delete nothing as they match empty list:
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[-1:2]
+ call assert_equal([0, 1, 2, 3], l)
+ py del l[-2:2]
+ call assert_equal([0, 1, 2, 3], l)
+ py del l[-3:2]
+ call assert_equal([0, 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[-4:2]
+ call assert_equal([2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[-5:2]
+ call assert_equal([2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[-6:2]
+ call assert_equal([2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[::2]
+ call assert_equal([1, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[3:0:-2]
+ call assert_equal([0, 2], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py del l[2:4:-2]
+ let l = [0, 1, 2, 3]
+endfunc
+
+func Test_python_dict_del_items()
+ let d = eval("{'0' : -1, '1' : 'asd', 'b' : [1, 2, function('strlen')], 'f' : function('min'), '-1' : {'a': 1}}")
+ py d = vim.bindeval('d')
+ py del d['-1']
+ py del d['f']
+ call assert_equal([1, 2, function('strlen')], pyeval('d.get(''b'', 1)'))
+ call assert_equal([1, 2, function('strlen')], pyeval('d.pop(''b'')'))
+ call assert_equal(1, pyeval('d.get(''b'', 1)'))
+ call assert_equal('asd', pyeval('d.pop(''1'', 2)'))
+ call assert_equal(2, pyeval('d.pop(''1'', 2)'))
+ call assert_equal('True', pyeval('repr(d.has_key(''0''))'))
+ call assert_equal('False', pyeval('repr(d.has_key(''1''))'))
+ call assert_equal('True', pyeval('repr(''0'' in d)'))
+ call assert_equal('False', pyeval('repr(''1'' in d)'))
+ call assert_equal("['0']", pyeval('repr(list(iter(d)))'))
+ call assert_equal({'0' : -1}, d)
+ call assert_equal("('0', -1L)", pyeval('repr(d.popitem())'))
+ call assert_equal('None', pyeval('repr(d.get(''0''))'))
+ call assert_equal('[]', pyeval('repr(list(iter(d)))'))
+endfunc
+
+" Slice assignment to a list
+func Test_python_slice_assignment()
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py l[0:0] = ['a']
+ call assert_equal(['a', 0, 1, 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py l[1:2] = ['b']
+ call assert_equal([0, 'b', 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py l[2:4] = ['c']
+ call assert_equal([0, 1, 'c'], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py l[4:4] = ['d']
+ call assert_equal([0, 1, 2, 3, 'd'], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py l[-1:2] = ['e']
+ call assert_equal([0, 1, 2, 'e', 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py l[-10:2] = ['f']
+ call assert_equal(['f', 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ py l[2:-10] = ['g']
+ call assert_equal([0, 1, 'g', 2, 3], l)
+
+ let l = []
+ py l = vim.bindeval('l')
+ py l[0:0] = ['h']
+ call assert_equal(['h'], l)
+
+ let l = range(8)
+ py l = vim.bindeval('l')
+ py l[2:6:2] = [10, 20]
+ call assert_equal([0, 1, 10, 3, 20, 5, 6, 7], l)
+
+ let l = range(8)
+ py l = vim.bindeval('l')
+ py l[6:2:-2] = [10, 20]
+ call assert_equal([0, 1, 2, 3, 20, 5, 10, 7], l)
+
+ let l = range(8)
+ py l = vim.bindeval('l')
+ py l[6:2] = ()
+ call assert_equal([0, 1, 2, 3, 4, 5, 6, 7], l)
+
+ let l = range(8)
+ py l = vim.bindeval('l')
+ py l[6:2:1] = ()
+ call assert_equal([0, 1, 2, 3, 4, 5, 6, 7], l)
+
+ let l = range(8)
+ py l = vim.bindeval('l')
+ py l[2:2:1] = ()
+ call assert_equal([0, 1, 2, 3, 4, 5, 6, 7], l)
+
+ call AssertException(["py x = l[10:11:0]"],
+ \ "Vim(python):ValueError: slice step cannot be zero")
+endfunc
+
+" Locked variables
+func Test_python_lockedvar()
+ new
+ py cb = vim.current.buffer
+ let l = [0, 1, 2, 3]
+ py l = vim.bindeval('l')
+ lockvar! l
+ py << trim EOF
+ try:
+ l[2]='i'
+ except vim.error:
+ cb.append('l[2] threw vim.error: ' + emsg(sys.exc_info()))
+ EOF
+ call assert_equal(['', "l[2] threw vim.error: error:('list is locked',)"],
+ \ getline(1, '$'))
+
+ " Try to concatenate a locked list
+ call AssertException(['py l += [4, 5]'],
+ \ 'Vim(python):vim.error: list is locked')
+
+ call assert_equal([0, 1, 2, 3], l)
+ unlockvar! l
+ close!
+endfunc
+
+" Test for calling a function
+func Test_python_function_call()
+ func New(...)
+ return ['NewStart'] + a:000 + ['NewEnd']
+ endfunc
+
+ func DictNew(...) dict
+ return ['DictNewStart'] + a:000 + ['DictNewEnd', self]
+ endfunc
+
+ new
+ let l = [function('New'), function('DictNew')]
+ py l = vim.bindeval('l')
+ py l.extend(list(l[0](1, 2, 3)))
+ call assert_equal([function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd'], l)
+ py l.extend(list(l[1](1, 2, 3, self={'a': 'b'})))
+ call assert_equal([function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}], l)
+ py l.extend([l[0].name])
+ call assert_equal([function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}, 'New'], l)
+ py ee('l[1](1, 2, 3)')
+ call assert_equal("l[1](1, 2, 3):error:('Vim:E725: Calling dict function without Dictionary: DictNew',)", getline(2))
+ %d
+ py f = l[0]
+ delfunction New
+ py ee('f(1, 2, 3)')
+ call assert_equal("f(1, 2, 3):error:('Vim:E117: Unknown function: New',)", getline(2))
+ close!
+ delfunction DictNew
+endfunc
+
+func Test_python_float()
+ let l = [0.0]
+ py l = vim.bindeval('l')
+ py l.extend([0.0])
+ call assert_equal([0.0, 0.0], l)
+endfunc
+
+" Test for Dict key errors
+func Test_python_dict_key_error()
+ let messages = []
+ py << trim EOF
+ d = vim.bindeval('{}')
+ m = vim.bindeval('messages')
+ def em(expr, g=globals(), l=locals()):
+ try:
+ exec(expr, g, l)
+ except:
+ m.extend([sys.exc_type.__name__])
+
+ em('d["abc1"]')
+ em('d["abc1"]="\\0"')
+ em('d["abc1"]=vim')
+ em('d[""]=1')
+ em('d["a\\0b"]=1')
+ em('d[u"a\\0b"]=1')
+ em('d.pop("abc1")')
+ em('d.popitem()')
+ del em
+ del m
+ EOF
+
+ call assert_equal(['KeyError', 'TypeError', 'TypeError', 'ValueError',
+ \ 'TypeError', 'TypeError', 'KeyError', 'KeyError'], messages)
+ unlet messages
+endfunc
+
+" Test for locked and scope attributes
+func Test_python_lock_scope_attr()
+ let d = {} | let dl = {} | lockvar dl
+ let res = []
+ for s in split("d dl v: g:")
+ let name = tr(s, ':', 's')
+ execute 'py ' .. name .. ' = vim.bindeval("' .. s .. '")'
+ call add(res, s .. ' : ' .. join(map(['locked', 'scope'],
+ \ 'v:val .. ":" .. pyeval(name .. "." .. v:val)'), ';'))
+ endfor
+ call assert_equal(['d : locked:0;scope:0', 'dl : locked:1;scope:0',
+ \ 'v: : locked:2;scope:1', 'g: : locked:0;scope:2'], res)
+
+ silent! let d.abc2 = 1
+ silent! let dl.abc3 = 1
+ py d.locked = True
+ py dl.locked = False
+ silent! let d.def = 1
+ silent! let dl.def = 1
+ call assert_equal({'abc2': 1}, d)
+ call assert_equal({'def': 1}, dl)
+ unlet d dl
+
+ let l = [] | let ll = [] | lockvar ll
+ let res = []
+ for s in split("l ll")
+ let name = tr(s, ':', 's')
+ execute 'py ' .. name .. '=vim.bindeval("' .. s .. '")'
+ call add(res, s .. ' : locked:' .. pyeval(name .. '.locked'))
+ endfor
+ call assert_equal(['l : locked:0', 'll : locked:1'], res)
+
+ silent! call extend(l, [0])
+ silent! call extend(ll, [0])
+ py l.locked = True
+ py ll.locked = False
+ silent! call extend(l, [1])
+ silent! call extend(ll, [1])
+ call assert_equal([0], l)
+ call assert_equal([1], ll)
+ unlet l ll
+
+ " Try changing an attribute of a fixed list
+ py a = vim.bindeval('v:argv')
+ call AssertException(['py a.locked = 0'],
+ \ 'Vim(python):TypeError: cannot modify fixed list')
+endfunc
+
+" Test for pyeval()
+func Test_python_pyeval()
+ let l = pyeval('range(3)')
+ call assert_equal([0, 1, 2], l)
+
+ let d = pyeval('{"a": "b", "c": 1, "d": ["e"]}')
+ call assert_equal([['a', 'b'], ['c', 1], ['d', ['e']]], sort(items(d)))
+
+ let v:errmsg = ''
+ call assert_equal(v:none, pyeval('None'))
+ call assert_equal('', v:errmsg)
+
+ py v = vim.eval('test_null_function()')
+ call assert_equal(v:none, pyeval('v'))
+
+ call assert_equal(0.0, pyeval('0.0'))
+
+ " Evaluate an invalid values
+ call AssertException(['let v = pyeval(''"\0"'')'], 'E859:')
+ call AssertException(['let v = pyeval(''{"\0" : 1}'')'], 'E859:')
+ call AssertException(['let v = pyeval("undefined_name")'],
+ \ "Vim(let):NameError: name 'undefined_name' is not defined")
+ call AssertException(['let v = pyeval("vim")'], 'E859:')
+endfunc
+
+" Test for vim.bindeval()
+func Test_python_vim_bindeval()
+ " Float
+ let f = 3.14
+ py f = vim.bindeval('f')
+ call assert_equal(3.14, pyeval('f'))
+
+ " Blob
+ let b = 0z12
+ py b = vim.bindeval('b')
+ call assert_equal("\x12", pyeval('b'))
+
+ " Bool
+ call assert_equal(1, pyeval("vim.bindeval('v:true')"))
+ call assert_equal(0, pyeval("vim.bindeval('v:false')"))
+ call assert_equal(v:none, pyeval("vim.bindeval('v:null')"))
+ call assert_equal(v:none, pyeval("vim.bindeval('v:none')"))
+
+ " channel/job
+ if has('channel')
+ call assert_equal(v:none, pyeval("vim.bindeval('test_null_channel()')"))
+ endif
+ if has('job')
+ call assert_equal(v:none, pyeval("vim.bindeval('test_null_job()')"))
+ endif
+endfunc
+
+" threading
+" Running pydo command (Test_pydo) before this test, stops the python thread
+" from running. So this test should be run before the pydo test
+func Test_aaa_python_threading()
+ let l = [0]
+ py l = vim.bindeval('l')
+ py << trim EOF
+ import threading
+ import time
+
+ class T(threading.Thread):
+ def __init__(self):
+ threading.Thread.__init__(self)
+ self.t = 0
+ self.running = True
+
+ def run(self):
+ while self.running:
+ self.t += 1
+ time.sleep(0.1)
+
+ t = T()
+ del T
+ t.start()
+ EOF
+
+ sleep 1
+ py t.running = False
+ py t.join()
+
+ " Check if the background thread is working. Count should be 10, but on a
+ " busy system (AppVeyor) it can be much lower.
+ py l[0] = t.t > 4
+ py del time
+ py del threading
+ py del t
+ call assert_equal([1], l)
+endfunc
+
+" settrace
+func Test_python_settrace()
+ let l = []
+ py l = vim.bindeval('l')
+ py << trim EOF
+ import sys
+
+ def traceit(frame, event, arg):
+ global l
+ if event == "line":
+ l.extend([frame.f_lineno])
+ return traceit
+
+ def trace_main():
+ for i in range(5):
+ pass
+ EOF
+ py sys.settrace(traceit)
+ py trace_main()
+ py sys.settrace(None)
+ py del traceit
+ py del trace_main
+ call assert_equal([1, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 1], l)
+endfunc
+
+" Slice
+func Test_python_list_slice()
+ py ll = vim.bindeval('[0, 1, 2, 3, 4, 5]')
+ py l = ll[:4]
+ call assert_equal([0, 1, 2, 3], pyeval('l'))
+ py l = ll[2:]
+ call assert_equal([2, 3, 4, 5], pyeval('l'))
+ py l = ll[:-4]
+ call assert_equal([0, 1], pyeval('l'))
+ py l = ll[-2:]
+ call assert_equal([4, 5], pyeval('l'))
+ py l = ll[2:4]
+ call assert_equal([2, 3], pyeval('l'))
+ py l = ll[4:2]
+ call assert_equal([], pyeval('l'))
+ py l = ll[-4:-2]
+ call assert_equal([2, 3], pyeval('l'))
+ py l = ll[-2:-4]
+ call assert_equal([], pyeval('l'))
+ py l = ll[:]
+ call assert_equal([0, 1, 2, 3, 4, 5], pyeval('l'))
+ py l = ll[0:6]
+ call assert_equal([0, 1, 2, 3, 4, 5], pyeval('l'))
+ py l = ll[-10:10]
+ call assert_equal([0, 1, 2, 3, 4, 5], pyeval('l'))
+ py l = ll[4:2:-1]
+ call assert_equal([4, 3], pyeval('l'))
+ py l = ll[::2]
+ call assert_equal([0, 2, 4], pyeval('l'))
+ py l = ll[4:2:1]
+ call assert_equal([], pyeval('l'))
+
+ " Error case: Use an invalid index
+ call AssertException(['py ll[-10] = 5'], 'Vim(python):vim.error: internal error:')
+
+ " Use a step value of 0
+ call AssertException(['py ll[0:3:0] = [1, 2, 3]'],
+ \ 'Vim(python):ValueError: slice step cannot be zero')
+
+ " Error case: Invalid slice type
+ call AssertException(["py x = ll['abc']"],
+ \ 'Vim(python):TypeError: index must be int or slice, not str')
+ py del l
+
+ " Error case: List with a null list item
+ let l = [test_null_list()]
+ py ll = vim.bindeval('l')
+ call AssertException(["py x = ll[:]"],
+ \ 'Vim(python):SystemError: error return without exception set')
+endfunc
+
+" Vars
+func Test_python_vars()
+ let g:foo = 'bac'
+ let w:abc3 = 'def'
+ let b:baz = 'bar'
+ let t:bar = 'jkl'
+ try
+ throw "Abc"
+ catch /Abc/
+ call assert_equal('Abc', pyeval('vim.vvars[''exception'']'))
+ endtry
+ call assert_equal('bac', pyeval('vim.vars[''foo'']'))
+ call assert_equal('def', pyeval('vim.current.window.vars[''abc3'']'))
+ call assert_equal('bar', pyeval('vim.current.buffer.vars[''baz'']'))
+ call assert_equal('jkl', pyeval('vim.current.tabpage.vars[''bar'']'))
+endfunc
+
+" Options
+" paste: boolean, global
+" previewheight number, global
+" operatorfunc: string, global
+" number: boolean, window-local
+" numberwidth: number, window-local
+" colorcolumn: string, window-local
+" statusline: string, window-local/global
+" autoindent: boolean, buffer-local
+" shiftwidth: number, buffer-local
+" omnifunc: string, buffer-local
+" preserveindent: boolean, buffer-local/global
+" path: string, buffer-local/global
+func Test_python_opts()
+ let g:res = []
+ let g:bufs = [bufnr('%')]
+ new
+ let g:bufs += [bufnr('%')]
+ vnew
+ let g:bufs += [bufnr('%')]
+ wincmd j
+ vnew
+ let g:bufs += [bufnr('%')]
+ wincmd l
+
+ func RecVars(opt)
+ let gval = string(eval('&g:' .. a:opt))
+ let wvals = join(map(range(1, 4),
+ \ 'v:val .. ":" .. string(getwinvar(v:val, "&" .. a:opt))'))
+ let bvals = join(map(copy(g:bufs),
+ \ 'v:val .. ":" .. string(getbufvar(v:val, "&" .. a:opt))'))
+ call add(g:res, ' G: ' .. gval)
+ call add(g:res, ' W: ' .. wvals)
+ call add(g:res, ' B: ' .. wvals)
+ endfunc
+
+ py << trim EOF
+ def e(s, g=globals(), l=locals()):
+ try:
+ exec(s, g, l)
+ except:
+ vim.command('return ' + repr(sys.exc_type.__name__))
+
+ def ev(s, g=globals(), l=locals()):
+ try:
+ return eval(s, g, l)
+ except:
+ vim.command('let exc=' + repr(sys.exc_type.__name__))
+ return 0
+ EOF
+
+ func E(s)
+ python e(vim.eval('a:s'))
+ endfunc
+
+ func Ev(s)
+ let r = pyeval('ev(vim.eval("a:s"))')
+ if exists('exc')
+ throw exc
+ endif
+ return r
+ endfunc
+
+ py gopts1 = vim.options
+ py wopts1 = vim.windows[2].options
+ py wopts2 = vim.windows[0].options
+ py wopts3 = vim.windows[1].options
+ py bopts1 = vim.buffers[vim.bindeval("g:bufs")[2]].options
+ py bopts2 = vim.buffers[vim.bindeval("g:bufs")[1]].options
+ py bopts3 = vim.buffers[vim.bindeval("g:bufs")[0]].options
+ call add(g:res, 'wopts iters equal: ' ..
+ \ pyeval('list(wopts1) == list(wopts2)'))
+ call add(g:res, 'bopts iters equal: ' ..
+ \ pyeval('list(bopts1) == list(bopts2)'))
+ py gset = set(iter(gopts1))
+ py wset = set(iter(wopts1))
+ py bset = set(iter(bopts1))
+
+ set path=.,..,,
+ let lst = []
+ let lst += [['paste', 1, 0, 1, 2, 1, 1, 0]]
+ let lst += [['previewheight', 5, 1, 6, 'a', 0, 1, 0]]
+ let lst += [['operatorfunc', 'A', 'B', 'C', 2, 0, 1, 0]]
+ let lst += [['number', 0, 1, 1, 0, 1, 0, 1]]
+ let lst += [['numberwidth', 2, 3, 5, -100, 0, 0, 1]]
+ let lst += [['colorcolumn', '+1', '+2', '+3', 'abc4', 0, 0, 1]]
+ let lst += [['statusline', '1', '2', '4', 0, 0, 1, 1]]
+ let lst += [['autoindent', 0, 1, 1, 2, 1, 0, 2]]
+ let lst += [['shiftwidth', 0, 2, 1, 3, 0, 0, 2]]
+ let lst += [['omnifunc', 'A', 'B', 'C', 1, 0, 0, 2]]
+ let lst += [['preserveindent', 0, 1, 1, 2, 1, 1, 2]]
+ let lst += [['path', '.,,', ',,', '.', 0, 0, 1, 2]]
+ for [oname, oval1, oval2, oval3, invval, bool, global, local] in lst
+ py oname = vim.eval('oname')
+ py oval1 = vim.bindeval('oval1')
+ py oval2 = vim.bindeval('oval2')
+ py oval3 = vim.bindeval('oval3')
+ if invval is 0 || invval is 1
+ py invval = bool(vim.bindeval('invval'))
+ else
+ py invval = vim.bindeval('invval')
+ endif
+ if bool
+ py oval1 = bool(oval1)
+ py oval2 = bool(oval2)
+ py oval3 = bool(oval3)
+ endif
+ call add(g:res, '>>> ' .. oname)
+ call add(g:res, ' g/w/b:' .. pyeval('oname in gset') .. '/' ..
+ \ pyeval('oname in wset') .. '/' .. pyeval('oname in bset'))
+ call add(g:res, ' g/w/b (in):' .. pyeval('oname in gopts1') .. '/' ..
+ \ pyeval('oname in wopts1') .. '/' .. pyeval('oname in bopts1'))
+ for v in ['gopts1', 'wopts1', 'bopts1']
+ try
+ call add(g:res, ' p/' .. v .. ': ' .. Ev('repr(' .. v .. '[''' .. oname .. '''])'))
+ catch
+ call add(g:res, ' p/' .. v .. '! ' .. v:exception)
+ endtry
+ let r = E(v .. '[''' .. oname .. ''']=invval')
+ if r isnot 0
+ call add(g:res, ' inv: ' .. string(invval) .. '! ' .. r)
+ endif
+ for vv in (v is# 'gopts1' ? [v] : [v, v[:-2] .. '2', v[:-2] .. '3'])
+ let val = substitute(vv, '^.opts', 'oval', '')
+ let r = E(vv .. '[''' .. oname .. ''']=' .. val)
+ if r isnot 0
+ call add(g:res, ' ' .. vv .. '! ' .. r)
+ endif
+ endfor
+ endfor
+ call RecVars(oname)
+ for v in ['wopts3', 'bopts3']
+ let r = E('del ' .. v .. '["' .. oname .. '"]')
+ if r isnot 0
+ call add(g:res, ' del ' .. v .. '! ' .. r)
+ endif
+ endfor
+ call RecVars(oname)
+ endfor
+ delfunction RecVars
+ delfunction E
+ delfunction Ev
+ py del ev
+ py del e
+ only
+ for buf in g:bufs[1:]
+ execute 'bwipeout!' buf
+ endfor
+ py del gopts1
+ py del wopts1
+ py del wopts2
+ py del wopts3
+ py del bopts1
+ py del bopts2
+ py del bopts3
+ py del oval1
+ py del oval2
+ py del oval3
+ py del oname
+ py del invval
+
+ let expected =<< trim END
+ wopts iters equal: 1
+ bopts iters equal: 1
+ >>> paste
+ g/w/b:1/0/0
+ g/w/b (in):1/0/0
+ p/gopts1: False
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1! KeyError
+ inv: 2! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 1
+ W: 1:1 2:1 3:1 4:1
+ B: 1:1 2:1 3:1 4:1
+ del wopts3! KeyError
+ del bopts3! KeyError
+ G: 1
+ W: 1:1 2:1 3:1 4:1
+ B: 1:1 2:1 3:1 4:1
+ >>> previewheight
+ g/w/b:1/0/0
+ g/w/b (in):1/0/0
+ p/gopts1: 12
+ inv: 'a'! TypeError
+ p/wopts1! KeyError
+ inv: 'a'! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1! KeyError
+ inv: 'a'! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 5
+ W: 1:5 2:5 3:5 4:5
+ B: 1:5 2:5 3:5 4:5
+ del wopts3! KeyError
+ del bopts3! KeyError
+ G: 5
+ W: 1:5 2:5 3:5 4:5
+ B: 1:5 2:5 3:5 4:5
+ >>> operatorfunc
+ g/w/b:1/0/0
+ g/w/b (in):1/0/0
+ p/gopts1: ''
+ inv: 2! TypeError
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1! KeyError
+ inv: 2! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 'A'
+ W: 1:'A' 2:'A' 3:'A' 4:'A'
+ B: 1:'A' 2:'A' 3:'A' 4:'A'
+ del wopts3! KeyError
+ del bopts3! KeyError
+ G: 'A'
+ W: 1:'A' 2:'A' 3:'A' 4:'A'
+ B: 1:'A' 2:'A' 3:'A' 4:'A'
+ >>> number
+ g/w/b:0/1/0
+ g/w/b (in):0/1/0
+ p/gopts1! KeyError
+ inv: 0! KeyError
+ gopts1! KeyError
+ p/wopts1: False
+ p/bopts1! KeyError
+ inv: 0! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 0
+ W: 1:1 2:1 3:0 4:0
+ B: 1:1 2:1 3:0 4:0
+ del wopts3! ValueError
+ del bopts3! KeyError
+ G: 0
+ W: 1:1 2:1 3:0 4:0
+ B: 1:1 2:1 3:0 4:0
+ >>> numberwidth
+ g/w/b:0/1/0
+ g/w/b (in):0/1/0
+ p/gopts1! KeyError
+ inv: -100! KeyError
+ gopts1! KeyError
+ p/wopts1: 4
+ inv: -100! error
+ p/bopts1! KeyError
+ inv: -100! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 4
+ W: 1:3 2:5 3:2 4:4
+ B: 1:3 2:5 3:2 4:4
+ del wopts3! ValueError
+ del bopts3! KeyError
+ G: 4
+ W: 1:3 2:5 3:2 4:4
+ B: 1:3 2:5 3:2 4:4
+ >>> colorcolumn
+ g/w/b:0/1/0
+ g/w/b (in):0/1/0
+ p/gopts1! KeyError
+ inv: 'abc4'! KeyError
+ gopts1! KeyError
+ p/wopts1: ''
+ inv: 'abc4'! error
+ p/bopts1! KeyError
+ inv: 'abc4'! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: ''
+ W: 1:'+2' 2:'+3' 3:'+1' 4:''
+ B: 1:'+2' 2:'+3' 3:'+1' 4:''
+ del wopts3! ValueError
+ del bopts3! KeyError
+ G: ''
+ W: 1:'+2' 2:'+3' 3:'+1' 4:''
+ B: 1:'+2' 2:'+3' 3:'+1' 4:''
+ >>> statusline
+ g/w/b:1/1/0
+ g/w/b (in):1/1/0
+ p/gopts1: ''
+ inv: 0! TypeError
+ p/wopts1: None
+ inv: 0! TypeError
+ p/bopts1! KeyError
+ inv: 0! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: '1'
+ W: 1:'2' 2:'4' 3:'1' 4:'1'
+ B: 1:'2' 2:'4' 3:'1' 4:'1'
+ del bopts3! KeyError
+ G: '1'
+ W: 1:'2' 2:'1' 3:'1' 4:'1'
+ B: 1:'2' 2:'1' 3:'1' 4:'1'
+ >>> autoindent
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 2! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: False
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ >>> shiftwidth
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 3! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 3! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: 8
+ G: 8
+ W: 1:0 2:2 3:8 4:1
+ B: 1:0 2:2 3:8 4:1
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: 8
+ W: 1:0 2:2 3:8 4:1
+ B: 1:0 2:2 3:8 4:1
+ >>> omnifunc
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 1! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 1! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: ''
+ inv: 1! TypeError
+ G: ''
+ W: 1:'A' 2:'B' 3:'' 4:'C'
+ B: 1:'A' 2:'B' 3:'' 4:'C'
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: ''
+ W: 1:'A' 2:'B' 3:'' 4:'C'
+ B: 1:'A' 2:'B' 3:'' 4:'C'
+ >>> preserveindent
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 2! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: False
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ >>> path
+ g/w/b:1/0/1
+ g/w/b (in):1/0/1
+ p/gopts1: '.,..,,'
+ inv: 0! TypeError
+ p/wopts1! KeyError
+ inv: 0! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: None
+ inv: 0! TypeError
+ G: '.,,'
+ W: 1:'.,,' 2:',,' 3:'.,,' 4:'.'
+ B: 1:'.,,' 2:',,' 3:'.,,' 4:'.'
+ del wopts3! KeyError
+ G: '.,,'
+ W: 1:'.,,' 2:',,' 3:'.,,' 4:'.,,'
+ B: 1:'.,,' 2:',,' 3:'.,,' 4:'.,,'
+ END
+
+ call assert_equal(expected, g:res)
+ unlet g:res
+
+ call assert_equal(0, pyeval("'' in vim.options"))
+
+ " use an empty key to index vim.options
+ call AssertException(["let v = pyeval(\"vim.options['']\")"],
+ \ 'Vim(let):ValueError: empty keys are not allowed')
+ call AssertException(["py vim.current.window.options[''] = 0"],
+ \ 'Vim(python):ValueError: empty keys are not allowed')
+ call AssertException(["py vim.current.window.options[{}] = 0"],
+ \ 'Vim(python):TypeError: expected str() or unicode() instance, but got dict')
+
+ " set one of the number options to a very large number
+ let cmd = ["py vim.options['previewheight'] = 9999999999999999"]
+ call AssertException(cmd, 'OverflowError:')
+
+ " unset a global-local string option
+ call AssertException(["py del vim.options['errorformat']"],
+ \ 'Vim(python):ValueError: unable to unset global option errorformat')
+endfunc
+
+" Test for vim.buffer object
+func Test_python_buffer()
+ new
+ call setline(1, "Hello\nWorld")
+ call assert_fails("let x = pyeval('vim.current.buffer[0]')", 'E859:')
+ %bw!
+
+ edit Xfile1
+ let bnr1 = bufnr()
+ py cb = vim.current.buffer
+ vnew Xfile2
+ let bnr2 = bufnr()
+ call setline(1, ['First line', 'Second line', 'Third line'])
+ py b = vim.current.buffer
+ wincmd w
+
+ " Test for getting lines from the buffer using a slice
+ call assert_equal(['First line'], pyeval('b[-10:1]'))
+ call assert_equal(['Third line'], pyeval('b[2:10]'))
+ call assert_equal([], pyeval('b[2:0]'))
+ call assert_equal([], pyeval('b[10:12]'))
+ call assert_equal([], pyeval('b[-10:-8]'))
+ call AssertException(["py x = b[0:3:0]"],
+ \ "Vim(python):TypeError: sequence index must be integer, not 'slice'")
+ call AssertException(["py b[0:3:0] = 'abc'"],
+ \ "Vim(python):TypeError: sequence index must be integer, not 'slice'")
+ call AssertException(["py x = b[{}]"],
+ \ "Vim(python):TypeError: sequence index must be integer, not 'dict'")
+ call AssertException(["py b[{}] = 'abc'"],
+ \ "Vim(python):TypeError: sequence index must be integer, not 'dict'")
+
+ " Test for getting lines using a range
+ call AssertException(["py x = b.range(0,3)[0:2:0]"],
+ \ "Vim(python):TypeError: sequence index must be integer, not 'slice'")
+ call AssertException(["py b.range(0,3)[0:2:0] = 'abc'"],
+ \ "Vim(python):TypeError: sequence index must be integer, not 'slice'")
+
+ " Tests BufferAppend and BufferItem
+ py cb.append(b[0])
+ call assert_equal(['First line'], getbufline(bnr1, 2))
+ %d
+
+ " Try to append using out-of-range line number
+ call AssertException(["py b.append('abc', 10)"],
+ \ 'Vim(python):IndexError: line number out of range')
+
+ " Append a non-string item
+ call AssertException(["py b.append([22])"],
+ \ 'Vim(python):TypeError: expected str() or unicode() instance, but got int')
+
+ " Tests BufferSlice and BufferAssSlice
+ py cb.append('abc5') # Will be overwritten
+ py cb[-1:] = b[:-2]
+ call assert_equal(['First line'], getbufline(bnr1, 2))
+ %d
+
+ " Test BufferLength and BufferAssSlice
+ py cb.append('def') # Will not be overwritten
+ py cb[len(cb):] = b[:]
+ call assert_equal(['def', 'First line', 'Second line', 'Third line'],
+ \ getbufline(bnr1, 2, '$'))
+ %d
+
+ " Test BufferAssItem and BufferMark
+ call setbufline(bnr1, 1, ['one', 'two', 'three'])
+ call cursor(1, 3)
+ normal ma
+ py cb.append('ghi') # Will be overwritten
+ py cb[-1] = repr((len(cb) - cb.mark('a')[0], cb.mark('a')[1]))
+ call assert_equal(['(3, 2)'], getbufline(bnr1, 4))
+ %d
+
+ " Test BufferRepr
+ py cb.append(repr(cb) + repr(b))
+ call assert_equal(['<buffer Xfile1><buffer Xfile2>'], getbufline(bnr1, 2))
+ %d
+
+ " Modify foreign buffer
+ py << trim EOF
+ b.append('foo')
+ b[0]='bar'
+ b[0:0]=['baz']
+ vim.command('call append("$", getbufline(%i, 1, "$"))' % b.number)
+ EOF
+ call assert_equal(['baz', 'bar', 'Second line', 'Third line', 'foo'],
+ \ getbufline(bnr2, 1, '$'))
+ %d
+
+ " Test assigning to name property
+ augroup BUFS
+ autocmd BufFilePost * python cb.append(vim.eval('expand("<abuf>")') + ':BufFilePost:' + vim.eval('bufnr("%")'))
+ autocmd BufFilePre * python cb.append(vim.eval('expand("<abuf>")') + ':BufFilePre:' + vim.eval('bufnr("%")'))
+ augroup END
+ py << trim EOF
+ import os
+ old_name = cb.name
+ cb.name = 'foo'
+ cb.append(cb.name[-11:].replace(os.path.sep, '/'))
+ b.name = 'bar'
+ cb.append(b.name[-11:].replace(os.path.sep, '/'))
+ cb.name = old_name
+ cb.append(cb.name[-14:].replace(os.path.sep, '/'))
+ del old_name
+ EOF
+ call assert_equal([bnr1 .. ':BufFilePre:' .. bnr1,
+ \ bnr1 .. ':BufFilePost:' .. bnr1,
+ \ 'testdir/foo',
+ \ bnr2 .. ':BufFilePre:' .. bnr2,
+ \ bnr2 .. ':BufFilePost:' .. bnr2,
+ \ 'testdir/bar',
+ \ bnr1 .. ':BufFilePre:' .. bnr1,
+ \ bnr1 .. ':BufFilePost:' .. bnr1,
+ \ 'testdir/Xfile1'], getbufline(bnr1, 2, '$'))
+ %d
+
+ " Test CheckBuffer
+ py << trim EOF
+ for _b in vim.buffers:
+ if _b is not cb:
+ vim.command('bwipeout! ' + str(_b.number))
+ del _b
+ cb.append('valid: b:%s, cb:%s' % (repr(b.valid), repr(cb.valid)))
+ EOF
+ call assert_equal('valid: b:False, cb:True', getline(2))
+ %d
+
+ py << trim EOF
+ for expr in ('b[1]','b[:] = ["A", "B"]','b[:]','b.append("abc6")', 'b.name = "!"'):
+ try:
+ exec(expr)
+ except vim.error:
+ pass
+ else:
+ # Usually a SEGV here
+ # Should not happen in any case
+ cb.append('No exception for ' + expr)
+ vim.command('cd .')
+ del b
+ EOF
+ call assert_equal([''], getline(1, '$'))
+
+ " Delete all the lines in a buffer
+ call setline(1, ['a', 'b', 'c'])
+ py vim.current.buffer[:] = []
+ call assert_equal([''], getline(1, '$'))
+
+ " Test for buffer marks
+ call assert_equal(v:none, pyeval("vim.current.buffer.mark('r')"))
+
+ " Test for modifying a 'nomodifiable' buffer
+ setlocal nomodifiable
+ call AssertException(["py vim.current.buffer[0] = 'abc'"],
+ \ "Vim(python):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py vim.current.buffer[0] = None"],
+ \ "Vim(python):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py vim.current.buffer[:] = None"],
+ \ "Vim(python):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py vim.current.buffer[:] = []"],
+ \ "Vim(python):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py vim.current.buffer.append('abc')"],
+ \ "Vim(python):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py vim.current.buffer.append([])"],
+ \ "Vim(python):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ setlocal modifiable
+
+ augroup BUFS
+ autocmd!
+ augroup END
+ augroup! BUFS
+ %bw!
+
+ " Range object for a deleted buffer
+ new Xpbuffile
+ call setline(1, ['one', 'two', 'three'])
+ py b = vim.current.buffer
+ py r = vim.current.buffer.range(0, 2)
+ call assert_equal('<range Xpbuffile (0:2)>', pyeval('repr(r)'))
+ %bw!
+ call AssertException(['py r[:] = []'],
+ \ 'Vim(python):vim.error: attempt to refer to deleted buffer')
+ call assert_match('<buffer object (deleted)', pyeval('repr(b)'))
+ call assert_match('<range object (for deleted buffer)', pyeval('repr(r)'))
+ call AssertException(["let n = pyeval('len(r)')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py r.append('abc')"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted buffer')
+
+ " object for a deleted buffer
+ call AssertException(["py b[0] = 'one'"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py b.append('one')"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["let n = pyeval('len(b)')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py pos = b.mark('a')"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py vim.current.buffer = b"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py rn = b.range(0, 2)"],
+ \ 'Vim(python):vim.error: attempt to refer to deleted buffer')
+endfunc
+
+" Test vim.buffers object
+func Test_python_buffers()
+ %bw!
+ edit Xpbuffile
+ py cb = vim.current.buffer
+ set hidden
+ edit a
+ buffer #
+ edit b
+ buffer #
+ edit c
+ buffer #
+ py << trim EOF
+ try:
+ from __builtin__ import next
+ except ImportError:
+ next = lambda o: o.next()
+ # Check GCing iterator that was not fully exhausted
+ i = iter(vim.buffers)
+ cb.append('i:' + str(next(i)))
+ # and also check creating more than one iterator at a time
+ i2 = iter(vim.buffers)
+ cb.append('i2:' + str(next(i2)))
+ cb.append('i:' + str(next(i)))
+ # The following should trigger GC and not cause any problems
+ del i
+ del i2
+ i3 = iter(vim.buffers)
+ cb.append('i3:' + str(next(i3)))
+ del i3
+ EOF
+ call assert_equal(['i:<buffer Xpbuffile>',
+ \ 'i2:<buffer Xpbuffile>', 'i:<buffer a>', 'i3:<buffer Xpbuffile>'],
+ \ getline(2, '$'))
+ %d
+
+ py << trim EOF
+ prevnum = 0
+ for b in vim.buffers:
+ # Check buffer order
+ if prevnum >= b.number:
+ cb.append('!!! Buffer numbers not in strictly ascending order')
+ # Check indexing: vim.buffers[number].number == number
+ cb.append(str(b.number) + ':' + repr(vim.buffers[b.number]) + \
+ '=' + repr(b))
+ prevnum = b.number
+ del prevnum
+
+ cb.append(str(len(vim.buffers)))
+ EOF
+ call assert_equal([bufnr('Xpbuffile') .. ':<buffer Xpbuffile>=<buffer Xpbuffile>',
+ \ bufnr('a') .. ':<buffer a>=<buffer a>',
+ \ bufnr('b') .. ':<buffer b>=<buffer b>',
+ \ bufnr('c') .. ':<buffer c>=<buffer c>', '4'], getline(2, '$'))
+ %d
+
+ py << trim EOF
+ bnums = list(map(lambda b: b.number, vim.buffers))[1:]
+
+ # Test wiping out buffer with existing iterator
+ i4 = iter(vim.buffers)
+ cb.append('i4:' + str(next(i4)))
+ vim.command('bwipeout! ' + str(bnums.pop(0)))
+ try:
+ next(i4)
+ except vim.error:
+ pass
+ else:
+ cb.append('!!!! No vim.error')
+ i4 = iter(vim.buffers)
+ vim.command('bwipeout! ' + str(bnums.pop(-1)))
+ vim.command('bwipeout! ' + str(bnums.pop(-1)))
+ cb.append('i4:' + str(next(i4)))
+ try:
+ next(i4)
+ except StopIteration:
+ cb.append('StopIteration')
+ del i4
+ del bnums
+ EOF
+ call assert_equal(['i4:<buffer Xpbuffile>',
+ \ 'i4:<buffer Xpbuffile>', 'StopIteration'], getline(2, '$'))
+ %bw!
+endfunc
+
+" Test vim.{tabpage,window}list and vim.{tabpage,window} objects
+func Test_python_tabpage_window()
+ %bw
+ edit Xpbuffile
+ py cb = vim.current.buffer
+ tabnew 0
+ tabnew 1
+ vnew a.1
+ tabnew 2
+ vnew a.2
+ vnew b.2
+ vnew c.2
+
+ call assert_equal(4, pyeval('vim.current.window.tabpage.number'))
+
+ py << trim EOF
+ cb.append('Number of tabs: ' + str(len(vim.tabpages)))
+ cb.append('Current tab pages:')
+ def W(w):
+ if repr(w).find('(unknown)') != -1:
+ return '<window object (unknown)>'
+ else:
+ return repr(w)
+
+ start = len(cb)
+
+ def Cursor(w):
+ if w.buffer is cb:
+ return repr((start - w.cursor[0], w.cursor[1]))
+ else:
+ return repr(w.cursor)
+
+ for t in vim.tabpages:
+ cb.append(' ' + repr(t) + '(' + str(t.number) + ')' + ': ' + \
+ str(len(t.windows)) + ' windows, current is ' + W(t.window))
+ cb.append(' Windows:')
+ for w in t.windows:
+ cb.append(' ' + W(w) + '(' + str(w.number) + ')' + \
+ ': displays buffer ' + repr(w.buffer) + \
+ '; cursor is at ' + Cursor(w))
+ # Other values depend on the size of the terminal, so they are checked
+ # partly:
+ for attr in ('height', 'row', 'width', 'col'):
+ try:
+ aval = getattr(w, attr)
+ if type(aval) is not long:
+ raise TypeError
+ if aval < 0:
+ raise ValueError
+ except Exception:
+ cb.append('!!!!!! Error while getting attribute ' + attr + \
+ ': ' + sys.exc_type.__name__)
+ del aval
+ del attr
+ w.cursor = (len(w.buffer), 0)
+ del W
+ del Cursor
+ cb.append('Number of windows in current tab page: ' + \
+ str(len(vim.windows)))
+ if list(vim.windows) != list(vim.current.tabpage.windows):
+ cb.append('!!!!!! Windows differ')
+ EOF
+
+ let expected =<< trim END
+ Number of tabs: 4
+ Current tab pages:
+ <tabpage 0>(1): 1 windows, current is <window object (unknown)>
+ Windows:
+ <window object (unknown)>(1): displays buffer <buffer Xpbuffile>; cursor is at (2, 0)
+ <tabpage 1>(2): 1 windows, current is <window object (unknown)>
+ Windows:
+ <window object (unknown)>(1): displays buffer <buffer 0>; cursor is at (1, 0)
+ <tabpage 2>(3): 2 windows, current is <window object (unknown)>
+ Windows:
+ <window object (unknown)>(1): displays buffer <buffer a.1>; cursor is at (1, 0)
+ <window object (unknown)>(2): displays buffer <buffer 1>; cursor is at (1, 0)
+ <tabpage 3>(4): 4 windows, current is <window 0>
+ Windows:
+ <window 0>(1): displays buffer <buffer c.2>; cursor is at (1, 0)
+ <window 1>(2): displays buffer <buffer b.2>; cursor is at (1, 0)
+ <window 2>(3): displays buffer <buffer a.2>; cursor is at (1, 0)
+ <window 3>(4): displays buffer <buffer 2>; cursor is at (1, 0)
+ Number of windows in current tab page: 4
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpbuffile'), 2, '$'))
+ %bw!
+endfunc
+
+" Test vim.current
+func Test_python_vim_current()
+ %bw
+ edit Xpbuffile
+ py cb = vim.current.buffer
+ tabnew 0
+ tabnew 1
+ vnew a.1
+ tabnew 2
+ vnew a.2
+ vnew b.2
+ vnew c.2
+
+ py << trim EOF
+ def H(o):
+ return repr(o)
+ cb.append('Current tab page: ' + repr(vim.current.tabpage))
+ cb.append('Current window: ' + repr(vim.current.window) + ': ' + \
+ H(vim.current.window) + ' is ' + H(vim.current.tabpage.window))
+ cb.append('Current buffer: ' + repr(vim.current.buffer) + ': ' + \
+ H(vim.current.buffer) + ' is ' + H(vim.current.window.buffer)+ \
+ ' is ' + H(vim.current.tabpage.window.buffer))
+ del H
+ EOF
+ let expected =<< trim END
+ Current tab page: <tabpage 3>
+ Current window: <window 0>: <window 0> is <window 0>
+ Current buffer: <buffer c.2>: <buffer c.2> is <buffer c.2> is <buffer c.2>
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpbuffile'), 2, '$'))
+ call deletebufline(bufnr('Xpbuffile'), 1, '$')
+
+ " Assigning: fails
+ py << trim EOF
+ try:
+ vim.current.window = vim.tabpages[0].window
+ except ValueError:
+ cb.append('ValueError at assigning foreign tab window')
+
+ for attr in ('window', 'tabpage', 'buffer'):
+ try:
+ setattr(vim.current, attr, None)
+ except TypeError:
+ cb.append('Type error at assigning None to vim.current.' + attr)
+ del attr
+ EOF
+
+ let expected =<< trim END
+ ValueError at assigning foreign tab window
+ Type error at assigning None to vim.current.window
+ Type error at assigning None to vim.current.tabpage
+ Type error at assigning None to vim.current.buffer
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpbuffile'), 2, '$'))
+ call deletebufline(bufnr('Xpbuffile'), 1, '$')
+
+ call setbufline(bufnr('Xpbuffile'), 1, 'python interface')
+ py << trim EOF
+ # Assigning: success
+ vim.current.tabpage = vim.tabpages[-2]
+ vim.current.buffer = cb
+ vim.current.window = vim.windows[0]
+ vim.current.window.cursor = (len(vim.current.buffer), 0)
+ cb.append('Current tab page: ' + repr(vim.current.tabpage))
+ cb.append('Current window: ' + repr(vim.current.window))
+ cb.append('Current buffer: ' + repr(vim.current.buffer))
+ cb.append('Current line: ' + repr(vim.current.line))
+ EOF
+
+ let expected =<< trim END
+ Current tab page: <tabpage 2>
+ Current window: <window 0>
+ Current buffer: <buffer Xpbuffile>
+ Current line: 'python interface'
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpbuffile'), 2, '$'))
+ py vim.current.line = 'one line'
+ call assert_equal('one line', getline('.'))
+ call deletebufline(bufnr('Xpbuffile'), 1, '$')
+
+ py << trim EOF
+ ws = list(vim.windows)
+ ts = list(vim.tabpages)
+ for b in vim.buffers:
+ if b is not cb:
+ vim.command('bwipeout! ' + str(b.number))
+ del b
+ cb.append('w.valid: ' + repr([w.valid for w in ws]))
+ cb.append('t.valid: ' + repr([t.valid for t in ts]))
+ del w
+ del t
+ del ts
+ del ws
+ EOF
+ let expected =<< trim END
+ w.valid: [True, False]
+ t.valid: [True, False, True, False]
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpbuffile'), 2, '$'))
+ %bw!
+endfunc
+
+" Test types
+func Test_python_types()
+ %d
+ py cb = vim.current.buffer
+ py << trim EOF
+ for expr, attr in (
+ ('vim.vars', 'Dictionary'),
+ ('vim.options', 'Options'),
+ ('vim.bindeval("{}")', 'Dictionary'),
+ ('vim.bindeval("[]")', 'List'),
+ ('vim.bindeval("function(\'tr\')")', 'Function'),
+ ('vim.current.buffer', 'Buffer'),
+ ('vim.current.range', 'Range'),
+ ('vim.current.window', 'Window'),
+ ('vim.current.tabpage', 'TabPage'),
+ ):
+ cb.append(expr + ':' + attr + ':' + \
+ repr(type(eval(expr)) is getattr(vim, attr)))
+ del expr
+ del attr
+ EOF
+ let expected =<< trim END
+ vim.vars:Dictionary:True
+ vim.options:Options:True
+ vim.bindeval("{}"):Dictionary:True
+ vim.bindeval("[]"):List:True
+ vim.bindeval("function('tr')"):Function:True
+ vim.current.buffer:Buffer:True
+ vim.current.range:Range:True
+ vim.current.window:Window:True
+ vim.current.tabpage:TabPage:True
+ END
+ call assert_equal(expected, getline(2, '$'))
+endfunc
+
+" Test __dir__() method
+func Test_python_dir_method()
+ %d
+ py cb = vim.current.buffer
+ py << trim EOF
+ for name, o in (
+ ('current', vim.current),
+ ('buffer', vim.current.buffer),
+ ('window', vim.current.window),
+ ('tabpage', vim.current.tabpage),
+ ('range', vim.current.range),
+ ('dictionary', vim.bindeval('{}')),
+ ('list', vim.bindeval('[]')),
+ ('function', vim.bindeval('function("tr")')),
+ ('output', sys.stdout),
+ ):
+ cb.append(name + ':' + ','.join(dir(o)))
+ del name
+ del o
+ EOF
+ let expected =<< trim END
+ current:__dir__,__members__,buffer,line,range,tabpage,window
+ buffer:__dir__,__members__,append,mark,name,number,options,range,valid,vars
+ window:__dir__,__members__,buffer,col,cursor,height,number,options,row,tabpage,valid,vars,width
+ tabpage:__dir__,__members__,number,valid,vars,window,windows
+ range:__dir__,__members__,append,end,start
+ dictionary:__dir__,__members__,get,has_key,items,keys,locked,pop,popitem,scope,update,values
+ list:__dir__,__members__,extend,locked
+ function:__dir__,__members__,args,auto_rebind,self,softspace
+ output:__dir__,__members__,close,closed,flush,isatty,readable,seekable,softspace,writable,write,writelines
+ END
+ call assert_equal(expected, getline(2, '$'))
+endfunc
+
+" Test vim.*.__new__
+func Test_python_new()
+ call assert_equal({}, pyeval('vim.Dictionary({})'))
+ call assert_equal({'a': 1}, pyeval('vim.Dictionary(a=1)'))
+ call assert_equal({'a': 1}, pyeval('vim.Dictionary(((''a'', 1),))'))
+ call assert_equal([], pyeval('vim.List()'))
+ call assert_equal(['a', 'b', 'c', '7'], pyeval('vim.List(iter(''abc7''))'))
+ call assert_equal(function('tr'), pyeval('vim.Function(''tr'')'))
+ call assert_equal(function('tr', [123, 3, 4]),
+ \ pyeval('vim.Function(''tr'', args=[123, 3, 4])'))
+ call assert_equal(function('tr'), pyeval('vim.Function(''tr'', args=[])'))
+ call assert_equal(function('tr', {}),
+ \ pyeval('vim.Function(''tr'', self={})'))
+ call assert_equal(function('tr', [123, 3, 4], {}),
+ \ pyeval('vim.Function(''tr'', args=[123, 3, 4], self={})'))
+ call assert_equal(function('tr'),
+ \ pyeval('vim.Function(''tr'', auto_rebind=False)'))
+ call assert_equal(function('tr', [123, 3, 4]),
+ \ pyeval('vim.Function(''tr'', args=[123, 3, 4], auto_rebind=False)'))
+ call assert_equal(function('tr'),
+ \ pyeval('vim.Function(''tr'', args=[], auto_rebind=False)'))
+ call assert_equal(function('tr', {}),
+ \ pyeval('vim.Function(''tr'', self={}, auto_rebind=False)'))
+ call assert_equal(function('tr', [123, 3, 4], {}),
+ \ pyeval('vim.Function(''tr'', args=[123, 3, 4], self={}, auto_rebind=False)'))
+endfunc
+
+" Test vim.Function
+func Test_python_vim_func()
+ func Args(...)
+ return a:000
+ endfunc
+
+ func SelfArgs(...) dict
+ return [a:000, self]
+ endfunc
+
+ " The following four lines should not crash
+ let Pt = function('tr', [[]], {'l': []})
+ py Pt = vim.bindeval('Pt')
+ unlet Pt
+ py del Pt
+
+ call assert_equal(3, pyeval('vim.strwidth("a\tb")'))
+
+ %bw!
+ py cb = vim.current.buffer
+ py << trim EOF
+ def ecall(out_prefix, func, *args, **kwargs):
+ line = out_prefix + ': '
+ try:
+ ret = func(*args, **kwargs)
+ except Exception:
+ line += '!exception: ' + emsg(sys.exc_info())
+ else:
+ line += '!result: ' + vim.Function('string')(ret)
+ cb.append(line)
+ a = vim.Function('Args')
+ pa1 = vim.Function('Args', args=['abcArgsPA1'])
+ pa2 = vim.Function('Args', args=[])
+ pa3 = vim.Function('Args', args=['abcArgsPA3'], self={'abcSelfPA3': 'abcSelfPA3Val'})
+ pa4 = vim.Function('Args', self={'abcSelfPA4': 'abcSelfPA4Val'})
+ cb.append('a: ' + repr(a))
+ cb.append('pa1: ' + repr(pa1))
+ cb.append('pa2: ' + repr(pa2))
+ cb.append('pa3: ' + repr(pa3))
+ cb.append('pa4: ' + repr(pa4))
+ sa = vim.Function('SelfArgs')
+ psa1 = vim.Function('SelfArgs', args=['abcArgsPSA1'])
+ psa2 = vim.Function('SelfArgs', args=[])
+ psa3 = vim.Function('SelfArgs', args=['abcArgsPSA3'], self={'abcSelfPSA3': 'abcSelfPSA3Val'})
+ psa4 = vim.Function('SelfArgs', self={'abcSelfPSA4': 'abcSelfPSA4Val'})
+ psa5 = vim.Function('SelfArgs', self={'abcSelfPSA5': 'abcSelfPSA5Val'}, auto_rebind=0)
+ psa6 = vim.Function('SelfArgs', args=['abcArgsPSA6'], self={'abcSelfPSA6': 'abcSelfPSA6Val'}, auto_rebind=())
+ psa7 = vim.Function('SelfArgs', args=['abcArgsPSA7'], auto_rebind=[])
+ psa8 = vim.Function('SelfArgs', auto_rebind=False)
+ psa9 = vim.Function('SelfArgs', self={'abcSelfPSA9': 'abcSelfPSA9Val'}, auto_rebind=True)
+ psaA = vim.Function('SelfArgs', args=['abcArgsPSAA'], self={'abcSelfPSAA': 'abcSelfPSAAVal'}, auto_rebind=1)
+ psaB = vim.Function('SelfArgs', args=['abcArgsPSAB'], auto_rebind={'abcARPSAB': 'abcARPSABVal'})
+ psaC = vim.Function('SelfArgs', auto_rebind=['abcARPSAC'])
+ cb.append('sa: ' + repr(sa))
+ cb.append('psa1: ' + repr(psa1))
+ cb.append('psa2: ' + repr(psa2))
+ cb.append('psa3: ' + repr(psa3))
+ cb.append('psa4: ' + repr(psa4))
+ cb.append('psa5: ' + repr(psa5))
+ cb.append('psa6: ' + repr(psa6))
+ cb.append('psa7: ' + repr(psa7))
+ cb.append('psa8: ' + repr(psa8))
+ cb.append('psa9: ' + repr(psa9))
+ cb.append('psaA: ' + repr(psaA))
+ cb.append('psaB: ' + repr(psaB))
+ cb.append('psaC: ' + repr(psaC))
+
+ psar = vim.Function('SelfArgs', args=[{'abcArgsPSAr': 'abcArgsPSArVal'}], self={'abcSelfPSAr': 'abcSelfPSArVal'})
+ psar.args[0]['abcArgsPSAr2'] = [psar.self, psar.args[0]]
+ psar.self['rec'] = psar
+ psar.self['self'] = psar.self
+ psar.self['args'] = psar.args
+
+ try:
+ cb.append('psar: ' + repr(psar))
+ except Exception:
+ cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
+ EOF
+
+ let expected =<< trim END
+ a: <vim.Function 'Args'>
+ pa1: <vim.Function 'Args', args=['abcArgsPA1']>
+ pa2: <vim.Function 'Args'>
+ pa3: <vim.Function 'Args', args=['abcArgsPA3'], self={'abcSelfPA3': 'abcSelfPA3Val'}>
+ pa4: <vim.Function 'Args', self={'abcSelfPA4': 'abcSelfPA4Val'}>
+ sa: <vim.Function 'SelfArgs'>
+ psa1: <vim.Function 'SelfArgs', args=['abcArgsPSA1']>
+ psa2: <vim.Function 'SelfArgs'>
+ psa3: <vim.Function 'SelfArgs', args=['abcArgsPSA3'], self={'abcSelfPSA3': 'abcSelfPSA3Val'}>
+ psa4: <vim.Function 'SelfArgs', self={'abcSelfPSA4': 'abcSelfPSA4Val'}>
+ psa5: <vim.Function 'SelfArgs', self={'abcSelfPSA5': 'abcSelfPSA5Val'}>
+ psa6: <vim.Function 'SelfArgs', args=['abcArgsPSA6'], self={'abcSelfPSA6': 'abcSelfPSA6Val'}>
+ psa7: <vim.Function 'SelfArgs', args=['abcArgsPSA7']>
+ psa8: <vim.Function 'SelfArgs'>
+ psa9: <vim.Function 'SelfArgs', self={'abcSelfPSA9': 'abcSelfPSA9Val'}, auto_rebind=True>
+ psaA: <vim.Function 'SelfArgs', args=['abcArgsPSAA'], self={'abcSelfPSAA': 'abcSelfPSAAVal'}, auto_rebind=True>
+ psaB: <vim.Function 'SelfArgs', args=['abcArgsPSAB']>
+ psaC: <vim.Function 'SelfArgs'>
+ psar: <vim.Function 'SelfArgs', args=[{'abcArgsPSAr2': [{'rec': function('SelfArgs', [{...}], {...}), 'self': {...}, 'abcSelfPSAr': 'abcSelfPSArVal', 'args': [{...}]}, {...}], 'abcArgsPSAr': 'abcArgsPSArVal'}], self={'rec': function('SelfArgs', [{'abcArgsPSAr2': [{...}, {...}], 'abcArgsPSAr': 'abcArgsPSArVal'}], {...}), 'self': {...}, 'abcSelfPSAr': 'abcSelfPSArVal', 'args': [{'abcArgsPSAr2': [{...}, {...}], 'abcArgsPSAr': 'abcArgsPSArVal'}]}>
+ END
+ call assert_equal(expected, getline(2, '$'))
+ %d
+
+ call assert_equal(function('Args'), pyeval('a'))
+ call assert_equal(function('Args', ['abcArgsPA1']), pyeval('pa1'))
+ call assert_equal(function('Args'), pyeval('pa2'))
+ call assert_equal(function('Args', ['abcArgsPA3'], {'abcSelfPA3': 'abcSelfPA3Val'}), pyeval('pa3'))
+ call assert_equal(function('Args', {'abcSelfPA4': 'abcSelfPA4Val'}), pyeval('pa4'))
+ call assert_equal(function('SelfArgs'), pyeval('sa'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA1']), pyeval('psa1'))
+ call assert_equal(function('SelfArgs'), pyeval('psa2'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA3'], {'abcSelfPSA3': 'abcSelfPSA3Val'}), pyeval('psa3'))
+ call assert_equal(function('SelfArgs', {'abcSelfPSA4': 'abcSelfPSA4Val'}), pyeval('psa4'))
+ call assert_equal(function('SelfArgs', {'abcSelfPSA5': 'abcSelfPSA5Val'}), pyeval('psa5'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA6'], {'abcSelfPSA6': 'abcSelfPSA6Val'}), pyeval('psa6'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA7']), pyeval('psa7'))
+ call assert_equal(function('SelfArgs'), pyeval('psa8'))
+ call assert_equal(function('SelfArgs', {'abcSelfPSA9': 'abcSelfPSA9Val'}), pyeval('psa9'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSAA'], {'abcSelfPSAA': 'abcSelfPSAAVal'}), pyeval('psaA'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSAB']), pyeval('psaB'))
+ call assert_equal(function('SelfArgs'), pyeval('psaC'))
+
+ let res = []
+ for v in ['sa', 'psa1', 'psa2', 'psa3', 'psa4', 'psa5', 'psa6', 'psa7',
+ \ 'psa8', 'psa9', 'psaA', 'psaB', 'psaC']
+ let d = {'f': pyeval(v)}
+ call add(res, 'd.' .. v .. '(): ' .. string(d.f()))
+ endfor
+
+ let expected =<< trim END
+ d.sa(): [[], {'f': function('SelfArgs')}]
+ d.psa1(): [['abcArgsPSA1'], {'f': function('SelfArgs', ['abcArgsPSA1'])}]
+ d.psa2(): [[], {'f': function('SelfArgs')}]
+ d.psa3(): [['abcArgsPSA3'], {'abcSelfPSA3': 'abcSelfPSA3Val'}]
+ d.psa4(): [[], {'abcSelfPSA4': 'abcSelfPSA4Val'}]
+ d.psa5(): [[], {'abcSelfPSA5': 'abcSelfPSA5Val'}]
+ d.psa6(): [['abcArgsPSA6'], {'abcSelfPSA6': 'abcSelfPSA6Val'}]
+ d.psa7(): [['abcArgsPSA7'], {'f': function('SelfArgs', ['abcArgsPSA7'])}]
+ d.psa8(): [[], {'f': function('SelfArgs')}]
+ d.psa9(): [[], {'f': function('SelfArgs', {'abcSelfPSA9': 'abcSelfPSA9Val'})}]
+ d.psaA(): [['abcArgsPSAA'], {'f': function('SelfArgs', ['abcArgsPSAA'], {'abcSelfPSAA': 'abcSelfPSAAVal'})}]
+ d.psaB(): [['abcArgsPSAB'], {'f': function('SelfArgs', ['abcArgsPSAB'])}]
+ d.psaC(): [[], {'f': function('SelfArgs')}]
+ END
+ call assert_equal(expected, res)
+
+ py ecall('a()', a, )
+ py ecall('pa1()', pa1, )
+ py ecall('pa2()', pa2, )
+ py ecall('pa3()', pa3, )
+ py ecall('pa4()', pa4, )
+ py ecall('sa()', sa, )
+ py ecall('psa1()', psa1, )
+ py ecall('psa2()', psa2, )
+ py ecall('psa3()', psa3, )
+ py ecall('psa4()', psa4, )
+
+ py ecall('a(42, 43)', a, 42, 43)
+ py ecall('pa1(42, 43)', pa1, 42, 43)
+ py ecall('pa2(42, 43)', pa2, 42, 43)
+ py ecall('pa3(42, 43)', pa3, 42, 43)
+ py ecall('pa4(42, 43)', pa4, 42, 43)
+ py ecall('sa(42, 43)', sa, 42, 43)
+ py ecall('psa1(42, 43)', psa1, 42, 43)
+ py ecall('psa2(42, 43)', psa2, 42, 43)
+ py ecall('psa3(42, 43)', psa3, 42, 43)
+ py ecall('psa4(42, 43)', psa4, 42, 43)
+
+ py ecall('a(42, self={"20": 1})', a, 42, self={'20': 1})
+ py ecall('pa1(42, self={"20": 1})', pa1, 42, self={'20': 1})
+ py ecall('pa2(42, self={"20": 1})', pa2, 42, self={'20': 1})
+ py ecall('pa3(42, self={"20": 1})', pa3, 42, self={'20': 1})
+ py ecall('pa4(42, self={"20": 1})', pa4, 42, self={'20': 1})
+ py ecall('sa(42, self={"20": 1})', sa, 42, self={'20': 1})
+ py ecall('psa1(42, self={"20": 1})', psa1, 42, self={'20': 1})
+ py ecall('psa2(42, self={"20": 1})', psa2, 42, self={'20': 1})
+ py ecall('psa3(42, self={"20": 1})', psa3, 42, self={'20': 1})
+ py ecall('psa4(42, self={"20": 1})', psa4, 42, self={'20': 1})
+
+ py ecall('a(self={"20": 1})', a, self={'20': 1})
+ py ecall('pa1(self={"20": 1})', pa1, self={'20': 1})
+ py ecall('pa2(self={"20": 1})', pa2, self={'20': 1})
+ py ecall('pa3(self={"20": 1})', pa3, self={'20': 1})
+ py ecall('pa4(self={"20": 1})', pa4, self={'20': 1})
+ py ecall('sa(self={"20": 1})', sa, self={'20': 1})
+ py ecall('psa1(self={"20": 1})', psa1, self={'20': 1})
+ py ecall('psa2(self={"20": 1})', psa2, self={'20': 1})
+ py ecall('psa3(self={"20": 1})', psa3, self={'20': 1})
+ py ecall('psa4(self={"20": 1})', psa4, self={'20': 1})
+
+ py << trim EOF
+ def s(v):
+ if v is None:
+ return repr(v)
+ else:
+ return vim.Function('string')(v)
+
+ cb.append('a.args: ' + s(a.args))
+ cb.append('pa1.args: ' + s(pa1.args))
+ cb.append('pa2.args: ' + s(pa2.args))
+ cb.append('pa3.args: ' + s(pa3.args))
+ cb.append('pa4.args: ' + s(pa4.args))
+ cb.append('sa.args: ' + s(sa.args))
+ cb.append('psa1.args: ' + s(psa1.args))
+ cb.append('psa2.args: ' + s(psa2.args))
+ cb.append('psa3.args: ' + s(psa3.args))
+ cb.append('psa4.args: ' + s(psa4.args))
+
+ cb.append('a.self: ' + s(a.self))
+ cb.append('pa1.self: ' + s(pa1.self))
+ cb.append('pa2.self: ' + s(pa2.self))
+ cb.append('pa3.self: ' + s(pa3.self))
+ cb.append('pa4.self: ' + s(pa4.self))
+ cb.append('sa.self: ' + s(sa.self))
+ cb.append('psa1.self: ' + s(psa1.self))
+ cb.append('psa2.self: ' + s(psa2.self))
+ cb.append('psa3.self: ' + s(psa3.self))
+ cb.append('psa4.self: ' + s(psa4.self))
+
+ cb.append('a.name: ' + s(a.name))
+ cb.append('pa1.name: ' + s(pa1.name))
+ cb.append('pa2.name: ' + s(pa2.name))
+ cb.append('pa3.name: ' + s(pa3.name))
+ cb.append('pa4.name: ' + s(pa4.name))
+ cb.append('sa.name: ' + s(sa.name))
+ cb.append('psa1.name: ' + s(psa1.name))
+ cb.append('psa2.name: ' + s(psa2.name))
+ cb.append('psa3.name: ' + s(psa3.name))
+ cb.append('psa4.name: ' + s(psa4.name))
+
+ cb.append('a.auto_rebind: ' + s(a.auto_rebind))
+ cb.append('pa1.auto_rebind: ' + s(pa1.auto_rebind))
+ cb.append('pa2.auto_rebind: ' + s(pa2.auto_rebind))
+ cb.append('pa3.auto_rebind: ' + s(pa3.auto_rebind))
+ cb.append('pa4.auto_rebind: ' + s(pa4.auto_rebind))
+ cb.append('sa.auto_rebind: ' + s(sa.auto_rebind))
+ cb.append('psa1.auto_rebind: ' + s(psa1.auto_rebind))
+ cb.append('psa2.auto_rebind: ' + s(psa2.auto_rebind))
+ cb.append('psa3.auto_rebind: ' + s(psa3.auto_rebind))
+ cb.append('psa4.auto_rebind: ' + s(psa4.auto_rebind))
+ cb.append('psa5.auto_rebind: ' + s(psa5.auto_rebind))
+ cb.append('psa6.auto_rebind: ' + s(psa6.auto_rebind))
+ cb.append('psa7.auto_rebind: ' + s(psa7.auto_rebind))
+ cb.append('psa8.auto_rebind: ' + s(psa8.auto_rebind))
+ cb.append('psa9.auto_rebind: ' + s(psa9.auto_rebind))
+ cb.append('psaA.auto_rebind: ' + s(psaA.auto_rebind))
+ cb.append('psaB.auto_rebind: ' + s(psaB.auto_rebind))
+ cb.append('psaC.auto_rebind: ' + s(psaC.auto_rebind))
+
+ del s
+
+ del a
+ del pa1
+ del pa2
+ del pa3
+ del pa4
+ del sa
+ del psa1
+ del psa2
+ del psa3
+ del psa4
+ del psa5
+ del psa6
+ del psa7
+ del psa8
+ del psa9
+ del psaA
+ del psaB
+ del psaC
+ del psar
+
+ del ecall
+ EOF
+
+ let expected =<< trim END
+ a(): !result: []
+ pa1(): !result: ['abcArgsPA1']
+ pa2(): !result: []
+ pa3(): !result: ['abcArgsPA3']
+ pa4(): !result: []
+ sa(): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa1(): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa2(): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa3(): !result: [['abcArgsPSA3'], {'abcSelfPSA3': 'abcSelfPSA3Val'}]
+ psa4(): !result: [[], {'abcSelfPSA4': 'abcSelfPSA4Val'}]
+ a(42, 43): !result: [42, 43]
+ pa1(42, 43): !result: ['abcArgsPA1', 42, 43]
+ pa2(42, 43): !result: [42, 43]
+ pa3(42, 43): !result: ['abcArgsPA3', 42, 43]
+ pa4(42, 43): !result: [42, 43]
+ sa(42, 43): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa1(42, 43): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa2(42, 43): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa3(42, 43): !result: [['abcArgsPSA3', 42, 43], {'abcSelfPSA3': 'abcSelfPSA3Val'}]
+ psa4(42, 43): !result: [[42, 43], {'abcSelfPSA4': 'abcSelfPSA4Val'}]
+ a(42, self={"20": 1}): !result: [42]
+ pa1(42, self={"20": 1}): !result: ['abcArgsPA1', 42]
+ pa2(42, self={"20": 1}): !result: [42]
+ pa3(42, self={"20": 1}): !result: ['abcArgsPA3', 42]
+ pa4(42, self={"20": 1}): !result: [42]
+ sa(42, self={"20": 1}): !result: [[42], {'20': 1}]
+ psa1(42, self={"20": 1}): !result: [['abcArgsPSA1', 42], {'20': 1}]
+ psa2(42, self={"20": 1}): !result: [[42], {'20': 1}]
+ psa3(42, self={"20": 1}): !result: [['abcArgsPSA3', 42], {'20': 1}]
+ psa4(42, self={"20": 1}): !result: [[42], {'20': 1}]
+ a(self={"20": 1}): !result: []
+ pa1(self={"20": 1}): !result: ['abcArgsPA1']
+ pa2(self={"20": 1}): !result: []
+ pa3(self={"20": 1}): !result: ['abcArgsPA3']
+ pa4(self={"20": 1}): !result: []
+ sa(self={"20": 1}): !result: [[], {'20': 1}]
+ psa1(self={"20": 1}): !result: [['abcArgsPSA1'], {'20': 1}]
+ psa2(self={"20": 1}): !result: [[], {'20': 1}]
+ psa3(self={"20": 1}): !result: [['abcArgsPSA3'], {'20': 1}]
+ psa4(self={"20": 1}): !result: [[], {'20': 1}]
+ a.args: None
+ pa1.args: ['abcArgsPA1']
+ pa2.args: None
+ pa3.args: ['abcArgsPA3']
+ pa4.args: None
+ sa.args: None
+ psa1.args: ['abcArgsPSA1']
+ psa2.args: None
+ psa3.args: ['abcArgsPSA3']
+ psa4.args: None
+ a.self: None
+ pa1.self: None
+ pa2.self: None
+ pa3.self: {'abcSelfPA3': 'abcSelfPA3Val'}
+ pa4.self: {'abcSelfPA4': 'abcSelfPA4Val'}
+ sa.self: None
+ psa1.self: None
+ psa2.self: None
+ psa3.self: {'abcSelfPSA3': 'abcSelfPSA3Val'}
+ psa4.self: {'abcSelfPSA4': 'abcSelfPSA4Val'}
+ a.name: 'Args'
+ pa1.name: 'Args'
+ pa2.name: 'Args'
+ pa3.name: 'Args'
+ pa4.name: 'Args'
+ sa.name: 'SelfArgs'
+ psa1.name: 'SelfArgs'
+ psa2.name: 'SelfArgs'
+ psa3.name: 'SelfArgs'
+ psa4.name: 'SelfArgs'
+ a.auto_rebind: 1
+ pa1.auto_rebind: 1
+ pa2.auto_rebind: 1
+ pa3.auto_rebind: 0
+ pa4.auto_rebind: 0
+ sa.auto_rebind: 1
+ psa1.auto_rebind: 1
+ psa2.auto_rebind: 1
+ psa3.auto_rebind: 0
+ psa4.auto_rebind: 0
+ psa5.auto_rebind: 0
+ psa6.auto_rebind: 0
+ psa7.auto_rebind: 1
+ psa8.auto_rebind: 1
+ psa9.auto_rebind: 1
+ psaA.auto_rebind: 1
+ psaB.auto_rebind: 1
+ psaC.auto_rebind: 1
+ END
+ call assert_equal(expected, getline(2, '$'))
+ %bw!
+endfunc
+
+" Test stdout/stderr
+func Test_python_stdin_stderr()
+ let caught_writeerr = 0
+ let caught_writelineerr = 0
+ redir => messages
+ py sys.stdout.write('abc8') ; sys.stdout.write('def')
+ try
+ py sys.stderr.write('abc9') ; sys.stderr.write('def')
+ catch /abc9def/
+ let caught_writeerr = 1
+ endtry
+ py sys.stdout.writelines(iter('abcA'))
+ try
+ py sys.stderr.writelines(iter('abcB'))
+ catch /abcB/
+ let caught_writelineerr = 1
+ endtry
+ redir END
+ call assert_equal("\nabc8def\nabcA", messages)
+ call assert_equal(1, caught_writeerr)
+ call assert_equal(1, caught_writelineerr)
+endfunc
+
+" Test subclassing
+func Test_python_subclass()
+ new
+ func Put(...)
+ return a:000
+ endfunc
+
+ py << trim EOF
+ class DupDict(vim.Dictionary):
+ def __setitem__(self, key, value):
+ super(DupDict, self).__setitem__(key, value)
+ super(DupDict, self).__setitem__('dup_' + key, value)
+ dd = DupDict()
+ dd['a'] = 'b'
+
+ class DupList(vim.List):
+ def __getitem__(self, idx):
+ return [super(DupList, self).__getitem__(idx)] * 2
+
+ dl = DupList()
+ dl2 = DupList(iter('abcC'))
+ dl.extend(dl2[0])
+
+ class DupFun(vim.Function):
+ def __call__(self, arg):
+ return super(DupFun, self).__call__(arg, arg)
+
+ df = DupFun('Put')
+ EOF
+
+ call assert_equal(['a', 'dup_a'], sort(keys(pyeval('dd'))))
+ call assert_equal(['a', 'a'], pyeval('dl'))
+ call assert_equal(['a', 'b', 'c', 'C'], pyeval('dl2'))
+ call assert_equal([2, 2], pyeval('df(2)'))
+ call assert_equal(1, pyeval('dl') is# pyeval('dl'))
+ call assert_equal(1, pyeval('dd') is# pyeval('dd'))
+ call assert_equal(function('Put'), pyeval('df'))
+ delfunction Put
+ py << trim EOF
+ del DupDict
+ del DupList
+ del DupFun
+ del dd
+ del dl
+ del dl2
+ del df
+ EOF
+ close!
+endfunc
+
+" Test chdir
+func Test_python_chdir()
+ new Xpycfile
+ py cb = vim.current.buffer
+ py << trim EOF
+ import os
+ fnamemodify = vim.Function('fnamemodify')
+ cb.append(fnamemodify('.', ':p:h:t'))
+ cb.append(vim.eval('@%'))
+ os.chdir('..')
+ path = fnamemodify('.', ':p:h:t')
+ if path != 'src' and path != 'src2':
+ # Running tests from a shadow directory, so move up another level
+ # This will result in @% looking like shadow/testdir/Xpycfile, hence the
+ # extra fnamemodify
+ os.chdir('..')
+ cb.append(fnamemodify('.', ':p:h:t'))
+ cb.append(fnamemodify(vim.eval('@%'), ':s?^%s.??' % path).replace(os.path.sep, '/'))
+ os.chdir(path)
+ del path
+ else:
+ # Also accept running from src2/testdir/ for MS-Windows CI.
+ cb.append(fnamemodify('.', ':p:h:t').replace('src2', 'src'))
+ cb.append(vim.eval('@%').replace(os.path.sep, '/'))
+ os.chdir('testdir')
+ cb.append(fnamemodify('.', ':p:h:t'))
+ cb.append(vim.eval('@%'))
+ del fnamemodify
+ EOF
+ call assert_equal(['testdir', 'Xpycfile', 'src', 'testdir/Xpycfile', 'testdir',
+ \ 'Xpycfile'], getline(2, '$'))
+ close!
+ call AssertException(["py vim.chdir(None)"], "Vim(python):TypeError:")
+endfunc
+
+" Test errors
+func Test_python_errors()
+ func F() dict
+ endfunc
+
+ func D()
+ endfunc
+
+ new
+ py cb = vim.current.buffer
+
+ py << trim EOF
+ d = vim.Dictionary()
+ ned = vim.Dictionary(foo='bar', baz='abcD')
+ dl = vim.Dictionary(a=1)
+ dl.locked = True
+ l = vim.List()
+ ll = vim.List('abcE')
+ ll.locked = True
+ nel = vim.List('abcO')
+ f = vim.Function('string')
+ fd = vim.Function('F')
+ fdel = vim.Function('D')
+ vim.command('delfunction D')
+
+ def subexpr_test(expr, name, subexprs):
+ cb.append('>>> Testing %s using %s' % (name, expr))
+ for subexpr in subexprs:
+ ee(expr % subexpr)
+ cb.append('<<< Finished')
+
+ def stringtochars_test(expr):
+ return subexpr_test(expr, 'StringToChars', (
+ '1', # Fail type checks
+ 'u"\\0"', # Fail PyString_AsStringAndSize(bytes, , NULL) check
+ '"\\0"', # Fail PyString_AsStringAndSize(object, , NULL) check
+ ))
+
+ class Mapping(object):
+ def __init__(self, d):
+ self.d = d
+
+ def __getitem__(self, key):
+ return self.d[key]
+
+ def keys(self):
+ return self.d.keys()
+
+ def items(self):
+ return self.d.items()
+
+ def convertfrompyobject_test(expr, recurse=True):
+ # pydict_to_tv
+ stringtochars_test(expr % '{%s : 1}')
+ if recurse:
+ convertfrompyobject_test(expr % '{"abcF" : %s}', False)
+ # pymap_to_tv
+ stringtochars_test(expr % 'Mapping({%s : 1})')
+ if recurse:
+ convertfrompyobject_test(expr % 'Mapping({"abcG" : %s})', False)
+ # pyseq_to_tv
+ iter_test(expr)
+ return subexpr_test(expr, 'ConvertFromPyObject', (
+ 'None', # Not conversible
+ '{"": 1}', # Empty key not allowed
+ '{u"": 1}', # Same, but with unicode object
+ 'FailingMapping()', #
+ 'FailingMappingKey()', #
+ 'FailingNumber()', #
+ ))
+
+ def convertfrompymapping_test(expr):
+ convertfrompyobject_test(expr)
+ return subexpr_test(expr, 'ConvertFromPyMapping', (
+ '[]',
+ ))
+
+ def iter_test(expr):
+ return subexpr_test(expr, '*Iter*', (
+ 'FailingIter()',
+ 'FailingIterNext()',
+ ))
+
+ def number_test(expr, natural=False, unsigned=False):
+ if natural:
+ unsigned = True
+ return subexpr_test(expr, 'NumberToLong', (
+ '[]',
+ 'None',
+ ) + (unsigned and ('-1',) or ())
+ + (natural and ('0',) or ()))
+
+ class FailingTrue(object):
+ def __nonzero__(self):
+ raise NotImplementedError('bool')
+
+ class FailingIter(object):
+ def __iter__(self):
+ raise NotImplementedError('iter')
+
+ class FailingIterNext(object):
+ def __iter__(self):
+ return self
+
+ def next(self):
+ raise NotImplementedError('next')
+
+ class FailingIterNextN(object):
+ def __init__(self, n):
+ self.n = n
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if self.n:
+ self.n -= 1
+ return 1
+ else:
+ raise NotImplementedError('next N')
+
+ class FailingMappingKey(object):
+ def __getitem__(self, item):
+ raise NotImplementedError('getitem:mappingkey')
+
+ def keys(self):
+ return list("abcH")
+
+ class FailingMapping(object):
+ def __getitem__(self):
+ raise NotImplementedError('getitem:mapping')
+
+ def keys(self):
+ raise NotImplementedError('keys')
+
+ class FailingList(list):
+ def __getitem__(self, idx):
+ if i == 2:
+ raise NotImplementedError('getitem:list')
+ else:
+ return super(FailingList, self).__getitem__(idx)
+
+ class NoArgsCall(object):
+ def __call__(self):
+ pass
+
+ class FailingCall(object):
+ def __call__(self, path):
+ raise NotImplementedError('call')
+
+ class FailingNumber(object):
+ def __int__(self):
+ raise NotImplementedError('int')
+
+ cb.append("> Output")
+ cb.append(">> OutputSetattr")
+ ee('del sys.stdout.softspace')
+ number_test('sys.stdout.softspace = %s', unsigned=True)
+ number_test('sys.stderr.softspace = %s', unsigned=True)
+ ee('assert sys.stdout.isatty()==False')
+ ee('assert sys.stdout.seekable()==False')
+ ee('sys.stdout.close()')
+ ee('sys.stdout.flush()')
+ ee('assert sys.stderr.isatty()==False')
+ ee('assert sys.stderr.seekable()==False')
+ ee('sys.stderr.close()')
+ ee('sys.stderr.flush()')
+ ee('sys.stdout.attr = None')
+ cb.append(">> OutputWrite")
+ ee('assert sys.stdout.writable()==True')
+ ee('assert sys.stdout.readable()==False')
+ ee('assert sys.stderr.writable()==True')
+ ee('assert sys.stderr.readable()==False')
+ ee('assert sys.stdout.closed()==False')
+ ee('assert sys.stderr.closed()==False')
+ ee('assert sys.stdout.errors=="strict"')
+ ee('assert sys.stderr.errors=="strict"')
+ ee('assert sys.stdout.encoding==sys.stderr.encoding')
+ ee('sys.stdout.write(None)')
+ cb.append(">> OutputWriteLines")
+ ee('sys.stdout.writelines(None)')
+ ee('sys.stdout.writelines([1])')
+ iter_test('sys.stdout.writelines(%s)')
+ cb.append("> VimCommand")
+ stringtochars_test('vim.command(%s)')
+ ee('vim.command("", 2)')
+ #! Not checked: vim->python exceptions translating: checked later
+ cb.append("> VimToPython")
+ #! Not checked: everything: needs errors in internal python functions
+ cb.append("> VimEval")
+ stringtochars_test('vim.eval(%s)')
+ ee('vim.eval("", FailingTrue())')
+ #! Not checked: everything: needs errors in internal python functions
+ cb.append("> VimEvalPy")
+ stringtochars_test('vim.bindeval(%s)')
+ ee('vim.eval("", 2)')
+ #! Not checked: vim->python exceptions translating: checked later
+ cb.append("> VimStrwidth")
+ stringtochars_test('vim.strwidth(%s)')
+ cb.append("> VimForeachRTP")
+ ee('vim.foreach_rtp(None)')
+ ee('vim.foreach_rtp(NoArgsCall())')
+ ee('vim.foreach_rtp(FailingCall())')
+ ee('vim.foreach_rtp(int, 2)')
+ cb.append('> import')
+ old_rtp = vim.options['rtp']
+ vim.options['rtp'] = os.getcwd().replace('\\', '\\\\').replace(',', '\\,')
+ ee('import xxx_no_such_module_xxx')
+ ee('import failing_import')
+ ee('import failing')
+ vim.options['rtp'] = old_rtp
+ del old_rtp
+ cb.append("> Options")
+ cb.append(">> OptionsItem")
+ ee('vim.options["abcQ"]')
+ ee('vim.options[""]')
+ stringtochars_test('vim.options[%s]')
+ cb.append(">> OptionsContains")
+ stringtochars_test('%s in vim.options')
+ cb.append("> Dictionary")
+ cb.append(">> DictionaryConstructor")
+ ee('vim.Dictionary("abcI")')
+ ##! Not checked: py_dict_alloc failure
+ cb.append(">> DictionarySetattr")
+ ee('del d.locked')
+ ee('d.locked = FailingTrue()')
+ ee('vim.vvars.locked = False')
+ ee('d.scope = True')
+ ee('d.xxx = True')
+ cb.append(">> _DictionaryItem")
+ ee('d.get("a", 2, 3)')
+ stringtochars_test('d.get(%s)')
+ ee('d.pop("a")')
+ ee('dl.pop("a")')
+ cb.append(">> DictionaryContains")
+ ee('"" in d')
+ ee('0 in d')
+ cb.append(">> DictionaryIterNext")
+ ee('for i in ned: ned["a"] = 1')
+ del i
+ cb.append(">> DictionaryAssItem")
+ ee('dl["b"] = 1')
+ stringtochars_test('d[%s] = 1')
+ convertfrompyobject_test('d["a"] = %s')
+ cb.append(">> DictionaryUpdate")
+ cb.append(">>> kwargs")
+ cb.append(">>> iter")
+ ee('d.update(FailingMapping())')
+ ee('d.update([FailingIterNext()])')
+ ee('d.update([FailingIterNextN(1)])')
+ iter_test('d.update(%s)')
+ convertfrompyobject_test('d.update(%s)')
+ stringtochars_test('d.update(((%s, 0),))')
+ convertfrompyobject_test('d.update((("a", %s),))')
+ cb.append(">> DictionaryPopItem")
+ ee('d.popitem(1, 2)')
+ cb.append(">> DictionaryHasKey")
+ ee('d.has_key()')
+ cb.append("> List")
+ cb.append(">> ListConstructor")
+ ee('vim.List(1, 2)')
+ ee('vim.List(a=1)')
+ iter_test('vim.List(%s)')
+ convertfrompyobject_test('vim.List([%s])')
+ cb.append(">> ListItem")
+ ee('l[1000]')
+ cb.append(">> ListAssItem")
+ ee('ll[1] = 2')
+ ee('l[1000] = 3')
+ cb.append(">> ListAssSlice")
+ ee('ll[1:100] = "abcJ"')
+ iter_test('l[:] = %s')
+ ee('nel[1:10:2] = "abcK"')
+ cb.append(repr(tuple(nel)))
+ ee('nel[1:10:2] = "a"')
+ cb.append(repr(tuple(nel)))
+ ee('nel[1:1:-1] = "a"')
+ cb.append(repr(tuple(nel)))
+ ee('nel[:] = FailingIterNextN(2)')
+ cb.append(repr(tuple(nel)))
+ convertfrompyobject_test('l[:] = [%s]')
+ cb.append(">> ListConcatInPlace")
+ iter_test('l.extend(%s)')
+ convertfrompyobject_test('l.extend([%s])')
+ cb.append(">> ListSetattr")
+ ee('del l.locked')
+ ee('l.locked = FailingTrue()')
+ ee('l.xxx = True')
+ cb.append("> Function")
+ cb.append(">> FunctionConstructor")
+ cb.append(">>> FunctionConstructor")
+ ee('vim.Function("123")')
+ ee('vim.Function("xxx_non_existent_function_xxx")')
+ ee('vim.Function("xxx#non#existent#function#xxx")')
+ ee('vim.Function("xxx_non_existent_function_xxx2", args=[])')
+ ee('vim.Function("xxx_non_existent_function_xxx3", self={})')
+ ee('vim.Function("xxx_non_existent_function_xxx4", args=[], self={})')
+ cb.append(">>> FunctionNew")
+ ee('vim.Function("tr", self="abcFuncSelf")')
+ ee('vim.Function("tr", args=427423)')
+ ee('vim.Function("tr", self="abcFuncSelf2", args="abcFuncArgs2")')
+ ee('vim.Function(self="abcFuncSelf2", args="abcFuncArgs2")')
+ ee('vim.Function("tr", "", self="abcFuncSelf2", args="abcFuncArgs2")')
+ ee('vim.Function("tr", "")')
+ cb.append(">> FunctionCall")
+ convertfrompyobject_test('f(%s)')
+ convertfrompymapping_test('fd(self=%s)')
+ cb.append("> TabPage")
+ cb.append(">> TabPageAttr")
+ ee('vim.current.tabpage.xxx')
+ cb.append("> TabList")
+ cb.append(">> TabListItem")
+ ee('vim.tabpages[1000]')
+ cb.append("> Window")
+ cb.append(">> WindowAttr")
+ ee('vim.current.window.xxx')
+ cb.append(">> WindowSetattr")
+ ee('vim.current.window.buffer = 0')
+ ee('vim.current.window.cursor = (100000000, 100000000)')
+ ee('vim.current.window.cursor = True')
+ number_test('vim.current.window.height = %s', unsigned=True)
+ number_test('vim.current.window.width = %s', unsigned=True)
+ ee('vim.current.window.xxxxxx = True')
+ cb.append("> WinList")
+ cb.append(">> WinListItem")
+ ee('vim.windows[1000]')
+ cb.append("> Buffer")
+ cb.append(">> StringToLine (indirect)")
+ ee('vim.current.buffer[0] = "\\na"')
+ ee('vim.current.buffer[0] = u"\\na"')
+ cb.append(">> SetBufferLine (indirect)")
+ ee('vim.current.buffer[0] = True')
+ cb.append(">> SetBufferLineList (indirect)")
+ ee('vim.current.buffer[:] = True')
+ ee('vim.current.buffer[:] = ["\\na", "bc"]')
+ cb.append(">> InsertBufferLines (indirect)")
+ ee('vim.current.buffer.append(None)')
+ ee('vim.current.buffer.append(["\\na", "bc"])')
+ ee('vim.current.buffer.append("\\nbc")')
+ cb.append(">> RBItem")
+ ee('vim.current.buffer[100000000]')
+ cb.append(">> RBAsItem")
+ ee('vim.current.buffer[100000000] = ""')
+ cb.append(">> BufferAttr")
+ ee('vim.current.buffer.xxx')
+ cb.append(">> BufferSetattr")
+ ee('vim.current.buffer.name = True')
+ ee('vim.current.buffer.xxx = True')
+ cb.append(">> BufferMark")
+ ee('vim.current.buffer.mark(0)')
+ ee('vim.current.buffer.mark("abcM")')
+ ee('vim.current.buffer.mark("!")')
+ cb.append(">> BufferRange")
+ ee('vim.current.buffer.range(1, 2, 3)')
+ cb.append("> BufMap")
+ cb.append(">> BufMapItem")
+ ee('vim.buffers[100000000]')
+ number_test('vim.buffers[%s]', natural=True)
+ cb.append("> Current")
+ cb.append(">> CurrentGetattr")
+ ee('vim.current.xxx')
+ cb.append(">> CurrentSetattr")
+ ee('vim.current.line = True')
+ ee('vim.current.buffer = True')
+ ee('vim.current.window = True')
+ ee('vim.current.tabpage = True')
+ ee('vim.current.xxx = True')
+ del d
+ del ned
+ del dl
+ del l
+ del ll
+ del nel
+ del f
+ del fd
+ del fdel
+ del subexpr_test
+ del stringtochars_test
+ del Mapping
+ del convertfrompyobject_test
+ del convertfrompymapping_test
+ del iter_test
+ del number_test
+ del FailingTrue
+ del FailingIter
+ del FailingIterNext
+ del FailingIterNextN
+ del FailingMapping
+ del FailingMappingKey
+ del FailingList
+ del NoArgsCall
+ del FailingCall
+ del FailingNumber
+ EOF
+ delfunction F
+
+ let expected =<< trim END
+ > Output
+ >> OutputSetattr
+ del sys.stdout.softspace:AttributeError:('cannot delete OutputObject attributes',)
+ >>> Testing NumberToLong using sys.stdout.softspace = %s
+ sys.stdout.softspace = []:TypeError:('expected int(), long() or something supporting coercing to long(), but got list',)
+ sys.stdout.softspace = None:TypeError:('expected int(), long() or something supporting coercing to long(), but got NoneType',)
+ sys.stdout.softspace = -1:ValueError:('number must be greater or equal to zero',)
+ <<< Finished
+ >>> Testing NumberToLong using sys.stderr.softspace = %s
+ sys.stderr.softspace = []:TypeError:('expected int(), long() or something supporting coercing to long(), but got list',)
+ sys.stderr.softspace = None:TypeError:('expected int(), long() or something supporting coercing to long(), but got NoneType',)
+ sys.stderr.softspace = -1:ValueError:('number must be greater or equal to zero',)
+ <<< Finished
+ assert sys.stdout.isatty()==False:NOT FAILED
+ assert sys.stdout.seekable()==False:NOT FAILED
+ sys.stdout.close():NOT FAILED
+ sys.stdout.flush():NOT FAILED
+ assert sys.stderr.isatty()==False:NOT FAILED
+ assert sys.stderr.seekable()==False:NOT FAILED
+ sys.stderr.close():NOT FAILED
+ sys.stderr.flush():NOT FAILED
+ sys.stdout.attr = None:AttributeError:('invalid attribute: attr',)
+ >> OutputWrite
+ assert sys.stdout.writable()==True:NOT FAILED
+ assert sys.stdout.readable()==False:NOT FAILED
+ assert sys.stderr.writable()==True:NOT FAILED
+ assert sys.stderr.readable()==False:NOT FAILED
+ assert sys.stdout.closed()==False:NOT FAILED
+ assert sys.stderr.closed()==False:NOT FAILED
+ assert sys.stdout.errors=="strict":NOT FAILED
+ assert sys.stderr.errors=="strict":NOT FAILED
+ assert sys.stdout.encoding==sys.stderr.encoding:NOT FAILED
+ sys.stdout.write(None):TypeError:('coercing to Unicode: need string or buffer, NoneType found',)
+ >> OutputWriteLines
+ sys.stdout.writelines(None):TypeError:("'NoneType' object is not iterable",)
+ sys.stdout.writelines([1]):TypeError:('coercing to Unicode: need string or buffer, int found',)
+ >>> Testing *Iter* using sys.stdout.writelines(%s)
+ sys.stdout.writelines(FailingIter()):NotImplementedError:('iter',)
+ sys.stdout.writelines(FailingIterNext()):NotImplementedError:('next',)
+ <<< Finished
+ > VimCommand
+ >>> Testing StringToChars using vim.command(%s)
+ vim.command(1):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.command(u"\0"):TypeError:('expected string without null bytes',)
+ vim.command("\0"):TypeError:('expected string without null bytes',)
+ <<< Finished
+ vim.command("", 2):TypeError:('command() takes exactly one argument (2 given)',)
+ > VimToPython
+ > VimEval
+ >>> Testing StringToChars using vim.eval(%s)
+ vim.eval(1):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.eval(u"\0"):TypeError:('expected string without null bytes',)
+ vim.eval("\0"):TypeError:('expected string without null bytes',)
+ <<< Finished
+ vim.eval("", FailingTrue()):TypeError:('function takes exactly 1 argument (2 given)',)
+ > VimEvalPy
+ >>> Testing StringToChars using vim.bindeval(%s)
+ vim.bindeval(1):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.bindeval(u"\0"):TypeError:('expected string without null bytes',)
+ vim.bindeval("\0"):TypeError:('expected string without null bytes',)
+ <<< Finished
+ vim.eval("", 2):TypeError:('function takes exactly 1 argument (2 given)',)
+ > VimStrwidth
+ >>> Testing StringToChars using vim.strwidth(%s)
+ vim.strwidth(1):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.strwidth(u"\0"):TypeError:('expected string without null bytes',)
+ vim.strwidth("\0"):TypeError:('expected string without null bytes',)
+ <<< Finished
+ > VimForeachRTP
+ vim.foreach_rtp(None):TypeError:("'NoneType' object is not callable",)
+ vim.foreach_rtp(NoArgsCall()):TypeError:('__call__() takes exactly 1 argument (2 given)',)
+ vim.foreach_rtp(FailingCall()):NotImplementedError:('call',)
+ vim.foreach_rtp(int, 2):TypeError:('foreach_rtp() takes exactly one argument (2 given)',)
+ > import
+ import xxx_no_such_module_xxx:ImportError:('No module named xxx_no_such_module_xxx',)
+ import failing_import:ImportError:()
+ import failing:NotImplementedError:()
+ > Options
+ >> OptionsItem
+ vim.options["abcQ"]:KeyError:('abcQ',)
+ vim.options[""]:ValueError:('empty keys are not allowed',)
+ >>> Testing StringToChars using vim.options[%s]
+ vim.options[1]:TypeError:('expected str() or unicode() instance, but got int',)
+ vim.options[u"\0"]:TypeError:('expected string without null bytes',)
+ vim.options["\0"]:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >> OptionsContains
+ >>> Testing StringToChars using %s in vim.options
+ 1 in vim.options:TypeError:('expected str() or unicode() instance, but got int',)
+ u"\0" in vim.options:TypeError:('expected string without null bytes',)
+ "\0" in vim.options:TypeError:('expected string without null bytes',)
+ <<< Finished
+ > Dictionary
+ >> DictionaryConstructor
+ vim.Dictionary("abcI"):ValueError:('expected sequence element of size 2, but got sequence of size 1',)
+ >> DictionarySetattr
+ del d.locked:AttributeError:('cannot delete vim.Dictionary attributes',)
+ d.locked = FailingTrue():NotImplementedError:('bool',)
+ vim.vvars.locked = False:TypeError:('cannot modify fixed dictionary',)
+ d.scope = True:AttributeError:('cannot set attribute scope',)
+ d.xxx = True:AttributeError:('cannot set attribute xxx',)
+ >> _DictionaryItem
+ d.get("a", 2, 3):TypeError:('function takes at most 2 arguments (3 given)',)
+ >>> Testing StringToChars using d.get(%s)
+ d.get(1):TypeError:('expected str() or unicode() instance, but got int',)
+ d.get(u"\0"):TypeError:('expected string without null bytes',)
+ d.get("\0"):TypeError:('expected string without null bytes',)
+ <<< Finished
+ d.pop("a"):KeyError:('a',)
+ dl.pop("a"):error:('dictionary is locked',)
+ >> DictionaryContains
+ "" in d:ValueError:('empty keys are not allowed',)
+ 0 in d:TypeError:('expected str() or unicode() instance, but got int',)
+ >> DictionaryIterNext
+ for i in ned: ned["a"] = 1:RuntimeError:('hashtab changed during iteration',)
+ >> DictionaryAssItem
+ dl["b"] = 1:error:('dictionary is locked',)
+ >>> Testing StringToChars using d[%s] = 1
+ d[1] = 1:TypeError:('expected str() or unicode() instance, but got int',)
+ d[u"\0"] = 1:TypeError:('expected string without null bytes',)
+ d["\0"] = 1:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = {%s : 1}
+ d["a"] = {1 : 1}:TypeError:('expected str() or unicode() instance, but got int',)
+ d["a"] = {u"\0" : 1}:TypeError:('expected string without null bytes',)
+ d["a"] = {"\0" : 1}:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = {"abcF" : {%s : 1}}
+ d["a"] = {"abcF" : {1 : 1}}:TypeError:('expected str() or unicode() instance, but got int',)
+ d["a"] = {"abcF" : {u"\0" : 1}}:TypeError:('expected string without null bytes',)
+ d["a"] = {"abcF" : {"\0" : 1}}:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = {"abcF" : Mapping({%s : 1})}
+ d["a"] = {"abcF" : Mapping({1 : 1})}:TypeError:('expected str() or unicode() instance, but got int',)
+ d["a"] = {"abcF" : Mapping({u"\0" : 1})}:TypeError:('expected string without null bytes',)
+ d["a"] = {"abcF" : Mapping({"\0" : 1})}:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using d["a"] = {"abcF" : %s}
+ d["a"] = {"abcF" : FailingIter()}:TypeError:('unable to convert FailingIter to a Vim structure',)
+ d["a"] = {"abcF" : FailingIterNext()}:NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d["a"] = {"abcF" : %s}
+ d["a"] = {"abcF" : None}:NOT FAILED
+ d["a"] = {"abcF" : {"": 1}}:ValueError:('empty keys are not allowed',)
+ d["a"] = {"abcF" : {u"": 1}}:ValueError:('empty keys are not allowed',)
+ d["a"] = {"abcF" : FailingMapping()}:NotImplementedError:('keys',)
+ d["a"] = {"abcF" : FailingMappingKey()}:NotImplementedError:('getitem:mappingkey',)
+ d["a"] = {"abcF" : FailingNumber()}:TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = Mapping({%s : 1})
+ d["a"] = Mapping({1 : 1}):TypeError:('expected str() or unicode() instance, but got int',)
+ d["a"] = Mapping({u"\0" : 1}):TypeError:('expected string without null bytes',)
+ d["a"] = Mapping({"\0" : 1}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = Mapping({"abcG" : {%s : 1}})
+ d["a"] = Mapping({"abcG" : {1 : 1}}):TypeError:('expected str() or unicode() instance, but got int',)
+ d["a"] = Mapping({"abcG" : {u"\0" : 1}}):TypeError:('expected string without null bytes',)
+ d["a"] = Mapping({"abcG" : {"\0" : 1}}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = Mapping({"abcG" : Mapping({%s : 1})})
+ d["a"] = Mapping({"abcG" : Mapping({1 : 1})}):TypeError:('expected str() or unicode() instance, but got int',)
+ d["a"] = Mapping({"abcG" : Mapping({u"\0" : 1})}):TypeError:('expected string without null bytes',)
+ d["a"] = Mapping({"abcG" : Mapping({"\0" : 1})}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using d["a"] = Mapping({"abcG" : %s})
+ d["a"] = Mapping({"abcG" : FailingIter()}):TypeError:('unable to convert FailingIter to a Vim structure',)
+ d["a"] = Mapping({"abcG" : FailingIterNext()}):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d["a"] = Mapping({"abcG" : %s})
+ d["a"] = Mapping({"abcG" : None}):NOT FAILED
+ d["a"] = Mapping({"abcG" : {"": 1}}):ValueError:('empty keys are not allowed',)
+ d["a"] = Mapping({"abcG" : {u"": 1}}):ValueError:('empty keys are not allowed',)
+ d["a"] = Mapping({"abcG" : FailingMapping()}):NotImplementedError:('keys',)
+ d["a"] = Mapping({"abcG" : FailingMappingKey()}):NotImplementedError:('getitem:mappingkey',)
+ d["a"] = Mapping({"abcG" : FailingNumber()}):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using d["a"] = %s
+ d["a"] = FailingIter():TypeError:('unable to convert FailingIter to a Vim structure',)
+ d["a"] = FailingIterNext():NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d["a"] = %s
+ d["a"] = None:NOT FAILED
+ d["a"] = {"": 1}:ValueError:('empty keys are not allowed',)
+ d["a"] = {u"": 1}:ValueError:('empty keys are not allowed',)
+ d["a"] = FailingMapping():NotImplementedError:('keys',)
+ d["a"] = FailingMappingKey():NotImplementedError:('getitem:mappingkey',)
+ d["a"] = FailingNumber():TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >> DictionaryUpdate
+ >>> kwargs
+ >>> iter
+ d.update(FailingMapping()):NotImplementedError:('keys',)
+ d.update([FailingIterNext()]):NotImplementedError:('next',)
+ d.update([FailingIterNextN(1)]):NotImplementedError:('next N',)
+ >>> Testing *Iter* using d.update(%s)
+ d.update(FailingIter()):NotImplementedError:('iter',)
+ d.update(FailingIterNext()):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing StringToChars using d.update({%s : 1})
+ d.update({1 : 1}):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update({u"\0" : 1}):TypeError:('expected string without null bytes',)
+ d.update({"\0" : 1}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update({"abcF" : {%s : 1}})
+ d.update({"abcF" : {1 : 1}}):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update({"abcF" : {u"\0" : 1}}):TypeError:('expected string without null bytes',)
+ d.update({"abcF" : {"\0" : 1}}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update({"abcF" : Mapping({%s : 1})})
+ d.update({"abcF" : Mapping({1 : 1})}):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update({"abcF" : Mapping({u"\0" : 1})}):TypeError:('expected string without null bytes',)
+ d.update({"abcF" : Mapping({"\0" : 1})}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using d.update({"abcF" : %s})
+ d.update({"abcF" : FailingIter()}):TypeError:('unable to convert FailingIter to a Vim structure',)
+ d.update({"abcF" : FailingIterNext()}):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update({"abcF" : %s})
+ d.update({"abcF" : None}):NOT FAILED
+ d.update({"abcF" : {"": 1}}):ValueError:('empty keys are not allowed',)
+ d.update({"abcF" : {u"": 1}}):ValueError:('empty keys are not allowed',)
+ d.update({"abcF" : FailingMapping()}):NotImplementedError:('keys',)
+ d.update({"abcF" : FailingMappingKey()}):NotImplementedError:('getitem:mappingkey',)
+ d.update({"abcF" : FailingNumber()}):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using d.update(Mapping({%s : 1}))
+ d.update(Mapping({1 : 1})):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update(Mapping({u"\0" : 1})):TypeError:('expected string without null bytes',)
+ d.update(Mapping({"\0" : 1})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update(Mapping({"abcG" : {%s : 1}}))
+ d.update(Mapping({"abcG" : {1 : 1}})):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update(Mapping({"abcG" : {u"\0" : 1}})):TypeError:('expected string without null bytes',)
+ d.update(Mapping({"abcG" : {"\0" : 1}})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update(Mapping({"abcG" : Mapping({%s : 1})}))
+ d.update(Mapping({"abcG" : Mapping({1 : 1})})):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update(Mapping({"abcG" : Mapping({u"\0" : 1})})):TypeError:('expected string without null bytes',)
+ d.update(Mapping({"abcG" : Mapping({"\0" : 1})})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using d.update(Mapping({"abcG" : %s}))
+ d.update(Mapping({"abcG" : FailingIter()})):TypeError:('unable to convert FailingIter to a Vim structure',)
+ d.update(Mapping({"abcG" : FailingIterNext()})):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update(Mapping({"abcG" : %s}))
+ d.update(Mapping({"abcG" : None})):NOT FAILED
+ d.update(Mapping({"abcG" : {"": 1}})):ValueError:('empty keys are not allowed',)
+ d.update(Mapping({"abcG" : {u"": 1}})):ValueError:('empty keys are not allowed',)
+ d.update(Mapping({"abcG" : FailingMapping()})):NotImplementedError:('keys',)
+ d.update(Mapping({"abcG" : FailingMappingKey()})):NotImplementedError:('getitem:mappingkey',)
+ d.update(Mapping({"abcG" : FailingNumber()})):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using d.update(%s)
+ d.update(FailingIter()):NotImplementedError:('iter',)
+ d.update(FailingIterNext()):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update(%s)
+ d.update(None):TypeError:("'NoneType' object is not iterable",)
+ d.update({"": 1}):ValueError:('empty keys are not allowed',)
+ d.update({u"": 1}):ValueError:('empty keys are not allowed',)
+ d.update(FailingMapping()):NotImplementedError:('keys',)
+ d.update(FailingMappingKey()):NotImplementedError:('getitem:mappingkey',)
+ d.update(FailingNumber()):TypeError:("'FailingNumber' object is not iterable",)
+ <<< Finished
+ >>> Testing StringToChars using d.update(((%s, 0),))
+ d.update(((1, 0),)):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update(((u"\0", 0),)):TypeError:('expected string without null bytes',)
+ d.update((("\0", 0),)):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", {%s : 1}),))
+ d.update((("a", {1 : 1}),)):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update((("a", {u"\0" : 1}),)):TypeError:('expected string without null bytes',)
+ d.update((("a", {"\0" : 1}),)):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", {"abcF" : {%s : 1}}),))
+ d.update((("a", {"abcF" : {1 : 1}}),)):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update((("a", {"abcF" : {u"\0" : 1}}),)):TypeError:('expected string without null bytes',)
+ d.update((("a", {"abcF" : {"\0" : 1}}),)):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", {"abcF" : Mapping({%s : 1})}),))
+ d.update((("a", {"abcF" : Mapping({1 : 1})}),)):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update((("a", {"abcF" : Mapping({u"\0" : 1})}),)):TypeError:('expected string without null bytes',)
+ d.update((("a", {"abcF" : Mapping({"\0" : 1})}),)):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using d.update((("a", {"abcF" : %s}),))
+ d.update((("a", {"abcF" : FailingIter()}),)):TypeError:('unable to convert FailingIter to a Vim structure',)
+ d.update((("a", {"abcF" : FailingIterNext()}),)):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update((("a", {"abcF" : %s}),))
+ d.update((("a", {"abcF" : None}),)):error:("failed to add key 'a' to dictionary",)
+ d.update((("a", {"abcF" : {"": 1}}),)):ValueError:('empty keys are not allowed',)
+ d.update((("a", {"abcF" : {u"": 1}}),)):ValueError:('empty keys are not allowed',)
+ d.update((("a", {"abcF" : FailingMapping()}),)):NotImplementedError:('keys',)
+ d.update((("a", {"abcF" : FailingMappingKey()}),)):NotImplementedError:('getitem:mappingkey',)
+ d.update((("a", {"abcF" : FailingNumber()}),)):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", Mapping({%s : 1})),))
+ d.update((("a", Mapping({1 : 1})),)):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update((("a", Mapping({u"\0" : 1})),)):TypeError:('expected string without null bytes',)
+ d.update((("a", Mapping({"\0" : 1})),)):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", Mapping({"abcG" : {%s : 1}})),))
+ d.update((("a", Mapping({"abcG" : {1 : 1}})),)):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update((("a", Mapping({"abcG" : {u"\0" : 1}})),)):TypeError:('expected string without null bytes',)
+ d.update((("a", Mapping({"abcG" : {"\0" : 1}})),)):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", Mapping({"abcG" : Mapping({%s : 1})})),))
+ d.update((("a", Mapping({"abcG" : Mapping({1 : 1})})),)):TypeError:('expected str() or unicode() instance, but got int',)
+ d.update((("a", Mapping({"abcG" : Mapping({u"\0" : 1})})),)):TypeError:('expected string without null bytes',)
+ d.update((("a", Mapping({"abcG" : Mapping({"\0" : 1})})),)):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using d.update((("a", Mapping({"abcG" : %s})),))
+ d.update((("a", Mapping({"abcG" : FailingIter()})),)):TypeError:('unable to convert FailingIter to a Vim structure',)
+ d.update((("a", Mapping({"abcG" : FailingIterNext()})),)):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update((("a", Mapping({"abcG" : %s})),))
+ d.update((("a", Mapping({"abcG" : None})),)):error:("failed to add key 'a' to dictionary",)
+ d.update((("a", Mapping({"abcG" : {"": 1}})),)):ValueError:('empty keys are not allowed',)
+ d.update((("a", Mapping({"abcG" : {u"": 1}})),)):ValueError:('empty keys are not allowed',)
+ d.update((("a", Mapping({"abcG" : FailingMapping()})),)):NotImplementedError:('keys',)
+ d.update((("a", Mapping({"abcG" : FailingMappingKey()})),)):NotImplementedError:('getitem:mappingkey',)
+ d.update((("a", Mapping({"abcG" : FailingNumber()})),)):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using d.update((("a", %s),))
+ d.update((("a", FailingIter()),)):TypeError:('unable to convert FailingIter to a Vim structure',)
+ d.update((("a", FailingIterNext()),)):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update((("a", %s),))
+ d.update((("a", None),)):error:("failed to add key 'a' to dictionary",)
+ d.update((("a", {"": 1}),)):ValueError:('empty keys are not allowed',)
+ d.update((("a", {u"": 1}),)):ValueError:('empty keys are not allowed',)
+ d.update((("a", FailingMapping()),)):NotImplementedError:('keys',)
+ d.update((("a", FailingMappingKey()),)):NotImplementedError:('getitem:mappingkey',)
+ d.update((("a", FailingNumber()),)):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >> DictionaryPopItem
+ d.popitem(1, 2):TypeError:('popitem() takes no arguments (2 given)',)
+ >> DictionaryHasKey
+ d.has_key():TypeError:('has_key() takes exactly one argument (0 given)',)
+ > List
+ >> ListConstructor
+ vim.List(1, 2):TypeError:('function takes at most 1 argument (2 given)',)
+ vim.List(a=1):TypeError:('list constructor does not accept keyword arguments',)
+ >>> Testing *Iter* using vim.List(%s)
+ vim.List(FailingIter()):NotImplementedError:('iter',)
+ vim.List(FailingIterNext()):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing StringToChars using vim.List([{%s : 1}])
+ vim.List([{1 : 1}]):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.List([{u"\0" : 1}]):TypeError:('expected string without null bytes',)
+ vim.List([{"\0" : 1}]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using vim.List([{"abcF" : {%s : 1}}])
+ vim.List([{"abcF" : {1 : 1}}]):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.List([{"abcF" : {u"\0" : 1}}]):TypeError:('expected string without null bytes',)
+ vim.List([{"abcF" : {"\0" : 1}}]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using vim.List([{"abcF" : Mapping({%s : 1})}])
+ vim.List([{"abcF" : Mapping({1 : 1})}]):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.List([{"abcF" : Mapping({u"\0" : 1})}]):TypeError:('expected string without null bytes',)
+ vim.List([{"abcF" : Mapping({"\0" : 1})}]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using vim.List([{"abcF" : %s}])
+ vim.List([{"abcF" : FailingIter()}]):TypeError:('unable to convert FailingIter to a Vim structure',)
+ vim.List([{"abcF" : FailingIterNext()}]):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using vim.List([{"abcF" : %s}])
+ vim.List([{"abcF" : None}]):NOT FAILED
+ vim.List([{"abcF" : {"": 1}}]):ValueError:('empty keys are not allowed',)
+ vim.List([{"abcF" : {u"": 1}}]):ValueError:('empty keys are not allowed',)
+ vim.List([{"abcF" : FailingMapping()}]):NotImplementedError:('keys',)
+ vim.List([{"abcF" : FailingMappingKey()}]):NotImplementedError:('getitem:mappingkey',)
+ vim.List([{"abcF" : FailingNumber()}]):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using vim.List([Mapping({%s : 1})])
+ vim.List([Mapping({1 : 1})]):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.List([Mapping({u"\0" : 1})]):TypeError:('expected string without null bytes',)
+ vim.List([Mapping({"\0" : 1})]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using vim.List([Mapping({"abcG" : {%s : 1}})])
+ vim.List([Mapping({"abcG" : {1 : 1}})]):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.List([Mapping({"abcG" : {u"\0" : 1}})]):TypeError:('expected string without null bytes',)
+ vim.List([Mapping({"abcG" : {"\0" : 1}})]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using vim.List([Mapping({"abcG" : Mapping({%s : 1})})])
+ vim.List([Mapping({"abcG" : Mapping({1 : 1})})]):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.List([Mapping({"abcG" : Mapping({u"\0" : 1})})]):TypeError:('expected string without null bytes',)
+ vim.List([Mapping({"abcG" : Mapping({"\0" : 1})})]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using vim.List([Mapping({"abcG" : %s})])
+ vim.List([Mapping({"abcG" : FailingIter()})]):TypeError:('unable to convert FailingIter to a Vim structure',)
+ vim.List([Mapping({"abcG" : FailingIterNext()})]):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using vim.List([Mapping({"abcG" : %s})])
+ vim.List([Mapping({"abcG" : None})]):NOT FAILED
+ vim.List([Mapping({"abcG" : {"": 1}})]):ValueError:('empty keys are not allowed',)
+ vim.List([Mapping({"abcG" : {u"": 1}})]):ValueError:('empty keys are not allowed',)
+ vim.List([Mapping({"abcG" : FailingMapping()})]):NotImplementedError:('keys',)
+ vim.List([Mapping({"abcG" : FailingMappingKey()})]):NotImplementedError:('getitem:mappingkey',)
+ vim.List([Mapping({"abcG" : FailingNumber()})]):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using vim.List([%s])
+ vim.List([FailingIter()]):TypeError:('unable to convert FailingIter to a Vim structure',)
+ vim.List([FailingIterNext()]):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using vim.List([%s])
+ vim.List([None]):NOT FAILED
+ vim.List([{"": 1}]):ValueError:('empty keys are not allowed',)
+ vim.List([{u"": 1}]):ValueError:('empty keys are not allowed',)
+ vim.List([FailingMapping()]):NotImplementedError:('keys',)
+ vim.List([FailingMappingKey()]):NotImplementedError:('getitem:mappingkey',)
+ vim.List([FailingNumber()]):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >> ListItem
+ l[1000]:IndexError:('list index out of range',)
+ >> ListAssItem
+ ll[1] = 2:error:('list is locked',)
+ l[1000] = 3:IndexError:('list index out of range',)
+ >> ListAssSlice
+ ll[1:100] = "abcJ":error:('list is locked',)
+ >>> Testing *Iter* using l[:] = %s
+ l[:] = FailingIter():NotImplementedError:('iter',)
+ l[:] = FailingIterNext():NotImplementedError:('next',)
+ <<< Finished
+ nel[1:10:2] = "abcK":ValueError:('attempt to assign sequence of size greater than 2 to extended slice',)
+ ('a', 'b', 'c', 'O')
+ nel[1:10:2] = "a":ValueError:('attempt to assign sequence of size 1 to extended slice of size 2',)
+ ('a', 'b', 'c', 'O')
+ nel[1:1:-1] = "a":ValueError:('attempt to assign sequence of size greater than 0 to extended slice',)
+ ('a', 'b', 'c', 'O')
+ nel[:] = FailingIterNextN(2):NotImplementedError:('next N',)
+ ('a', 'b', 'c', 'O')
+ >>> Testing StringToChars using l[:] = [{%s : 1}]
+ l[:] = [{1 : 1}]:TypeError:('expected str() or unicode() instance, but got int',)
+ l[:] = [{u"\0" : 1}]:TypeError:('expected string without null bytes',)
+ l[:] = [{"\0" : 1}]:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [{"abcF" : {%s : 1}}]
+ l[:] = [{"abcF" : {1 : 1}}]:TypeError:('expected str() or unicode() instance, but got int',)
+ l[:] = [{"abcF" : {u"\0" : 1}}]:TypeError:('expected string without null bytes',)
+ l[:] = [{"abcF" : {"\0" : 1}}]:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [{"abcF" : Mapping({%s : 1})}]
+ l[:] = [{"abcF" : Mapping({1 : 1})}]:TypeError:('expected str() or unicode() instance, but got int',)
+ l[:] = [{"abcF" : Mapping({u"\0" : 1})}]:TypeError:('expected string without null bytes',)
+ l[:] = [{"abcF" : Mapping({"\0" : 1})}]:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using l[:] = [{"abcF" : %s}]
+ l[:] = [{"abcF" : FailingIter()}]:TypeError:('unable to convert FailingIter to a Vim structure',)
+ l[:] = [{"abcF" : FailingIterNext()}]:NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l[:] = [{"abcF" : %s}]
+ l[:] = [{"abcF" : None}]:NOT FAILED
+ l[:] = [{"abcF" : {"": 1}}]:ValueError:('empty keys are not allowed',)
+ l[:] = [{"abcF" : {u"": 1}}]:ValueError:('empty keys are not allowed',)
+ l[:] = [{"abcF" : FailingMapping()}]:NotImplementedError:('keys',)
+ l[:] = [{"abcF" : FailingMappingKey()}]:NotImplementedError:('getitem:mappingkey',)
+ l[:] = [{"abcF" : FailingNumber()}]:TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [Mapping({%s : 1})]
+ l[:] = [Mapping({1 : 1})]:TypeError:('expected str() or unicode() instance, but got int',)
+ l[:] = [Mapping({u"\0" : 1})]:TypeError:('expected string without null bytes',)
+ l[:] = [Mapping({"\0" : 1})]:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [Mapping({"abcG" : {%s : 1}})]
+ l[:] = [Mapping({"abcG" : {1 : 1}})]:TypeError:('expected str() or unicode() instance, but got int',)
+ l[:] = [Mapping({"abcG" : {u"\0" : 1}})]:TypeError:('expected string without null bytes',)
+ l[:] = [Mapping({"abcG" : {"\0" : 1}})]:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [Mapping({"abcG" : Mapping({%s : 1})})]
+ l[:] = [Mapping({"abcG" : Mapping({1 : 1})})]:TypeError:('expected str() or unicode() instance, but got int',)
+ l[:] = [Mapping({"abcG" : Mapping({u"\0" : 1})})]:TypeError:('expected string without null bytes',)
+ l[:] = [Mapping({"abcG" : Mapping({"\0" : 1})})]:TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using l[:] = [Mapping({"abcG" : %s})]
+ l[:] = [Mapping({"abcG" : FailingIter()})]:TypeError:('unable to convert FailingIter to a Vim structure',)
+ l[:] = [Mapping({"abcG" : FailingIterNext()})]:NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l[:] = [Mapping({"abcG" : %s})]
+ l[:] = [Mapping({"abcG" : None})]:NOT FAILED
+ l[:] = [Mapping({"abcG" : {"": 1}})]:ValueError:('empty keys are not allowed',)
+ l[:] = [Mapping({"abcG" : {u"": 1}})]:ValueError:('empty keys are not allowed',)
+ l[:] = [Mapping({"abcG" : FailingMapping()})]:NotImplementedError:('keys',)
+ l[:] = [Mapping({"abcG" : FailingMappingKey()})]:NotImplementedError:('getitem:mappingkey',)
+ l[:] = [Mapping({"abcG" : FailingNumber()})]:TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using l[:] = [%s]
+ l[:] = [FailingIter()]:TypeError:('unable to convert FailingIter to a Vim structure',)
+ l[:] = [FailingIterNext()]:NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l[:] = [%s]
+ l[:] = [None]:NOT FAILED
+ l[:] = [{"": 1}]:ValueError:('empty keys are not allowed',)
+ l[:] = [{u"": 1}]:ValueError:('empty keys are not allowed',)
+ l[:] = [FailingMapping()]:NotImplementedError:('keys',)
+ l[:] = [FailingMappingKey()]:NotImplementedError:('getitem:mappingkey',)
+ l[:] = [FailingNumber()]:TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >> ListConcatInPlace
+ >>> Testing *Iter* using l.extend(%s)
+ l.extend(FailingIter()):NotImplementedError:('iter',)
+ l.extend(FailingIterNext()):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing StringToChars using l.extend([{%s : 1}])
+ l.extend([{1 : 1}]):TypeError:('expected str() or unicode() instance, but got int',)
+ l.extend([{u"\0" : 1}]):TypeError:('expected string without null bytes',)
+ l.extend([{"\0" : 1}]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l.extend([{"abcF" : {%s : 1}}])
+ l.extend([{"abcF" : {1 : 1}}]):TypeError:('expected str() or unicode() instance, but got int',)
+ l.extend([{"abcF" : {u"\0" : 1}}]):TypeError:('expected string without null bytes',)
+ l.extend([{"abcF" : {"\0" : 1}}]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l.extend([{"abcF" : Mapping({%s : 1})}])
+ l.extend([{"abcF" : Mapping({1 : 1})}]):TypeError:('expected str() or unicode() instance, but got int',)
+ l.extend([{"abcF" : Mapping({u"\0" : 1})}]):TypeError:('expected string without null bytes',)
+ l.extend([{"abcF" : Mapping({"\0" : 1})}]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using l.extend([{"abcF" : %s}])
+ l.extend([{"abcF" : FailingIter()}]):TypeError:('unable to convert FailingIter to a Vim structure',)
+ l.extend([{"abcF" : FailingIterNext()}]):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l.extend([{"abcF" : %s}])
+ l.extend([{"abcF" : None}]):NOT FAILED
+ l.extend([{"abcF" : {"": 1}}]):ValueError:('empty keys are not allowed',)
+ l.extend([{"abcF" : {u"": 1}}]):ValueError:('empty keys are not allowed',)
+ l.extend([{"abcF" : FailingMapping()}]):NotImplementedError:('keys',)
+ l.extend([{"abcF" : FailingMappingKey()}]):NotImplementedError:('getitem:mappingkey',)
+ l.extend([{"abcF" : FailingNumber()}]):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using l.extend([Mapping({%s : 1})])
+ l.extend([Mapping({1 : 1})]):TypeError:('expected str() or unicode() instance, but got int',)
+ l.extend([Mapping({u"\0" : 1})]):TypeError:('expected string without null bytes',)
+ l.extend([Mapping({"\0" : 1})]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l.extend([Mapping({"abcG" : {%s : 1}})])
+ l.extend([Mapping({"abcG" : {1 : 1}})]):TypeError:('expected str() or unicode() instance, but got int',)
+ l.extend([Mapping({"abcG" : {u"\0" : 1}})]):TypeError:('expected string without null bytes',)
+ l.extend([Mapping({"abcG" : {"\0" : 1}})]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using l.extend([Mapping({"abcG" : Mapping({%s : 1})})])
+ l.extend([Mapping({"abcG" : Mapping({1 : 1})})]):TypeError:('expected str() or unicode() instance, but got int',)
+ l.extend([Mapping({"abcG" : Mapping({u"\0" : 1})})]):TypeError:('expected string without null bytes',)
+ l.extend([Mapping({"abcG" : Mapping({"\0" : 1})})]):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using l.extend([Mapping({"abcG" : %s})])
+ l.extend([Mapping({"abcG" : FailingIter()})]):TypeError:('unable to convert FailingIter to a Vim structure',)
+ l.extend([Mapping({"abcG" : FailingIterNext()})]):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l.extend([Mapping({"abcG" : %s})])
+ l.extend([Mapping({"abcG" : None})]):NOT FAILED
+ l.extend([Mapping({"abcG" : {"": 1}})]):ValueError:('empty keys are not allowed',)
+ l.extend([Mapping({"abcG" : {u"": 1}})]):ValueError:('empty keys are not allowed',)
+ l.extend([Mapping({"abcG" : FailingMapping()})]):NotImplementedError:('keys',)
+ l.extend([Mapping({"abcG" : FailingMappingKey()})]):NotImplementedError:('getitem:mappingkey',)
+ l.extend([Mapping({"abcG" : FailingNumber()})]):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using l.extend([%s])
+ l.extend([FailingIter()]):TypeError:('unable to convert FailingIter to a Vim structure',)
+ l.extend([FailingIterNext()]):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l.extend([%s])
+ l.extend([None]):NOT FAILED
+ l.extend([{"": 1}]):ValueError:('empty keys are not allowed',)
+ l.extend([{u"": 1}]):ValueError:('empty keys are not allowed',)
+ l.extend([FailingMapping()]):NotImplementedError:('keys',)
+ l.extend([FailingMappingKey()]):NotImplementedError:('getitem:mappingkey',)
+ l.extend([FailingNumber()]):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >> ListSetattr
+ del l.locked:AttributeError:('cannot delete vim.List attributes',)
+ l.locked = FailingTrue():NotImplementedError:('bool',)
+ l.xxx = True:AttributeError:('cannot set attribute xxx',)
+ > Function
+ >> FunctionConstructor
+ >>> FunctionConstructor
+ vim.Function("123"):ValueError:('unnamed function 123 does not exist',)
+ vim.Function("xxx_non_existent_function_xxx"):ValueError:('function xxx_non_existent_function_xxx does not exist',)
+ vim.Function("xxx#non#existent#function#xxx"):NOT FAILED
+ vim.Function("xxx_non_existent_function_xxx2", args=[]):ValueError:('function xxx_non_existent_function_xxx2 does not exist',)
+ vim.Function("xxx_non_existent_function_xxx3", self={}):ValueError:('function xxx_non_existent_function_xxx3 does not exist',)
+ vim.Function("xxx_non_existent_function_xxx4", args=[], self={}):ValueError:('function xxx_non_existent_function_xxx4 does not exist',)
+ >>> FunctionNew
+ vim.Function("tr", self="abcFuncSelf"):TypeError:('unable to convert str to a Vim dictionary',)
+ vim.Function("tr", args=427423):TypeError:('unable to convert int to a Vim list',)
+ vim.Function("tr", self="abcFuncSelf2", args="abcFuncArgs2"):TypeError:('unable to convert str to a Vim dictionary',)
+ vim.Function(self="abcFuncSelf2", args="abcFuncArgs2"):TypeError:('unable to convert str to a Vim dictionary',)
+ vim.Function("tr", "", self="abcFuncSelf2", args="abcFuncArgs2"):TypeError:('unable to convert str to a Vim dictionary',)
+ vim.Function("tr", ""):TypeError:('function takes exactly 1 argument (2 given)',)
+ >> FunctionCall
+ >>> Testing StringToChars using f({%s : 1})
+ f({1 : 1}):TypeError:('expected str() or unicode() instance, but got int',)
+ f({u"\0" : 1}):TypeError:('expected string without null bytes',)
+ f({"\0" : 1}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using f({"abcF" : {%s : 1}})
+ f({"abcF" : {1 : 1}}):TypeError:('expected str() or unicode() instance, but got int',)
+ f({"abcF" : {u"\0" : 1}}):TypeError:('expected string without null bytes',)
+ f({"abcF" : {"\0" : 1}}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using f({"abcF" : Mapping({%s : 1})})
+ f({"abcF" : Mapping({1 : 1})}):TypeError:('expected str() or unicode() instance, but got int',)
+ f({"abcF" : Mapping({u"\0" : 1})}):TypeError:('expected string without null bytes',)
+ f({"abcF" : Mapping({"\0" : 1})}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using f({"abcF" : %s})
+ f({"abcF" : FailingIter()}):TypeError:('unable to convert FailingIter to a Vim structure',)
+ f({"abcF" : FailingIterNext()}):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using f({"abcF" : %s})
+ f({"abcF" : None}):NOT FAILED
+ f({"abcF" : {"": 1}}):ValueError:('empty keys are not allowed',)
+ f({"abcF" : {u"": 1}}):ValueError:('empty keys are not allowed',)
+ f({"abcF" : FailingMapping()}):NotImplementedError:('keys',)
+ f({"abcF" : FailingMappingKey()}):NotImplementedError:('getitem:mappingkey',)
+ f({"abcF" : FailingNumber()}):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using f(Mapping({%s : 1}))
+ f(Mapping({1 : 1})):TypeError:('expected str() or unicode() instance, but got int',)
+ f(Mapping({u"\0" : 1})):TypeError:('expected string without null bytes',)
+ f(Mapping({"\0" : 1})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using f(Mapping({"abcG" : {%s : 1}}))
+ f(Mapping({"abcG" : {1 : 1}})):TypeError:('expected str() or unicode() instance, but got int',)
+ f(Mapping({"abcG" : {u"\0" : 1}})):TypeError:('expected string without null bytes',)
+ f(Mapping({"abcG" : {"\0" : 1}})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using f(Mapping({"abcG" : Mapping({%s : 1})}))
+ f(Mapping({"abcG" : Mapping({1 : 1})})):TypeError:('expected str() or unicode() instance, but got int',)
+ f(Mapping({"abcG" : Mapping({u"\0" : 1})})):TypeError:('expected string without null bytes',)
+ f(Mapping({"abcG" : Mapping({"\0" : 1})})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using f(Mapping({"abcG" : %s}))
+ f(Mapping({"abcG" : FailingIter()})):TypeError:('unable to convert FailingIter to a Vim structure',)
+ f(Mapping({"abcG" : FailingIterNext()})):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using f(Mapping({"abcG" : %s}))
+ f(Mapping({"abcG" : None})):NOT FAILED
+ f(Mapping({"abcG" : {"": 1}})):ValueError:('empty keys are not allowed',)
+ f(Mapping({"abcG" : {u"": 1}})):ValueError:('empty keys are not allowed',)
+ f(Mapping({"abcG" : FailingMapping()})):NotImplementedError:('keys',)
+ f(Mapping({"abcG" : FailingMappingKey()})):NotImplementedError:('getitem:mappingkey',)
+ f(Mapping({"abcG" : FailingNumber()})):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using f(%s)
+ f(FailingIter()):TypeError:('unable to convert FailingIter to a Vim structure',)
+ f(FailingIterNext()):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using f(%s)
+ f(None):NOT FAILED
+ f({"": 1}):ValueError:('empty keys are not allowed',)
+ f({u"": 1}):ValueError:('empty keys are not allowed',)
+ f(FailingMapping()):NotImplementedError:('keys',)
+ f(FailingMappingKey()):NotImplementedError:('getitem:mappingkey',)
+ f(FailingNumber()):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using fd(self={%s : 1})
+ fd(self={1 : 1}):TypeError:('expected str() or unicode() instance, but got int',)
+ fd(self={u"\0" : 1}):TypeError:('expected string without null bytes',)
+ fd(self={"\0" : 1}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using fd(self={"abcF" : {%s : 1}})
+ fd(self={"abcF" : {1 : 1}}):TypeError:('expected str() or unicode() instance, but got int',)
+ fd(self={"abcF" : {u"\0" : 1}}):TypeError:('expected string without null bytes',)
+ fd(self={"abcF" : {"\0" : 1}}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using fd(self={"abcF" : Mapping({%s : 1})})
+ fd(self={"abcF" : Mapping({1 : 1})}):TypeError:('expected str() or unicode() instance, but got int',)
+ fd(self={"abcF" : Mapping({u"\0" : 1})}):TypeError:('expected string without null bytes',)
+ fd(self={"abcF" : Mapping({"\0" : 1})}):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using fd(self={"abcF" : %s})
+ fd(self={"abcF" : FailingIter()}):TypeError:('unable to convert FailingIter to a Vim structure',)
+ fd(self={"abcF" : FailingIterNext()}):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using fd(self={"abcF" : %s})
+ fd(self={"abcF" : None}):NOT FAILED
+ fd(self={"abcF" : {"": 1}}):ValueError:('empty keys are not allowed',)
+ fd(self={"abcF" : {u"": 1}}):ValueError:('empty keys are not allowed',)
+ fd(self={"abcF" : FailingMapping()}):NotImplementedError:('keys',)
+ fd(self={"abcF" : FailingMappingKey()}):NotImplementedError:('getitem:mappingkey',)
+ fd(self={"abcF" : FailingNumber()}):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing StringToChars using fd(self=Mapping({%s : 1}))
+ fd(self=Mapping({1 : 1})):TypeError:('expected str() or unicode() instance, but got int',)
+ fd(self=Mapping({u"\0" : 1})):TypeError:('expected string without null bytes',)
+ fd(self=Mapping({"\0" : 1})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using fd(self=Mapping({"abcG" : {%s : 1}}))
+ fd(self=Mapping({"abcG" : {1 : 1}})):TypeError:('expected str() or unicode() instance, but got int',)
+ fd(self=Mapping({"abcG" : {u"\0" : 1}})):TypeError:('expected string without null bytes',)
+ fd(self=Mapping({"abcG" : {"\0" : 1}})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing StringToChars using fd(self=Mapping({"abcG" : Mapping({%s : 1})}))
+ fd(self=Mapping({"abcG" : Mapping({1 : 1})})):TypeError:('expected str() or unicode() instance, but got int',)
+ fd(self=Mapping({"abcG" : Mapping({u"\0" : 1})})):TypeError:('expected string without null bytes',)
+ fd(self=Mapping({"abcG" : Mapping({"\0" : 1})})):TypeError:('expected string without null bytes',)
+ <<< Finished
+ >>> Testing *Iter* using fd(self=Mapping({"abcG" : %s}))
+ fd(self=Mapping({"abcG" : FailingIter()})):TypeError:('unable to convert FailingIter to a Vim structure',)
+ fd(self=Mapping({"abcG" : FailingIterNext()})):NotImplementedError:('next',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using fd(self=Mapping({"abcG" : %s}))
+ fd(self=Mapping({"abcG" : None})):NOT FAILED
+ fd(self=Mapping({"abcG" : {"": 1}})):ValueError:('empty keys are not allowed',)
+ fd(self=Mapping({"abcG" : {u"": 1}})):ValueError:('empty keys are not allowed',)
+ fd(self=Mapping({"abcG" : FailingMapping()})):NotImplementedError:('keys',)
+ fd(self=Mapping({"abcG" : FailingMappingKey()})):NotImplementedError:('getitem:mappingkey',)
+ fd(self=Mapping({"abcG" : FailingNumber()})):TypeError:('long() argument must be a string or a number',)
+ <<< Finished
+ >>> Testing *Iter* using fd(self=%s)
+ fd(self=FailingIter()):TypeError:('unable to convert FailingIter to a Vim dictionary',)
+ fd(self=FailingIterNext()):TypeError:('unable to convert FailingIterNext to a Vim dictionary',)
+ <<< Finished
+ >>> Testing ConvertFromPyObject using fd(self=%s)
+ fd(self=None):TypeError:('unable to convert NoneType to a Vim dictionary',)
+ fd(self={"": 1}):ValueError:('empty keys are not allowed',)
+ fd(self={u"": 1}):ValueError:('empty keys are not allowed',)
+ fd(self=FailingMapping()):NotImplementedError:('keys',)
+ fd(self=FailingMappingKey()):NotImplementedError:('getitem:mappingkey',)
+ fd(self=FailingNumber()):TypeError:('unable to convert FailingNumber to a Vim dictionary',)
+ <<< Finished
+ >>> Testing ConvertFromPyMapping using fd(self=%s)
+ fd(self=[]):TypeError:('unable to convert list to a Vim dictionary',)
+ <<< Finished
+ > TabPage
+ >> TabPageAttr
+ vim.current.tabpage.xxx:AttributeError:('xxx',)
+ > TabList
+ >> TabListItem
+ vim.tabpages[1000]:IndexError:('no such tab page',)
+ > Window
+ >> WindowAttr
+ vim.current.window.xxx:AttributeError:('xxx',)
+ >> WindowSetattr
+ vim.current.window.buffer = 0:TypeError:('readonly attribute: buffer',)
+ vim.current.window.cursor = (100000000, 100000000):error:('cursor position outside buffer',)
+ vim.current.window.cursor = True:TypeError:('argument must be 2-item sequence, not bool',)
+ >>> Testing NumberToLong using vim.current.window.height = %s
+ vim.current.window.height = []:TypeError:('expected int(), long() or something supporting coercing to long(), but got list',)
+ vim.current.window.height = None:TypeError:('expected int(), long() or something supporting coercing to long(), but got NoneType',)
+ vim.current.window.height = -1:ValueError:('number must be greater or equal to zero',)
+ <<< Finished
+ >>> Testing NumberToLong using vim.current.window.width = %s
+ vim.current.window.width = []:TypeError:('expected int(), long() or something supporting coercing to long(), but got list',)
+ vim.current.window.width = None:TypeError:('expected int(), long() or something supporting coercing to long(), but got NoneType',)
+ vim.current.window.width = -1:ValueError:('number must be greater or equal to zero',)
+ <<< Finished
+ vim.current.window.xxxxxx = True:AttributeError:('xxxxxx',)
+ > WinList
+ >> WinListItem
+ vim.windows[1000]:IndexError:('no such window',)
+ > Buffer
+ >> StringToLine (indirect)
+ vim.current.buffer[0] = "\na":error:('string cannot contain newlines',)
+ vim.current.buffer[0] = u"\na":error:('string cannot contain newlines',)
+ >> SetBufferLine (indirect)
+ vim.current.buffer[0] = True:TypeError:('bad argument type for built-in operation',)
+ >> SetBufferLineList (indirect)
+ vim.current.buffer[:] = True:TypeError:('bad argument type for built-in operation',)
+ vim.current.buffer[:] = ["\na", "bc"]:error:('string cannot contain newlines',)
+ >> InsertBufferLines (indirect)
+ vim.current.buffer.append(None):TypeError:('bad argument type for built-in operation',)
+ vim.current.buffer.append(["\na", "bc"]):error:('string cannot contain newlines',)
+ vim.current.buffer.append("\nbc"):error:('string cannot contain newlines',)
+ >> RBItem
+ vim.current.buffer[100000000]:IndexError:('line number out of range',)
+ >> RBAsItem
+ vim.current.buffer[100000000] = "":IndexError:('line number out of range',)
+ >> BufferAttr
+ vim.current.buffer.xxx:AttributeError:('xxx',)
+ >> BufferSetattr
+ vim.current.buffer.name = True:TypeError:('expected str() or unicode() instance, but got bool',)
+ vim.current.buffer.xxx = True:AttributeError:('xxx',)
+ >> BufferMark
+ vim.current.buffer.mark(0):TypeError:('expected str() or unicode() instance, but got int',)
+ vim.current.buffer.mark("abcM"):ValueError:('mark name must be a single character',)
+ vim.current.buffer.mark("!"):error:('invalid mark name',)
+ >> BufferRange
+ vim.current.buffer.range(1, 2, 3):TypeError:('function takes exactly 2 arguments (3 given)',)
+ > BufMap
+ >> BufMapItem
+ vim.buffers[100000000]:KeyError:(100000000,)
+ >>> Testing NumberToLong using vim.buffers[%s]
+ vim.buffers[[]]:TypeError:('expected int(), long() or something supporting coercing to long(), but got list',)
+ vim.buffers[None]:TypeError:('expected int(), long() or something supporting coercing to long(), but got NoneType',)
+ vim.buffers[-1]:ValueError:('number must be greater than zero',)
+ vim.buffers[0]:ValueError:('number must be greater than zero',)
+ <<< Finished
+ > Current
+ >> CurrentGetattr
+ vim.current.xxx:AttributeError:('xxx',)
+ >> CurrentSetattr
+ vim.current.line = True:TypeError:('bad argument type for built-in operation',)
+ vim.current.buffer = True:TypeError:('expected vim.Buffer object, but got bool',)
+ vim.current.window = True:TypeError:('expected vim.Window object, but got bool',)
+ vim.current.tabpage = True:TypeError:('expected vim.TabPage object, but got bool',)
+ vim.current.xxx = True:AttributeError:('xxx',)
+ END
+
+ call assert_equal(expected, getline(2, '$'))
+ close!
+endfunc
+
+" Test import
+func Test_python_import()
+ new
+ py cb = vim.current.buffer
+
+ py << trim EOF
+ sys.path.insert(0, os.path.join(os.getcwd(), 'python_before'))
+ sys.path.append(os.path.join(os.getcwd(), 'python_after'))
+ vim.options['rtp'] = os.getcwd().replace(',', '\\,').replace('\\', '\\\\')
+ l = []
+ def callback(path):
+ l.append(path[-len('/testdir'):].replace(os.path.sep, '/'))
+ vim.foreach_rtp(callback)
+ cb.append(repr(l))
+ del l
+ def callback(path):
+ return path[-len('/testdir'):].replace(os.path.sep, '/')
+ cb.append(repr(vim.foreach_rtp(callback)))
+ del callback
+ from module import dir as d
+ from modulex import ddir
+ cb.append(d + ',' + ddir)
+ import before
+ cb.append(before.dir)
+ import after
+ cb.append(after.dir)
+ import topmodule as tm
+ import topmodule.submodule as tms
+ import topmodule.submodule.subsubmodule.subsubsubmodule as tmsss
+ cb.append(tm.__file__.replace('.pyc', '.py').replace(os.path.sep, '/')[-len('modulex/topmodule/__init__.py'):])
+ cb.append(tms.__file__.replace('.pyc', '.py').replace(os.path.sep, '/')[-len('modulex/topmodule/submodule/__init__.py'):])
+ cb.append(tmsss.__file__.replace('.pyc', '.py').replace(os.path.sep, '/')[-len('modulex/topmodule/submodule/subsubmodule/subsubsubmodule.py'):])
+
+ del before
+ del after
+ del d
+ del ddir
+ del tm
+ del tms
+ del tmsss
+ EOF
+
+ let expected =<< trim END
+ ['/testdir']
+ '/testdir'
+ 2,xx
+ before
+ after
+ pythonx/topmodule/__init__.py
+ pythonx/topmodule/submodule/__init__.py
+ pythonx/topmodule/submodule/subsubmodule/subsubsubmodule.py
+ END
+ call assert_equal(expected, getline(2, '$'))
+ close!
+
+ " Try to import a non-existing module with a dot (.)
+ call AssertException(['py import a.b.c'], 'ImportError:')
+endfunc
+
+" Test exceptions
+func Test_python_exception()
+ func Exe(e)
+ execute a:e
+ endfunc
+
+ new
+ py cb = vim.current.buffer
+
+ py << trim EOF
+ Exe = vim.bindeval('function("Exe")')
+ ee('vim.command("throw \'abcN\'")')
+ ee('Exe("throw \'def\'")')
+ ee('vim.eval("Exe(\'throw \'\'ghi\'\'\')")')
+ ee('vim.eval("Exe(\'echoerr \'\'jkl\'\'\')")')
+ ee('vim.eval("Exe(\'xxx_non_existent_command_xxx\')")')
+ ee('vim.eval("xxx_unknown_function_xxx()")')
+ ee('vim.bindeval("Exe(\'xxx_non_existent_command_xxx\')")')
+ del Exe
+ EOF
+ delfunction Exe
+
+ let expected =<< trim END
+ vim.command("throw 'abcN'"):error:('abcN',)
+ Exe("throw 'def'"):error:('def',)
+ vim.eval("Exe('throw ''ghi''')"):error:('ghi',)
+ vim.eval("Exe('echoerr ''jkl''')"):error:('Vim(echoerr):jkl',)
+ vim.eval("Exe('xxx_non_existent_command_xxx')"):error:('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',)
+ vim.eval("xxx_unknown_function_xxx()"):error:('Vim:E117: Unknown function: xxx_unknown_function_xxx',)
+ vim.bindeval("Exe('xxx_non_existent_command_xxx')"):error:('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',)
+ END
+ call assert_equal(expected, getline(2, '$'))
+ close!
+endfunc
+
+" Regression: interrupting vim.command propagates to next vim.command
+func Test_python_keyboard_interrupt()
+ new
+ py cb = vim.current.buffer
+ py << trim EOF
+ def test_keyboard_interrupt():
+ try:
+ vim.command('while 1 | endwhile')
+ except KeyboardInterrupt:
+ cb.append('Caught KeyboardInterrupt')
+ except Exception:
+ cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
+ else:
+ cb.append('!!!!!!!! No exception')
+ try:
+ vim.command('$ put =\'Running :put\'')
+ except KeyboardInterrupt:
+ cb.append('!!!!!!!! Caught KeyboardInterrupt')
+ except Exception:
+ cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
+ else:
+ cb.append('No exception')
+ EOF
+
+ debuggreedy
+ call inputsave()
+ call feedkeys("s\ns\ns\ns\nq\n")
+ redir => output
+ debug silent! py test_keyboard_interrupt()
+ redir END
+ 0 debuggreedy
+ call inputrestore()
+ py del test_keyboard_interrupt
+
+ let expected =<< trim END
+ Caught KeyboardInterrupt
+ Running :put
+ No exception
+ END
+ call assert_equal(expected, getline(2, '$'))
+ call assert_equal('', output)
+ close!
+endfunc
+
+func Test_python_non_utf8_string()
+ smap <Esc>@ <A-@>
+ python vim.command('redir => _tmp_smaps | smap | redir END')
+ python vim.eval('_tmp_smaps').splitlines()
+ sunmap <Esc>@
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_python3.vim b/src/testdir/test_python3.vim
new file mode 100644
index 0000000..5de9464
--- /dev/null
+++ b/src/testdir/test_python3.vim
@@ -0,0 +1,4114 @@
+" Test for python 3 commands.
+
+source check.vim
+CheckFeature python3
+source shared.vim
+
+func Create_vim_list()
+ return [1]
+endfunction
+
+func Create_vim_dict()
+ return {'a': 1}
+endfunction
+
+let s:system_error_pat = 'Vim(py3):SystemError: \(<built-in function eval> returned NULL without setting an \(error\|exception\)\|error return without exception set\)'
+
+" This function should be called first. This sets up python functions used by
+" the other tests.
+func Test_AAA_python3_setup()
+ py3 << trim EOF
+ import vim
+ import sys
+ import re
+
+ py33_type_error_pattern = re.compile('^__call__\(\) takes (\d+) positional argument but (\d+) were given$')
+ py37_exception_repr = re.compile(r'([^\(\),])(\)+)$')
+ py39_type_error_pattern = re.compile('\w+\.([^(]+\(\) takes)')
+ py310_type_error_pattern = re.compile('takes (\d+) positional argument but (\d+) were given')
+
+ def emsg(ei):
+ return ei[0].__name__ + ':' + repr(ei[1].args)
+
+ def ee(expr, g=globals(), l=locals()):
+ cb = vim.current.buffer
+ try:
+ try:
+ exec(expr, g, l)
+ except Exception as e:
+ if sys.version_info >= (3, 3) and e.__class__ is AttributeError and str(e).find('has no attribute')>=0 and not str(e).startswith("'vim."):
+ msg = repr((e.__class__, AttributeError(str(e)[str(e).rfind(" '") + 2:-1])))
+ elif sys.version_info >= (3, 3) and e.__class__ is ImportError and str(e).find('No module named \'') >= 0:
+ msg = repr((e.__class__, ImportError(str(e).replace("'", ''))))
+ elif sys.version_info >= (3, 6) and e.__class__ is ModuleNotFoundError:
+ # Python 3.6 gives ModuleNotFoundError, change it to an ImportError
+ msg = repr((ImportError, ImportError(str(e).replace("'", ''))))
+ elif sys.version_info >= (3, 3) and e.__class__ is TypeError:
+ m = py33_type_error_pattern.search(str(e))
+ if m:
+ msg = '__call__() takes exactly {0} positional argument ({1} given)'.format(m.group(1), m.group(2))
+ msg = repr((e.__class__, TypeError(msg)))
+ else:
+ msg = repr((e.__class__, e))
+ # Messages changed with Python 3.6, change new to old.
+ newmsg1 = """'argument must be str, bytes or bytearray, not None'"""
+ oldmsg1 = '''"Can't convert 'NoneType' object to str implicitly"'''
+ if msg.find(newmsg1) > -1:
+ msg = msg.replace(newmsg1, oldmsg1)
+ newmsg2 = """'argument must be str, bytes or bytearray, not int'"""
+ oldmsg2 = '''"Can't convert 'int' object to str implicitly"'''
+ if msg.find(newmsg2) > -1:
+ msg = msg.replace(newmsg2, oldmsg2)
+ # Python 3.9 reports errors like "vim.command() takes ..." instead of "command() takes ..."
+ msg = py39_type_error_pattern.sub(r'\1', msg)
+ msg = py310_type_error_pattern.sub(r'takes exactly \1 positional argument (\2 given)', msg)
+ elif sys.version_info >= (3, 5) and e.__class__ is ValueError and str(e) == 'embedded null byte':
+ msg = repr((TypeError, TypeError('expected bytes with no null')))
+ else:
+ msg = repr((e.__class__, e))
+ # Some Python versions say can't, others cannot.
+ if msg.find('can\'t') > -1:
+ msg = msg.replace('can\'t', 'cannot')
+ # Some Python versions use single quote, some double quote
+ if msg.find('"cannot ') > -1:
+ msg = msg.replace('"cannot ', '\'cannot ')
+ if msg.find(' attributes"') > -1:
+ msg = msg.replace(' attributes"', ' attributes\'')
+ if sys.version_info >= (3, 7):
+ msg = py37_exception_repr.sub(r'\1,\2', msg)
+ cb.append(expr + ':' + msg)
+ else:
+ cb.append(expr + ':NOT FAILED')
+ except Exception as e:
+ msg = repr((e.__class__, e))
+ if sys.version_info >= (3, 7):
+ msg = py37_exception_repr.sub(r'\1,\2', msg)
+ cb.append(expr + '::' + msg)
+ EOF
+endfunc
+
+func Test_py3do()
+ " Check deleting lines does not trigger an ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ py3do vim.command("%d_")
+ bwipe!
+
+ " Check switching to another buffer does not trigger an ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ py3do vim.command("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ bwipe!
+ bwipe!
+
+ " Try modifying a buffer with 'nomodifiable' set
+ set nomodifiable
+ call assert_fails('py3do toupper(line)', 'E21:')
+ set modifiable
+
+ " Invalid command
+ call AssertException(['py3do non_existing_cmd'],
+ \ "Vim(py3do):NameError: name 'non_existing_cmd' is not defined")
+ call AssertException(["py3do raise Exception('test')"],
+ \ 'Vim(py3do):Exception: test')
+ call AssertException(["py3do {lambda}"],
+ \ 'Vim(py3do):SyntaxError: invalid syntax')
+endfunc
+
+func Test_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ py3do vim.current.window.cursor = (1, 5)
+ call assert_equal([1, 6], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 6], [line('.'), col('.')])
+endfunc
+
+func Test_vim_function()
+ " Check creating vim.Function object
+
+ func s:foo()
+ return matchstr(expand('<sfile>'), '<SNR>\zs\d\+_foo$')
+ endfunc
+ let name = '<SNR>' . s:foo()
+
+ try
+ py3 f = vim.bindeval('function("s:foo")')
+ call assert_equal(name, py3eval('f.name'))
+ catch
+ call assert_false(v:exception)
+ endtry
+
+ try
+ py3 f = vim.Function(b'\x80\xfdR' + vim.eval('s:foo()').encode())
+ call assert_equal(name, 'f.name'->py3eval())
+ catch
+ call assert_false(v:exception)
+ endtry
+
+ " Non-existing function attribute
+ call AssertException(["let x = py3eval('f.abc')"],
+ \ "Vim(let):AttributeError: 'vim.function' object has no attribute 'abc'")
+
+ py3 del f
+ delfunc s:foo
+endfunc
+
+func Test_skipped_python3_command_does_not_affect_pyxversion()
+ set pyxversion=0
+ if 0
+ python3 import vim
+ endif
+ call assert_equal(0, &pyxversion) " This assertion would have failed with Vim 8.0.0251. (pyxversion was introduced in 8.0.0251.)
+endfunc
+
+func _SetUpHiddenBuffer()
+ new
+ edit hidden
+ setlocal bufhidden=hide
+
+ enew
+ let lnum = 0
+ while lnum < 10
+ call append( 1, string( lnum ) )
+ let lnum = lnum + 1
+ endwhile
+ normal G
+
+ call assert_equal( line( '.' ), 11 )
+endfunc
+
+func _CleanUpHiddenBuffer()
+ bwipe! hidden
+ bwipe!
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Clear()
+ call _SetUpHiddenBuffer()
+ py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = None
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_List()
+ call _SetUpHiddenBuffer()
+ py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = [ 'test' ]
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Str()
+ call _SetUpHiddenBuffer()
+ py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = 'test'
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_ClearLine()
+ call _SetUpHiddenBuffer()
+ py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = None
+ call assert_equal( line( '.' ), 11 )
+ call _CleanUpHiddenBuffer()
+endfunc
+
+func _SetUpVisibleBuffer()
+ new
+ let lnum = 0
+ while lnum < 10
+ call append( 1, string( lnum ) )
+ let lnum = lnum + 1
+ endwhile
+ normal G
+ call assert_equal( line( '.' ), 11 )
+endfunc
+
+func Test_Write_To_Current_Buffer_Fixes_Cursor_Clear()
+ call _SetUpVisibleBuffer()
+
+ py3 vim.current.buffer[:] = None
+ call assert_equal( line( '.' ), 1 )
+
+ bwipe!
+endfunc
+
+func Test_Write_To_Current_Buffer_Fixes_Cursor_List()
+ call _SetUpVisibleBuffer()
+
+ py3 vim.current.buffer[:] = [ 'test' ]
+ call assert_equal( line( '.' ), 1 )
+
+ bwipe!
+endfunc
+
+func Test_Write_To_Current_Buffer_Fixes_Cursor_Str()
+ call _SetUpVisibleBuffer()
+
+ py3 vim.current.buffer[-1] = None
+ call assert_equal( line( '.' ), 10 )
+
+ bwipe!
+endfunc
+
+func Test_Catch_Exception_Message()
+ try
+ py3 raise RuntimeError( 'TEST' )
+ catch /.*/
+ call assert_match( '^Vim(.*):RuntimeError: TEST$', v:exception )
+ endtry
+endfunc
+
+func Test_unicode()
+ " this crashed Vim once
+ if &tenc != ''
+ throw "Skipped: 'termencoding' is not empty"
+ endif
+
+ set encoding=utf32
+ py3 print('hello')
+
+ if !has('win32')
+ set encoding=debug
+ py3 print('hello')
+
+ set encoding=euc-tw
+ py3 print('hello')
+ endif
+
+ set encoding=utf8
+endfunc
+
+" Test vim.eval() with various types.
+func Test_python3_vim_val()
+ call assert_equal("\n8", execute('py3 print(vim.eval("3+5"))'))
+ call assert_equal("\n3.140000", execute('py3 print(vim.eval("1.01+2.13"))'))
+ call assert_equal("\n0.000000", execute('py3 print(vim.eval("0.0/(1.0/0.0)"))'))
+ call assert_equal("\n0.000000", execute('py3 print(vim.eval("0.0/(1.0/0.0)"))'))
+ call assert_equal("\n-0.000000", execute('py3 print(vim.eval("0.0/(-1.0/0.0)"))'))
+ " Commented out: output of infinity and nan depend on platforms.
+ " call assert_equal("\ninf", execute('py3 print(vim.eval("1.0/0.0"))'))
+ " call assert_equal("\n-inf", execute('py3 print(vim.eval("-1.0/0.0"))'))
+ " call assert_equal("\n-nan", execute('py3 print(vim.eval("0.0/0.0"))'))
+ call assert_equal("\nabc", execute('py3 print(vim.eval("\"abc\""))'))
+ call assert_equal("\n['1', '2']", execute('py3 print(vim.eval("[1, 2]"))'))
+ call assert_equal("\n{'1': '2'}", execute('py3 print(vim.eval("{1:2}"))'))
+ call assert_equal("\nTrue", execute('py3 print(vim.eval("v:true"))'))
+ call assert_equal("\nFalse", execute('py3 print(vim.eval("v:false"))'))
+ call assert_equal("\nNone", execute('py3 print(vim.eval("v:null"))'))
+ call assert_equal("\nNone", execute('py3 print(vim.eval("v:none"))'))
+ call assert_equal("\nb'\\xab\\x12'", execute('py3 print(vim.eval("0zab12"))'))
+
+ call assert_fails('py3 vim.eval("1+")', 'E15: Invalid expression')
+endfunc
+
+" Test range objects, see :help python-range
+func Test_python3_range()
+ new
+ py3 b = vim.current.buffer
+
+ call setline(1, range(1, 6))
+ py3 r = b.range(2, 4)
+ call assert_equal(6, py3eval('len(b)'))
+ call assert_equal(3, py3eval('len(r)'))
+ call assert_equal('3', py3eval('b[2]'))
+ call assert_equal('4', py3eval('r[2]'))
+
+ call assert_fails('py3 r[3] = "x"', ['Traceback', 'IndexError: line number out of range'])
+ call assert_fails('py3 x = r[3]', ['Traceback', 'IndexError: line number out of range'])
+ call assert_fails('py3 r["a"] = "x"', ['Traceback', 'TypeError: index must be int or slice, not str'])
+ call assert_fails('py3 x = r["a"]', ['Traceback', 'TypeError: index must be int or slice, not str'])
+
+ py3 del r[:]
+ call assert_equal(['1', '5', '6'], getline(1, '$'))
+
+ %d | call setline(1, range(1, 6))
+ py3 r = b.range(2, 5)
+ py3 del r[2]
+ call assert_equal(['1', '2', '3', '5', '6'], getline(1, '$'))
+
+ %d | call setline(1, range(1, 6))
+ py3 r = b.range(2, 4)
+ py3 vim.command("%d,%dnorm Ax" % (r.start + 1, r.end + 1))
+ call assert_equal(['1', '2x', '3x', '4x', '5', '6'], getline(1, '$'))
+
+ %d | call setline(1, range(1, 4))
+ py3 r = b.range(2, 3)
+ py3 r.append(['a', 'b'])
+ call assert_equal(['1', '2', '3', 'a', 'b', '4'], getline(1, '$'))
+ py3 r.append(['c', 'd'], 0)
+ call assert_equal(['1', 'c', 'd', '2', '3', 'a', 'b', '4'], getline(1, '$'))
+
+ %d | call setline(1, range(1, 5))
+ py3 r = b.range(2, 4)
+ py3 r.append('a')
+ call assert_equal(['1', '2', '3', '4', 'a', '5'], getline(1, '$'))
+ py3 r.append('b', 1)
+ call assert_equal(['1', '2', 'b', '3', '4', 'a', '5'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for resetting options with local values to global values
+func Test_python3_opt_reset_local_to_global()
+ new
+
+ py3 curbuf = vim.current.buffer
+ py3 curwin = vim.current.window
+
+ " List of buffer-local options. Each list item has [option name, global
+ " value, buffer-local value, buffer-local value after reset] to use in the
+ " test.
+ let bopts = [
+ \ ['autoread', 1, 0, -1],
+ \ ['equalprg', 'geprg', 'leprg', ''],
+ \ ['keywordprg', 'gkprg', 'lkprg', ''],
+ \ ['path', 'gpath', 'lpath', ''],
+ \ ['backupcopy', 'yes', 'no', ''],
+ \ ['tags', 'gtags', 'ltags', ''],
+ \ ['tagcase', 'ignore', 'match', ''],
+ \ ['define', 'gdef', 'ldef', ''],
+ \ ['include', 'ginc', 'linc', ''],
+ \ ['dict', 'gdict', 'ldict', ''],
+ \ ['thesaurus', 'gtsr', 'ltsr', ''],
+ \ ['formatprg', 'gfprg', 'lfprg', ''],
+ \ ['errorformat', '%f:%l:%m', '%s-%l-%m', ''],
+ \ ['grepprg', 'ggprg', 'lgprg', ''],
+ \ ['makeprg', 'gmprg', 'lmprg', ''],
+ \ ['cryptmethod', 'blowfish2', 'zip', ''],
+ \ ['lispwords', 'abc', 'xyz', ''],
+ \ ['makeencoding', 'utf-8', 'latin1', ''],
+ \ ['undolevels', 100, 200, -123456]]
+ if has('balloon_eval')
+ call add(bopts, ['balloonexpr', 'gbexpr', 'lbexpr', ''])
+ endif
+
+ " Set the global and buffer-local option values and then clear the
+ " buffer-local option value.
+ for opt in bopts
+ py3 << trim END
+ pyopt = vim.bindeval("opt")
+ vim.options[pyopt[0]] = pyopt[1]
+ curbuf.options[pyopt[0]] = pyopt[2]
+ END
+ exe "call assert_equal(opt[2], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[2], &l:" .. opt[0] .. ")"
+ py3 del curbuf.options[pyopt[0]]
+ exe "call assert_equal(opt[1], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[3], &l:" .. opt[0] .. ")"
+ exe "set " .. opt[0] .. "&"
+ endfor
+
+ " Set the global and window-local option values and then clear the
+ " window-local option value.
+ let wopts = [
+ \ ['scrolloff', 5, 10, -1],
+ \ ['sidescrolloff', 6, 12, -1],
+ \ ['statusline', '%<%f', '%<%F', '']]
+ for opt in wopts
+ py3 << trim
+ pyopt = vim.bindeval("opt")
+ vim.options[pyopt[0]] = pyopt[1]
+ curwin.options[pyopt[0]] = pyopt[2]
+ .
+ exe "call assert_equal(opt[2], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[2], &l:" .. opt[0] .. ")"
+ py3 del curwin.options[pyopt[0]]
+ exe "call assert_equal(opt[1], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[3], &l:" .. opt[0] .. ")"
+ exe "set " .. opt[0] .. "&"
+ endfor
+
+ close!
+endfunc
+
+" Test for various heredoc syntax
+func Test_python3_heredoc()
+ python3 << END
+s='A'
+END
+ python3 <<
+s+='B'
+.
+ python3 << trim END
+ s+='C'
+ END
+ python3 << trim
+ s+='D'
+ .
+ python3 << trim eof
+ s+='E'
+ eof
+ call assert_equal('ABCDE', pyxeval('s'))
+endfunc
+
+" Test for the buffer range object
+func Test_python3_range2()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ py3 b = vim.current.buffer
+ py3 r = b.range(1, 3)
+ call assert_equal(0, py3eval('r.start'))
+ call assert_equal(2, py3eval('r.end'))
+ call assert_equal('one', py3eval('r[0]'))
+ call assert_equal('one', py3eval('r[-3]'))
+ call AssertException(["let x = py3eval('r[-4]')"],
+ \ 'Vim(let):IndexError: line number out of range')
+ call assert_equal(['two', 'three'], py3eval('r[1:]'))
+ py3 r[0] = 'green'
+ call assert_equal(['green', 'two', 'three'], getline(1, '$'))
+ py3 r[0:2] = ['red', 'blue']
+ call assert_equal(['red', 'blue', 'three'], getline(1, '$'))
+
+ " try different invalid start/end index for the range slice
+ %d
+ call setline(1, ['one', 'two', 'three'])
+ py3 r[-10:1] = ["a"]
+ py3 r[10:12] = ["b"]
+ py3 r[-10:-9] = ["c"]
+ py3 r[1:0] = ["d"]
+ call assert_equal(['c', 'd', 'a', 'two', 'three', 'b'], getline(1, '$'))
+
+ " The following code used to trigger an ml_get error
+ %d
+ let x = py3eval('r[:]')
+
+ " Non-existing range attribute
+ call AssertException(["let x = py3eval('r.abc')"],
+ \ "Vim(let):AttributeError: 'vim.range' object has no attribute 'abc'")
+
+ close!
+endfunc
+
+" Test for the python tabpage object
+func Test_python3_tabpage()
+ tabnew
+ py3 t = vim.tabpages[1]
+ py3 wl = t.windows
+ tabclose
+ " Accessing a closed tabpage
+ call AssertException(["let n = py3eval('t.number')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted tab page')
+ call AssertException(["let n = py3eval('len(wl)')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted tab page')
+ call AssertException(["py3 w = wl[0]"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted tab page')
+ call AssertException(["py3 vim.current.tabpage = t"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted tab page')
+ call assert_match('<tabpage object (deleted)', py3eval('repr(t)'))
+ %bw!
+endfunc
+
+" Test for the python window object
+func Test_python3_window()
+ " Test for setting the window height
+ 10new
+ py3 vim.current.window.height = 5
+ call assert_equal(5, winheight(0))
+ py3 vim.current.window.height = 3.2
+ call assert_equal(3, winheight(0))
+
+ " Test for setting the window width
+ 10vnew
+ py3 vim.current.window.width = 6
+ call assert_equal(6, winwidth(0))
+
+ " Try accessing a closed window
+ py3 w = vim.current.window
+ py3 wopts = w.options
+ close
+ " Access the attributes of a closed window
+ call AssertException(["let n = py3eval('w.number')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted window')
+ call AssertException(["py3 w.height = 5"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted window')
+ call AssertException(["py3 vim.current.window = w"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted window')
+ " Try to set one of the options of the closed window
+ " The following caused ASAN failure
+ call AssertException(["py3 wopts['list'] = False"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted window')
+ call assert_match('<window object (deleted)', py3eval("repr(w)"))
+ %bw!
+endfunc
+
+" This was causing trouble because "curbuf" was not matching curwin->w_buffer
+func Test_python3_window_set_height()
+ enew!
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ call cursor(2, 1)
+ set foldmethod=expr
+ new
+ wincmd w
+ python3 vim.windows[0].height = 5
+ call assert_equal(5, winheight(1))
+
+ call feedkeys('j', 'xt')
+ call assert_equal(3, getpos('.')[1])
+
+ bwipe!
+ bwipe!
+endfunc
+
+" Test for the python List object
+func Test_python3_list()
+ " Try to convert a null List
+ call AssertException(["py3 t = vim.eval('test_null_list()')"],
+ \ s:system_error_pat)
+
+ " Try to convert a List with a null List item
+ call AssertException(["py3 t = vim.eval('[test_null_list()]')"],
+ \ s:system_error_pat)
+
+ " Try to bind a null List variable (works because an empty list is used)
+ let cmds =<< trim END
+ let l = test_null_list()
+ py3 ll = vim.bindeval('l')
+ END
+ call AssertException(cmds, '')
+
+ let l = []
+ py3 l = vim.bindeval('l')
+ py3 f = vim.bindeval('function("strlen")')
+ " Extending List directly with different types
+ py3 l += [1, "as'd", [1, 2, f, {'a': 1}]]
+ call assert_equal([1, "as'd", [1, 2, function("strlen"), {'a': 1}]], l)
+ call assert_equal([1, 2, function("strlen"), {'a': 1}], l[-1])
+ call assert_fails('echo l[-4]', 'E684:')
+
+ " List assignment
+ py3 l[0] = 0
+ call assert_equal([0, "as'd", [1, 2, function("strlen"), {'a': 1}]], l)
+ py3 l[-2] = f
+ call assert_equal([0, function("strlen"), [1, 2, function("strlen"), {'a': 1}]], l)
+
+ " appending to a list
+ let l = [1, 2]
+ py3 ll = vim.bindeval('l')
+ py3 ll[2] = 8
+ call assert_equal([1, 2, 8], l)
+
+ " iterating over list from Python
+ py3 print([x for x in vim.Function("getline")(1, 2)])
+
+ " Using dict as an index
+ call AssertException(['py3 ll[{}] = 10'],
+ \ 'Vim(py3):TypeError: index must be int or slice, not dict')
+endfunc
+
+" Test for the python Dict object
+func Test_python3_dict()
+ " Try to convert a null Dict
+ call AssertException(["py3 t = vim.eval('test_null_dict()')"],
+ \ s:system_error_pat)
+
+ " Try to convert a Dict with a null List value
+ call AssertException(["py3 t = vim.eval(\"{'a' : test_null_list()}\")"],
+ \ s:system_error_pat)
+
+ " Try to convert a Dict with a null string key
+ py3 t = vim.eval("{test_null_string() : 10}")
+ call assert_fails("let d = py3eval('t')", 'E859:')
+
+ " Dict length
+ let d = {'a' : 10, 'b' : 20}
+ py3 d = vim.bindeval('d')
+ call assert_equal(2, py3eval('len(d)'))
+
+ " Deleting a non-existing key
+ call AssertException(["py3 del d['c']"], "Vim(py3):KeyError: 'c'")
+endfunc
+
+" Extending Dictionary directly with different types
+func Test_python3_dict_extend()
+ let d = {}
+ func d.f()
+ return 1
+ endfunc
+
+ py3 f = vim.bindeval('function("strlen")')
+ py3 << trim EOF
+ d = vim.bindeval('d')
+ d['1'] = 'asd'
+ d.update() # Must not do anything, including throwing errors
+ d.update(b = [1, 2, f])
+ d.update((('-1', {'a': 1}),))
+ d.update({'0': -1})
+ dk = d.keys()
+ dv = d.values()
+ di = d.items()
+ dk.sort(key=repr)
+ dv.sort(key=repr)
+ di.sort(key=repr)
+ EOF
+
+ " Try extending a locked dictionary
+ lockvar d
+ call AssertException(["py3 d.update({'b' : 20})"],
+ \ 'Vim(py3):vim.error: dictionary is locked')
+ unlockvar d
+
+ call assert_equal(1, py3eval("d['f'](self={})"))
+ call assert_equal("[b'-1', b'0', b'1', b'b', b'f']", py3eval('repr(dk)'))
+ call assert_equal("[-1, <vim.Function '1'>, <vim.dictionary object at >, <vim.list object at >, b'asd']", substitute(py3eval('repr(dv)'),'0x\x\+','','g'))
+ call assert_equal("[(b'-1', <vim.dictionary object at >), (b'0', -1), (b'1', b'asd'), (b'b', <vim.list object at >), (b'f', <vim.Function '1'>)]", substitute(py3eval('repr(di)'),'0x\x\+','','g'))
+ call assert_equal(['0', '1', 'b', 'f', '-1'], keys(d))
+ call assert_equal("[-1, 'asd', [1, 2, function('strlen')], function('1'), {'a': 1}]", string(values(d)))
+ py3 del dk
+ py3 del di
+ py3 del dv
+endfunc
+
+func Test_python3_list_del_items()
+ " removing items with del
+ let l = [0, function("strlen"), [1, 2, function("strlen"), {'a': 1}]]
+ py3 l = vim.bindeval('l')
+ py3 del l[2]
+ call assert_equal("[0, function('strlen')]", string(l))
+
+ let l = range(8)
+ py3 l = vim.bindeval('l')
+ py3 del l[:3]
+ py3 del l[1:]
+ call assert_equal([3], l)
+
+ " removing items out of range: silently skip items that don't exist
+
+ " The following two ranges delete nothing as they match empty list:
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[2:1]
+ call assert_equal([0, 1, 2, 3], l)
+ py3 del l[2:2]
+ call assert_equal([0, 1, 2, 3], l)
+ py3 del l[2:3]
+ call assert_equal([0, 1, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[2:4]
+ call assert_equal([0, 1], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[2:5]
+ call assert_equal([0, 1], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[2:6]
+ call assert_equal([0, 1], l)
+
+ " The following two ranges delete nothing as they match empty list:
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[-1:2]
+ call assert_equal([0, 1, 2, 3], l)
+ py3 del l[-2:2]
+ call assert_equal([0, 1, 2, 3], l)
+ py3 del l[-3:2]
+ call assert_equal([0, 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[-4:2]
+ call assert_equal([2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[-5:2]
+ call assert_equal([2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[-6:2]
+ call assert_equal([2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[::2]
+ call assert_equal([1, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[3:0:-2]
+ call assert_equal([0, 2], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 del l[2:4:-2]
+ let l = [0, 1, 2, 3]
+endfunc
+
+func Test_python3_dict_del_items()
+ let d = eval("{'0' : -1, '1' : 'asd', 'b' : [1, 2, function('strlen')], 'f' : function('min'), '-1' : {'a': 1}}")
+ py3 d = vim.bindeval('d')
+ py3 del d['-1']
+ py3 del d['f']
+ call assert_equal([1, 2, function('strlen')], py3eval('d.get(''b'', 1)'))
+ call assert_equal([1, 2, function('strlen')], py3eval('d.pop(''b'')'))
+ call assert_equal(1, py3eval('d.get(''b'', 1)'))
+ call assert_equal('asd', py3eval('d.pop(''1'', 2)'))
+ call assert_equal(2, py3eval('d.pop(''1'', 2)'))
+ call assert_equal('True', py3eval('repr(d.has_key(''0''))'))
+ call assert_equal('False', py3eval('repr(d.has_key(''1''))'))
+ call assert_equal('True', py3eval('repr(''0'' in d)'))
+ call assert_equal('False', py3eval('repr(''1'' in d)'))
+ call assert_equal("[b'0']", py3eval('repr(list(iter(d)))'))
+ call assert_equal({'0' : -1}, d)
+ call assert_equal("(b'0', -1)", py3eval('repr(d.popitem())'))
+ call assert_equal('None', py3eval('repr(d.get(''0''))'))
+ call assert_equal('[]', py3eval('repr(list(iter(d)))'))
+endfunc
+
+" Slice assignment to a list
+func Test_python3_slice_assignment()
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 l[0:0] = ['a']
+ call assert_equal(['a', 0, 1, 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 l[1:2] = ['b']
+ call assert_equal([0, 'b', 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 l[2:4] = ['c']
+ call assert_equal([0, 1, 'c'], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 l[4:4] = ['d']
+ call assert_equal([0, 1, 2, 3, 'd'], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 l[-1:2] = ['e']
+ call assert_equal([0, 1, 2, 'e', 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 l[-10:2] = ['f']
+ call assert_equal(['f', 2, 3], l)
+
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ py3 l[2:-10] = ['g']
+ call assert_equal([0, 1, 'g', 2, 3], l)
+
+ let l = []
+ py3 l = vim.bindeval('l')
+ py3 l[0:0] = ['h']
+ call assert_equal(['h'], l)
+
+ let l = range(8)
+ py3 l = vim.bindeval('l')
+ py3 l[2:6:2] = [10, 20]
+ call assert_equal([0, 1, 10, 3, 20, 5, 6, 7], l)
+
+ let l = range(8)
+ py3 l = vim.bindeval('l')
+ py3 l[6:2:-2] = [10, 20]
+ call assert_equal([0, 1, 2, 3, 20, 5, 10, 7], l)
+
+ let l = range(8)
+ py3 l = vim.bindeval('l')
+ py3 l[6:2] = ()
+ call assert_equal([0, 1, 2, 3, 4, 5, 6, 7], l)
+
+ let l = range(8)
+ py3 l = vim.bindeval('l')
+ py3 l[6:2:1] = ()
+ call assert_equal([0, 1, 2, 3, 4, 5, 6, 7], l)
+
+ let l = range(8)
+ py3 l = vim.bindeval('l')
+ py3 l[2:2:1] = ()
+ call assert_equal([0, 1, 2, 3, 4, 5, 6, 7], l)
+
+ call AssertException(["py3 x = l[10:11:0]"],
+ \ "Vim(py3):ValueError: slice step cannot be zero")
+endfunc
+
+" Locked variables
+func Test_python3_lockedvar()
+ new
+ py3 cb = vim.current.buffer
+ let l = [0, 1, 2, 3]
+ py3 l = vim.bindeval('l')
+ lockvar! l
+ py3 << trim EOF
+ try:
+ l[2]='i'
+ except vim.error:
+ cb.append('l[2] threw vim.error: ' + emsg(sys.exc_info()))
+ EOF
+ call assert_equal(['', "l[2] threw vim.error: error:('list is locked',)"],
+ \ getline(1, '$'))
+
+ " Try to concatenate a locked list
+ call AssertException(['py3 l += [4, 5]'], 'Vim(py3):vim.error: list is locked')
+
+ call assert_equal([0, 1, 2, 3], l)
+ unlockvar! l
+ close!
+endfunc
+
+" Test for calling a function
+func Test_python3_function_call()
+ func New(...)
+ return ['NewStart'] + a:000 + ['NewEnd']
+ endfunc
+
+ func DictNew(...) dict
+ return ['DictNewStart'] + a:000 + ['DictNewEnd', self]
+ endfunc
+
+ new
+ let l = [function('New'), function('DictNew')]
+ py3 l = vim.bindeval('l')
+ py3 l.extend(list(l[0](1, 2, 3)))
+ call assert_equal([function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd'], l)
+ py3 l.extend(list(l[1](1, 2, 3, self={'a': 'b'})))
+ call assert_equal([function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}], l)
+ py3 l += [[l[0].name]]
+ call assert_equal([function('New'), function('DictNew'), 'NewStart', 1, 2, 3, 'NewEnd', 'DictNewStart', 1, 2, 3, 'DictNewEnd', {'a': 'b'}, ['New']], l)
+ py3 ee('l[1](1, 2, 3)')
+ call assert_equal("l[1](1, 2, 3):(<class 'vim.error'>, error('Vim:E725: Calling dict function without Dictionary: DictNew',))", getline(2))
+ %d
+ py3 f = l[0]
+ delfunction New
+ py3 ee('f(1, 2, 3)')
+ call assert_equal("f(1, 2, 3):(<class 'vim.error'>, error('Vim:E117: Unknown function: New',))", getline(2))
+ close!
+ delfunction DictNew
+endfunc
+
+func Test_python3_float()
+ let l = [0.0]
+ py3 l = vim.bindeval('l')
+ py3 l.extend([0.0])
+ call assert_equal([0.0, 0.0], l)
+endfunc
+
+" Test for Dict key errors
+func Test_python3_dict_key_error()
+ let messages = []
+ py3 << trim EOF
+ import sys
+ d = vim.bindeval('{}')
+ m = vim.bindeval('messages')
+ def em(expr, g=globals(), l=locals()):
+ try:
+ exec(expr, g, l)
+ except Exception as e:
+ if sys.version_info >= (3, 5) and e.__class__ is ValueError and str(e) == 'embedded null byte':
+ m.extend([TypeError.__name__])
+ else:
+ m.extend([e.__class__.__name__])
+
+ em('d["abc1"]')
+ em('d["abc1"]="\\0"')
+ em('d["abc1"]=vim')
+ em('d[""]=1')
+ em('d["a\\0b"]=1')
+ em('d[b"a\\0b"]=1')
+ em('d.pop("abc1")')
+ em('d.popitem()')
+ del em
+ del m
+ EOF
+
+ call assert_equal(['KeyError', 'TypeError', 'TypeError', 'ValueError',
+ \ 'TypeError', 'TypeError', 'KeyError', 'KeyError'], messages)
+ unlet messages
+endfunc
+
+" Test for locked and scope attributes
+func Test_python3_lock_scope_attr()
+ let d = {} | let dl = {} | lockvar dl
+ let res = []
+ for s in split("d dl v: g:")
+ let name = tr(s, ':', 's')
+ execute 'py3 ' .. name .. ' = vim.bindeval("' .. s .. '")'
+ call add(res, s .. ' : ' .. join(map(['locked', 'scope'],
+ \ 'v:val .. ":" .. py3eval(name .. "." .. v:val)'), ';'))
+ endfor
+ call assert_equal(['d : locked:0;scope:0', 'dl : locked:1;scope:0',
+ \ 'v: : locked:2;scope:1', 'g: : locked:0;scope:2'], res)
+
+ silent! let d.abc2 = 1
+ silent! let dl.abc3 = 1
+ py3 d.locked = True
+ py3 dl.locked = False
+ silent! let d.def = 1
+ silent! let dl.def = 1
+ call assert_equal({'abc2': 1}, d)
+ call assert_equal({'def': 1}, dl)
+ unlet d dl
+
+ let l = [] | let ll = [] | lockvar ll
+ let res = []
+ for s in split("l ll")
+ let name = tr(s, ':', 's')
+ execute 'py3 ' .. name .. '=vim.bindeval("' .. s .. '")'
+ call add(res, s .. ' : locked:' .. py3eval(name .. '.locked'))
+ endfor
+ call assert_equal(['l : locked:0', 'll : locked:1'], res)
+
+ silent! call extend(l, [0])
+ silent! call extend(ll, [0])
+ py3 l.locked = True
+ py3 ll.locked = False
+ silent! call extend(l, [1])
+ silent! call extend(ll, [1])
+ call assert_equal([0], l)
+ call assert_equal([1], ll)
+ unlet l ll
+
+ " Try changing an attribute of a fixed list
+ py3 a = vim.bindeval('v:argv')
+ call AssertException(['py3 a.locked = 0'],
+ \ 'Vim(py3):TypeError: cannot modify fixed list')
+endfunc
+
+" Test for py3eval()
+func Test_python3_pyeval()
+ let l = py3eval('[0, 1, 2]')
+ call assert_equal([0, 1, 2], l)
+
+ let d = py3eval('{"a": "b", "c": 1, "d": ["e"]}')
+ call assert_equal([['a', 'b'], ['c', 1], ['d', ['e']]], sort(items(d)))
+
+ let v:errmsg = ''
+ call assert_equal(v:none, py3eval('None'))
+ call assert_equal('', v:errmsg)
+
+ py3 v = vim.eval('test_null_function()')
+ call assert_equal(v:none, py3eval('v'))
+
+ call assert_equal(0.0, py3eval('0.0'))
+
+ " Evaluate an invalid values
+ call AssertException(['let v = py3eval(''"\0"'')'], 'E859:')
+ call AssertException(['let v = py3eval(''{"\0" : 1}'')'], 'E859:')
+ call AssertException(['let v = py3eval("undefined_name")'],
+ \ "Vim(let):NameError: name 'undefined_name' is not defined")
+ call AssertException(['let v = py3eval("vim")'], 'E859:')
+endfunc
+
+" Test for vim.bindeval()
+func Test_python3_vim_bindeval()
+ " Float
+ let f = 3.14
+ py3 f = vim.bindeval('f')
+ call assert_equal(3.14, py3eval('f'))
+
+ " Blob
+ let b = 0z12
+ py3 b = vim.bindeval('b')
+ call assert_equal("\x12", py3eval('b'))
+
+ " Bool
+ call assert_equal(1, py3eval("vim.bindeval('v:true')"))
+ call assert_equal(0, py3eval("vim.bindeval('v:false')"))
+ call assert_equal(v:none, py3eval("vim.bindeval('v:null')"))
+ call assert_equal(v:none, py3eval("vim.bindeval('v:none')"))
+
+ " channel/job
+ if has('channel')
+ call assert_equal(v:none, py3eval("vim.bindeval('test_null_channel()')"))
+ endif
+ if has('job')
+ call assert_equal(v:none, py3eval("vim.bindeval('test_null_job()')"))
+ endif
+endfunc
+
+" threading
+" Running py3do command (Test_pydo) before this test, stops the python thread
+" from running. So this test should be run before the pydo test
+func Test_aaa_python3_threading()
+ let l = [0]
+ py3 l = vim.bindeval('l')
+ py3 << trim EOF
+ import threading
+ import time
+
+ class T(threading.Thread):
+ def __init__(self):
+ threading.Thread.__init__(self)
+ self.t = 0
+ self.running = True
+
+ def run(self):
+ while self.running:
+ self.t += 1
+ time.sleep(0.1)
+
+ t = T()
+ del T
+ t.start()
+ EOF
+
+ sleep 1
+ py3 t.running = False
+ py3 t.join()
+
+ " Check if the background thread is working. Count should be 10, but on a
+ " busy system (AppVeyor) it can be much lower.
+ py3 l[0] = t.t > 4
+ py3 del time
+ py3 del threading
+ py3 del t
+ call assert_equal([1], l)
+endfunc
+
+" settrace
+func Test_python3_settrace()
+ let l = []
+ py3 l = vim.bindeval('l')
+ py3 << trim EOF
+ import sys
+
+ def traceit(frame, event, arg):
+ global l
+ if event == "line":
+ l += [frame.f_lineno]
+ return traceit
+
+ def trace_main():
+ for i in range(5):
+ pass
+ EOF
+ py3 sys.settrace(traceit)
+ py3 trace_main()
+ py3 sys.settrace(None)
+ py3 del traceit
+ py3 del trace_main
+ call assert_equal([1, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 1], l)
+endfunc
+
+" Slice
+func Test_python3_list_slice()
+ py3 ll = vim.bindeval('[0, 1, 2, 3, 4, 5]')
+ py3 l = ll[:4]
+ call assert_equal([0, 1, 2, 3], py3eval('l'))
+ py3 l = ll[2:]
+ call assert_equal([2, 3, 4, 5], py3eval('l'))
+ py3 l = ll[:-4]
+ call assert_equal([0, 1], py3eval('l'))
+ py3 l = ll[-2:]
+ call assert_equal([4, 5], py3eval('l'))
+ py3 l = ll[2:4]
+ call assert_equal([2, 3], py3eval('l'))
+ py3 l = ll[4:2]
+ call assert_equal([], py3eval('l'))
+ py3 l = ll[-4:-2]
+ call assert_equal([2, 3], py3eval('l'))
+ py3 l = ll[-2:-4]
+ call assert_equal([], py3eval('l'))
+ py3 l = ll[:]
+ call assert_equal([0, 1, 2, 3, 4, 5], py3eval('l'))
+ py3 l = ll[0:6]
+ call assert_equal([0, 1, 2, 3, 4, 5], py3eval('l'))
+ py3 l = ll[-10:10]
+ call assert_equal([0, 1, 2, 3, 4, 5], py3eval('l'))
+ py3 l = ll[4:2:-1]
+ call assert_equal([4, 3], py3eval('l'))
+ py3 l = ll[::2]
+ call assert_equal([0, 2, 4], py3eval('l'))
+ py3 l = ll[4:2:1]
+ call assert_equal([], py3eval('l'))
+
+ " Error case: Use an invalid index
+ call AssertException(['py3 ll[-10] = 5'], 'Vim(py3):vim.error: internal error:')
+
+ " Use a step value of 0
+ call AssertException(['py3 ll[0:3:0] = [1, 2, 3]'],
+ \ 'Vim(py3):ValueError: slice step cannot be zero')
+
+ " Error case: Invalid slice type
+ call AssertException(["py3 x = ll['abc']"],
+ \ "Vim(py3):TypeError: index must be int or slice, not str")
+ py3 del l
+
+ " Error case: List with a null list item
+ let l = [test_null_list()]
+ py3 ll = vim.bindeval('l')
+ call AssertException(["py3 x = ll[:]"],
+ \ s:system_error_pat)
+endfunc
+
+" Vars
+func Test_python3_vars()
+ let g:foo = 'bac'
+ let w:abc3 = 'def'
+ let b:baz = 'bar'
+ let t:bar = 'jkl'
+ try
+ throw "Abc"
+ catch /Abc/
+ call assert_equal('Abc', py3eval('vim.vvars[''exception'']'))
+ endtry
+ call assert_equal('bac', py3eval('vim.vars[''foo'']'))
+ call assert_equal('def', py3eval('vim.current.window.vars[''abc3'']'))
+ call assert_equal('bar', py3eval('vim.current.buffer.vars[''baz'']'))
+ call assert_equal('jkl', py3eval('vim.current.tabpage.vars[''bar'']'))
+endfunc
+
+" Options
+" paste: boolean, global
+" previewheight number, global
+" operatorfunc: string, global
+" number: boolean, window-local
+" numberwidth: number, window-local
+" colorcolumn: string, window-local
+" statusline: string, window-local/global
+" autoindent: boolean, buffer-local
+" shiftwidth: number, buffer-local
+" omnifunc: string, buffer-local
+" preserveindent: boolean, buffer-local/global
+" path: string, buffer-local/global
+func Test_python3_opts()
+ let g:res = []
+ let g:bufs = [bufnr('%')]
+ new
+ let g:bufs += [bufnr('%')]
+ vnew
+ let g:bufs += [bufnr('%')]
+ wincmd j
+ vnew
+ let g:bufs += [bufnr('%')]
+ wincmd l
+
+ func RecVars(opt)
+ let gval = string(eval('&g:' .. a:opt))
+ let wvals = join(map(range(1, 4),
+ \ 'v:val .. ":" .. string(getwinvar(v:val, "&" .. a:opt))'))
+ let bvals = join(map(copy(g:bufs),
+ \ 'v:val .. ":" .. string(getbufvar(v:val, "&" .. a:opt))'))
+ call add(g:res, ' G: ' .. gval)
+ call add(g:res, ' W: ' .. wvals)
+ call add(g:res, ' B: ' .. wvals)
+ endfunc
+
+ py3 << trim EOF
+ def e(s, g=globals(), l=locals()):
+ try:
+ exec(s, g, l)
+ except Exception as e:
+ vim.command('return ' + repr(e.__class__.__name__))
+
+ def ev(s, g=globals(), l=locals()):
+ try:
+ return eval(s, g, l)
+ except Exception as e:
+ vim.command('let exc=' + repr(e.__class__.__name__))
+ return 0
+ EOF
+
+ func E(s)
+ python3 e(vim.eval('a:s'))
+ endfunc
+
+ func Ev(s)
+ let r = py3eval('ev(vim.eval("a:s"))')
+ if exists('exc')
+ throw exc
+ endif
+ return r
+ endfunc
+
+ py3 gopts1 = vim.options
+ py3 wopts1 = vim.windows[2].options
+ py3 wopts2 = vim.windows[0].options
+ py3 wopts3 = vim.windows[1].options
+ py3 bopts1 = vim.buffers[vim.bindeval("g:bufs")[2]].options
+ py3 bopts2 = vim.buffers[vim.bindeval("g:bufs")[1]].options
+ py3 bopts3 = vim.buffers[vim.bindeval("g:bufs")[0]].options
+ call add(g:res, 'wopts iters equal: ' ..
+ \ py3eval('list(wopts1) == list(wopts2)'))
+ call add(g:res, 'bopts iters equal: ' ..
+ \ py3eval('list(bopts1) == list(bopts2)'))
+ py3 gset = set(iter(gopts1))
+ py3 wset = set(iter(wopts1))
+ py3 bset = set(iter(bopts1))
+
+ set path=.,..,,
+ let lst = []
+ let lst += [['paste', 1, 0, 1, 2, 1, 1, 0]]
+ let lst += [['previewheight', 5, 1, 6, 'a', 0, 1, 0]]
+ let lst += [['operatorfunc', 'A', 'B', 'C', 2, 0, 1, 0]]
+ let lst += [['number', 0, 1, 1, 0, 1, 0, 1]]
+ let lst += [['numberwidth', 2, 3, 5, -100, 0, 0, 1]]
+ let lst += [['colorcolumn', '+1', '+2', '+3', 'abc4', 0, 0, 1]]
+ let lst += [['statusline', '1', '2', '4', 0, 0, 1, 1]]
+ let lst += [['autoindent', 0, 1, 1, 2, 1, 0, 2]]
+ let lst += [['shiftwidth', 0, 2, 1, 3, 0, 0, 2]]
+ let lst += [['omnifunc', 'A', 'B', 'C', 1, 0, 0, 2]]
+ let lst += [['preserveindent', 0, 1, 1, 2, 1, 1, 2]]
+ let lst += [['path', '.,,', ',,', '.', 0, 0, 1, 2]]
+ for [oname, oval1, oval2, oval3, invval, bool, global, local] in lst
+ py3 oname = vim.eval('oname')
+ py3 oval1 = vim.bindeval('oval1')
+ py3 oval2 = vim.bindeval('oval2')
+ py3 oval3 = vim.bindeval('oval3')
+ if invval is 0 || invval is 1
+ py3 invval = bool(vim.bindeval('invval'))
+ else
+ py3 invval = vim.bindeval('invval')
+ endif
+ if bool
+ py3 oval1 = bool(oval1)
+ py3 oval2 = bool(oval2)
+ py3 oval3 = bool(oval3)
+ endif
+ call add(g:res, '>>> ' .. oname)
+ call add(g:res, ' g/w/b:' .. py3eval('oname in gset') .. '/' ..
+ \ py3eval('oname in wset') .. '/' .. py3eval('oname in bset'))
+ call add(g:res, ' g/w/b (in):' .. py3eval('oname in gopts1') .. '/' ..
+ \ py3eval('oname in wopts1') .. '/' .. py3eval('oname in bopts1'))
+ for v in ['gopts1', 'wopts1', 'bopts1']
+ try
+ call add(g:res, ' p/' .. v .. ': ' .. Ev('repr(' .. v .. '[''' .. oname .. '''])'))
+ catch
+ call add(g:res, ' p/' .. v .. '! ' .. v:exception)
+ endtry
+ let r = E(v .. '[''' .. oname .. ''']=invval')
+ if r isnot 0
+ call add(g:res, ' inv: ' .. string(invval) .. '! ' .. r)
+ endif
+ for vv in (v is# 'gopts1' ? [v] : [v, v[:-2] .. '2', v[:-2] .. '3'])
+ let val = substitute(vv, '^.opts', 'oval', '')
+ let r = E(vv .. '[''' .. oname .. ''']=' .. val)
+ if r isnot 0
+ call add(g:res, ' ' .. vv .. '! ' .. r)
+ endif
+ endfor
+ endfor
+ call RecVars(oname)
+ for v in ['wopts3', 'bopts3']
+ let r = E('del ' .. v .. '["' .. oname .. '"]')
+ if r isnot 0
+ call add(g:res, ' del ' .. v .. '! ' .. r)
+ endif
+ endfor
+ call RecVars(oname)
+ endfor
+ delfunction RecVars
+ delfunction E
+ delfunction Ev
+ py3 del ev
+ py3 del e
+ only
+ for buf in g:bufs[1:]
+ execute 'bwipeout!' buf
+ endfor
+ py3 del gopts1
+ py3 del wopts1
+ py3 del wopts2
+ py3 del wopts3
+ py3 del bopts1
+ py3 del bopts2
+ py3 del bopts3
+ py3 del oval1
+ py3 del oval2
+ py3 del oval3
+ py3 del oname
+ py3 del invval
+
+ let expected =<< trim END
+ wopts iters equal: 1
+ bopts iters equal: 1
+ >>> paste
+ g/w/b:1/0/0
+ g/w/b (in):1/0/0
+ p/gopts1: False
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1! KeyError
+ inv: 2! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 1
+ W: 1:1 2:1 3:1 4:1
+ B: 1:1 2:1 3:1 4:1
+ del wopts3! KeyError
+ del bopts3! KeyError
+ G: 1
+ W: 1:1 2:1 3:1 4:1
+ B: 1:1 2:1 3:1 4:1
+ >>> previewheight
+ g/w/b:1/0/0
+ g/w/b (in):1/0/0
+ p/gopts1: 12
+ inv: 'a'! TypeError
+ p/wopts1! KeyError
+ inv: 'a'! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1! KeyError
+ inv: 'a'! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 5
+ W: 1:5 2:5 3:5 4:5
+ B: 1:5 2:5 3:5 4:5
+ del wopts3! KeyError
+ del bopts3! KeyError
+ G: 5
+ W: 1:5 2:5 3:5 4:5
+ B: 1:5 2:5 3:5 4:5
+ >>> operatorfunc
+ g/w/b:1/0/0
+ g/w/b (in):1/0/0
+ p/gopts1: b''
+ inv: 2! TypeError
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1! KeyError
+ inv: 2! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 'A'
+ W: 1:'A' 2:'A' 3:'A' 4:'A'
+ B: 1:'A' 2:'A' 3:'A' 4:'A'
+ del wopts3! KeyError
+ del bopts3! KeyError
+ G: 'A'
+ W: 1:'A' 2:'A' 3:'A' 4:'A'
+ B: 1:'A' 2:'A' 3:'A' 4:'A'
+ >>> number
+ g/w/b:0/1/0
+ g/w/b (in):0/1/0
+ p/gopts1! KeyError
+ inv: 0! KeyError
+ gopts1! KeyError
+ p/wopts1: False
+ p/bopts1! KeyError
+ inv: 0! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 0
+ W: 1:1 2:1 3:0 4:0
+ B: 1:1 2:1 3:0 4:0
+ del wopts3! ValueError
+ del bopts3! KeyError
+ G: 0
+ W: 1:1 2:1 3:0 4:0
+ B: 1:1 2:1 3:0 4:0
+ >>> numberwidth
+ g/w/b:0/1/0
+ g/w/b (in):0/1/0
+ p/gopts1! KeyError
+ inv: -100! KeyError
+ gopts1! KeyError
+ p/wopts1: 4
+ inv: -100! error
+ p/bopts1! KeyError
+ inv: -100! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: 4
+ W: 1:3 2:5 3:2 4:4
+ B: 1:3 2:5 3:2 4:4
+ del wopts3! ValueError
+ del bopts3! KeyError
+ G: 4
+ W: 1:3 2:5 3:2 4:4
+ B: 1:3 2:5 3:2 4:4
+ >>> colorcolumn
+ g/w/b:0/1/0
+ g/w/b (in):0/1/0
+ p/gopts1! KeyError
+ inv: 'abc4'! KeyError
+ gopts1! KeyError
+ p/wopts1: b''
+ inv: 'abc4'! error
+ p/bopts1! KeyError
+ inv: 'abc4'! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: ''
+ W: 1:'+2' 2:'+3' 3:'+1' 4:''
+ B: 1:'+2' 2:'+3' 3:'+1' 4:''
+ del wopts3! ValueError
+ del bopts3! KeyError
+ G: ''
+ W: 1:'+2' 2:'+3' 3:'+1' 4:''
+ B: 1:'+2' 2:'+3' 3:'+1' 4:''
+ >>> statusline
+ g/w/b:1/1/0
+ g/w/b (in):1/1/0
+ p/gopts1: b''
+ inv: 0! TypeError
+ p/wopts1: None
+ inv: 0! TypeError
+ p/bopts1! KeyError
+ inv: 0! KeyError
+ bopts1! KeyError
+ bopts2! KeyError
+ bopts3! KeyError
+ G: '1'
+ W: 1:'2' 2:'4' 3:'1' 4:'1'
+ B: 1:'2' 2:'4' 3:'1' 4:'1'
+ del bopts3! KeyError
+ G: '1'
+ W: 1:'2' 2:'1' 3:'1' 4:'1'
+ B: 1:'2' 2:'1' 3:'1' 4:'1'
+ >>> autoindent
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 2! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: False
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ >>> shiftwidth
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 3! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 3! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: 8
+ G: 8
+ W: 1:0 2:2 3:8 4:1
+ B: 1:0 2:2 3:8 4:1
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: 8
+ W: 1:0 2:2 3:8 4:1
+ B: 1:0 2:2 3:8 4:1
+ >>> omnifunc
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 1! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 1! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: b''
+ inv: 1! TypeError
+ G: ''
+ W: 1:'A' 2:'B' 3:'' 4:'C'
+ B: 1:'A' 2:'B' 3:'' 4:'C'
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: ''
+ W: 1:'A' 2:'B' 3:'' 4:'C'
+ B: 1:'A' 2:'B' 3:'' 4:'C'
+ >>> preserveindent
+ g/w/b:0/0/1
+ g/w/b (in):0/0/1
+ p/gopts1! KeyError
+ inv: 2! KeyError
+ gopts1! KeyError
+ p/wopts1! KeyError
+ inv: 2! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: False
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ del wopts3! KeyError
+ del bopts3! ValueError
+ G: 0
+ W: 1:0 2:1 3:0 4:1
+ B: 1:0 2:1 3:0 4:1
+ >>> path
+ g/w/b:1/0/1
+ g/w/b (in):1/0/1
+ p/gopts1: b'.,..,,'
+ inv: 0! TypeError
+ p/wopts1! KeyError
+ inv: 0! KeyError
+ wopts1! KeyError
+ wopts2! KeyError
+ wopts3! KeyError
+ p/bopts1: None
+ inv: 0! TypeError
+ G: '.,,'
+ W: 1:'.,,' 2:',,' 3:'.,,' 4:'.'
+ B: 1:'.,,' 2:',,' 3:'.,,' 4:'.'
+ del wopts3! KeyError
+ G: '.,,'
+ W: 1:'.,,' 2:',,' 3:'.,,' 4:'.,,'
+ B: 1:'.,,' 2:',,' 3:'.,,' 4:'.,,'
+ END
+
+ call assert_equal(expected, g:res)
+ unlet g:res
+
+ call assert_equal(0, py3eval("'' in vim.options"))
+
+ " use an empty key to index vim.options
+ call AssertException(["let v = py3eval(\"vim.options['']\")"],
+ \ 'Vim(let):ValueError: empty keys are not allowed')
+ call AssertException(["py3 vim.current.window.options[''] = 0"],
+ \ 'Vim(py3):ValueError: empty keys are not allowed')
+ call AssertException(["py3 vim.current.window.options[{}] = 0"],
+ \ 'Vim(py3):TypeError: expected bytes() or str() instance, but got dict')
+
+ " set one of the number options to a very large number
+ let cmd = ["py3 vim.options['previewheight'] = 9999999999999999"]
+ call AssertException(cmd, "Vim(py3):OverflowError:")
+
+ " unset a global-local string option
+ call AssertException(["py3 del vim.options['errorformat']"],
+ \ 'Vim(py3):ValueError: unable to unset global option errorformat')
+endfunc
+
+" Test for vim.buffer object
+func Test_python3_buffer()
+ new
+ call setline(1, "Hello\nWorld")
+ call assert_fails("let x = py3eval('vim.current.buffer[0]')", 'E859:')
+ %bw!
+
+ edit Xfile1
+ let bnr1 = bufnr()
+ py3 cb = vim.current.buffer
+ vnew Xfile2
+ let bnr2 = bufnr()
+ call setline(1, ['First line', 'Second line', 'Third line'])
+ py3 b = vim.current.buffer
+ wincmd w
+
+ " Test for getting lines from the buffer using a slice
+ call assert_equal(['First line'], py3eval('b[-10:1]'))
+ call assert_equal(['Third line'], py3eval('b[2:10]'))
+ call assert_equal([], py3eval('b[2:0]'))
+ call assert_equal([], py3eval('b[10:12]'))
+ call assert_equal([], py3eval('b[-10:-8]'))
+ call AssertException(["py3 x = b[0:3:0]"],
+ \ 'Vim(py3):ValueError: slice step cannot be zero')
+ call AssertException(["py3 b[0:3:0] = 'abc'"],
+ \ 'Vim(py3):ValueError: slice step cannot be zero')
+ call AssertException(["py3 x = b[{}]"],
+ \ 'Vim(py3):TypeError: index must be int or slice, not dict')
+ call AssertException(["py3 b[{}] = 'abc'"],
+ \ 'Vim(py3):TypeError: index must be int or slice, not dict')
+
+ " Test for getting lines using a range
+ call AssertException(["py3 x = b.range(0,3)[0:2:0]"],
+ \ "Vim(py3):ValueError: slice step cannot be zero")
+ call AssertException(["py3 b.range(0,3)[0:2:0] = 'abc'"],
+ \ "Vim(py3):ValueError: slice step cannot be zero")
+
+ " Tests BufferAppend and BufferItem
+ py3 cb.append(b[0])
+ call assert_equal(['First line'], getbufline(bnr1, 2))
+ %d
+
+ " Try to append using out-of-range line number
+ call AssertException(["py3 b.append('abc', 10)"],
+ \ 'Vim(py3):IndexError: line number out of range')
+
+ " Append a non-string item
+ call AssertException(["py3 b.append([22])"],
+ \ 'Vim(py3):TypeError: expected bytes() or str() instance, but got int')
+
+ " Tests BufferSlice and BufferAssSlice
+ py3 cb.append('abc5') # Will be overwritten
+ py3 cb[-1:] = b[:-2]
+ call assert_equal(['First line'], getbufline(bnr1, 2))
+ %d
+
+ " Test BufferLength and BufferAssSlice
+ py3 cb.append('def') # Will not be overwritten
+ py3 cb[len(cb):] = b[:]
+ call assert_equal(['def', 'First line', 'Second line', 'Third line'],
+ \ getbufline(bnr1, 2, '$'))
+ %d
+
+ " Test BufferAssItem and BufferMark
+ call setbufline(bnr1, 1, ['one', 'two', 'three'])
+ call cursor(1, 3)
+ normal ma
+ py3 cb.append('ghi') # Will be overwritten
+ py3 cb[-1] = repr((len(cb) - cb.mark('a')[0], cb.mark('a')[1]))
+ call assert_equal(['(3, 2)'], getbufline(bnr1, 4))
+ %d
+
+ " Test BufferRepr
+ py3 cb.append(repr(cb) + repr(b))
+ call assert_equal(['<buffer Xfile1><buffer Xfile2>'], getbufline(bnr1, 2))
+ %d
+
+ " Modify foreign buffer
+ py3 << trim EOF
+ b.append('foo')
+ b[0]='bar'
+ b[0:0]=['baz']
+ vim.command('call append("$", getbufline(%i, 1, "$"))' % b.number)
+ EOF
+ call assert_equal(['baz', 'bar', 'Second line', 'Third line', 'foo'],
+ \ getbufline(bnr2, 1, '$'))
+ %d
+
+ " Test assigning to name property
+ augroup BUFS
+ autocmd BufFilePost * python3 cb.append(vim.eval('expand("<abuf>")') + ':BufFilePost:' + vim.eval('bufnr("%")'))
+ autocmd BufFilePre * python3 cb.append(vim.eval('expand("<abuf>")') + ':BufFilePre:' + vim.eval('bufnr("%")'))
+ augroup END
+ py3 << trim EOF
+ import os
+ old_name = cb.name
+ cb.name = 'foo'
+ cb.append(cb.name[-11:].replace(os.path.sep, '/'))
+ b.name = 'bar'
+ cb.append(b.name[-11:].replace(os.path.sep, '/'))
+ cb.name = old_name
+ cb.append(cb.name[-14:].replace(os.path.sep, '/'))
+ del old_name
+ EOF
+ call assert_equal([bnr1 .. ':BufFilePre:' .. bnr1,
+ \ bnr1 .. ':BufFilePost:' .. bnr1,
+ \ 'testdir/foo',
+ \ bnr2 .. ':BufFilePre:' .. bnr2,
+ \ bnr2 .. ':BufFilePost:' .. bnr2,
+ \ 'testdir/bar',
+ \ bnr1 .. ':BufFilePre:' .. bnr1,
+ \ bnr1 .. ':BufFilePost:' .. bnr1,
+ \ 'testdir/Xfile1'], getbufline(bnr1, 2, '$'))
+ %d
+
+ " Test CheckBuffer
+ py3 << trim EOF
+ for _b in vim.buffers:
+ if _b is not cb:
+ vim.command('bwipeout! ' + str(_b.number))
+ del _b
+ cb.append('valid: b:%s, cb:%s' % (repr(b.valid), repr(cb.valid)))
+ EOF
+ call assert_equal('valid: b:False, cb:True', getline(2))
+ %d
+
+ py3 << trim EOF
+ for expr in ('b[1]','b[:] = ["A", "B"]','b[:]','b.append("abc6")'):
+ try:
+ exec(expr)
+ except vim.error:
+ pass
+ else:
+ # Usually a SEGV here
+ # Should not happen in any case
+ cb.append('No exception for ' + expr)
+ vim.command('cd .')
+ del b
+ EOF
+ call assert_equal([''], getline(1, '$'))
+
+ " Delete all the lines in a buffer
+ call setline(1, ['a', 'b', 'c'])
+ py3 vim.current.buffer[:] = []
+ call assert_equal([''], getline(1, '$'))
+
+ " Test for buffer marks
+ call assert_equal(v:none, py3eval("vim.current.buffer.mark('r')"))
+
+ " Test for modifying a 'nomodifiable' buffer
+ setlocal nomodifiable
+ call AssertException(["py3 vim.current.buffer[0] = 'abc'"],
+ \ "Vim(py3):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py3 vim.current.buffer[0] = None"],
+ \ "Vim(py3):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py3 vim.current.buffer[:] = None"],
+ \ "Vim(py3):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py3 vim.current.buffer[:] = []"],
+ \ "Vim(py3):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py3 vim.current.buffer.append('abc')"],
+ \ "Vim(py3):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ call AssertException(["py3 vim.current.buffer.append([])"],
+ \ "Vim(py3):vim.error: Vim:E21: Cannot make changes, 'modifiable' is off")
+ setlocal modifiable
+
+ augroup BUFS
+ autocmd!
+ augroup END
+ augroup! BUFS
+ %bw!
+
+ " Range object for a deleted buffer
+ new Xp3buffile
+ call setline(1, ['one', 'two', 'three'])
+ py3 b = vim.current.buffer
+ py3 r = vim.current.buffer.range(0, 2)
+ call assert_equal('<range Xp3buffile (0:2)>', py3eval('repr(r)'))
+ %bw!
+ call AssertException(['py3 r[:] = []'],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted buffer')
+ call assert_match('<buffer object (deleted)', py3eval('repr(b)'))
+ call assert_match('<range object (for deleted buffer)', py3eval('repr(r)'))
+ call AssertException(["let n = py3eval('len(r)')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py3 r.append('abc')"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted buffer')
+
+ " object for a deleted buffer
+ call AssertException(["py3 b[0] = 'one'"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py3 b.append('one')"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["let n = py3eval('len(b)')"],
+ \ 'Vim(let):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py3 pos = b.mark('a')"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py3 vim.current.buffer = b"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted buffer')
+ call AssertException(["py3 rn = b.range(0, 2)"],
+ \ 'Vim(py3):vim.error: attempt to refer to deleted buffer')
+endfunc
+
+" Test vim.buffers object
+func Test_python3_buffers()
+ %bw!
+ edit Xp3buffile
+ py3 cb = vim.current.buffer
+ set hidden
+ edit a
+ buffer #
+ edit b
+ buffer #
+ edit c
+ buffer #
+ py3 << trim EOF
+ # Check GCing iterator that was not fully exhausted
+ i = iter(vim.buffers)
+ cb.append('i:' + str(next(i)))
+ # and also check creating more than one iterator at a time
+ i2 = iter(vim.buffers)
+ cb.append('i2:' + str(next(i2)))
+ cb.append('i:' + str(next(i)))
+ # The following should trigger GC and not cause any problems
+ del i
+ del i2
+ i3 = iter(vim.buffers)
+ cb.append('i3:' + str(next(i3)))
+ del i3
+ EOF
+ call assert_equal(['i:<buffer Xp3buffile>',
+ \ 'i2:<buffer Xp3buffile>', 'i:<buffer a>', 'i3:<buffer Xp3buffile>'],
+ \ getline(2, '$'))
+ %d
+
+ py3 << trim EOF
+ prevnum = 0
+ for b in vim.buffers:
+ # Check buffer order
+ if prevnum >= b.number:
+ cb.append('!!! Buffer numbers not in strictly ascending order')
+ # Check indexing: vim.buffers[number].number == number
+ cb.append(str(b.number) + ':' + repr(vim.buffers[b.number]) + \
+ '=' + repr(b))
+ prevnum = b.number
+ del prevnum
+
+ cb.append(str(len(vim.buffers)))
+ EOF
+ call assert_equal([bufnr('Xp3buffile') .. ':<buffer Xp3buffile>=<buffer Xp3buffile>',
+ \ bufnr('a') .. ':<buffer a>=<buffer a>',
+ \ bufnr('b') .. ':<buffer b>=<buffer b>',
+ \ bufnr('c') .. ':<buffer c>=<buffer c>', '4'], getline(2, '$'))
+ %d
+
+ py3 << trim EOF
+ bnums = list(map(lambda b: b.number, vim.buffers))[1:]
+
+ # Test wiping out buffer with existing iterator
+ i4 = iter(vim.buffers)
+ cb.append('i4:' + str(next(i4)))
+ vim.command('bwipeout! ' + str(bnums.pop(0)))
+ try:
+ next(i4)
+ except vim.error:
+ pass
+ else:
+ cb.append('!!!! No vim.error')
+ i4 = iter(vim.buffers)
+ vim.command('bwipeout! ' + str(bnums.pop(-1)))
+ vim.command('bwipeout! ' + str(bnums.pop(-1)))
+ cb.append('i4:' + str(next(i4)))
+ try:
+ next(i4)
+ except StopIteration:
+ cb.append('StopIteration')
+ del i4
+ del bnums
+ EOF
+ call assert_equal(['i4:<buffer Xp3buffile>',
+ \ 'i4:<buffer Xp3buffile>', 'StopIteration'], getline(2, '$'))
+ %bw!
+endfunc
+
+" Test vim.{tabpage,window}list and vim.{tabpage,window} objects
+func Test_python3_tabpage_window()
+ %bw
+ edit Xp3buffile
+ py3 cb = vim.current.buffer
+ tabnew 0
+ tabnew 1
+ vnew a.1
+ tabnew 2
+ vnew a.2
+ vnew b.2
+ vnew c.2
+
+ py3 << trim EOF
+ cb.append('Number of tabs: ' + str(len(vim.tabpages)))
+ cb.append('Current tab pages:')
+ def W(w):
+ if '(unknown)' in repr(w):
+ return '<window object (unknown)>'
+ else:
+ return repr(w)
+
+ def Cursor(w, start=len(cb)):
+ if w.buffer is cb:
+ return repr((start - w.cursor[0], w.cursor[1]))
+ else:
+ return repr(w.cursor)
+
+ for t in vim.tabpages:
+ cb.append(' ' + repr(t) + '(' + str(t.number) + ')' + ': ' + \
+ str(len(t.windows)) + ' windows, current is ' + W(t.window))
+ cb.append(' Windows:')
+ for w in t.windows:
+ cb.append(' ' + W(w) + '(' + str(w.number) + ')' + \
+ ': displays buffer ' + repr(w.buffer) + \
+ '; cursor is at ' + Cursor(w))
+ # Other values depend on the size of the terminal, so they are checked
+ # partly:
+ for attr in ('height', 'row', 'width', 'col'):
+ try:
+ aval = getattr(w, attr)
+ if type(aval) is not int:
+ raise TypeError
+ if aval < 0:
+ raise ValueError
+ except Exception as e:
+ cb.append('!!!!!! Error while getting attribute ' + attr + \
+ ': ' + e.__class__.__name__)
+ del aval
+ del attr
+ w.cursor = (len(w.buffer), 0)
+ del W
+ del Cursor
+ cb.append('Number of windows in current tab page: ' + \
+ str(len(vim.windows)))
+ if list(vim.windows) != list(vim.current.tabpage.windows):
+ cb.append('!!!!!! Windows differ')
+ EOF
+
+ let expected =<< trim END
+ Number of tabs: 4
+ Current tab pages:
+ <tabpage 0>(1): 1 windows, current is <window object (unknown)>
+ Windows:
+ <window object (unknown)>(1): displays buffer <buffer Xp3buffile>; cursor is at (2, 0)
+ <tabpage 1>(2): 1 windows, current is <window object (unknown)>
+ Windows:
+ <window object (unknown)>(1): displays buffer <buffer 0>; cursor is at (1, 0)
+ <tabpage 2>(3): 2 windows, current is <window object (unknown)>
+ Windows:
+ <window object (unknown)>(1): displays buffer <buffer a.1>; cursor is at (1, 0)
+ <window object (unknown)>(2): displays buffer <buffer 1>; cursor is at (1, 0)
+ <tabpage 3>(4): 4 windows, current is <window 0>
+ Windows:
+ <window 0>(1): displays buffer <buffer c.2>; cursor is at (1, 0)
+ <window 1>(2): displays buffer <buffer b.2>; cursor is at (1, 0)
+ <window 2>(3): displays buffer <buffer a.2>; cursor is at (1, 0)
+ <window 3>(4): displays buffer <buffer 2>; cursor is at (1, 0)
+ Number of windows in current tab page: 4
+ END
+ call assert_equal(expected, getbufline(bufnr('Xp3buffile'), 2, '$'))
+ %bw!
+endfunc
+
+" Test vim.current
+func Test_python3_vim_current()
+ %bw
+ edit Xpy3cfile
+ py3 cb = vim.current.buffer
+ tabnew 0
+ tabnew 1
+ vnew a.1
+ tabnew 2
+ vnew a.2
+ vnew b.2
+ vnew c.2
+
+ py3 << trim EOF
+ def H(o):
+ return repr(o)
+ cb.append('Current tab page: ' + repr(vim.current.tabpage))
+ cb.append('Current window: ' + repr(vim.current.window) + ': ' + \
+ H(vim.current.window) + ' is ' + H(vim.current.tabpage.window))
+ cb.append('Current buffer: ' + repr(vim.current.buffer) + ': ' + \
+ H(vim.current.buffer) + ' is ' + H(vim.current.window.buffer)+ \
+ ' is ' + H(vim.current.tabpage.window.buffer))
+ del H
+ EOF
+ let expected =<< trim END
+ Current tab page: <tabpage 3>
+ Current window: <window 0>: <window 0> is <window 0>
+ Current buffer: <buffer c.2>: <buffer c.2> is <buffer c.2> is <buffer c.2>
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpy3cfile'), 2, '$'))
+ call deletebufline(bufnr('Xpy3cfile'), 1, '$')
+
+ " Assigning: fails
+ py3 << trim EOF
+ try:
+ vim.current.window = vim.tabpages[0].window
+ except ValueError:
+ cb.append('ValueError at assigning foreign tab window')
+
+ for attr in ('window', 'tabpage', 'buffer'):
+ try:
+ setattr(vim.current, attr, None)
+ except TypeError:
+ cb.append('Type error at assigning None to vim.current.' + attr)
+ del attr
+ EOF
+
+ let expected =<< trim END
+ ValueError at assigning foreign tab window
+ Type error at assigning None to vim.current.window
+ Type error at assigning None to vim.current.tabpage
+ Type error at assigning None to vim.current.buffer
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpy3cfile'), 2, '$'))
+ call deletebufline(bufnr('Xpy3cfile'), 1, '$')
+
+ call setbufline(bufnr('Xpy3cfile'), 1, 'python interface')
+ py3 << trim EOF
+ # Assigning: success
+ vim.current.tabpage = vim.tabpages[-2]
+ vim.current.buffer = cb
+ vim.current.window = vim.windows[0]
+ vim.current.window.cursor = (len(vim.current.buffer), 0)
+ cb.append('Current tab page: ' + repr(vim.current.tabpage))
+ cb.append('Current window: ' + repr(vim.current.window))
+ cb.append('Current buffer: ' + repr(vim.current.buffer))
+ cb.append('Current line: ' + repr(vim.current.line))
+ EOF
+
+ let expected =<< trim END
+ Current tab page: <tabpage 2>
+ Current window: <window 0>
+ Current buffer: <buffer Xpy3cfile>
+ Current line: 'python interface'
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpy3cfile'), 2, '$'))
+ py3 vim.current.line = 'one line'
+ call assert_equal('one line', getline('.'))
+ call deletebufline(bufnr('Xpy3cfile'), 1, '$')
+
+ py3 << trim EOF
+ ws = list(vim.windows)
+ ts = list(vim.tabpages)
+ for b in vim.buffers:
+ if b is not cb:
+ vim.command('bwipeout! ' + str(b.number))
+ del b
+ cb.append('w.valid: ' + repr([w.valid for w in ws]))
+ cb.append('t.valid: ' + repr([t.valid for t in ts]))
+ del w
+ del t
+ del ts
+ del ws
+ EOF
+ let expected =<< trim END
+ w.valid: [True, False]
+ t.valid: [True, False, True, False]
+ END
+ call assert_equal(expected, getbufline(bufnr('Xpy3cfile'), 2, '$'))
+ %bw!
+endfunc
+
+" Test types
+func Test_python3_types()
+ %d
+ py3 cb = vim.current.buffer
+ py3 << trim EOF
+ for expr, attr in (
+ ('vim.vars', 'Dictionary'),
+ ('vim.options', 'Options'),
+ ('vim.bindeval("{}")', 'Dictionary'),
+ ('vim.bindeval("[]")', 'List'),
+ ('vim.bindeval("function(\'tr\')")', 'Function'),
+ ('vim.current.buffer', 'Buffer'),
+ ('vim.current.range', 'Range'),
+ ('vim.current.window', 'Window'),
+ ('vim.current.tabpage', 'TabPage'),
+ ):
+ cb.append(expr + ':' + attr + ':' + \
+ repr(type(eval(expr)) is getattr(vim, attr)))
+ del expr
+ del attr
+ EOF
+ let expected =<< trim END
+ vim.vars:Dictionary:True
+ vim.options:Options:True
+ vim.bindeval("{}"):Dictionary:True
+ vim.bindeval("[]"):List:True
+ vim.bindeval("function('tr')"):Function:True
+ vim.current.buffer:Buffer:True
+ vim.current.range:Range:True
+ vim.current.window:Window:True
+ vim.current.tabpage:TabPage:True
+ END
+ call assert_equal(expected, getline(2, '$'))
+endfunc
+
+" Test __dir__() method
+func Test_python3_dir_method()
+ %d
+ py3 cb = vim.current.buffer
+ py3 << trim EOF
+ for name, o in (
+ ('current', vim.current),
+ ('buffer', vim.current.buffer),
+ ('window', vim.current.window),
+ ('tabpage', vim.current.tabpage),
+ ('range', vim.current.range),
+ ('dictionary', vim.bindeval('{}')),
+ ('list', vim.bindeval('[]')),
+ ('function', vim.bindeval('function("tr")')),
+ ('output', sys.stdout),
+ ):
+ cb.append(name + ':' + ','.join(dir(o)))
+ del name
+ del o
+ EOF
+ let expected =<< trim END
+ current:__dir__,buffer,line,range,tabpage,window
+ buffer:__dir__,append,mark,name,number,options,range,valid,vars
+ window:__dir__,buffer,col,cursor,height,number,options,row,tabpage,valid,vars,width
+ tabpage:__dir__,number,valid,vars,window,windows
+ range:__dir__,append,end,start
+ dictionary:__dir__,get,has_key,items,keys,locked,pop,popitem,scope,update,values
+ list:__dir__,extend,locked
+ function:__dir__,args,auto_rebind,self,softspace
+ output:__dir__,close,closed,flush,isatty,readable,seekable,softspace,writable,write,writelines
+ END
+ call assert_equal(expected, getline(2, '$'))
+endfunc
+
+" Test vim.*.__new__
+func Test_python3_new()
+ call assert_equal({}, py3eval('vim.Dictionary({})'))
+ call assert_equal({'a': 1}, py3eval('vim.Dictionary(a=1)'))
+ call assert_equal({'a': 1}, py3eval('vim.Dictionary(((''a'', 1),))'))
+ call assert_equal([], py3eval('vim.List()'))
+ call assert_equal(['a', 'b', 'c', '7'], py3eval('vim.List(iter(''abc7''))'))
+ call assert_equal(function('tr'), py3eval('vim.Function(''tr'')'))
+ call assert_equal(function('tr', [123, 3, 4]),
+ \ py3eval('vim.Function(''tr'', args=[123, 3, 4])'))
+ call assert_equal(function('tr'), py3eval('vim.Function(''tr'', args=[])'))
+ call assert_equal(function('tr', {}),
+ \ py3eval('vim.Function(''tr'', self={})'))
+ call assert_equal(function('tr', [123, 3, 4], {}),
+ \ py3eval('vim.Function(''tr'', args=[123, 3, 4], self={})'))
+ call assert_equal(function('tr'),
+ \ py3eval('vim.Function(''tr'', auto_rebind=False)'))
+ call assert_equal(function('tr', [123, 3, 4]),
+ \ py3eval('vim.Function(''tr'', args=[123, 3, 4], auto_rebind=False)'))
+ call assert_equal(function('tr'),
+ \ py3eval('vim.Function(''tr'', args=[], auto_rebind=False)'))
+ call assert_equal(function('tr', {}),
+ \ py3eval('vim.Function(''tr'', self={}, auto_rebind=False)'))
+ call assert_equal(function('tr', [123, 3, 4], {}),
+ \ py3eval('vim.Function(''tr'', args=[123, 3, 4], self={}, auto_rebind=False)'))
+endfunc
+
+" Test vim.Function
+func Test_python3_vim_func()
+ func Args(...)
+ return a:000
+ endfunc
+
+ func SelfArgs(...) dict
+ return [a:000, self]
+ endfunc
+
+ " The following four lines should not crash
+ let Pt = function('tr', [[]], {'l': []})
+ py3 Pt = vim.bindeval('Pt')
+ unlet Pt
+ py3 del Pt
+
+ call assert_equal(3, py3eval('vim.strwidth("a\tb")'))
+
+ %bw!
+ py3 cb = vim.current.buffer
+ py3 << trim EOF
+ def ecall(out_prefix, func, *args, **kwargs):
+ line = out_prefix + ': '
+ try:
+ ret = func(*args, **kwargs)
+ except Exception:
+ line += '!exception: ' + emsg(sys.exc_info())
+ else:
+ line += '!result: ' + str(vim.Function('string')(ret), 'utf-8')
+ cb.append(line)
+ a = vim.Function('Args')
+ pa1 = vim.Function('Args', args=['abcArgsPA1'])
+ pa2 = vim.Function('Args', args=[])
+ pa3 = vim.Function('Args', args=['abcArgsPA3'], self={'abcSelfPA3': 'abcSelfPA3Val'})
+ pa4 = vim.Function('Args', self={'abcSelfPA4': 'abcSelfPA4Val'})
+ cb.append('a: ' + repr(a))
+ cb.append('pa1: ' + repr(pa1))
+ cb.append('pa2: ' + repr(pa2))
+ cb.append('pa3: ' + repr(pa3))
+ cb.append('pa4: ' + repr(pa4))
+ sa = vim.Function('SelfArgs')
+ psa1 = vim.Function('SelfArgs', args=['abcArgsPSA1'])
+ psa2 = vim.Function('SelfArgs', args=[])
+ psa3 = vim.Function('SelfArgs', args=['abcArgsPSA3'], self={'abcSelfPSA3': 'abcSelfPSA3Val'})
+ psa4 = vim.Function('SelfArgs', self={'abcSelfPSA4': 'abcSelfPSA4Val'})
+ psa5 = vim.Function('SelfArgs', self={'abcSelfPSA5': 'abcSelfPSA5Val'}, auto_rebind=0)
+ psa6 = vim.Function('SelfArgs', args=['abcArgsPSA6'], self={'abcSelfPSA6': 'abcSelfPSA6Val'}, auto_rebind=())
+ psa7 = vim.Function('SelfArgs', args=['abcArgsPSA7'], auto_rebind=[])
+ psa8 = vim.Function('SelfArgs', auto_rebind=False)
+ psa9 = vim.Function('SelfArgs', self={'abcSelfPSA9': 'abcSelfPSA9Val'}, auto_rebind=True)
+ psaA = vim.Function('SelfArgs', args=['abcArgsPSAA'], self={'abcSelfPSAA': 'abcSelfPSAAVal'}, auto_rebind=1)
+ psaB = vim.Function('SelfArgs', args=['abcArgsPSAB'], auto_rebind={'abcARPSAB': 'abcARPSABVal'})
+ psaC = vim.Function('SelfArgs', auto_rebind=['abcARPSAC'])
+ cb.append('sa: ' + repr(sa))
+ cb.append('psa1: ' + repr(psa1))
+ cb.append('psa2: ' + repr(psa2))
+ cb.append('psa3: ' + repr(psa3))
+ cb.append('psa4: ' + repr(psa4))
+ cb.append('psa5: ' + repr(psa5))
+ cb.append('psa6: ' + repr(psa6))
+ cb.append('psa7: ' + repr(psa7))
+ cb.append('psa8: ' + repr(psa8))
+ cb.append('psa9: ' + repr(psa9))
+ cb.append('psaA: ' + repr(psaA))
+ cb.append('psaB: ' + repr(psaB))
+ cb.append('psaC: ' + repr(psaC))
+
+ psar = vim.Function('SelfArgs', args=[{'abcArgsPSAr': 'abcArgsPSArVal'}], self={'abcSelfPSAr': 'abcSelfPSArVal'})
+ psar.args[0]['abcArgsPSAr2'] = [psar.self, psar.args[0]]
+ psar.self['rec'] = psar
+ psar.self['self'] = psar.self
+ psar.self['args'] = psar.args
+
+ try:
+ cb.append('psar: ' + repr(psar))
+ except Exception:
+ cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
+ EOF
+
+ let expected =<< trim END
+ a: <vim.Function 'Args'>
+ pa1: <vim.Function 'Args', args=['abcArgsPA1']>
+ pa2: <vim.Function 'Args'>
+ pa3: <vim.Function 'Args', args=['abcArgsPA3'], self={'abcSelfPA3': 'abcSelfPA3Val'}>
+ pa4: <vim.Function 'Args', self={'abcSelfPA4': 'abcSelfPA4Val'}>
+ sa: <vim.Function 'SelfArgs'>
+ psa1: <vim.Function 'SelfArgs', args=['abcArgsPSA1']>
+ psa2: <vim.Function 'SelfArgs'>
+ psa3: <vim.Function 'SelfArgs', args=['abcArgsPSA3'], self={'abcSelfPSA3': 'abcSelfPSA3Val'}>
+ psa4: <vim.Function 'SelfArgs', self={'abcSelfPSA4': 'abcSelfPSA4Val'}>
+ psa5: <vim.Function 'SelfArgs', self={'abcSelfPSA5': 'abcSelfPSA5Val'}>
+ psa6: <vim.Function 'SelfArgs', args=['abcArgsPSA6'], self={'abcSelfPSA6': 'abcSelfPSA6Val'}>
+ psa7: <vim.Function 'SelfArgs', args=['abcArgsPSA7']>
+ psa8: <vim.Function 'SelfArgs'>
+ psa9: <vim.Function 'SelfArgs', self={'abcSelfPSA9': 'abcSelfPSA9Val'}, auto_rebind=True>
+ psaA: <vim.Function 'SelfArgs', args=['abcArgsPSAA'], self={'abcSelfPSAA': 'abcSelfPSAAVal'}, auto_rebind=True>
+ psaB: <vim.Function 'SelfArgs', args=['abcArgsPSAB']>
+ psaC: <vim.Function 'SelfArgs'>
+ psar: <vim.Function 'SelfArgs', args=[{'abcArgsPSAr2': [{'rec': function('SelfArgs', [{...}], {...}), 'self': {...}, 'abcSelfPSAr': 'abcSelfPSArVal', 'args': [{...}]}, {...}], 'abcArgsPSAr': 'abcArgsPSArVal'}], self={'rec': function('SelfArgs', [{'abcArgsPSAr2': [{...}, {...}], 'abcArgsPSAr': 'abcArgsPSArVal'}], {...}), 'self': {...}, 'abcSelfPSAr': 'abcSelfPSArVal', 'args': [{'abcArgsPSAr2': [{...}, {...}], 'abcArgsPSAr': 'abcArgsPSArVal'}]}>
+ END
+ call assert_equal(expected, getline(2, '$'))
+ %d
+
+ call assert_equal(function('Args'), py3eval('a'))
+ call assert_equal(function('Args', ['abcArgsPA1']), py3eval('pa1'))
+ call assert_equal(function('Args'), py3eval('pa2'))
+ call assert_equal(function('Args', ['abcArgsPA3'], {'abcSelfPA3': 'abcSelfPA3Val'}), py3eval('pa3'))
+ call assert_equal(function('Args', {'abcSelfPA4': 'abcSelfPA4Val'}), py3eval('pa4'))
+ call assert_equal(function('SelfArgs'), py3eval('sa'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA1']), py3eval('psa1'))
+ call assert_equal(function('SelfArgs'), py3eval('psa2'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA3'], {'abcSelfPSA3': 'abcSelfPSA3Val'}), py3eval('psa3'))
+ call assert_equal(function('SelfArgs', {'abcSelfPSA4': 'abcSelfPSA4Val'}), py3eval('psa4'))
+ call assert_equal(function('SelfArgs', {'abcSelfPSA5': 'abcSelfPSA5Val'}), py3eval('psa5'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA6'], {'abcSelfPSA6': 'abcSelfPSA6Val'}), py3eval('psa6'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSA7']), py3eval('psa7'))
+ call assert_equal(function('SelfArgs'), py3eval('psa8'))
+ call assert_equal(function('SelfArgs', {'abcSelfPSA9': 'abcSelfPSA9Val'}), py3eval('psa9'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSAA'], {'abcSelfPSAA': 'abcSelfPSAAVal'}), py3eval('psaA'))
+ call assert_equal(function('SelfArgs', ['abcArgsPSAB']), py3eval('psaB'))
+ call assert_equal(function('SelfArgs'), py3eval('psaC'))
+
+ let res = []
+ for v in ['sa', 'psa1', 'psa2', 'psa3', 'psa4', 'psa5', 'psa6', 'psa7',
+ \ 'psa8', 'psa9', 'psaA', 'psaB', 'psaC']
+ let d = {'f': py3eval(v)}
+ call add(res, 'd.' .. v .. '(): ' .. string(d.f()))
+ endfor
+
+ let expected =<< trim END
+ d.sa(): [[], {'f': function('SelfArgs')}]
+ d.psa1(): [['abcArgsPSA1'], {'f': function('SelfArgs', ['abcArgsPSA1'])}]
+ d.psa2(): [[], {'f': function('SelfArgs')}]
+ d.psa3(): [['abcArgsPSA3'], {'abcSelfPSA3': 'abcSelfPSA3Val'}]
+ d.psa4(): [[], {'abcSelfPSA4': 'abcSelfPSA4Val'}]
+ d.psa5(): [[], {'abcSelfPSA5': 'abcSelfPSA5Val'}]
+ d.psa6(): [['abcArgsPSA6'], {'abcSelfPSA6': 'abcSelfPSA6Val'}]
+ d.psa7(): [['abcArgsPSA7'], {'f': function('SelfArgs', ['abcArgsPSA7'])}]
+ d.psa8(): [[], {'f': function('SelfArgs')}]
+ d.psa9(): [[], {'f': function('SelfArgs', {'abcSelfPSA9': 'abcSelfPSA9Val'})}]
+ d.psaA(): [['abcArgsPSAA'], {'f': function('SelfArgs', ['abcArgsPSAA'], {'abcSelfPSAA': 'abcSelfPSAAVal'})}]
+ d.psaB(): [['abcArgsPSAB'], {'f': function('SelfArgs', ['abcArgsPSAB'])}]
+ d.psaC(): [[], {'f': function('SelfArgs')}]
+ END
+ call assert_equal(expected, res)
+
+ py3 ecall('a()', a, )
+ py3 ecall('pa1()', pa1, )
+ py3 ecall('pa2()', pa2, )
+ py3 ecall('pa3()', pa3, )
+ py3 ecall('pa4()', pa4, )
+ py3 ecall('sa()', sa, )
+ py3 ecall('psa1()', psa1, )
+ py3 ecall('psa2()', psa2, )
+ py3 ecall('psa3()', psa3, )
+ py3 ecall('psa4()', psa4, )
+
+ py3 ecall('a(42, 43)', a, 42, 43)
+ py3 ecall('pa1(42, 43)', pa1, 42, 43)
+ py3 ecall('pa2(42, 43)', pa2, 42, 43)
+ py3 ecall('pa3(42, 43)', pa3, 42, 43)
+ py3 ecall('pa4(42, 43)', pa4, 42, 43)
+ py3 ecall('sa(42, 43)', sa, 42, 43)
+ py3 ecall('psa1(42, 43)', psa1, 42, 43)
+ py3 ecall('psa2(42, 43)', psa2, 42, 43)
+ py3 ecall('psa3(42, 43)', psa3, 42, 43)
+ py3 ecall('psa4(42, 43)', psa4, 42, 43)
+
+ py3 ecall('a(42, self={"20": 1})', a, 42, self={'20': 1})
+ py3 ecall('pa1(42, self={"20": 1})', pa1, 42, self={'20': 1})
+ py3 ecall('pa2(42, self={"20": 1})', pa2, 42, self={'20': 1})
+ py3 ecall('pa3(42, self={"20": 1})', pa3, 42, self={'20': 1})
+ py3 ecall('pa4(42, self={"20": 1})', pa4, 42, self={'20': 1})
+ py3 ecall('sa(42, self={"20": 1})', sa, 42, self={'20': 1})
+ py3 ecall('psa1(42, self={"20": 1})', psa1, 42, self={'20': 1})
+ py3 ecall('psa2(42, self={"20": 1})', psa2, 42, self={'20': 1})
+ py3 ecall('psa3(42, self={"20": 1})', psa3, 42, self={'20': 1})
+ py3 ecall('psa4(42, self={"20": 1})', psa4, 42, self={'20': 1})
+
+ py3 ecall('a(self={"20": 1})', a, self={'20': 1})
+ py3 ecall('pa1(self={"20": 1})', pa1, self={'20': 1})
+ py3 ecall('pa2(self={"20": 1})', pa2, self={'20': 1})
+ py3 ecall('pa3(self={"20": 1})', pa3, self={'20': 1})
+ py3 ecall('pa4(self={"20": 1})', pa4, self={'20': 1})
+ py3 ecall('sa(self={"20": 1})', sa, self={'20': 1})
+ py3 ecall('psa1(self={"20": 1})', psa1, self={'20': 1})
+ py3 ecall('psa2(self={"20": 1})', psa2, self={'20': 1})
+ py3 ecall('psa3(self={"20": 1})', psa3, self={'20': 1})
+ py3 ecall('psa4(self={"20": 1})', psa4, self={'20': 1})
+
+ py3 << trim EOF
+ def s(v):
+ if v is None:
+ return repr(v)
+ else:
+ return str(vim.Function('string')(v), 'utf-8')
+
+ cb.append('a.args: ' + s(a.args))
+ cb.append('pa1.args: ' + s(pa1.args))
+ cb.append('pa2.args: ' + s(pa2.args))
+ cb.append('pa3.args: ' + s(pa3.args))
+ cb.append('pa4.args: ' + s(pa4.args))
+ cb.append('sa.args: ' + s(sa.args))
+ cb.append('psa1.args: ' + s(psa1.args))
+ cb.append('psa2.args: ' + s(psa2.args))
+ cb.append('psa3.args: ' + s(psa3.args))
+ cb.append('psa4.args: ' + s(psa4.args))
+
+ cb.append('a.self: ' + s(a.self))
+ cb.append('pa1.self: ' + s(pa1.self))
+ cb.append('pa2.self: ' + s(pa2.self))
+ cb.append('pa3.self: ' + s(pa3.self))
+ cb.append('pa4.self: ' + s(pa4.self))
+ cb.append('sa.self: ' + s(sa.self))
+ cb.append('psa1.self: ' + s(psa1.self))
+ cb.append('psa2.self: ' + s(psa2.self))
+ cb.append('psa3.self: ' + s(psa3.self))
+ cb.append('psa4.self: ' + s(psa4.self))
+
+ cb.append('a.name: ' + s(a.name))
+ cb.append('pa1.name: ' + s(pa1.name))
+ cb.append('pa2.name: ' + s(pa2.name))
+ cb.append('pa3.name: ' + s(pa3.name))
+ cb.append('pa4.name: ' + s(pa4.name))
+ cb.append('sa.name: ' + s(sa.name))
+ cb.append('psa1.name: ' + s(psa1.name))
+ cb.append('psa2.name: ' + s(psa2.name))
+ cb.append('psa3.name: ' + s(psa3.name))
+ cb.append('psa4.name: ' + s(psa4.name))
+
+ cb.append('a.auto_rebind: ' + s(a.auto_rebind))
+ cb.append('pa1.auto_rebind: ' + s(pa1.auto_rebind))
+ cb.append('pa2.auto_rebind: ' + s(pa2.auto_rebind))
+ cb.append('pa3.auto_rebind: ' + s(pa3.auto_rebind))
+ cb.append('pa4.auto_rebind: ' + s(pa4.auto_rebind))
+ cb.append('sa.auto_rebind: ' + s(sa.auto_rebind))
+ cb.append('psa1.auto_rebind: ' + s(psa1.auto_rebind))
+ cb.append('psa2.auto_rebind: ' + s(psa2.auto_rebind))
+ cb.append('psa3.auto_rebind: ' + s(psa3.auto_rebind))
+ cb.append('psa4.auto_rebind: ' + s(psa4.auto_rebind))
+ cb.append('psa5.auto_rebind: ' + s(psa5.auto_rebind))
+ cb.append('psa6.auto_rebind: ' + s(psa6.auto_rebind))
+ cb.append('psa7.auto_rebind: ' + s(psa7.auto_rebind))
+ cb.append('psa8.auto_rebind: ' + s(psa8.auto_rebind))
+ cb.append('psa9.auto_rebind: ' + s(psa9.auto_rebind))
+ cb.append('psaA.auto_rebind: ' + s(psaA.auto_rebind))
+ cb.append('psaB.auto_rebind: ' + s(psaB.auto_rebind))
+ cb.append('psaC.auto_rebind: ' + s(psaC.auto_rebind))
+
+ del s
+
+ del a
+ del pa1
+ del pa2
+ del pa3
+ del pa4
+ del sa
+ del psa1
+ del psa2
+ del psa3
+ del psa4
+ del psa5
+ del psa6
+ del psa7
+ del psa8
+ del psa9
+ del psaA
+ del psaB
+ del psaC
+ del psar
+
+ del ecall
+ EOF
+
+ let expected =<< trim END
+ a(): !result: []
+ pa1(): !result: ['abcArgsPA1']
+ pa2(): !result: []
+ pa3(): !result: ['abcArgsPA3']
+ pa4(): !result: []
+ sa(): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa1(): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa2(): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa3(): !result: [['abcArgsPSA3'], {'abcSelfPSA3': 'abcSelfPSA3Val'}]
+ psa4(): !result: [[], {'abcSelfPSA4': 'abcSelfPSA4Val'}]
+ a(42, 43): !result: [42, 43]
+ pa1(42, 43): !result: ['abcArgsPA1', 42, 43]
+ pa2(42, 43): !result: [42, 43]
+ pa3(42, 43): !result: ['abcArgsPA3', 42, 43]
+ pa4(42, 43): !result: [42, 43]
+ sa(42, 43): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa1(42, 43): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa2(42, 43): !exception: error:('Vim:E725: Calling dict function without Dictionary: SelfArgs',)
+ psa3(42, 43): !result: [['abcArgsPSA3', 42, 43], {'abcSelfPSA3': 'abcSelfPSA3Val'}]
+ psa4(42, 43): !result: [[42, 43], {'abcSelfPSA4': 'abcSelfPSA4Val'}]
+ a(42, self={"20": 1}): !result: [42]
+ pa1(42, self={"20": 1}): !result: ['abcArgsPA1', 42]
+ pa2(42, self={"20": 1}): !result: [42]
+ pa3(42, self={"20": 1}): !result: ['abcArgsPA3', 42]
+ pa4(42, self={"20": 1}): !result: [42]
+ sa(42, self={"20": 1}): !result: [[42], {'20': 1}]
+ psa1(42, self={"20": 1}): !result: [['abcArgsPSA1', 42], {'20': 1}]
+ psa2(42, self={"20": 1}): !result: [[42], {'20': 1}]
+ psa3(42, self={"20": 1}): !result: [['abcArgsPSA3', 42], {'20': 1}]
+ psa4(42, self={"20": 1}): !result: [[42], {'20': 1}]
+ a(self={"20": 1}): !result: []
+ pa1(self={"20": 1}): !result: ['abcArgsPA1']
+ pa2(self={"20": 1}): !result: []
+ pa3(self={"20": 1}): !result: ['abcArgsPA3']
+ pa4(self={"20": 1}): !result: []
+ sa(self={"20": 1}): !result: [[], {'20': 1}]
+ psa1(self={"20": 1}): !result: [['abcArgsPSA1'], {'20': 1}]
+ psa2(self={"20": 1}): !result: [[], {'20': 1}]
+ psa3(self={"20": 1}): !result: [['abcArgsPSA3'], {'20': 1}]
+ psa4(self={"20": 1}): !result: [[], {'20': 1}]
+ a.args: None
+ pa1.args: ['abcArgsPA1']
+ pa2.args: None
+ pa3.args: ['abcArgsPA3']
+ pa4.args: None
+ sa.args: None
+ psa1.args: ['abcArgsPSA1']
+ psa2.args: None
+ psa3.args: ['abcArgsPSA3']
+ psa4.args: None
+ a.self: None
+ pa1.self: None
+ pa2.self: None
+ pa3.self: {'abcSelfPA3': 'abcSelfPA3Val'}
+ pa4.self: {'abcSelfPA4': 'abcSelfPA4Val'}
+ sa.self: None
+ psa1.self: None
+ psa2.self: None
+ psa3.self: {'abcSelfPSA3': 'abcSelfPSA3Val'}
+ psa4.self: {'abcSelfPSA4': 'abcSelfPSA4Val'}
+ a.name: 'Args'
+ pa1.name: 'Args'
+ pa2.name: 'Args'
+ pa3.name: 'Args'
+ pa4.name: 'Args'
+ sa.name: 'SelfArgs'
+ psa1.name: 'SelfArgs'
+ psa2.name: 'SelfArgs'
+ psa3.name: 'SelfArgs'
+ psa4.name: 'SelfArgs'
+ a.auto_rebind: 1
+ pa1.auto_rebind: 1
+ pa2.auto_rebind: 1
+ pa3.auto_rebind: 0
+ pa4.auto_rebind: 0
+ sa.auto_rebind: 1
+ psa1.auto_rebind: 1
+ psa2.auto_rebind: 1
+ psa3.auto_rebind: 0
+ psa4.auto_rebind: 0
+ psa5.auto_rebind: 0
+ psa6.auto_rebind: 0
+ psa7.auto_rebind: 1
+ psa8.auto_rebind: 1
+ psa9.auto_rebind: 1
+ psaA.auto_rebind: 1
+ psaB.auto_rebind: 1
+ psaC.auto_rebind: 1
+ END
+ call assert_equal(expected, getline(2, '$'))
+ %bw!
+endfunc
+
+" Test stdout/stderr
+func Test_python3_stdin_stderr()
+ let caught_writeerr = 0
+ let caught_writelineerr = 0
+ redir => messages
+ py3 sys.stdout.write('abc8') ; sys.stdout.write('def')
+ try
+ py3 sys.stderr.write('abc9') ; sys.stderr.write('def')
+ catch /abc9def/
+ let caught_writeerr = 1
+ endtry
+ py3 sys.stdout.writelines(iter('abcA'))
+ try
+ py3 sys.stderr.writelines(iter('abcB'))
+ catch /abcB/
+ let caught_writelineerr = 1
+ endtry
+ redir END
+ call assert_equal("\nabc8def\nabcA", messages)
+ call assert_equal(1, caught_writeerr)
+ call assert_equal(1, caught_writelineerr)
+endfunc
+
+" Test subclassing
+func Test_python3_subclass()
+ new
+ func Put(...)
+ return a:000
+ endfunc
+
+ py3 << trim EOF
+ class DupDict(vim.Dictionary):
+ def __setitem__(self, key, value):
+ super(DupDict, self).__setitem__(key, value)
+ super(DupDict, self).__setitem__('dup_' + key, value)
+ dd = DupDict()
+ dd['a'] = 'b'
+
+ class DupList(vim.List):
+ def __getitem__(self, idx):
+ return [super(DupList, self).__getitem__(idx)] * 2
+
+ dl = DupList()
+ dl2 = DupList(iter('abcC'))
+ dl.extend(dl2[0])
+
+ class DupFun(vim.Function):
+ def __call__(self, arg):
+ return super(DupFun, self).__call__(arg, arg)
+
+ df = DupFun('Put')
+ EOF
+
+ call assert_equal(['a', 'dup_a'], sort(keys(py3eval('dd'))))
+ call assert_equal(['a', 'a'], py3eval('dl'))
+ call assert_equal(['a', 'b', 'c', 'C'], py3eval('dl2'))
+ call assert_equal([2, 2], py3eval('df(2)'))
+ call assert_equal(1, py3eval('dl') is# py3eval('dl'))
+ call assert_equal(1, py3eval('dd') is# py3eval('dd'))
+ call assert_equal(function('Put'), py3eval('df'))
+ delfunction Put
+ py3 << trim EOF
+ del DupDict
+ del DupList
+ del DupFun
+ del dd
+ del dl
+ del dl2
+ del df
+ EOF
+ close!
+endfunc
+
+" Test chdir
+func Test_python3_chdir()
+ new Xp3cdfile
+ py3 cb = vim.current.buffer
+ py3 << trim EOF
+ import os
+ fnamemodify = vim.Function('fnamemodify')
+ cb.append(str(fnamemodify('.', ':p:h:t')))
+ cb.append(vim.eval('@%'))
+ os.chdir('..')
+ path = fnamemodify('.', ':p:h:t')
+ if path != b'src' and path != b'src2':
+ # Running tests from a shadow directory, so move up another level
+ # This will result in @% looking like shadow/testdir/Xp3cdfile, hence the
+ # slicing to remove the leading path and path separator
+ os.chdir('..')
+ cb.append(str(fnamemodify('.', ':p:h:t')))
+ cb.append(vim.eval('@%')[len(path)+1:].replace(os.path.sep, '/'))
+ os.chdir(path)
+ else:
+ # Also accept running from src2/testdir/ for MS-Windows CI.
+ cb.append(str(fnamemodify('.', ':p:h:t').replace(b'src2', b'src')))
+ cb.append(vim.eval('@%').replace(os.path.sep, '/'))
+ del path
+ os.chdir('testdir')
+ cb.append(str(fnamemodify('.', ':p:h:t')))
+ cb.append(vim.eval('@%'))
+ del fnamemodify
+ EOF
+ call assert_equal(["b'testdir'", 'Xp3cdfile', "b'src'", 'testdir/Xp3cdfile',
+ \"b'testdir'", 'Xp3cdfile'], getline(2, '$'))
+ close!
+ call AssertException(["py3 vim.chdir(None)"], "Vim(py3):TypeError:")
+endfunc
+
+" Test errors
+func Test_python3_errors()
+ func F() dict
+ endfunc
+
+ func D()
+ endfunc
+
+ new
+ py3 cb = vim.current.buffer
+
+ py3 << trim EOF
+ import os
+ d = vim.Dictionary()
+ ned = vim.Dictionary(foo='bar', baz='abcD')
+ dl = vim.Dictionary(a=1)
+ dl.locked = True
+ l = vim.List()
+ ll = vim.List('abcE')
+ ll.locked = True
+ nel = vim.List('abcO')
+ f = vim.Function('string')
+ fd = vim.Function('F')
+ fdel = vim.Function('D')
+ vim.command('delfunction D')
+
+ def subexpr_test(expr, name, subexprs):
+ cb.append('>>> Testing %s using %s' % (name, expr))
+ for subexpr in subexprs:
+ ee(expr % subexpr)
+ cb.append('<<< Finished')
+
+ def stringtochars_test(expr):
+ return subexpr_test(expr, 'StringToChars', (
+ '1', # Fail type checks
+ 'b"\\0"', # Fail PyString_AsStringAndSize(object, , NULL) check
+ '"\\0"', # Fail PyString_AsStringAndSize(bytes, , NULL) check
+ ))
+
+ class Mapping(object):
+ def __init__(self, d):
+ self.d = d
+
+ def __getitem__(self, key):
+ return self.d[key]
+
+ def keys(self):
+ return self.d.keys()
+
+ def items(self):
+ return self.d.items()
+
+ def convertfrompyobject_test(expr, recurse=True):
+ # pydict_to_tv
+ stringtochars_test(expr % '{%s : 1}')
+ if recurse:
+ convertfrompyobject_test(expr % '{"abcF" : %s}', False)
+ # pymap_to_tv
+ stringtochars_test(expr % 'Mapping({%s : 1})')
+ if recurse:
+ convertfrompyobject_test(expr % 'Mapping({"abcG" : %s})', False)
+ # pyseq_to_tv
+ iter_test(expr)
+ return subexpr_test(expr, 'ConvertFromPyObject', (
+ 'None', # Not conversible
+ '{b"": 1}', # Empty key not allowed
+ '{"": 1}', # Same, but with unicode object
+ 'FailingMapping()', #
+ 'FailingMappingKey()', #
+ 'FailingNumber()', #
+ ))
+
+ def convertfrompymapping_test(expr):
+ convertfrompyobject_test(expr)
+ return subexpr_test(expr, 'ConvertFromPyMapping', (
+ '[]',
+ ))
+
+ def iter_test(expr):
+ return subexpr_test(expr, '*Iter*', (
+ 'FailingIter()',
+ 'FailingIterNext()',
+ ))
+
+ def number_test(expr, natural=False, unsigned=False):
+ if natural:
+ unsigned = True
+ return subexpr_test(expr, 'NumberToLong', (
+ '[]',
+ 'None',
+ ) + (('-1',) if unsigned else ())
+ + (('0',) if natural else ()))
+
+ class FailingTrue(object):
+ def __bool__(self):
+ raise NotImplementedError('bool')
+
+ class FailingIter(object):
+ def __iter__(self):
+ raise NotImplementedError('iter')
+
+ class FailingIterNext(object):
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ raise NotImplementedError('next')
+
+ class FailingIterNextN(object):
+ def __init__(self, n):
+ self.n = n
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.n:
+ self.n -= 1
+ return 1
+ else:
+ raise NotImplementedError('next N')
+
+ class FailingMappingKey(object):
+ def __getitem__(self, item):
+ raise NotImplementedError('getitem:mappingkey')
+
+ def keys(self):
+ return list("abcH")
+
+ class FailingMapping(object):
+ def __getitem__(self):
+ raise NotImplementedError('getitem:mapping')
+
+ def keys(self):
+ raise NotImplementedError('keys')
+
+ class FailingList(list):
+ def __getitem__(self, idx):
+ if i == 2:
+ raise NotImplementedError('getitem:list')
+ else:
+ return super(FailingList, self).__getitem__(idx)
+
+ class NoArgsCall(object):
+ def __call__(self):
+ pass
+
+ class FailingCall(object):
+ def __call__(self, path):
+ raise NotImplementedError('call')
+
+ class FailingNumber(object):
+ def __int__(self):
+ raise NotImplementedError('int')
+
+ cb.append("> Output")
+ cb.append(">> OutputSetattr")
+ ee('del sys.stdout.softspace')
+ number_test('sys.stdout.softspace = %s', unsigned=True)
+ number_test('sys.stderr.softspace = %s', unsigned=True)
+ ee('assert sys.stdout.isatty()==False')
+ ee('assert sys.stdout.seekable()==False')
+ ee('sys.stdout.close()')
+ ee('sys.stdout.flush()')
+ ee('assert sys.stderr.isatty()==False')
+ ee('assert sys.stderr.seekable()==False')
+ ee('sys.stderr.close()')
+ ee('sys.stderr.flush()')
+ ee('sys.stdout.attr = None')
+ cb.append(">> OutputWrite")
+ ee('assert sys.stdout.writable()==True')
+ ee('assert sys.stdout.readable()==False')
+ ee('assert sys.stderr.writable()==True')
+ ee('assert sys.stderr.readable()==False')
+ ee('assert sys.stdout.closed()==False')
+ ee('assert sys.stderr.closed()==False')
+ ee('assert sys.stdout.errors=="strict"')
+ ee('assert sys.stderr.errors=="strict"')
+ ee('assert sys.stdout.encoding==sys.stderr.encoding')
+ ee('sys.stdout.write(None)')
+ cb.append(">> OutputWriteLines")
+ ee('sys.stdout.writelines(None)')
+ ee('sys.stdout.writelines([1])')
+ iter_test('sys.stdout.writelines(%s)')
+ cb.append("> VimCommand")
+ stringtochars_test('vim.command(%s)')
+ ee('vim.command("", 2)')
+ #! Not checked: vim->python exceptions translating: checked later
+ cb.append("> VimToPython")
+ #! Not checked: everything: needs errors in internal python functions
+ cb.append("> VimEval")
+ stringtochars_test('vim.eval(%s)')
+ ee('vim.eval("", FailingTrue())')
+ #! Not checked: everything: needs errors in internal python functions
+ cb.append("> VimEvalPy")
+ stringtochars_test('vim.bindeval(%s)')
+ ee('vim.eval("", 2)')
+ #! Not checked: vim->python exceptions translating: checked later
+ cb.append("> VimStrwidth")
+ stringtochars_test('vim.strwidth(%s)')
+ cb.append("> VimForeachRTP")
+ ee('vim.foreach_rtp(None)')
+ ee('vim.foreach_rtp(NoArgsCall())')
+ ee('vim.foreach_rtp(FailingCall())')
+ ee('vim.foreach_rtp(int, 2)')
+ cb.append('> import')
+ old_rtp = vim.options['rtp']
+ vim.options['rtp'] = os.getcwd().replace('\\', '\\\\').replace(',', '\\,')
+ ee('import xxx_no_such_module_xxx')
+ ee('import failing_import')
+ ee('import failing')
+ vim.options['rtp'] = old_rtp
+ del old_rtp
+ cb.append("> Options")
+ cb.append(">> OptionsItem")
+ ee('vim.options["abcQ"]')
+ ee('vim.options[""]')
+ stringtochars_test('vim.options[%s]')
+ cb.append(">> OptionsContains")
+ stringtochars_test('%s in vim.options')
+ cb.append("> Dictionary")
+ cb.append(">> DictionaryConstructor")
+ ee('vim.Dictionary("abcI")')
+ ##! Not checked: py_dict_alloc failure
+ cb.append(">> DictionarySetattr")
+ ee('del d.locked')
+ ee('d.locked = FailingTrue()')
+ ee('vim.vvars.locked = False')
+ ee('d.scope = True')
+ ee('d.xxx = True')
+ cb.append(">> _DictionaryItem")
+ ee('d.get("a", 2, 3)')
+ stringtochars_test('d.get(%s)')
+ ee('d.pop("a")')
+ ee('dl.pop("a")')
+ cb.append(">> DictionaryContains")
+ ee('"" in d')
+ ee('0 in d')
+ cb.append(">> DictionaryIterNext")
+ ee('for i in ned: ned["a"] = 1')
+ del i
+ cb.append(">> DictionaryAssItem")
+ ee('dl["b"] = 1')
+ stringtochars_test('d[%s] = 1')
+ convertfrompyobject_test('d["a"] = %s')
+ cb.append(">> DictionaryUpdate")
+ cb.append(">>> kwargs")
+ cb.append(">>> iter")
+ ee('d.update(FailingMapping())')
+ ee('d.update([FailingIterNext()])')
+ ee('d.update([FailingIterNextN(1)])')
+ iter_test('d.update(%s)')
+ convertfrompyobject_test('d.update(%s)')
+ stringtochars_test('d.update(((%s, 0),))')
+ convertfrompyobject_test('d.update((("a", %s),))')
+ cb.append(">> DictionaryPopItem")
+ ee('d.popitem(1, 2)')
+ cb.append(">> DictionaryHasKey")
+ ee('d.has_key()')
+ cb.append("> List")
+ cb.append(">> ListConstructor")
+ ee('vim.List(1, 2)')
+ ee('vim.List(a=1)')
+ iter_test('vim.List(%s)')
+ convertfrompyobject_test('vim.List([%s])')
+ cb.append(">> ListItem")
+ ee('l[1000]')
+ cb.append(">> ListAssItem")
+ ee('ll[1] = 2')
+ ee('l[1000] = 3')
+ cb.append(">> ListAssSlice")
+ ee('ll[1:100] = "abcJ"')
+ iter_test('l[:] = %s')
+ ee('nel[1:10:2] = "abcK"')
+ cb.append(repr(tuple(nel)))
+ ee('nel[1:10:2] = "a"')
+ cb.append(repr(tuple(nel)))
+ ee('nel[1:1:-1] = "a"')
+ cb.append(repr(tuple(nel)))
+ ee('nel[:] = FailingIterNextN(2)')
+ cb.append(repr(tuple(nel)))
+ convertfrompyobject_test('l[:] = [%s]')
+ cb.append(">> ListConcatInPlace")
+ iter_test('l.extend(%s)')
+ convertfrompyobject_test('l.extend([%s])')
+ cb.append(">> ListSetattr")
+ ee('del l.locked')
+ ee('l.locked = FailingTrue()')
+ ee('l.xxx = True')
+ cb.append("> Function")
+ cb.append(">> FunctionConstructor")
+ cb.append(">>> FunctionConstructor")
+ ee('vim.Function("123")')
+ ee('vim.Function("xxx_non_existent_function_xxx")')
+ ee('vim.Function("xxx#non#existent#function#xxx")')
+ ee('vim.Function("xxx_non_existent_function_xxx2", args=[])')
+ ee('vim.Function("xxx_non_existent_function_xxx3", self={})')
+ ee('vim.Function("xxx_non_existent_function_xxx4", args=[], self={})')
+ cb.append(">>> FunctionNew")
+ ee('vim.Function("tr", self="abcFuncSelf")')
+ ee('vim.Function("tr", args=427423)')
+ ee('vim.Function("tr", self="abcFuncSelf2", args="abcFuncArgs2")')
+ ee('vim.Function(self="abcFuncSelf2", args="abcFuncArgs2")')
+ ee('vim.Function("tr", "", self="abcFuncSelf2", args="abcFuncArgs2")')
+ ee('vim.Function("tr", "")')
+ cb.append(">> FunctionCall")
+ convertfrompyobject_test('f(%s)')
+ convertfrompymapping_test('fd(self=%s)')
+ cb.append("> TabPage")
+ cb.append(">> TabPageAttr")
+ ee('vim.current.tabpage.xxx')
+ cb.append("> TabList")
+ cb.append(">> TabListItem")
+ ee('vim.tabpages[1000]')
+ cb.append("> Window")
+ cb.append(">> WindowAttr")
+ ee('vim.current.window.xxx')
+ cb.append(">> WindowSetattr")
+ ee('vim.current.window.buffer = 0')
+ ee('vim.current.window.cursor = (100000000, 100000000)')
+ ee('vim.current.window.cursor = True')
+ number_test('vim.current.window.height = %s', unsigned=True)
+ number_test('vim.current.window.width = %s', unsigned=True)
+ ee('vim.current.window.xxxxxx = True')
+ cb.append("> WinList")
+ cb.append(">> WinListItem")
+ ee('vim.windows[1000]')
+ cb.append("> Buffer")
+ cb.append(">> StringToLine (indirect)")
+ ee('vim.current.buffer[0] = "\\na"')
+ ee('vim.current.buffer[0] = b"\\na"')
+ cb.append(">> SetBufferLine (indirect)")
+ ee('vim.current.buffer[0] = True')
+ cb.append(">> SetBufferLineList (indirect)")
+ ee('vim.current.buffer[:] = True')
+ ee('vim.current.buffer[:] = ["\\na", "bc"]')
+ cb.append(">> InsertBufferLines (indirect)")
+ ee('vim.current.buffer.append(None)')
+ ee('vim.current.buffer.append(["\\na", "bc"])')
+ ee('vim.current.buffer.append("\\nbc")')
+ cb.append(">> RBItem")
+ ee('vim.current.buffer[100000000]')
+ cb.append(">> RBAsItem")
+ ee('vim.current.buffer[100000000] = ""')
+ cb.append(">> BufferAttr")
+ ee('vim.current.buffer.xxx')
+ cb.append(">> BufferSetattr")
+ ee('vim.current.buffer.name = True')
+ ee('vim.current.buffer.xxx = True')
+ cb.append(">> BufferMark")
+ ee('vim.current.buffer.mark(0)')
+ ee('vim.current.buffer.mark("abcM")')
+ ee('vim.current.buffer.mark("!")')
+ cb.append(">> BufferRange")
+ ee('vim.current.buffer.range(1, 2, 3)')
+ cb.append("> BufMap")
+ cb.append(">> BufMapItem")
+ ee('vim.buffers[100000000]')
+ number_test('vim.buffers[%s]', natural=True)
+ cb.append("> Current")
+ cb.append(">> CurrentGetattr")
+ ee('vim.current.xxx')
+ cb.append(">> CurrentSetattr")
+ ee('vim.current.line = True')
+ ee('vim.current.buffer = True')
+ ee('vim.current.window = True')
+ ee('vim.current.tabpage = True')
+ ee('vim.current.xxx = True')
+ del d
+ del ned
+ del dl
+ del l
+ del ll
+ del nel
+ del f
+ del fd
+ del fdel
+ del subexpr_test
+ del stringtochars_test
+ del Mapping
+ del convertfrompyobject_test
+ del convertfrompymapping_test
+ del iter_test
+ del number_test
+ del FailingTrue
+ del FailingIter
+ del FailingIterNext
+ del FailingIterNextN
+ del FailingMapping
+ del FailingMappingKey
+ del FailingList
+ del NoArgsCall
+ del FailingCall
+ del FailingNumber
+ EOF
+ delfunction F
+
+ let expected =<< trim END
+ > Output
+ >> OutputSetattr
+ del sys.stdout.softspace:(<class 'AttributeError'>, AttributeError('cannot delete OutputObject attributes',))
+ >>> Testing NumberToLong using sys.stdout.softspace = %s
+ sys.stdout.softspace = []:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got list',))
+ sys.stdout.softspace = None:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got NoneType',))
+ sys.stdout.softspace = -1:(<class 'ValueError'>, ValueError('number must be greater or equal to zero',))
+ <<< Finished
+ >>> Testing NumberToLong using sys.stderr.softspace = %s
+ sys.stderr.softspace = []:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got list',))
+ sys.stderr.softspace = None:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got NoneType',))
+ sys.stderr.softspace = -1:(<class 'ValueError'>, ValueError('number must be greater or equal to zero',))
+ <<< Finished
+ assert sys.stdout.isatty()==False:NOT FAILED
+ assert sys.stdout.seekable()==False:NOT FAILED
+ sys.stdout.close():NOT FAILED
+ sys.stdout.flush():NOT FAILED
+ assert sys.stderr.isatty()==False:NOT FAILED
+ assert sys.stderr.seekable()==False:NOT FAILED
+ sys.stderr.close():NOT FAILED
+ sys.stderr.flush():NOT FAILED
+ sys.stdout.attr = None:(<class 'AttributeError'>, AttributeError('invalid attribute: attr',))
+ >> OutputWrite
+ assert sys.stdout.writable()==True:NOT FAILED
+ assert sys.stdout.readable()==False:NOT FAILED
+ assert sys.stderr.writable()==True:NOT FAILED
+ assert sys.stderr.readable()==False:NOT FAILED
+ assert sys.stdout.closed()==False:NOT FAILED
+ assert sys.stderr.closed()==False:NOT FAILED
+ assert sys.stdout.errors=="strict":NOT FAILED
+ assert sys.stderr.errors=="strict":NOT FAILED
+ assert sys.stdout.encoding==sys.stderr.encoding:NOT FAILED
+ sys.stdout.write(None):(<class 'TypeError'>, TypeError("Can't convert 'NoneType' object to str implicitly",))
+ >> OutputWriteLines
+ sys.stdout.writelines(None):(<class 'TypeError'>, TypeError("'NoneType' object is not iterable",))
+ sys.stdout.writelines([1]):(<class 'TypeError'>, TypeError("Can't convert 'int' object to str implicitly",))
+ >>> Testing *Iter* using sys.stdout.writelines(%s)
+ sys.stdout.writelines(FailingIter()):(<class 'NotImplementedError'>, NotImplementedError('iter',))
+ sys.stdout.writelines(FailingIterNext()):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ > VimCommand
+ >>> Testing StringToChars using vim.command(%s)
+ vim.command(1):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.command(b"\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.command("\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ vim.command("", 2):(<class 'TypeError'>, TypeError('command() takes exactly one argument (2 given)',))
+ > VimToPython
+ > VimEval
+ >>> Testing StringToChars using vim.eval(%s)
+ vim.eval(1):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.eval(b"\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.eval("\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ vim.eval("", FailingTrue()):(<class 'TypeError'>, TypeError('function takes exactly 1 argument (2 given)',))
+ > VimEvalPy
+ >>> Testing StringToChars using vim.bindeval(%s)
+ vim.bindeval(1):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.bindeval(b"\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.bindeval("\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ vim.eval("", 2):(<class 'TypeError'>, TypeError('function takes exactly 1 argument (2 given)',))
+ > VimStrwidth
+ >>> Testing StringToChars using vim.strwidth(%s)
+ vim.strwidth(1):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.strwidth(b"\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.strwidth("\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ > VimForeachRTP
+ vim.foreach_rtp(None):(<class 'TypeError'>, TypeError("'NoneType' object is not callable",))
+ vim.foreach_rtp(NoArgsCall()):(<class 'TypeError'>, TypeError('__call__() takes exactly 1 positional argument (2 given)',))
+ vim.foreach_rtp(FailingCall()):(<class 'NotImplementedError'>, NotImplementedError('call',))
+ vim.foreach_rtp(int, 2):(<class 'TypeError'>, TypeError('foreach_rtp() takes exactly one argument (2 given)',))
+ > import
+ import xxx_no_such_module_xxx:(<class 'ImportError'>, ImportError('No module named xxx_no_such_module_xxx',))
+ import failing_import:(<class 'ImportError'>, ImportError())
+ import failing:(<class 'NotImplementedError'>, NotImplementedError())
+ > Options
+ >> OptionsItem
+ vim.options["abcQ"]:(<class 'KeyError'>, KeyError('abcQ',))
+ vim.options[""]:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ >>> Testing StringToChars using vim.options[%s]
+ vim.options[1]:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.options[b"\0"]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.options["\0"]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >> OptionsContains
+ >>> Testing StringToChars using %s in vim.options
+ 1 in vim.options:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ b"\0" in vim.options:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ "\0" in vim.options:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ > Dictionary
+ >> DictionaryConstructor
+ vim.Dictionary("abcI"):(<class 'ValueError'>, ValueError('expected sequence element of size 2, but got sequence of size 1',))
+ >> DictionarySetattr
+ del d.locked:(<class 'AttributeError'>, AttributeError('cannot delete vim.Dictionary attributes',))
+ d.locked = FailingTrue():(<class 'NotImplementedError'>, NotImplementedError('bool',))
+ vim.vvars.locked = False:(<class 'TypeError'>, TypeError('cannot modify fixed dictionary',))
+ d.scope = True:(<class 'AttributeError'>, AttributeError('cannot set attribute scope',))
+ d.xxx = True:(<class 'AttributeError'>, AttributeError('cannot set attribute xxx',))
+ >> _DictionaryItem
+ d.get("a", 2, 3):(<class 'TypeError'>, TypeError('function takes at most 2 arguments (3 given)',))
+ >>> Testing StringToChars using d.get(%s)
+ d.get(1):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.get(b"\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.get("\0"):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ d.pop("a"):(<class 'KeyError'>, KeyError('a',))
+ dl.pop("a"):(<class 'vim.error'>, error('dictionary is locked',))
+ >> DictionaryContains
+ "" in d:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ 0 in d:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ >> DictionaryIterNext
+ for i in ned: ned["a"] = 1:(<class 'RuntimeError'>, RuntimeError('hashtab changed during iteration',))
+ >> DictionaryAssItem
+ dl["b"] = 1:(<class 'vim.error'>, error('dictionary is locked',))
+ >>> Testing StringToChars using d[%s] = 1
+ d[1] = 1:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d[b"\0"] = 1:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d["\0"] = 1:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = {%s : 1}
+ d["a"] = {1 : 1}:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d["a"] = {b"\0" : 1}:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d["a"] = {"\0" : 1}:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = {"abcF" : {%s : 1}}
+ d["a"] = {"abcF" : {1 : 1}}:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d["a"] = {"abcF" : {b"\0" : 1}}:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d["a"] = {"abcF" : {"\0" : 1}}:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = {"abcF" : Mapping({%s : 1})}
+ d["a"] = {"abcF" : Mapping({1 : 1})}:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d["a"] = {"abcF" : Mapping({b"\0" : 1})}:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d["a"] = {"abcF" : Mapping({"\0" : 1})}:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using d["a"] = {"abcF" : %s}
+ d["a"] = {"abcF" : FailingIter()}:(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d["a"] = {"abcF" : FailingIterNext()}:(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d["a"] = {"abcF" : %s}
+ d["a"] = {"abcF" : None}:NOT FAILED
+ d["a"] = {"abcF" : {b"": 1}}:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d["a"] = {"abcF" : {"": 1}}:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d["a"] = {"abcF" : FailingMapping()}:(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d["a"] = {"abcF" : FailingMappingKey()}:(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d["a"] = {"abcF" : FailingNumber()}:(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = Mapping({%s : 1})
+ d["a"] = Mapping({1 : 1}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d["a"] = Mapping({b"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d["a"] = Mapping({"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = Mapping({"abcG" : {%s : 1}})
+ d["a"] = Mapping({"abcG" : {1 : 1}}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d["a"] = Mapping({"abcG" : {b"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d["a"] = Mapping({"abcG" : {"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d["a"] = Mapping({"abcG" : Mapping({%s : 1})})
+ d["a"] = Mapping({"abcG" : Mapping({1 : 1})}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d["a"] = Mapping({"abcG" : Mapping({b"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d["a"] = Mapping({"abcG" : Mapping({"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using d["a"] = Mapping({"abcG" : %s})
+ d["a"] = Mapping({"abcG" : FailingIter()}):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d["a"] = Mapping({"abcG" : FailingIterNext()}):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d["a"] = Mapping({"abcG" : %s})
+ d["a"] = Mapping({"abcG" : None}):NOT FAILED
+ d["a"] = Mapping({"abcG" : {b"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d["a"] = Mapping({"abcG" : {"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d["a"] = Mapping({"abcG" : FailingMapping()}):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d["a"] = Mapping({"abcG" : FailingMappingKey()}):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d["a"] = Mapping({"abcG" : FailingNumber()}):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using d["a"] = %s
+ d["a"] = FailingIter():(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d["a"] = FailingIterNext():(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d["a"] = %s
+ d["a"] = None:NOT FAILED
+ d["a"] = {b"": 1}:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d["a"] = {"": 1}:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d["a"] = FailingMapping():(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d["a"] = FailingMappingKey():(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d["a"] = FailingNumber():(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >> DictionaryUpdate
+ >>> kwargs
+ >>> iter
+ d.update(FailingMapping()):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d.update([FailingIterNext()]):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ d.update([FailingIterNextN(1)]):(<class 'NotImplementedError'>, NotImplementedError('next N',))
+ >>> Testing *Iter* using d.update(%s)
+ d.update(FailingIter()):(<class 'NotImplementedError'>, NotImplementedError('iter',))
+ d.update(FailingIterNext()):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing StringToChars using d.update({%s : 1})
+ d.update({1 : 1}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update({b"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update({"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update({"abcF" : {%s : 1}})
+ d.update({"abcF" : {1 : 1}}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update({"abcF" : {b"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update({"abcF" : {"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update({"abcF" : Mapping({%s : 1})})
+ d.update({"abcF" : Mapping({1 : 1})}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update({"abcF" : Mapping({b"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update({"abcF" : Mapping({"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using d.update({"abcF" : %s})
+ d.update({"abcF" : FailingIter()}):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d.update({"abcF" : FailingIterNext()}):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update({"abcF" : %s})
+ d.update({"abcF" : None}):NOT FAILED
+ d.update({"abcF" : {b"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update({"abcF" : {"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update({"abcF" : FailingMapping()}):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d.update({"abcF" : FailingMappingKey()}):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d.update({"abcF" : FailingNumber()}):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using d.update(Mapping({%s : 1}))
+ d.update(Mapping({1 : 1})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update(Mapping({b"\0" : 1})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update(Mapping({"\0" : 1})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update(Mapping({"abcG" : {%s : 1}}))
+ d.update(Mapping({"abcG" : {1 : 1}})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update(Mapping({"abcG" : {b"\0" : 1}})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update(Mapping({"abcG" : {"\0" : 1}})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update(Mapping({"abcG" : Mapping({%s : 1})}))
+ d.update(Mapping({"abcG" : Mapping({1 : 1})})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update(Mapping({"abcG" : Mapping({b"\0" : 1})})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update(Mapping({"abcG" : Mapping({"\0" : 1})})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using d.update(Mapping({"abcG" : %s}))
+ d.update(Mapping({"abcG" : FailingIter()})):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d.update(Mapping({"abcG" : FailingIterNext()})):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update(Mapping({"abcG" : %s}))
+ d.update(Mapping({"abcG" : None})):NOT FAILED
+ d.update(Mapping({"abcG" : {b"": 1}})):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update(Mapping({"abcG" : {"": 1}})):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update(Mapping({"abcG" : FailingMapping()})):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d.update(Mapping({"abcG" : FailingMappingKey()})):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d.update(Mapping({"abcG" : FailingNumber()})):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using d.update(%s)
+ d.update(FailingIter()):(<class 'NotImplementedError'>, NotImplementedError('iter',))
+ d.update(FailingIterNext()):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update(%s)
+ d.update(None):(<class 'TypeError'>, TypeError("'NoneType' object is not iterable",))
+ d.update({b"": 1}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update({"": 1}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update(FailingMapping()):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d.update(FailingMappingKey()):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d.update(FailingNumber()):(<class 'TypeError'>, TypeError("'FailingNumber' object is not iterable",))
+ <<< Finished
+ >>> Testing StringToChars using d.update(((%s, 0),))
+ d.update(((1, 0),)):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update(((b"\0", 0),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update((("\0", 0),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", {%s : 1}),))
+ d.update((("a", {1 : 1}),)):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update((("a", {b"\0" : 1}),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update((("a", {"\0" : 1}),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", {"abcF" : {%s : 1}}),))
+ d.update((("a", {"abcF" : {1 : 1}}),)):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update((("a", {"abcF" : {b"\0" : 1}}),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update((("a", {"abcF" : {"\0" : 1}}),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", {"abcF" : Mapping({%s : 1})}),))
+ d.update((("a", {"abcF" : Mapping({1 : 1})}),)):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update((("a", {"abcF" : Mapping({b"\0" : 1})}),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update((("a", {"abcF" : Mapping({"\0" : 1})}),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using d.update((("a", {"abcF" : %s}),))
+ d.update((("a", {"abcF" : FailingIter()}),)):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d.update((("a", {"abcF" : FailingIterNext()}),)):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update((("a", {"abcF" : %s}),))
+ d.update((("a", {"abcF" : None}),)):(<class 'vim.error'>, error("failed to add key 'a' to dictionary",))
+ d.update((("a", {"abcF" : {b"": 1}}),)):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update((("a", {"abcF" : {"": 1}}),)):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update((("a", {"abcF" : FailingMapping()}),)):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d.update((("a", {"abcF" : FailingMappingKey()}),)):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d.update((("a", {"abcF" : FailingNumber()}),)):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", Mapping({%s : 1})),))
+ d.update((("a", Mapping({1 : 1})),)):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update((("a", Mapping({b"\0" : 1})),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update((("a", Mapping({"\0" : 1})),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", Mapping({"abcG" : {%s : 1}})),))
+ d.update((("a", Mapping({"abcG" : {1 : 1}})),)):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update((("a", Mapping({"abcG" : {b"\0" : 1}})),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update((("a", Mapping({"abcG" : {"\0" : 1}})),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using d.update((("a", Mapping({"abcG" : Mapping({%s : 1})})),))
+ d.update((("a", Mapping({"abcG" : Mapping({1 : 1})})),)):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ d.update((("a", Mapping({"abcG" : Mapping({b"\0" : 1})})),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ d.update((("a", Mapping({"abcG" : Mapping({"\0" : 1})})),)):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using d.update((("a", Mapping({"abcG" : %s})),))
+ d.update((("a", Mapping({"abcG" : FailingIter()})),)):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d.update((("a", Mapping({"abcG" : FailingIterNext()})),)):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update((("a", Mapping({"abcG" : %s})),))
+ d.update((("a", Mapping({"abcG" : None})),)):(<class 'vim.error'>, error("failed to add key 'a' to dictionary",))
+ d.update((("a", Mapping({"abcG" : {b"": 1}})),)):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update((("a", Mapping({"abcG" : {"": 1}})),)):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update((("a", Mapping({"abcG" : FailingMapping()})),)):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d.update((("a", Mapping({"abcG" : FailingMappingKey()})),)):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d.update((("a", Mapping({"abcG" : FailingNumber()})),)):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using d.update((("a", %s),))
+ d.update((("a", FailingIter()),)):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ d.update((("a", FailingIterNext()),)):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using d.update((("a", %s),))
+ d.update((("a", None),)):(<class 'vim.error'>, error("failed to add key 'a' to dictionary",))
+ d.update((("a", {b"": 1}),)):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update((("a", {"": 1}),)):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ d.update((("a", FailingMapping()),)):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ d.update((("a", FailingMappingKey()),)):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ d.update((("a", FailingNumber()),)):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >> DictionaryPopItem
+ d.popitem(1, 2):(<class 'TypeError'>, TypeError('popitem() takes no arguments (2 given)',))
+ >> DictionaryHasKey
+ d.has_key():(<class 'TypeError'>, TypeError('has_key() takes exactly one argument (0 given)',))
+ > List
+ >> ListConstructor
+ vim.List(1, 2):(<class 'TypeError'>, TypeError('function takes at most 1 argument (2 given)',))
+ vim.List(a=1):(<class 'TypeError'>, TypeError('list constructor does not accept keyword arguments',))
+ >>> Testing *Iter* using vim.List(%s)
+ vim.List(FailingIter()):(<class 'NotImplementedError'>, NotImplementedError('iter',))
+ vim.List(FailingIterNext()):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing StringToChars using vim.List([{%s : 1}])
+ vim.List([{1 : 1}]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.List([{b"\0" : 1}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.List([{"\0" : 1}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using vim.List([{"abcF" : {%s : 1}}])
+ vim.List([{"abcF" : {1 : 1}}]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.List([{"abcF" : {b"\0" : 1}}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.List([{"abcF" : {"\0" : 1}}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using vim.List([{"abcF" : Mapping({%s : 1})}])
+ vim.List([{"abcF" : Mapping({1 : 1})}]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.List([{"abcF" : Mapping({b"\0" : 1})}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.List([{"abcF" : Mapping({"\0" : 1})}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using vim.List([{"abcF" : %s}])
+ vim.List([{"abcF" : FailingIter()}]):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ vim.List([{"abcF" : FailingIterNext()}]):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using vim.List([{"abcF" : %s}])
+ vim.List([{"abcF" : None}]):NOT FAILED
+ vim.List([{"abcF" : {b"": 1}}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ vim.List([{"abcF" : {"": 1}}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ vim.List([{"abcF" : FailingMapping()}]):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ vim.List([{"abcF" : FailingMappingKey()}]):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ vim.List([{"abcF" : FailingNumber()}]):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using vim.List([Mapping({%s : 1})])
+ vim.List([Mapping({1 : 1})]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.List([Mapping({b"\0" : 1})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.List([Mapping({"\0" : 1})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using vim.List([Mapping({"abcG" : {%s : 1}})])
+ vim.List([Mapping({"abcG" : {1 : 1}})]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.List([Mapping({"abcG" : {b"\0" : 1}})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.List([Mapping({"abcG" : {"\0" : 1}})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using vim.List([Mapping({"abcG" : Mapping({%s : 1})})])
+ vim.List([Mapping({"abcG" : Mapping({1 : 1})})]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.List([Mapping({"abcG" : Mapping({b"\0" : 1})})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ vim.List([Mapping({"abcG" : Mapping({"\0" : 1})})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using vim.List([Mapping({"abcG" : %s})])
+ vim.List([Mapping({"abcG" : FailingIter()})]):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ vim.List([Mapping({"abcG" : FailingIterNext()})]):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using vim.List([Mapping({"abcG" : %s})])
+ vim.List([Mapping({"abcG" : None})]):NOT FAILED
+ vim.List([Mapping({"abcG" : {b"": 1}})]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ vim.List([Mapping({"abcG" : {"": 1}})]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ vim.List([Mapping({"abcG" : FailingMapping()})]):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ vim.List([Mapping({"abcG" : FailingMappingKey()})]):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ vim.List([Mapping({"abcG" : FailingNumber()})]):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using vim.List([%s])
+ vim.List([FailingIter()]):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ vim.List([FailingIterNext()]):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using vim.List([%s])
+ vim.List([None]):NOT FAILED
+ vim.List([{b"": 1}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ vim.List([{"": 1}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ vim.List([FailingMapping()]):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ vim.List([FailingMappingKey()]):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ vim.List([FailingNumber()]):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >> ListItem
+ l[1000]:(<class 'IndexError'>, IndexError('list index out of range',))
+ >> ListAssItem
+ ll[1] = 2:(<class 'vim.error'>, error('list is locked',))
+ l[1000] = 3:(<class 'IndexError'>, IndexError('list index out of range',))
+ >> ListAssSlice
+ ll[1:100] = "abcJ":(<class 'vim.error'>, error('list is locked',))
+ >>> Testing *Iter* using l[:] = %s
+ l[:] = FailingIter():(<class 'NotImplementedError'>, NotImplementedError('iter',))
+ l[:] = FailingIterNext():(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ nel[1:10:2] = "abcK":(<class 'ValueError'>, ValueError('attempt to assign sequence of size greater than 2 to extended slice',))
+ (b'a', b'b', b'c', b'O')
+ nel[1:10:2] = "a":(<class 'ValueError'>, ValueError('attempt to assign sequence of size 1 to extended slice of size 2',))
+ (b'a', b'b', b'c', b'O')
+ nel[1:1:-1] = "a":(<class 'ValueError'>, ValueError('attempt to assign sequence of size greater than 0 to extended slice',))
+ (b'a', b'b', b'c', b'O')
+ nel[:] = FailingIterNextN(2):(<class 'NotImplementedError'>, NotImplementedError('next N',))
+ (b'a', b'b', b'c', b'O')
+ >>> Testing StringToChars using l[:] = [{%s : 1}]
+ l[:] = [{1 : 1}]:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l[:] = [{b"\0" : 1}]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l[:] = [{"\0" : 1}]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [{"abcF" : {%s : 1}}]
+ l[:] = [{"abcF" : {1 : 1}}]:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l[:] = [{"abcF" : {b"\0" : 1}}]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l[:] = [{"abcF" : {"\0" : 1}}]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [{"abcF" : Mapping({%s : 1})}]
+ l[:] = [{"abcF" : Mapping({1 : 1})}]:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l[:] = [{"abcF" : Mapping({b"\0" : 1})}]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l[:] = [{"abcF" : Mapping({"\0" : 1})}]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using l[:] = [{"abcF" : %s}]
+ l[:] = [{"abcF" : FailingIter()}]:(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ l[:] = [{"abcF" : FailingIterNext()}]:(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l[:] = [{"abcF" : %s}]
+ l[:] = [{"abcF" : None}]:NOT FAILED
+ l[:] = [{"abcF" : {b"": 1}}]:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l[:] = [{"abcF" : {"": 1}}]:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l[:] = [{"abcF" : FailingMapping()}]:(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ l[:] = [{"abcF" : FailingMappingKey()}]:(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ l[:] = [{"abcF" : FailingNumber()}]:(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [Mapping({%s : 1})]
+ l[:] = [Mapping({1 : 1})]:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l[:] = [Mapping({b"\0" : 1})]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l[:] = [Mapping({"\0" : 1})]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [Mapping({"abcG" : {%s : 1}})]
+ l[:] = [Mapping({"abcG" : {1 : 1}})]:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l[:] = [Mapping({"abcG" : {b"\0" : 1}})]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l[:] = [Mapping({"abcG" : {"\0" : 1}})]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l[:] = [Mapping({"abcG" : Mapping({%s : 1})})]
+ l[:] = [Mapping({"abcG" : Mapping({1 : 1})})]:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l[:] = [Mapping({"abcG" : Mapping({b"\0" : 1})})]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l[:] = [Mapping({"abcG" : Mapping({"\0" : 1})})]:(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using l[:] = [Mapping({"abcG" : %s})]
+ l[:] = [Mapping({"abcG" : FailingIter()})]:(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ l[:] = [Mapping({"abcG" : FailingIterNext()})]:(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l[:] = [Mapping({"abcG" : %s})]
+ l[:] = [Mapping({"abcG" : None})]:NOT FAILED
+ l[:] = [Mapping({"abcG" : {b"": 1}})]:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l[:] = [Mapping({"abcG" : {"": 1}})]:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l[:] = [Mapping({"abcG" : FailingMapping()})]:(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ l[:] = [Mapping({"abcG" : FailingMappingKey()})]:(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ l[:] = [Mapping({"abcG" : FailingNumber()})]:(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using l[:] = [%s]
+ l[:] = [FailingIter()]:(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ l[:] = [FailingIterNext()]:(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l[:] = [%s]
+ l[:] = [None]:NOT FAILED
+ l[:] = [{b"": 1}]:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l[:] = [{"": 1}]:(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l[:] = [FailingMapping()]:(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ l[:] = [FailingMappingKey()]:(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ l[:] = [FailingNumber()]:(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >> ListConcatInPlace
+ >>> Testing *Iter* using l.extend(%s)
+ l.extend(FailingIter()):(<class 'NotImplementedError'>, NotImplementedError('iter',))
+ l.extend(FailingIterNext()):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing StringToChars using l.extend([{%s : 1}])
+ l.extend([{1 : 1}]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l.extend([{b"\0" : 1}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l.extend([{"\0" : 1}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l.extend([{"abcF" : {%s : 1}}])
+ l.extend([{"abcF" : {1 : 1}}]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l.extend([{"abcF" : {b"\0" : 1}}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l.extend([{"abcF" : {"\0" : 1}}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l.extend([{"abcF" : Mapping({%s : 1})}])
+ l.extend([{"abcF" : Mapping({1 : 1})}]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l.extend([{"abcF" : Mapping({b"\0" : 1})}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l.extend([{"abcF" : Mapping({"\0" : 1})}]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using l.extend([{"abcF" : %s}])
+ l.extend([{"abcF" : FailingIter()}]):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ l.extend([{"abcF" : FailingIterNext()}]):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l.extend([{"abcF" : %s}])
+ l.extend([{"abcF" : None}]):NOT FAILED
+ l.extend([{"abcF" : {b"": 1}}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l.extend([{"abcF" : {"": 1}}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l.extend([{"abcF" : FailingMapping()}]):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ l.extend([{"abcF" : FailingMappingKey()}]):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ l.extend([{"abcF" : FailingNumber()}]):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using l.extend([Mapping({%s : 1})])
+ l.extend([Mapping({1 : 1})]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l.extend([Mapping({b"\0" : 1})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l.extend([Mapping({"\0" : 1})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l.extend([Mapping({"abcG" : {%s : 1}})])
+ l.extend([Mapping({"abcG" : {1 : 1}})]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l.extend([Mapping({"abcG" : {b"\0" : 1}})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l.extend([Mapping({"abcG" : {"\0" : 1}})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using l.extend([Mapping({"abcG" : Mapping({%s : 1})})])
+ l.extend([Mapping({"abcG" : Mapping({1 : 1})})]):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ l.extend([Mapping({"abcG" : Mapping({b"\0" : 1})})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ l.extend([Mapping({"abcG" : Mapping({"\0" : 1})})]):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using l.extend([Mapping({"abcG" : %s})])
+ l.extend([Mapping({"abcG" : FailingIter()})]):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ l.extend([Mapping({"abcG" : FailingIterNext()})]):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l.extend([Mapping({"abcG" : %s})])
+ l.extend([Mapping({"abcG" : None})]):NOT FAILED
+ l.extend([Mapping({"abcG" : {b"": 1}})]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l.extend([Mapping({"abcG" : {"": 1}})]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l.extend([Mapping({"abcG" : FailingMapping()})]):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ l.extend([Mapping({"abcG" : FailingMappingKey()})]):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ l.extend([Mapping({"abcG" : FailingNumber()})]):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using l.extend([%s])
+ l.extend([FailingIter()]):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ l.extend([FailingIterNext()]):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using l.extend([%s])
+ l.extend([None]):NOT FAILED
+ l.extend([{b"": 1}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l.extend([{"": 1}]):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ l.extend([FailingMapping()]):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ l.extend([FailingMappingKey()]):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ l.extend([FailingNumber()]):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >> ListSetattr
+ del l.locked:(<class 'AttributeError'>, AttributeError('cannot delete vim.List attributes',))
+ l.locked = FailingTrue():(<class 'NotImplementedError'>, NotImplementedError('bool',))
+ l.xxx = True:(<class 'AttributeError'>, AttributeError('cannot set attribute xxx',))
+ > Function
+ >> FunctionConstructor
+ >>> FunctionConstructor
+ vim.Function("123"):(<class 'ValueError'>, ValueError('unnamed function 123 does not exist',))
+ vim.Function("xxx_non_existent_function_xxx"):(<class 'ValueError'>, ValueError('function xxx_non_existent_function_xxx does not exist',))
+ vim.Function("xxx#non#existent#function#xxx"):NOT FAILED
+ vim.Function("xxx_non_existent_function_xxx2", args=[]):(<class 'ValueError'>, ValueError('function xxx_non_existent_function_xxx2 does not exist',))
+ vim.Function("xxx_non_existent_function_xxx3", self={}):(<class 'ValueError'>, ValueError('function xxx_non_existent_function_xxx3 does not exist',))
+ vim.Function("xxx_non_existent_function_xxx4", args=[], self={}):(<class 'ValueError'>, ValueError('function xxx_non_existent_function_xxx4 does not exist',))
+ >>> FunctionNew
+ vim.Function("tr", self="abcFuncSelf"):(<class 'AttributeError'>, AttributeError('keys',))
+ vim.Function("tr", args=427423):(<class 'TypeError'>, TypeError('unable to convert int to a Vim list',))
+ vim.Function("tr", self="abcFuncSelf2", args="abcFuncArgs2"):(<class 'AttributeError'>, AttributeError('keys',))
+ vim.Function(self="abcFuncSelf2", args="abcFuncArgs2"):(<class 'AttributeError'>, AttributeError('keys',))
+ vim.Function("tr", "", self="abcFuncSelf2", args="abcFuncArgs2"):(<class 'AttributeError'>, AttributeError('keys',))
+ vim.Function("tr", ""):(<class 'TypeError'>, TypeError('function takes exactly 1 argument (2 given)',))
+ >> FunctionCall
+ >>> Testing StringToChars using f({%s : 1})
+ f({1 : 1}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ f({b"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ f({"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using f({"abcF" : {%s : 1}})
+ f({"abcF" : {1 : 1}}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ f({"abcF" : {b"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ f({"abcF" : {"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using f({"abcF" : Mapping({%s : 1})})
+ f({"abcF" : Mapping({1 : 1})}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ f({"abcF" : Mapping({b"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ f({"abcF" : Mapping({"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using f({"abcF" : %s})
+ f({"abcF" : FailingIter()}):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ f({"abcF" : FailingIterNext()}):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using f({"abcF" : %s})
+ f({"abcF" : None}):NOT FAILED
+ f({"abcF" : {b"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ f({"abcF" : {"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ f({"abcF" : FailingMapping()}):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ f({"abcF" : FailingMappingKey()}):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ f({"abcF" : FailingNumber()}):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using f(Mapping({%s : 1}))
+ f(Mapping({1 : 1})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ f(Mapping({b"\0" : 1})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ f(Mapping({"\0" : 1})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using f(Mapping({"abcG" : {%s : 1}}))
+ f(Mapping({"abcG" : {1 : 1}})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ f(Mapping({"abcG" : {b"\0" : 1}})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ f(Mapping({"abcG" : {"\0" : 1}})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using f(Mapping({"abcG" : Mapping({%s : 1})}))
+ f(Mapping({"abcG" : Mapping({1 : 1})})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ f(Mapping({"abcG" : Mapping({b"\0" : 1})})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ f(Mapping({"abcG" : Mapping({"\0" : 1})})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using f(Mapping({"abcG" : %s}))
+ f(Mapping({"abcG" : FailingIter()})):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ f(Mapping({"abcG" : FailingIterNext()})):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using f(Mapping({"abcG" : %s}))
+ f(Mapping({"abcG" : None})):NOT FAILED
+ f(Mapping({"abcG" : {b"": 1}})):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ f(Mapping({"abcG" : {"": 1}})):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ f(Mapping({"abcG" : FailingMapping()})):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ f(Mapping({"abcG" : FailingMappingKey()})):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ f(Mapping({"abcG" : FailingNumber()})):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using f(%s)
+ f(FailingIter()):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ f(FailingIterNext()):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using f(%s)
+ f(None):NOT FAILED
+ f({b"": 1}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ f({"": 1}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ f(FailingMapping()):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ f(FailingMappingKey()):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ f(FailingNumber()):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using fd(self={%s : 1})
+ fd(self={1 : 1}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ fd(self={b"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ fd(self={"\0" : 1}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using fd(self={"abcF" : {%s : 1}})
+ fd(self={"abcF" : {1 : 1}}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ fd(self={"abcF" : {b"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ fd(self={"abcF" : {"\0" : 1}}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using fd(self={"abcF" : Mapping({%s : 1})})
+ fd(self={"abcF" : Mapping({1 : 1})}):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ fd(self={"abcF" : Mapping({b"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ fd(self={"abcF" : Mapping({"\0" : 1})}):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using fd(self={"abcF" : %s})
+ fd(self={"abcF" : FailingIter()}):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ fd(self={"abcF" : FailingIterNext()}):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using fd(self={"abcF" : %s})
+ fd(self={"abcF" : None}):NOT FAILED
+ fd(self={"abcF" : {b"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ fd(self={"abcF" : {"": 1}}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ fd(self={"abcF" : FailingMapping()}):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ fd(self={"abcF" : FailingMappingKey()}):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ fd(self={"abcF" : FailingNumber()}):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing StringToChars using fd(self=Mapping({%s : 1}))
+ fd(self=Mapping({1 : 1})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ fd(self=Mapping({b"\0" : 1})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ fd(self=Mapping({"\0" : 1})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using fd(self=Mapping({"abcG" : {%s : 1}}))
+ fd(self=Mapping({"abcG" : {1 : 1}})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ fd(self=Mapping({"abcG" : {b"\0" : 1}})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ fd(self=Mapping({"abcG" : {"\0" : 1}})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing StringToChars using fd(self=Mapping({"abcG" : Mapping({%s : 1})}))
+ fd(self=Mapping({"abcG" : Mapping({1 : 1})})):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ fd(self=Mapping({"abcG" : Mapping({b"\0" : 1})})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ fd(self=Mapping({"abcG" : Mapping({"\0" : 1})})):(<class 'TypeError'>, TypeError('expected bytes with no null',))
+ <<< Finished
+ >>> Testing *Iter* using fd(self=Mapping({"abcG" : %s}))
+ fd(self=Mapping({"abcG" : FailingIter()})):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim structure',))
+ fd(self=Mapping({"abcG" : FailingIterNext()})):(<class 'NotImplementedError'>, NotImplementedError('next',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using fd(self=Mapping({"abcG" : %s}))
+ fd(self=Mapping({"abcG" : None})):NOT FAILED
+ fd(self=Mapping({"abcG" : {b"": 1}})):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ fd(self=Mapping({"abcG" : {"": 1}})):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ fd(self=Mapping({"abcG" : FailingMapping()})):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ fd(self=Mapping({"abcG" : FailingMappingKey()})):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ fd(self=Mapping({"abcG" : FailingNumber()})):(<class 'NotImplementedError'>, NotImplementedError('int',))
+ <<< Finished
+ >>> Testing *Iter* using fd(self=%s)
+ fd(self=FailingIter()):(<class 'TypeError'>, TypeError('unable to convert FailingIter to a Vim dictionary',))
+ fd(self=FailingIterNext()):(<class 'TypeError'>, TypeError('unable to convert FailingIterNext to a Vim dictionary',))
+ <<< Finished
+ >>> Testing ConvertFromPyObject using fd(self=%s)
+ fd(self=None):(<class 'TypeError'>, TypeError('unable to convert NoneType to a Vim dictionary',))
+ fd(self={b"": 1}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ fd(self={"": 1}):(<class 'ValueError'>, ValueError('empty keys are not allowed',))
+ fd(self=FailingMapping()):(<class 'NotImplementedError'>, NotImplementedError('keys',))
+ fd(self=FailingMappingKey()):(<class 'NotImplementedError'>, NotImplementedError('getitem:mappingkey',))
+ fd(self=FailingNumber()):(<class 'TypeError'>, TypeError('unable to convert FailingNumber to a Vim dictionary',))
+ <<< Finished
+ >>> Testing ConvertFromPyMapping using fd(self=%s)
+ fd(self=[]):(<class 'AttributeError'>, AttributeError('keys',))
+ <<< Finished
+ > TabPage
+ >> TabPageAttr
+ vim.current.tabpage.xxx:(<class 'AttributeError'>, AttributeError("'vim.tabpage' object has no attribute 'xxx'",))
+ > TabList
+ >> TabListItem
+ vim.tabpages[1000]:(<class 'IndexError'>, IndexError('no such tab page',))
+ > Window
+ >> WindowAttr
+ vim.current.window.xxx:(<class 'AttributeError'>, AttributeError("'vim.window' object has no attribute 'xxx'",))
+ >> WindowSetattr
+ vim.current.window.buffer = 0:(<class 'TypeError'>, TypeError('readonly attribute: buffer',))
+ vim.current.window.cursor = (100000000, 100000000):(<class 'vim.error'>, error('cursor position outside buffer',))
+ vim.current.window.cursor = True:(<class 'TypeError'>, TypeError('argument must be 2-item sequence, not bool',))
+ >>> Testing NumberToLong using vim.current.window.height = %s
+ vim.current.window.height = []:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got list',))
+ vim.current.window.height = None:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got NoneType',))
+ vim.current.window.height = -1:(<class 'ValueError'>, ValueError('number must be greater or equal to zero',))
+ <<< Finished
+ >>> Testing NumberToLong using vim.current.window.width = %s
+ vim.current.window.width = []:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got list',))
+ vim.current.window.width = None:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got NoneType',))
+ vim.current.window.width = -1:(<class 'ValueError'>, ValueError('number must be greater or equal to zero',))
+ <<< Finished
+ vim.current.window.xxxxxx = True:(<class 'AttributeError'>, AttributeError('xxxxxx',))
+ > WinList
+ >> WinListItem
+ vim.windows[1000]:(<class 'IndexError'>, IndexError('no such window',))
+ > Buffer
+ >> StringToLine (indirect)
+ vim.current.buffer[0] = "\na":(<class 'vim.error'>, error('string cannot contain newlines',))
+ vim.current.buffer[0] = b"\na":(<class 'vim.error'>, error('string cannot contain newlines',))
+ >> SetBufferLine (indirect)
+ vim.current.buffer[0] = True:(<class 'TypeError'>, TypeError('bad argument type for built-in operation',))
+ >> SetBufferLineList (indirect)
+ vim.current.buffer[:] = True:(<class 'TypeError'>, TypeError('bad argument type for built-in operation',))
+ vim.current.buffer[:] = ["\na", "bc"]:(<class 'vim.error'>, error('string cannot contain newlines',))
+ >> InsertBufferLines (indirect)
+ vim.current.buffer.append(None):(<class 'TypeError'>, TypeError('bad argument type for built-in operation',))
+ vim.current.buffer.append(["\na", "bc"]):(<class 'vim.error'>, error('string cannot contain newlines',))
+ vim.current.buffer.append("\nbc"):(<class 'vim.error'>, error('string cannot contain newlines',))
+ >> RBItem
+ vim.current.buffer[100000000]:(<class 'IndexError'>, IndexError('line number out of range',))
+ >> RBAsItem
+ vim.current.buffer[100000000] = "":(<class 'IndexError'>, IndexError('line number out of range',))
+ >> BufferAttr
+ vim.current.buffer.xxx:(<class 'AttributeError'>, AttributeError("'vim.buffer' object has no attribute 'xxx'",))
+ >> BufferSetattr
+ vim.current.buffer.name = True:(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got bool',))
+ vim.current.buffer.xxx = True:(<class 'AttributeError'>, AttributeError('xxx',))
+ >> BufferMark
+ vim.current.buffer.mark(0):(<class 'TypeError'>, TypeError('expected bytes() or str() instance, but got int',))
+ vim.current.buffer.mark("abcM"):(<class 'ValueError'>, ValueError('mark name must be a single character',))
+ vim.current.buffer.mark("!"):(<class 'vim.error'>, error('invalid mark name',))
+ >> BufferRange
+ vim.current.buffer.range(1, 2, 3):(<class 'TypeError'>, TypeError('function takes exactly 2 arguments (3 given)',))
+ > BufMap
+ >> BufMapItem
+ vim.buffers[100000000]:(<class 'KeyError'>, KeyError(100000000,))
+ >>> Testing NumberToLong using vim.buffers[%s]
+ vim.buffers[[]]:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got list',))
+ vim.buffers[None]:(<class 'TypeError'>, TypeError('expected int() or something supporting coercing to int(), but got NoneType',))
+ vim.buffers[-1]:(<class 'ValueError'>, ValueError('number must be greater than zero',))
+ vim.buffers[0]:(<class 'ValueError'>, ValueError('number must be greater than zero',))
+ <<< Finished
+ > Current
+ >> CurrentGetattr
+ vim.current.xxx:(<class 'AttributeError'>, AttributeError("'vim.currentdata' object has no attribute 'xxx'",))
+ >> CurrentSetattr
+ vim.current.line = True:(<class 'TypeError'>, TypeError('bad argument type for built-in operation',))
+ vim.current.buffer = True:(<class 'TypeError'>, TypeError('expected vim.Buffer object, but got bool',))
+ vim.current.window = True:(<class 'TypeError'>, TypeError('expected vim.Window object, but got bool',))
+ vim.current.tabpage = True:(<class 'TypeError'>, TypeError('expected vim.TabPage object, but got bool',))
+ vim.current.xxx = True:(<class 'AttributeError'>, AttributeError('xxx',))
+ END
+
+ let actual = getline(2, '$')
+ let n_expected = len(expected)
+ let n_actual = len(actual)
+ call assert_equal(n_expected, n_actual, 'number of lines to compare')
+
+ " Compare line by line so the errors are easier to understand. Missing lines
+ " are compared with an empty string.
+ for i in range(n_expected > n_actual ? n_expected : n_actual)
+ call assert_equal(i >= n_expected ? '' : expected[i], i >= n_actual ? '' : actual[i])
+ endfor
+ close!
+endfunc
+
+" Test import
+func Test_python3_import()
+ new
+ py3 cb = vim.current.buffer
+
+ py3 << trim EOF
+ sys.path.insert(0, os.path.join(os.getcwd(), 'python_before'))
+ sys.path.append(os.path.join(os.getcwd(), 'python_after'))
+ vim.options['rtp'] = os.getcwd().replace(',', '\\,').replace('\\', '\\\\')
+ l = []
+ def callback(path):
+ l.append(os.path.relpath(path))
+ vim.foreach_rtp(callback)
+ cb.append(repr(l))
+ del l
+ def callback(path):
+ return os.path.relpath(path)
+ cb.append(repr(vim.foreach_rtp(callback)))
+ del callback
+ from module import dir as d
+ from modulex import ddir
+ cb.append(d + ',' + ddir)
+ import before
+ cb.append(before.dir)
+ import after
+ cb.append(after.dir)
+ import topmodule as tm
+ import topmodule.submodule as tms
+ import topmodule.submodule.subsubmodule.subsubsubmodule as tmsss
+ cb.append(tm.__file__.replace(os.path.sep, '/')[-len('modulex/topmodule/__init__.py'):])
+ cb.append(tms.__file__.replace(os.path.sep, '/')[-len('modulex/topmodule/submodule/__init__.py'):])
+ cb.append(tmsss.__file__.replace(os.path.sep, '/')[-len('modulex/topmodule/submodule/subsubmodule/subsubsubmodule.py'):])
+
+ del before
+ del after
+ del d
+ del ddir
+ del tm
+ del tms
+ del tmsss
+ EOF
+
+ let expected =<< trim END
+ ['.']
+ '.'
+ 3,xx
+ before
+ after
+ pythonx/topmodule/__init__.py
+ pythonx/topmodule/submodule/__init__.py
+ pythonx/topmodule/submodule/subsubmodule/subsubsubmodule.py
+ END
+ call assert_equal(expected, getline(2, '$'))
+ close!
+
+ " Try to import a non-existing module with a dot (.)
+ call AssertException(['py3 import a.b.c'], "No module named 'a'")
+endfunc
+
+" Test exceptions
+func Test_python3_exception()
+ func Exe(e)
+ execute a:e
+ endfunc
+
+ new
+ py3 cb = vim.current.buffer
+
+ py3 << trim EOF
+ Exe = vim.bindeval('function("Exe")')
+ ee('vim.command("throw \'abcN\'")')
+ ee('Exe("throw \'def\'")')
+ ee('vim.eval("Exe(\'throw \'\'ghi\'\'\')")')
+ ee('vim.eval("Exe(\'echoerr \'\'jkl\'\'\')")')
+ ee('vim.eval("Exe(\'xxx_non_existent_command_xxx\')")')
+ ee('vim.eval("xxx_unknown_function_xxx()")')
+ ee('vim.bindeval("Exe(\'xxx_non_existent_command_xxx\')")')
+ del Exe
+ EOF
+ delfunction Exe
+
+ let expected =<< trim END
+ vim.command("throw 'abcN'"):(<class 'vim.error'>, error('abcN',))
+ Exe("throw 'def'"):(<class 'vim.error'>, error('def',))
+ vim.eval("Exe('throw ''ghi''')"):(<class 'vim.error'>, error('ghi',))
+ vim.eval("Exe('echoerr ''jkl''')"):(<class 'vim.error'>, error('Vim(echoerr):jkl',))
+ vim.eval("Exe('xxx_non_existent_command_xxx')"):(<class 'vim.error'>, error('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',))
+ vim.eval("xxx_unknown_function_xxx()"):(<class 'vim.error'>, error('Vim:E117: Unknown function: xxx_unknown_function_xxx',))
+ vim.bindeval("Exe('xxx_non_existent_command_xxx')"):(<class 'vim.error'>, error('Vim:E492: Not an editor command: xxx_non_existent_command_xxx',))
+ END
+ call assert_equal(expected, getline(2, '$'))
+ close!
+endfunc
+
+" Regression: interrupting vim.command propagates to next vim.command
+func Test_python3_keyboard_interrupt()
+ new
+ py3 cb = vim.current.buffer
+ py3 << trim EOF
+ def test_keyboard_interrupt():
+ try:
+ vim.command('while 1 | endwhile')
+ except KeyboardInterrupt:
+ cb.append('Caught KeyboardInterrupt')
+ except Exception:
+ cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
+ else:
+ cb.append('!!!!!!!! No exception')
+ try:
+ vim.command('$ put =\'Running :put\'')
+ except KeyboardInterrupt:
+ cb.append('!!!!!!!! Caught KeyboardInterrupt')
+ except Exception:
+ cb.append('!!!!!!!! Caught exception: ' + emsg(sys.exc_info()))
+ else:
+ cb.append('No exception')
+ EOF
+
+ debuggreedy
+ call inputsave()
+ call feedkeys("s\ns\ns\ns\nq\n")
+ redir => output
+ debug silent! py3 test_keyboard_interrupt()
+ redir END
+ 0 debuggreedy
+ call inputrestore()
+ py3 del test_keyboard_interrupt
+
+ let expected =<< trim END
+ Caught KeyboardInterrupt
+ Running :put
+ No exception
+ END
+ call assert_equal(expected, getline(2, '$'))
+ call assert_equal('', output)
+ close!
+endfunc
+
+" Regression: Iterator for a Vim object should hold a reference.
+func Test_python3_iter_ref()
+ let g:list_iter_ref_count_increase = -1
+ let g:dict_iter_ref_count_increase = -1
+ let g:bufmap_iter_ref_count_increase = -1
+ let g:options_iter_ref_count_increase = -1
+
+ py3 << trim EOF
+ import sys
+ import vim
+
+ def test_python3_iter_ref():
+ create_list = vim.Function('Create_vim_list')
+ v = create_list()
+ base_ref_count = sys.getrefcount(v)
+ for el in v:
+ vim.vars['list_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count
+
+ create_dict = vim.Function('Create_vim_dict')
+ v = create_dict()
+ base_ref_count = sys.getrefcount(v)
+ for el in v:
+ vim.vars['dict_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count
+
+ v = vim.buffers
+ base_ref_count = sys.getrefcount(v)
+ for el in v:
+ vim.vars['bufmap_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count
+
+ v = vim.options
+ base_ref_count = sys.getrefcount(v)
+ for el in v:
+ vim.vars['options_iter_ref_count_increase'] = sys.getrefcount(v) - base_ref_count
+
+ test_python3_iter_ref()
+ EOF
+
+ call assert_equal(1, g:list_iter_ref_count_increase)
+ call assert_equal(1, g:dict_iter_ref_count_increase)
+ call assert_equal(1, g:bufmap_iter_ref_count_increase)
+ call assert_equal(1, g:options_iter_ref_count_increase)
+endfunc
+
+func Test_python3_non_utf8_string()
+ smap <Esc>@ <A-@>
+ py3 vim.command('redir => _tmp_smaps | smap | redir END')
+ py3 vim.eval('_tmp_smaps').splitlines()
+ sunmap <Esc>@
+endfunc
+
+func Test_python3_fold_hidden_buffer()
+ CheckFeature folding
+
+ set fdm=expr fde=Fde(v:lnum)
+ let b:regex = '^'
+ func Fde(lnum)
+ let ld = [{}]
+ let lines = bufnr('%')->getbufline(1, '$')
+ let was_import = 0
+ for lnum in range(1, len(lines))
+ let line = lines[lnum]
+ call add(ld, {'a': b:regex})
+ let ld[lnum].foldexpr = was_import ? 1 : '>1'
+ let was_import = 1
+ endfor
+ return ld[a:lnum].foldexpr
+ endfunc
+
+ call setline(1, repeat([''], 15) + repeat(['from'], 3))
+ eval repeat(['x'], 17)->writefile('Xa.txt', 'D')
+ split Xa.txt
+ py3 import vim
+ py3 b = vim.current.buffer
+ py3 aaa = b[:]
+ hide
+ py3 b[:] = aaa
+
+ set fdm& fde&
+ delfunc Fde
+ bwipe! Xa.txt
+endfunc
+
+" Test to catch regression fix #10437.
+func Test_python3_hidden_buf_mod_does_not_mess_up_display()
+ CheckRunVimInTerminal
+
+ let testfile = 'Xtest.vim'
+ let lines =<< trim END
+ set hidden number
+ new
+ hide
+ sil call setline(1, repeat(['aaa'], &lines) + ['bbbbbb'])
+ fu Func()
+ python3 << EOF
+ import vim
+ b = vim.buffers[2]
+ b[:] = ['', '']
+ EOF
+ endfu
+ norm! Gzb
+ call feedkeys(":call Func()\r", 'n')
+ END
+ call writefile(lines, testfile, 'D')
+
+ let rows = 10
+ let bufnr = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
+ call TermWait(bufnr, 100)
+ call assert_equal('run', job_status(term_getjob(bufnr)))
+ let g:test_is_flaky = 0
+ call WaitForAssert({-> assert_match('^ 3 aaa$', term_getline(bufnr, 1))})
+ call WaitForAssert({-> assert_match('^ 11 bbbbbb$', term_getline(bufnr, rows - 1))})
+
+ call term_sendkeys(bufnr, ":qall!\<CR>")
+ call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(bufnr)))})
+
+ exe bufnr . 'bwipe!'
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_pyx2.vim b/src/testdir/test_pyx2.vim
new file mode 100644
index 0000000..7432ceb
--- /dev/null
+++ b/src/testdir/test_pyx2.vim
@@ -0,0 +1,103 @@
+" Test for pyx* commands and functions with Python 2.
+
+set pyx=2
+source check.vim
+CheckFeature python
+
+let s:py2pattern = '^2\.[0-7]\.\d\+'
+let s:py3pattern = '^3\.\d\+\.\d\+'
+
+
+func Test_has_pythonx()
+ call assert_true(has('pythonx'))
+endfunc
+
+
+func Test_pyx()
+ redir => var
+ pyx << trim EOF
+ import sys
+ print(sys.version)
+ EOF
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+endfunc
+
+
+func Test_pyxdo()
+ pyx import sys
+ enew
+ pyxdo return sys.version.split("\n")[0]
+ call assert_match(s:py2pattern, split(getline('.'))[0])
+endfunc
+
+
+func Test_pyxeval()
+ pyx import sys
+ call assert_match(s:py2pattern, split('sys.version'->pyxeval())[0])
+endfunc
+
+
+func Test_pyxfile()
+ " No special comments nor shebangs
+ redir => var
+ pyxfile pyxfile/pyx.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ " Python 2 special comment
+ redir => var
+ pyxfile pyxfile/py2_magic.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ " Python 2 shebang
+ redir => var
+ pyxfile pyxfile/py2_shebang.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ if has('python3')
+ " Python 3 special comment
+ redir => var
+ pyxfile pyxfile/py3_magic.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ " Python 3 shebang
+ redir => var
+ pyxfile pyxfile/py3_shebang.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+ endif
+endfunc
+
+func Test_Catch_Exception_Message()
+ try
+ pyx raise RuntimeError( 'TEST' )
+ catch /.*/
+ call assert_match( '^Vim(.*):RuntimeError: TEST$', v:exception )
+ endtry
+endfunc
+
+" Test for various heredoc syntaxes
+func Test_pyx2_heredoc()
+ pyx << END
+result='A'
+END
+ pyx <<
+result+='B'
+.
+ pyx << trim END
+ result+='C'
+ END
+ pyx << trim
+ result+='D'
+ .
+ pyx << trim eof
+ result+='E'
+ eof
+ call assert_equal('ABCDE', pyxeval('result'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_pyx3.vim b/src/testdir/test_pyx3.vim
new file mode 100644
index 0000000..5d38420
--- /dev/null
+++ b/src/testdir/test_pyx3.vim
@@ -0,0 +1,103 @@
+" Test for pyx* commands and functions with Python 3.
+
+set pyx=3
+source check.vim
+CheckFeature python3
+
+let s:py2pattern = '^2\.[0-7]\.\d\+'
+let s:py3pattern = '^3\.\d\+\.\d\+'
+
+
+func Test_has_pythonx()
+ call assert_true(has('pythonx'))
+endfunc
+
+
+func Test_pyx()
+ redir => var
+ pyx << trim EOF
+ import sys
+ print(sys.version)
+ EOF
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+endfunc
+
+
+func Test_pyxdo()
+ pyx import sys
+ enew
+ pyxdo return sys.version.split("\n")[0]
+ call assert_match(s:py3pattern, split(getline('.'))[0])
+endfunc
+
+
+func Test_pyxeval()
+ pyx import sys
+ call assert_match(s:py3pattern, split(pyxeval('sys.version'))[0])
+endfunc
+
+
+func Test_pyxfile()
+ " No special comments nor shebangs
+ redir => var
+ pyxfile pyxfile/pyx.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ " Python 3 special comment
+ redir => var
+ pyxfile pyxfile/py3_magic.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ " Python 3 shebang
+ redir => var
+ pyxfile pyxfile/py3_shebang.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ if has('python')
+ " Python 2 special comment
+ redir => var
+ pyxfile pyxfile/py2_magic.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ " Python 2 shebang
+ redir => var
+ pyxfile pyxfile/py2_shebang.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+ endif
+endfunc
+
+func Test_Catch_Exception_Message()
+ try
+ pyx raise RuntimeError( 'TEST' )
+ catch /.*/
+ call assert_match( '^Vim(.*):RuntimeError: TEST$', v:exception )
+ endtry
+endfunc
+
+" Test for various heredoc syntaxes
+func Test_pyx3_heredoc()
+ pyx << END
+result='A'
+END
+ pyx <<
+result+='B'
+.
+ pyx << trim END
+ result+='C'
+ END
+ pyx << trim
+ result+='D'
+ .
+ pyx << trim eof
+ result+='E'
+ eof
+ call assert_equal('ABCDE', pyxeval('result'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim
new file mode 100644
index 0000000..bc180fc
--- /dev/null
+++ b/src/testdir/test_quickfix.vim
@@ -0,0 +1,6408 @@
+" Test for the quickfix feature.
+
+source check.vim
+import './vim9.vim' as v9
+CheckFeature quickfix
+
+source screendump.vim
+
+set encoding=utf-8
+
+func s:setup_commands(cchar)
+ if a:cchar == 'c'
+ command! -nargs=* -bang Xlist <mods>clist<bang> <args>
+ command! -nargs=* Xgetexpr <mods>cgetexpr <args>
+ command! -nargs=* Xaddexpr <mods>caddexpr <args>
+ command! -nargs=* -count Xolder <mods><count>colder <args>
+ command! -nargs=* Xnewer <mods>cnewer <args>
+ command! -nargs=* Xopen <mods> copen <args>
+ command! -nargs=* Xwindow <mods>cwindow <args>
+ command! -nargs=* Xbottom <mods>cbottom <args>
+ command! -nargs=* Xclose <mods>cclose <args>
+ command! -nargs=* -bang Xfile <mods>cfile<bang> <args>
+ command! -nargs=* Xgetfile <mods>cgetfile <args>
+ command! -nargs=* Xaddfile <mods>caddfile <args>
+ command! -nargs=* -bang Xbuffer <mods>cbuffer<bang> <args>
+ command! -nargs=* Xgetbuffer <mods>cgetbuffer <args>
+ command! -nargs=* Xaddbuffer <mods>caddbuffer <args>
+ command! -nargs=* Xrewind <mods>crewind <args>
+ command! -count -nargs=* -bang Xnext <mods><count>cnext<bang> <args>
+ command! -count -nargs=* -bang Xprev <mods><count>cprev<bang> <args>
+ command! -nargs=* -bang Xfirst <mods>cfirst<bang> <args>
+ command! -nargs=* -bang Xlast <mods>clast<bang> <args>
+ command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args>
+ command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
+ command! -nargs=* Xexpr <mods>cexpr <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>vimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args>
+ command! -nargs=* Xgrep <mods> grep <args>
+ command! -nargs=* Xgrepadd <mods> grepadd <args>
+ command! -nargs=* Xhelpgrep helpgrep <args>
+ command! -nargs=0 -count Xcc <count>cc
+ command! -count=1 -nargs=0 Xbelow <mods><count>cbelow
+ command! -count=1 -nargs=0 Xabove <mods><count>cabove
+ command! -count=1 -nargs=0 Xbefore <mods><count>cbefore
+ command! -count=1 -nargs=0 Xafter <mods><count>cafter
+ let g:Xgetlist = function('getqflist')
+ let g:Xsetlist = function('setqflist')
+ call setqflist([], 'f')
+ else
+ command! -nargs=* -bang Xlist <mods>llist<bang> <args>
+ command! -nargs=* Xgetexpr <mods>lgetexpr <args>
+ command! -nargs=* Xaddexpr <mods>laddexpr <args>
+ command! -nargs=* -count Xolder <mods><count>lolder <args>
+ command! -nargs=* Xnewer <mods>lnewer <args>
+ command! -nargs=* Xopen <mods> lopen <args>
+ command! -nargs=* Xwindow <mods>lwindow <args>
+ command! -nargs=* Xbottom <mods>lbottom <args>
+ command! -nargs=* Xclose <mods>lclose <args>
+ command! -nargs=* -bang Xfile <mods>lfile<bang> <args>
+ command! -nargs=* Xgetfile <mods>lgetfile <args>
+ command! -nargs=* Xaddfile <mods>laddfile <args>
+ command! -nargs=* -bang Xbuffer <mods>lbuffer<bang> <args>
+ command! -nargs=* Xgetbuffer <mods>lgetbuffer <args>
+ command! -nargs=* Xaddbuffer <mods>laddbuffer <args>
+ command! -nargs=* Xrewind <mods>lrewind <args>
+ command! -count -nargs=* -bang Xnext <mods><count>lnext<bang> <args>
+ command! -count -nargs=* -bang Xprev <mods><count>lprev<bang> <args>
+ command! -nargs=* -bang Xfirst <mods>lfirst<bang> <args>
+ command! -nargs=* -bang Xlast <mods>llast<bang> <args>
+ command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args>
+ command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
+ command! -nargs=* Xexpr <mods>lexpr <args>
+ command! -count=999 -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args>
+ command! -nargs=* Xgrep <mods> lgrep <args>
+ command! -nargs=* Xgrepadd <mods> lgrepadd <args>
+ command! -nargs=* Xhelpgrep lhelpgrep <args>
+ command! -nargs=0 -count Xcc <count>ll
+ command! -count=1 -nargs=0 Xbelow <mods><count>lbelow
+ command! -count=1 -nargs=0 Xabove <mods><count>labove
+ command! -count=1 -nargs=0 Xbefore <mods><count>lbefore
+ command! -count=1 -nargs=0 Xafter <mods><count>lafter
+ let g:Xgetlist = function('getloclist', [0])
+ let g:Xsetlist = function('setloclist', [0])
+ call setloclist(0, [], 'f')
+ endif
+endfunc
+
+" This must be run before any error lists are created.
+func Test_AA_cc_no_errors()
+ call assert_fails('cc', 'E42:')
+ call assert_fails('ll', 'E42:')
+endfunc
+
+" Tests for the :clist and :llist commands
+func XlistTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ if a:cchar == 'l'
+ call assert_fails('llist', 'E776:')
+ endif
+ " With an empty list, command should return error
+ Xgetexpr []
+ silent! Xlist
+ call assert_true(v:errmsg ==# 'E42: No Errors')
+
+ " Populate the list and then try
+ let lines =<< trim END
+ non-error 1
+ Xtestfile1:1:3:Line1
+ non-error 2
+ Xtestfile2:2:2:Line2
+ non-error| 3
+ Xtestfile3:3:1:Line3
+ END
+ Xgetexpr lines
+
+ " List only valid entries
+ let l = split(execute('Xlist', ''), "\n")
+ call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
+ \ ' 4 Xtestfile2:2 col 2: Line2',
+ \ ' 6 Xtestfile3:3 col 1: Line3'], l)
+
+ " List all the entries
+ let l = split(execute('Xlist!', ''), "\n")
+ call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1',
+ \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2',
+ \ ' 5: non-error| 3', ' 6 Xtestfile3:3 col 1: Line3'], l)
+
+ " List a range of errors
+ let l = split(execute('Xlist 3,6', ''), "\n")
+ call assert_equal([' 4 Xtestfile2:2 col 2: Line2',
+ \ ' 6 Xtestfile3:3 col 1: Line3'], l)
+
+ let l = split(execute('Xlist! 3,4', ''), "\n")
+ call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
+
+ let l = split(execute('Xlist -6,-4', ''), "\n")
+ call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l)
+
+ let l = split(execute('Xlist! -5,-3', ''), "\n")
+ call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
+ \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
+
+ " Test for '+'
+ let l = split(execute('Xlist! +2', ''), "\n")
+ call assert_equal([' 2 Xtestfile1:1 col 3: Line1',
+ \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l)
+
+ " Ranged entries
+ call g:Xsetlist([{'lnum':10,'text':'Line1'},
+ \ {'lnum':20,'col':10,'text':'Line2'},
+ \ {'lnum':30,'col':15,'end_col':20,'text':'Line3'},
+ \ {'lnum':40,'end_lnum':45,'text':'Line4'},
+ \ {'lnum':50,'end_lnum':55,'col':15,'text':'Line5'},
+ \ {'lnum':60,'end_lnum':65,'col':25,'end_col':35,'text':'Line6'}])
+ let l = split(execute('Xlist', ""), "\n")
+ call assert_equal([' 1:10: Line1',
+ \ ' 2:20 col 10: Line2',
+ \ ' 3:30 col 15-20: Line3',
+ \ ' 4:40-45: Line4',
+ \ ' 5:50-55 col 15: Line5',
+ \ ' 6:60-65 col 25-35: Line6'], l)
+
+ " Different types of errors
+ call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11},
+ \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22},
+ \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33},
+ \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44},
+ \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55}])
+ let l = split(execute('Xlist', ""), "\n")
+ call assert_equal([' 1:10 col 5 warning 11: Warning',
+ \ ' 2:20 col 10 error 22: Error',
+ \ ' 3:30 col 15 info 33: Info',
+ \ ' 4:40 col 20 x 44: Other',
+ \ ' 5:50 col 25 55: one'], l)
+
+ " Test for module names, one needs to explicitly set `'valid':v:true` so
+ call g:Xsetlist([
+ \ {'lnum':10,'col':5,'type':'W','module':'Data.Text','text':'ModuleWarning','nr':11,'valid':v:true},
+ \ {'lnum':20,'col':10,'type':'W','module':'Data.Text','filename':'Data/Text.hs','text':'ModuleWarning','nr':22,'valid':v:true},
+ \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}])
+ let l = split(execute('Xlist', ""), "\n")
+ call assert_equal([' 1 Data.Text:10 col 5 warning 11: ModuleWarning',
+ \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning',
+ \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l)
+
+ " Very long line should be displayed.
+ let text = 'Line' .. repeat('1234567890', 130)
+ let lines = ['Xtestfile9:2:9:' .. text]
+ Xgetexpr lines
+
+ let l = split(execute('Xlist', ''), "\n")
+ call assert_equal([' 1 Xtestfile9:2 col 9: ' .. text] , l)
+
+ " For help entries in the quickfix list, only the filename without directory
+ " should be displayed
+ Xhelpgrep setqflist()
+ let l = split(execute('Xlist 1', ''), "\n")
+ call assert_match('^ 1 [^\\/]\{-}:', l[0])
+
+ " Error cases
+ call assert_fails('Xlist abc', 'E488:')
+endfunc
+
+func Test_clist()
+ call XlistTests('c')
+ call XlistTests('l')
+endfunc
+
+" Tests for the :colder, :cnewer, :lolder and :lnewer commands
+" Note that this test assumes that a quickfix/location list is
+" already set by the caller.
+func XageTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ if a:cchar == 'l'
+ " No location list for the current window
+ call assert_fails('lolder', 'E776:')
+ call assert_fails('lnewer', 'E776:')
+ endif
+
+ let list = [{'bufnr': bufnr('%'), 'lnum': 1}]
+ call g:Xsetlist(list)
+
+ " Jumping to a non existent list should return error
+ silent! Xolder 99
+ call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack')
+
+ silent! Xnewer 99
+ call assert_true(v:errmsg ==# 'E381: At top of quickfix stack')
+
+ " Add three quickfix/location lists
+ Xgetexpr ['Xtestfile1:1:3:Line1']
+ Xgetexpr ['Xtestfile2:2:2:Line2']
+ Xgetexpr ['Xtestfile3:3:1:Line3']
+
+ " Go back two lists
+ Xolder
+ let l = g:Xgetlist()
+ call assert_equal('Line2', l[0].text)
+
+ " Go forward two lists
+ Xnewer
+ let l = g:Xgetlist()
+ call assert_equal('Line3', l[0].text)
+
+ " Test for the optional count argument
+ Xolder 2
+ let l = g:Xgetlist()
+ call assert_equal('Line1', l[0].text)
+
+ Xnewer 2
+ let l = g:Xgetlist()
+ call assert_equal('Line3', l[0].text)
+endfunc
+
+func Test_cage()
+ call XageTests('c')
+ call XageTests('l')
+endfunc
+
+" Tests for the :cwindow, :lwindow :cclose, :lclose, :copen and :lopen
+" commands
+func XwindowTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Opening the location list window without any errors should fail
+ if a:cchar == 'l'
+ call assert_fails('lopen', 'E776:')
+ call assert_fails('lwindow', 'E776:')
+ endif
+
+ " Create a list with no valid entries
+ Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3']
+
+ " Quickfix/Location window should not open with no valid errors
+ Xwindow
+ call assert_true(winnr('$') == 1)
+
+ " Create a list with valid entries
+ let lines =<< trim END
+ Xtestfile1:1:3:Line1
+ Xtestfile2:2:2:Line2
+ Xtestfile3:3:1:Line3
+ END
+ Xgetexpr lines
+
+ " Open the window
+ Xwindow
+ call assert_true(winnr('$') == 2 && winnr() == 2 &&
+ \ getline('.') ==# 'Xtestfile1|1 col 3| Line1')
+ redraw!
+
+ " Close the window
+ Xclose
+ call assert_true(winnr('$') == 1)
+
+ " Create a list with no valid entries
+ Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3']
+
+ " Open the window
+ Xopen 5
+ call assert_true(winnr('$') == 2 && getline('.') ==# '|| non-error 1'
+ \ && winheight(0) == 5)
+
+ " Opening the window again, should move the cursor to that window
+ wincmd t
+ Xopen 7
+ call assert_true(winnr('$') == 2 && winnr() == 2 &&
+ \ winheight(0) == 7 &&
+ \ getline('.') ==# '|| non-error 1')
+
+ " :cnext in quickfix window should move to the next entry
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+
+ " Calling cwindow should close the quickfix window with no valid errors
+ Xwindow
+ call assert_true(winnr('$') == 1)
+
+ " Specifying the width should adjust the width for a vertically split
+ " quickfix window.
+ vert Xopen
+ call assert_equal(10, winwidth(0))
+ vert Xopen 12
+ call assert_equal(12, winwidth(0))
+ Xclose
+
+ " Horizontally or vertically splitting the quickfix window should create a
+ " normal window/buffer
+ Xopen
+ wincmd s
+ call assert_equal(0, getwininfo(win_getid())[0].quickfix)
+ call assert_equal(0, getwininfo(win_getid())[0].loclist)
+ call assert_notequal('quickfix', &buftype)
+ close
+ Xopen
+ wincmd v
+ call assert_equal(0, getwininfo(win_getid())[0].quickfix)
+ call assert_equal(0, getwininfo(win_getid())[0].loclist)
+ call assert_notequal('quickfix', &buftype)
+ close
+ Xopen
+ Xclose
+
+ if a:cchar == 'c'
+ " Opening the quickfix window in multiple tab pages should reuse the
+ " quickfix buffer
+ let lines =<< trim END
+ Xtestfile1:1:3:Line1
+ Xtestfile2:2:2:Line2
+ Xtestfile3:3:1:Line3
+ END
+ Xgetexpr lines
+ Xopen
+ let qfbufnum = bufnr('%')
+ tabnew
+ Xopen
+ call assert_equal(qfbufnum, bufnr('%'))
+ new | only | tabonly
+ endif
+endfunc
+
+func Test_cwindow()
+ call XwindowTests('c')
+ call XwindowTests('l')
+endfunc
+
+func Test_copenHeight()
+ copen
+ wincmd H
+ let height = winheight(0)
+ copen 10
+ call assert_equal(height, winheight(0))
+ quit
+endfunc
+
+func Test_copenHeight_tabline()
+ set tabline=foo showtabline=2
+ copen
+ wincmd H
+ let height = winheight(0)
+ copen 10
+ call assert_equal(height, winheight(0))
+ quit
+ set tabline& showtabline&
+endfunc
+
+" Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile
+" commands.
+func XfileTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ let lines =<< trim END
+ Xtestfile1:700:10:Line 700
+ Xtestfile2:800:15:Line 800
+ END
+ call writefile(lines, 'Xqftestfile1', 'D')
+
+ enew!
+ Xfile Xqftestfile1
+ let l = g:Xgetlist()
+ call assert_true(len(l) == 2 &&
+ \ l[0].lnum == 700 && l[0].col == 10 && l[0].text ==# 'Line 700' &&
+ \ l[1].lnum == 800 && l[1].col == 15 && l[1].text ==# 'Line 800')
+
+ " Test with a non existent file
+ call assert_fails('Xfile non_existent_file', 'E40:')
+
+ " Run cfile/lfile from a modified buffer
+ enew!
+ silent! put ='Quickfix'
+ silent! Xfile Xqftestfile1
+ call assert_true(v:errmsg ==# 'E37: No write since last change (add ! to override)')
+
+ call writefile(['Xtestfile3:900:30:Line 900'], 'Xqftestfile1')
+ Xaddfile Xqftestfile1
+ let l = g:Xgetlist()
+ call assert_true(len(l) == 3 &&
+ \ l[2].lnum == 900 && l[2].col == 30 && l[2].text ==# 'Line 900')
+
+ let lines =<< trim END
+ Xtestfile1:222:77:Line 222
+ Xtestfile2:333:88:Line 333
+ END
+ call writefile(lines, 'Xqftestfile1')
+
+ enew!
+ Xgetfile Xqftestfile1
+ let l = g:Xgetlist()
+ call assert_true(len(l) == 2 &&
+ \ l[0].lnum == 222 && l[0].col == 77 && l[0].text ==# 'Line 222' &&
+ \ l[1].lnum == 333 && l[1].col == 88 && l[1].text ==# 'Line 333')
+
+ " Test for a file with a long line and without a newline at the end
+ let text = repeat('x', 1024)
+ let t = 'a.txt:18:' . text
+ call writefile([t], 'Xqftestfile1', 'b')
+ silent! Xfile Xqftestfile1
+ call assert_equal(text, g:Xgetlist()[0].text)
+endfunc
+
+func Test_cfile()
+ call XfileTests('c')
+ call XfileTests('l')
+endfunc
+
+" Tests for the :cbuffer, :lbuffer, :caddbuffer, :laddbuffer, :cgetbuffer and
+" :lgetbuffer commands.
+func XbufferTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ enew!
+ let lines =<< trim END
+ Xtestfile7:700:10:Line 700
+ Xtestfile8:800:15:Line 800
+ END
+ silent! call setline(1, lines)
+ Xbuffer!
+ let l = g:Xgetlist()
+ call assert_true(len(l) == 2 &&
+ \ l[0].lnum == 700 && l[0].col == 10 && l[0].text ==# 'Line 700' &&
+ \ l[1].lnum == 800 && l[1].col == 15 && l[1].text ==# 'Line 800')
+
+ enew!
+ let lines =<< trim END
+ Xtestfile9:900:55:Line 900
+ Xtestfile10:950:66:Line 950
+ END
+ silent! call setline(1, lines)
+ Xgetbuffer
+ let l = g:Xgetlist()
+ call assert_true(len(l) == 2 &&
+ \ l[0].lnum == 900 && l[0].col == 55 && l[0].text ==# 'Line 900' &&
+ \ l[1].lnum == 950 && l[1].col == 66 && l[1].text ==# 'Line 950')
+
+ enew!
+ let lines =<< trim END
+ Xtestfile11:700:20:Line 700
+ Xtestfile12:750:25:Line 750
+ END
+ silent! call setline(1, lines)
+ Xaddbuffer
+ let l = g:Xgetlist()
+ call assert_true(len(l) == 4 &&
+ \ l[1].lnum == 950 && l[1].col == 66 && l[1].text ==# 'Line 950' &&
+ \ l[2].lnum == 700 && l[2].col == 20 && l[2].text ==# 'Line 700' &&
+ \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750')
+ enew!
+
+ " Check for invalid buffer
+ call assert_fails('Xbuffer 199', 'E474:')
+
+ " Check for unloaded buffer
+ edit Xtestfile1
+ let bnr = bufnr('%')
+ enew!
+ call assert_fails('Xbuffer ' . bnr, 'E681:')
+
+ " Check for invalid range
+ " Using Xbuffer will not run the range check in the cbuffer/lbuffer
+ " commands. So directly call the commands.
+ if (a:cchar == 'c')
+ call assert_fails('900,999cbuffer', 'E16:')
+ else
+ call assert_fails('900,999lbuffer', 'E16:')
+ endif
+endfunc
+
+func Test_cbuffer()
+ call XbufferTests('c')
+ call XbufferTests('l')
+endfunc
+
+func XexprTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call assert_fails('Xexpr 10', 'E777:')
+endfunc
+
+func Test_cexpr()
+ call XexprTests('c')
+ call XexprTests('l')
+endfunc
+
+" Tests for :cnext, :cprev, :cfirst, :clast commands
+func Xtest_browse(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ " Jumping to first or next location list entry without any error should
+ " result in failure
+ if a:cchar == 'c'
+ let err = 'E42:'
+ let cmd = '$cc'
+ else
+ let err = 'E776:'
+ let cmd = '$ll'
+ endif
+ call assert_fails('Xnext', err)
+ call assert_fails('Xprev', err)
+ call assert_fails('Xnfile', err)
+ call assert_fails('Xpfile', err)
+ call assert_fails(cmd, err)
+
+ Xexpr ''
+ call assert_fails(cmd, 'E42:')
+
+ call s:create_test_file('Xqftestfile1')
+ call s:create_test_file('Xqftestfile2')
+
+ let lines =<< trim END
+ Xqftestfile1:5:Line5
+ Xqftestfile1:6:Line6
+ Xqftestfile2:10:Line10
+ Xqftestfile2:11:Line11
+ RegularLine1
+ RegularLine2
+ END
+ Xgetexpr lines
+
+ Xfirst
+ call assert_fails('-5Xcc', 'E16:')
+ call assert_fails('Xprev', 'E553:')
+ call assert_fails('Xpfile', 'E553:')
+ Xnfile
+ call assert_equal('Xqftestfile2', @%)
+ call assert_equal(10, line('.'))
+ Xpfile
+ call assert_equal('Xqftestfile1', @%)
+ call assert_equal(6, line('.'))
+ 5Xcc
+ call assert_equal(5, g:Xgetlist({'idx':0}).idx)
+ 2Xcc
+ call assert_equal(2, g:Xgetlist({'idx':0}).idx)
+ if a:cchar == 'c'
+ cc
+ else
+ ll
+ endif
+ call assert_equal(2, g:Xgetlist({'idx':0}).idx)
+ 10Xcc
+ call assert_equal(6, g:Xgetlist({'idx':0}).idx)
+ Xlast
+ Xprev
+ call assert_equal('Xqftestfile2', @%)
+ call assert_equal(11, line('.'))
+ call assert_fails('Xnext', 'E553:')
+ call assert_fails('Xnfile', 'E553:')
+ " To process the range using quickfix list entries, directly use the
+ " quickfix commands (don't use the user defined commands)
+ if a:cchar == 'c'
+ $cc
+ else
+ $ll
+ endif
+ call assert_equal(6, g:Xgetlist({'idx':0}).idx)
+ Xrewind
+ call assert_equal('Xqftestfile1', @%)
+ call assert_equal(5, line('.'))
+
+ 10Xnext
+ call assert_equal('Xqftestfile2', @%)
+ call assert_equal(11, line('.'))
+ 10Xprev
+ call assert_equal('Xqftestfile1', @%)
+ call assert_equal(5, line('.'))
+
+ " Jumping to an error from the error window using cc command
+ let lines =<< trim END
+ Xqftestfile1:5:Line5
+ Xqftestfile1:6:Line6
+ Xqftestfile2:10:Line10
+ Xqftestfile2:11:Line11
+ END
+ Xgetexpr lines
+ Xopen
+ 10Xcc
+ call assert_equal(11, line('.'))
+ call assert_equal('Xqftestfile2', @%)
+ Xopen
+ call cursor(2, 1)
+ if a:cchar == 'c'
+ .cc
+ else
+ .ll
+ endif
+ call assert_equal(6, line('.'))
+ call assert_equal('Xqftestfile1', @%)
+
+ " Jumping to an error from the error window (when only the error window is
+ " present)
+ Xopen | only
+ Xlast 1
+ call assert_equal(5, line('.'))
+ call assert_equal('Xqftestfile1', @%)
+
+ Xexpr ""
+ call assert_fails('Xnext', 'E42:')
+
+ call delete('Xqftestfile1')
+ call delete('Xqftestfile2')
+
+ " Should be able to use next/prev with invalid entries
+ Xexpr ""
+ call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal(0, g:Xgetlist({'size' : 0}).size)
+ Xaddexpr ['foo', 'bar', 'baz', 'quux', 'sh|moo']
+ call assert_equal(5, g:Xgetlist({'size' : 0}).size)
+ Xlast
+ call assert_equal(5, g:Xgetlist({'idx' : 0}).idx)
+ Xfirst
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+ 2Xnext
+ call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
+endfunc
+
+func Test_browse()
+ call Xtest_browse('c')
+ call Xtest_browse('l')
+endfunc
+
+" Test for memory allocation failures
+func Xnomem_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call test_alloc_fail(GetAllocId('qf_dirname_start'), 0, 0)
+ call assert_fails('Xvimgrep vim runtest.vim', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_dirname_now'), 0, 0)
+ call assert_fails('Xvimgrep vim runtest.vim', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_namebuf'), 0, 0)
+ call assert_fails('Xfile runtest.vim', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_errmsg'), 0, 0)
+ call assert_fails('Xfile runtest.vim', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_pattern'), 0, 0)
+ call assert_fails('Xfile runtest.vim', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_efm_fmtstr'), 0, 0)
+ set efm=%f
+ call assert_fails('Xexpr ["Xfile1"]', 'E342:')
+ set efm&
+
+ call test_alloc_fail(GetAllocId('qf_efm_fmtpart'), 0, 0)
+ set efm=%f:%l:%m,%f-%l-%m
+ call assert_fails('Xaddexpr ["Xfile2", "Xfile3"]', 'E342:')
+ set efm&
+
+ call test_alloc_fail(GetAllocId('qf_title'), 0, 0)
+ call assert_fails('Xexpr ""', 'E342:')
+ call assert_equal('', g:Xgetlist({'all': 1}).title)
+
+ call test_alloc_fail(GetAllocId('qf_mef_name'), 0, 0)
+ set makeef=Xtmp##.err
+ call assert_fails('Xgrep needle haystack', 'E342:')
+ set makeef&
+
+ call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0)
+ call assert_fails('Xexpr "Xfile1:10:Line10"', 'E342:')
+
+ if a:cchar == 'l'
+ for id in ['qf_qfline', 'qf_qfinfo']
+ lgetexpr ["Xfile1:10:L10", "Xfile2:20:L20"]
+ call test_alloc_fail(GetAllocId(id), 0, 0)
+ call assert_fails('new', 'E342:')
+ call assert_equal(2, winnr('$'))
+ call assert_equal([], getloclist(0))
+ %bw!
+ endfor
+ endif
+
+ call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0)
+ try
+ call assert_fails('Xvimgrep vim runtest.vim', 'E342:')
+ catch /^Vim:Interrupt$/
+ endtry
+
+ call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0)
+ try
+ call assert_fails('Xvimgrep /vim/f runtest.vim', 'E342:')
+ catch /^Vim:Interrupt$/
+ endtry
+
+ let l = getqflist({"lines": ["Xfile1:10:L10"]})
+ call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0)
+ call assert_fails('call g:Xsetlist(l.items)', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0)
+ try
+ call assert_fails('Xhelpgrep quickfix', 'E342:')
+ catch /^Vim:Interrupt$/
+ endtry
+
+ call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0)
+ call assert_fails('let l = g:Xgetlist({"lines": ["Xfile1:10:L10"]})', 'E342:')
+ call assert_equal(#{items: []}, l)
+
+ if a:cchar == 'l'
+ call setqflist([], 'f')
+ call setloclist(0, [], 'f')
+ call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0)
+ call assert_fails('lhelpgrep quickfix', 'E342:')
+ call assert_equal([], getloclist(0))
+
+ call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0)
+ call assert_fails('lvimgrep vim runtest.vim', 'E342:')
+
+ let l = getqflist({"lines": ["Xfile1:10:L10"]})
+ call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0)
+ call assert_fails('call setloclist(0, l.items)', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0)
+ call assert_fails('lbuffer', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0)
+ call assert_fails('lexpr ["Xfile1:10:L10", "Xfile2:20:L20"]', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0)
+ call assert_fails('lfile runtest.vim', 'E342:')
+ endif
+
+ call test_alloc_fail(GetAllocId('qf_dirstack'), 0, 0)
+ set efm=%DEntering\ dir\ %f,%f:%l:%m
+ call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E342:')
+ set efm&
+
+ call test_alloc_fail(GetAllocId('qf_dirstack'), 0, 0)
+ set efm=%+P[%f],(%l)%m
+ call assert_fails('Xexpr ["[runtest.vim]", "(1)Hello"]', 'E342:')
+ set efm&
+
+ call test_alloc_fail(GetAllocId('qf_multiline_pfx'), 0, 0)
+ set efm=%EError,%Cline\ %l,%Z%m
+ call assert_fails('Xexpr ["Error", "line 1", "msg"]', 'E342:')
+ set efm&
+
+ call test_alloc_fail(GetAllocId('qf_makecmd'), 0, 0)
+ call assert_fails('Xgrep vim runtest.vim', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0)
+ call assert_fails('Xexpr repeat("a", 8192)', 'E342:')
+
+ call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0)
+ call assert_fails('Xexpr [repeat("a", 8192)]', 'E342:')
+
+ new
+ call setline(1, repeat('a', 8192))
+ call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0)
+ call assert_fails('Xbuffer', 'E342:')
+ %bw!
+
+ call writefile([repeat('a', 8192)], 'Xtest', 'D')
+ call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0)
+ call assert_fails('Xfile Xtest', 'E342:')
+endfunc
+
+func Test_nomem()
+ call Xnomem_tests('c')
+ call Xnomem_tests('l')
+endfunc
+
+func s:test_xhelpgrep(cchar)
+ call s:setup_commands(a:cchar)
+ Xhelpgrep quickfix
+ Xopen
+ if a:cchar == 'c'
+ let title_text = ':helpgrep quickfix'
+ else
+ let title_text = ':lhelpgrep quickfix'
+ endif
+ call assert_true(w:quickfix_title =~ title_text, w:quickfix_title)
+
+ " Jumping to a help topic should open the help window
+ only
+ Xnext
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr('$') == 2)
+ " Jumping to the next match should reuse the help window
+ Xnext
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr() == 1)
+ call assert_true(winnr('$') == 2)
+ " Jumping to the next match from the quickfix window should reuse the help
+ " window
+ Xopen
+ Xnext
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr() == 1)
+ call assert_true(winnr('$') == 2)
+ call assert_match('|\d\+ col \d\+-\d\+|', getbufline(winbufnr(2), 1)[0])
+
+ " This wipes out the buffer, make sure that doesn't cause trouble.
+ Xclose
+
+ " When the current window is vertically split, jumping to a help match
+ " should open the help window at the top.
+ only | enew
+ let w1 = win_getid()
+ vert new
+ let w2 = win_getid()
+ Xnext
+ let w3 = win_getid()
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr() == 1)
+ " See jump_to_help_window() for details
+ let w2_width = winwidth(w2)
+ if w2_width != &columns && w2_width < 80
+ call assert_equal(['col', [['leaf', w3],
+ \ ['row', [['leaf', w2], ['leaf', w1]]]]], winlayout())
+ else
+ call assert_equal(['row', [['col', [['leaf', w3], ['leaf', w2]]],
+ \ ['leaf', w1]]] , winlayout())
+ endif
+
+ new | only
+ set buftype=help
+ set modified
+ call assert_fails('Xnext', 'E37:')
+ set nomodified
+ new | only
+
+ if a:cchar == 'l'
+ " When a help window is present, running :lhelpgrep should reuse the
+ " help window and not the current window
+ new | only
+ call g:Xsetlist([], 'f')
+ help index.txt
+ wincmd w
+ lhelpgrep quickfix
+ call assert_equal(1, winnr())
+ call assert_notequal([], getloclist(1))
+ call assert_equal([], getloclist(2))
+ endif
+
+ new | only
+
+ " Search for non existing help string
+ call assert_fails('Xhelpgrep a1b2c3', 'E480:')
+ " Invalid regular expression
+ call assert_fails('Xhelpgrep \@<!', 'E866:')
+endfunc
+
+func Test_helpgrep()
+ call s:test_xhelpgrep('c')
+ helpclose
+ call s:test_xhelpgrep('l')
+endfunc
+
+def Test_helpgrep_vim9_restore_cpo()
+ assert_equal('aABceFs', &cpo)
+
+ var rtp_save = &rtp
+ var dir = 'Xruntime/after'
+ &rtp ..= ',' .. dir
+ mkdir(dir .. '/ftplugin', 'pR')
+ writefile(['vim9script'], dir .. '/ftplugin/qf.vim')
+ filetype plugin on
+ silent helpgrep grail
+ cwindow
+ silent helpgrep grail
+
+ assert_equal('aABceFs', &cpo)
+ &rtp = rtp_save
+ cclose
+ helpclose
+enddef
+
+" When running the :helpgrep command, if an autocmd modifies the 'cpoptions'
+" value, then Vim crashes. (issue fixed by 7.2b-004 and 8.2.4453)
+func Test_helpgrep_restore_cpo_aucmd()
+ let save_cpo = &cpo
+ augroup QF_Test
+ au!
+ autocmd BufNew * set cpo=acd
+ augroup END
+
+ helpgrep quickfix
+ call assert_equal('acd', &cpo)
+ %bw!
+
+ set cpo&vim
+ augroup QF_Test
+ au!
+ autocmd BufReadPost * set cpo=
+ augroup END
+
+ helpgrep buffer
+ call assert_equal('', &cpo)
+
+ augroup QF_Test
+ au!
+ augroup END
+ %bw!
+ let &cpo = save_cpo
+endfunc
+
+def Test_vim9_cexpr()
+ var text = 'somefile:95:error'
+ cexpr text
+ var l = getqflist()
+ assert_equal(1, l->len())
+ assert_equal(95, l[0].lnum)
+ assert_equal('error', l[0].text)
+
+ text = 'somefile:77:warning'
+ caddexpr text
+ l = getqflist()
+ assert_equal(2, l->len())
+ assert_equal(77, l[1].lnum)
+ assert_equal('warning', l[1].text)
+enddef
+
+func Test_errortitle()
+ augroup QfBufWinEnter
+ au!
+ au BufWinEnter * :let g:a=get(w:, 'quickfix_title', 'NONE')
+ augroup END
+ copen
+ let a=[{'lnum': 308, 'bufnr': bufnr(''), 'col': 58, 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', 'pattern': '', 'text': ' au BufWinEnter * :let g:a=get(w:, ''quickfix_title'', ''NONE'')'}]
+ call setqflist(a)
+ call assert_equal(':setqflist()', g:a)
+ augroup QfBufWinEnter
+ au!
+ augroup END
+ augroup! QfBufWinEnter
+endfunc
+
+func Test_vimgreptitle()
+ augroup QfBufWinEnter
+ au!
+ au BufWinEnter * :let g:a=get(w:, 'quickfix_title', 'NONE')
+ augroup END
+ try
+ vimgrep /pattern/j file
+ catch /E480/
+ endtry
+ copen
+ call assert_equal(': vimgrep /pattern/j file', g:a)
+ augroup QfBufWinEnter
+ au!
+ augroup END
+ augroup! QfBufWinEnter
+endfunc
+
+func Test_bufwinenter_once()
+ augroup QfBufWinEnter
+ au!
+ au BufWinEnter * let g:got_afile ..= 'got ' .. expand('<afile>')
+ augroup END
+ let g:got_afile = ''
+ copen
+ call assert_equal('got quickfix', g:got_afile)
+
+ cclose
+ unlet g:got_afile
+ augroup QfBufWinEnter
+ au!
+ augroup END
+ augroup! QfBufWinEnter
+endfunc
+
+func XqfTitleTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xgetexpr ['file:1:1:message']
+ let l = g:Xgetlist()
+ if a:cchar == 'c'
+ call setqflist(l, 'r')
+ else
+ call setloclist(0, l, 'r')
+ endif
+
+ Xopen
+ if a:cchar == 'c'
+ let title = ':setqflist()'
+ else
+ let title = ':setloclist()'
+ endif
+ call assert_equal(title, w:quickfix_title)
+ Xclose
+endfunc
+
+" Tests for quickfix window's title
+func Test_qf_title()
+ call XqfTitleTests('c')
+ call XqfTitleTests('l')
+endfunc
+
+" Tests for 'errorformat'
+func Test_efm()
+ let save_efm = &efm
+ set efm=%EEEE%m,%WWWW%m,%+CCCC%.%#,%-GGGG%.%#
+ cgetexpr ['WWWW', 'EEEE', 'CCCC']
+ let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))
+ call assert_equal("[['W', 1], ['E^@CCCC', 1]]", l)
+ cgetexpr ['WWWW', 'GGGG', 'EEEE', 'CCCC']
+ let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))
+ call assert_equal("[['W', 1], ['E^@CCCC', 1]]", l)
+ cgetexpr ['WWWW', 'GGGG', 'ZZZZ', 'EEEE', 'CCCC', 'YYYY']
+ let l = strtrans(string(map(getqflist(), '[v:val.text, v:val.valid]')))
+ call assert_equal("[['W', 1], ['ZZZZ', 0], ['E^@CCCC', 1], ['YYYY', 0]]", l)
+ let &efm = save_efm
+endfunc
+
+" This will test for problems in quickfix:
+" A. incorrectly copying location lists which caused the location list to show
+" a different name than the file that was actually being displayed.
+" B. not reusing the window for which the location list window is opened but
+" instead creating new windows.
+" C. make sure that the location list window is not reused instead of the
+" window it belongs to.
+"
+" Set up the test environment:
+func ReadTestProtocol(name)
+ let base = substitute(a:name, '\v^test://(.*)%(\.[^.]+)?', '\1', '')
+ let word = substitute(base, '\v(.*)\..*', '\1', '')
+
+ setl modifiable
+ setl noreadonly
+ setl noswapfile
+ setl bufhidden=delete
+ %del _
+ " For problem 2:
+ " 'buftype' has to be set to reproduce the constant opening of new windows
+ setl buftype=nofile
+
+ call setline(1, word)
+
+ setl nomodified
+ setl nomodifiable
+ setl readonly
+ exe 'doautocmd BufRead ' . substitute(a:name, '\v^test://(.*)', '\1', '')
+endfunc
+
+func Test_locationlist()
+ enew
+
+ augroup testgroup
+ au!
+ autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>"))
+ augroup END
+
+ let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ]
+
+ let qflist = []
+ for word in words
+ call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', })
+ " NOTE: problem 1:
+ " intentionally not setting 'lnum' so that the quickfix entries are not
+ " valid
+ eval qflist->setloclist(0, ' ')
+ endfor
+
+ " Test A
+ lrewind
+ enew
+ lopen
+ 4lnext
+ vert split
+ wincmd L
+ lopen
+ wincmd p
+ lnext
+ let fileName = expand("%")
+ wincmd p
+ let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '')
+ let fileName = substitute(fileName, '\\', '/', 'g')
+ let locationListFileName = substitute(locationListFileName, '\\', '/', 'g')
+ call assert_equal("test://bar.txt", fileName)
+ call assert_equal("test://bar.txt", locationListFileName)
+
+ wincmd n | only
+
+ " Test B:
+ lrewind
+ lopen
+ 2
+ exe "normal \<CR>"
+ wincmd p
+ 3
+ exe "normal \<CR>"
+ wincmd p
+ 4
+ exe "normal \<CR>"
+ call assert_equal(2, winnr('$'))
+ wincmd n | only
+
+ " Test C:
+ lrewind
+ lopen
+ " Let's move the location list window to the top to check whether it (the
+ " first window found) will be reused when we try to open new windows:
+ wincmd K
+ 2
+ exe "normal \<CR>"
+ wincmd p
+ 3
+ exe "normal \<CR>"
+ wincmd p
+ 4
+ exe "normal \<CR>"
+ 1wincmd w
+ call assert_equal('quickfix', &buftype)
+ 2wincmd w
+ let bufferName = expand("%")
+ let bufferName = substitute(bufferName, '\\', '/', 'g')
+ call assert_equal('test://quux.txt', bufferName)
+
+ wincmd n | only
+
+ augroup! testgroup
+endfunc
+
+func Test_locationlist_curwin_was_closed()
+ augroup testgroup
+ au!
+ autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>"))
+ augroup END
+
+ func! R(n)
+ quit
+ endfunc
+
+ new
+ let q = []
+ call add(q, {'filename': 'test_curwin.txt' })
+ call setloclist(0, q)
+ call assert_fails('lrewind', 'E924:')
+
+ augroup! testgroup
+ delfunc R
+endfunc
+
+func Test_locationlist_cross_tab_jump()
+ call writefile(['loclistfoo'], 'loclistfoo', 'D')
+ call writefile(['loclistbar'], 'loclistbar', 'D')
+ set switchbuf=usetab
+
+ edit loclistfoo
+ tabedit loclistbar
+ silent lgrep loclistfoo loclist*
+ call assert_equal(1, tabpagenr())
+
+ enew | only | tabonly
+ set switchbuf&vim
+endfunc
+
+" More tests for 'errorformat'
+func Test_efm1()
+ " The 'errorformat' setting is different on non-Unix systems.
+ " This test works only on Unix-like systems.
+ CheckUnix
+
+ let l =<< trim [DATA]
+ "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set.
+ "Xtestfile", line 6 col 19; this is an error
+ gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c
+ Xtestfile:9: parse error before `asd'
+ make: *** [vim] Error 1
+ in file "Xtestfile" linenr 10: there is an error
+
+ 2 returned
+ "Xtestfile", line 11 col 1; this is an error
+ "Xtestfile", line 12 col 2; this is another error
+ "Xtestfile", line 14:10; this is an error in column 10
+ =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time
+ "Xtestfile", linenr 16: yet another problem
+ Error in "Xtestfile" at line 17:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17
+ ^
+ Error in "Xtestfile" at line 18:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18
+ .............^
+ Error in "Xtestfile" at line 19:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19
+ --------------^
+ Error in "Xtestfile" at line 20:
+ x should be a dot
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20
+ ^
+
+ Does anyone know what is the problem and how to correction it?
+ "Xtestfile", line 21 col 9: What is the title of the quickfix window?
+ "Xtestfile", line 22 col 9: What is the title of the quickfix window?
+ [DATA]
+
+ call writefile(l, 'Xerrorfile1', 'D')
+ call delete('loclistbar')
+ call writefile(l[:-2], 'Xerrorfile2', 'D')
+
+ let m =<< [DATA]
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22
+[DATA]
+ call writefile(m, 'Xtestfile', 'D')
+
+ let save_efm = &efm
+ set efm+==%f=\\,\ line\ %l%*\\D%v%*[^\ ]\ %m
+ set efm^=%AError\ in\ \"%f\"\ at\ line\ %l:,%Z%p^,%C%m
+
+ exe 'cf Xerrorfile2'
+ clast
+ copen
+ call assert_equal(':cf Xerrorfile2', w:quickfix_title)
+ wincmd p
+
+ exe 'cf Xerrorfile1'
+ call assert_equal([4, 12], [line('.'), col('.')])
+ cn
+ call assert_equal([6, 19], [line('.'), col('.')])
+ cn
+ call assert_equal([9, 2], [line('.'), col('.')])
+ cn
+ call assert_equal([10, 2], [line('.'), col('.')])
+ cn
+ call assert_equal([11, 1], [line('.'), col('.')])
+ cn
+ call assert_equal([12, 2], [line('.'), col('.')])
+ cn
+ call assert_equal([14, 10], [line('.'), col('.')])
+ cn
+ call assert_equal([15, 3, 10], [line('.'), col('.'), virtcol('.')])
+ cn
+ call assert_equal([16, 2], [line('.'), col('.')])
+ cn
+ call assert_equal([17, 6], [line('.'), col('.')])
+ cn
+ call assert_equal([18, 7], [line('.'), col('.')])
+ cn
+ call assert_equal([19, 8], [line('.'), col('.')])
+ cn
+ call assert_equal([20, 9], [line('.'), col('.')])
+ clast
+ cprev
+ cprev
+ wincmd w
+ call assert_equal(':cf Xerrorfile1', w:quickfix_title)
+ wincmd p
+
+ let &efm = save_efm
+endfunc
+
+" Test for quickfix directory stack support
+func s:dir_stack_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ let save_efm=&efm
+ set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
+
+ let lines =<< trim END
+ Entering dir 'dir1/a'
+ habits2.txt:1:Nine Healthy Habits
+ Entering dir 'b'
+ habits3.txt:2:0 Hours of television
+ habits2.txt:7:5 Small meals
+ Entering dir 'dir1/c'
+ habits4.txt:3:1 Hour of exercise
+ Leaving dir 'dir1/c'
+ Leaving dir 'dir1/a'
+ habits1.txt:4:2 Liters of water
+ Entering dir 'dir2'
+ habits5.txt:5:3 Cups of hot green tea
+ Leaving dir 'dir2'
+ END
+
+ Xexpr ""
+ for l in lines
+ Xaddexpr l
+ endfor
+
+ let qf = g:Xgetlist()
+
+ call assert_equal('dir1/a/habits2.txt', bufname(qf[1].bufnr))
+ call assert_equal(1, qf[1].lnum)
+ call assert_equal('dir1/a/b/habits3.txt', bufname(qf[3].bufnr))
+ call assert_equal(2, qf[3].lnum)
+ call assert_equal('dir1/a/habits2.txt', bufname(qf[4].bufnr))
+ call assert_equal(7, qf[4].lnum)
+ call assert_equal('dir1/c/habits4.txt', bufname(qf[6].bufnr))
+ call assert_equal(3, qf[6].lnum)
+ call assert_equal('habits1.txt', bufname(qf[9].bufnr))
+ call assert_equal(4, qf[9].lnum)
+ call assert_equal('dir2/habits5.txt', bufname(qf[11].bufnr))
+ call assert_equal(5, qf[11].lnum)
+
+ let &efm=save_efm
+endfunc
+
+" Tests for %D and %X errorformat options
+func Test_efm_dirstack()
+ " Create the directory stack and files
+ call mkdir('dir1', 'R')
+ call mkdir('dir1/a')
+ call mkdir('dir1/a/b')
+ call mkdir('dir1/c')
+ call mkdir('dir2', 'R')
+
+ let lines =<< trim END
+ Nine Healthy Habits
+ 0 Hours of television
+ 1 Hour of exercise
+ 2 Liters of water
+ 3 Cups of hot green tea
+ 4 Short mental breaks
+ 5 Small meals
+ 6 AM wake up time
+ 7 Minutes of laughter
+ 8 Hours of sleep (at least)
+ 9 PM end of the day and off to bed
+ END
+ call writefile(lines, 'habits1.txt', 'D')
+ call writefile(lines, 'dir1/a/habits2.txt')
+ call writefile(lines, 'dir1/a/b/habits3.txt')
+ call writefile(lines, 'dir1/c/habits4.txt')
+ call writefile(lines, 'dir2/habits5.txt')
+
+ call s:dir_stack_tests('c')
+ call s:dir_stack_tests('l')
+endfunc
+
+" Test for resync after continuing an ignored message
+func Xefm_ignore_continuations(cchar)
+ call s:setup_commands(a:cchar)
+
+ let save_efm = &efm
+
+ let &efm =
+ \ '%Eerror %m %l,' .
+ \ '%-Wignored %m %l,' .
+ \ '%+Cmore ignored %m %l,' .
+ \ '%Zignored end'
+ let lines =<< trim END
+ ignored warning 1
+ more ignored continuation 2
+ ignored end
+ error resync 4
+ END
+ Xgetexpr lines
+ let l = map(g:Xgetlist(), '[v:val.text, v:val.valid, v:val.lnum, v:val.type]')
+ call assert_equal([['resync', 1, 4, 'E']], l)
+
+ let &efm = save_efm
+endfunc
+
+func Test_efm_ignore_continuations()
+ call Xefm_ignore_continuations('c')
+ call Xefm_ignore_continuations('l')
+endfunc
+
+" Tests for invalid error format specifies
+func Xinvalid_efm_Tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ let save_efm = &efm
+
+ set efm=%f:%l:%m,%f:%f:%l:%m
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E372:')
+
+ set efm=%f:%l:%m,%f:%l:%r:%m
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E373:')
+
+ set efm=%f:%l:%m,%O:%f:%l:%m
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E373:')
+
+ set efm=%f:%l:%m,%f:%l:%*[^a-z
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E374:')
+
+ set efm=%f:%l:%m,%f:%l:%*c
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E375:')
+
+ set efm=%f:%l:%m,%L%M%N
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E376:')
+
+ set efm=%f:%l:%m,%f:%l:%m:%R
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E377:')
+
+ " Invalid regular expression
+ set efm=%\\%%k
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E867:')
+
+ set efm=
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E378:')
+
+ " Empty directory name. When there is an error in parsing new entries, make
+ " sure the previous quickfix list is made the current list.
+ set efm&
+ cexpr ["one", "two"]
+ let qf_id = getqflist(#{id: 0}).id
+ set efm=%DEntering\ dir\ abc,%f:%l:%m
+ call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E379:')
+ call assert_equal(qf_id, getqflist(#{id: 0}).id)
+
+ let &efm = save_efm
+endfunc
+
+func Test_invalid_efm()
+ call Xinvalid_efm_Tests('c')
+ call Xinvalid_efm_Tests('l')
+endfunc
+
+" TODO:
+" Add tests for the following formats in 'errorformat'
+" %r %O
+func Test_efm2()
+ let save_efm = &efm
+
+ " Test for %s format in efm
+ set efm=%f:%s
+ cexpr 'Xtestfile:Line search text'
+ let l = getqflist()
+ call assert_equal('^\VLine search text\$', l[0].pattern)
+ call assert_equal(0, l[0].lnum)
+
+ let l = split(execute('clist', ''), "\n")
+ call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l)
+
+ " Test for a long line
+ cexpr 'Xtestfile:' . repeat('a', 1026)
+ let l = getqflist()
+ call assert_equal('^\V' . repeat('a', 1019) . '\$', l[0].pattern)
+
+ " Test for %P, %Q and %t format specifiers
+ let lines =<< trim [DATA]
+ [Xtestfile1]
+ (1,17) error: ';' missing
+ (21,2) warning: variable 'z' not defined
+ (67,3) error: end of file found before string ended
+ --
+
+ [Xtestfile2]
+ --
+
+ [Xtestfile3]
+ NEW compiler v1.1
+ (2,2) warning: variable 'x' not defined
+ (67,3) warning: 's' already defined
+ --
+ [DATA]
+
+ set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r
+ " To exercise the push/pop file functionality in quickfix, the test files
+ " need to be created.
+ call writefile(['Line1'], 'Xtestfile1', 'D')
+ call writefile(['Line2'], 'Xtestfile2', 'D')
+ call writefile(['Line3'], 'Xtestfile3', 'D')
+ cexpr ""
+ for l in lines
+ caddexpr l
+ endfor
+ let l = getqflist()
+ call assert_equal(12, len(l))
+ call assert_equal(21, l[2].lnum)
+ call assert_equal(2, l[2].col)
+ call assert_equal('w', l[2].type)
+ call assert_equal('e', l[3].type)
+
+ " Test for %P, %Q with non-existing files
+ cexpr lines
+ let l = getqflist()
+ call assert_equal(14, len(l))
+ call assert_equal('[Xtestfile1]', l[0].text)
+ call assert_equal('[Xtestfile2]', l[6].text)
+ call assert_equal('[Xtestfile3]', l[9].text)
+
+ " Tests for %E, %C and %Z format specifiers
+ let lines =<< trim [DATA]
+ Error 275
+ line 42
+ column 3
+ ' ' expected after '--'
+ [DATA]
+
+ set efm=%EError\ %n,%Cline\ %l,%Ccolumn\ %c,%Z%m
+ cgetexpr lines
+ let l = getqflist()
+ call assert_equal(275, l[0].nr)
+ call assert_equal(42, l[0].lnum)
+ call assert_equal(3, l[0].col)
+ call assert_equal('E', l[0].type)
+ call assert_equal("\n' ' expected after '--'", l[0].text)
+
+ " Test for %>
+ let lines =<< trim [DATA]
+ Error in line 147 of foo.c:
+ unknown variable 'i'
+ [DATA]
+
+ set efm=unknown\ variable\ %m,%E%>Error\ in\ line\ %l\ of\ %f:,%Z%m
+ cgetexpr lines
+ let l = getqflist()
+ call assert_equal(147, l[0].lnum)
+ call assert_equal('E', l[0].type)
+ call assert_equal("\nunknown variable 'i'", l[0].text)
+
+ " Test for %A, %C and other formats
+ let lines =<< trim [DATA]
+ ==============================================================
+ FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)
+ --------------------------------------------------------------
+ Traceback (most recent call last):
+ File "unittests/dbfacadeTest.py", line 89, in testFoo
+ self.assertEquals(34, dtid)
+ File "/usr/lib/python2.2/unittest.py", line 286, in
+ failUnlessEqual
+ raise self.failureException, \\
+ W:AssertionError: 34 != 33
+
+ --------------------------------------------------------------
+ Ran 27 tests in 0.063s
+ [DATA]
+
+ set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%t:%m
+ cgetexpr lines
+ let l = getqflist()
+ call assert_equal(8, len(l))
+ call assert_equal(89, l[4].lnum)
+ call assert_equal(1, l[4].valid)
+ call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr))
+ call assert_equal('W', l[4].type)
+
+ " Test for %o
+ set efm=%f(%o):%l\ %m
+ cgetexpr ['Xotestfile(Language.PureScript.Types):20 Error']
+ call writefile(['Line1'], 'Xotestfile', 'D')
+ let l = getqflist()
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('Language.PureScript.Types', l[0].module)
+ copen
+ call assert_equal('Language.PureScript.Types|20| Error', getline(1))
+ call feedkeys("\<CR>", 'xn')
+ call assert_equal('Xotestfile', expand('%:t'))
+ cclose
+ bd
+
+ " Test for a long module name
+ cexpr 'Xtest(' . repeat('m', 1026) . '):15 message'
+ let l = getqflist()
+ call assert_equal(repeat('m', 1024), l[0].module)
+ call assert_equal(15, l[0].lnum)
+ call assert_equal('message', l[0].text)
+
+ " The following sequence of commands used to crash Vim
+ set efm=%W%m
+ cgetexpr ['msg1']
+ let l = getqflist()
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('msg1', l[0].text)
+ set efm=%C%m
+ lexpr 'msg2'
+ let l = getloclist(0)
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('msg2', l[0].text)
+ lopen
+ call setqflist([], 'r')
+ caddbuf
+ let l = getqflist()
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('|| msg2', l[0].text)
+
+ " When matching error lines, case should be ignored. Test for this.
+ set noignorecase
+ let l=getqflist({'lines' : ['Xtest:FOO10:Line 20'], 'efm':'%f:foo%l:%m'})
+ call assert_equal(10, l.items[0].lnum)
+ call assert_equal('Line 20', l.items[0].text)
+ set ignorecase&
+
+ new | only
+ let &efm = save_efm
+endfunc
+
+" Test for '%t' (error type) field in 'efm'
+func Test_efm_error_type()
+ let save_efm = &efm
+
+ " error type
+ set efm=%f:%l:%t:%m
+ let lines =<< trim END
+ Xfile1:10:E:msg1
+ Xfile1:20:W:msg2
+ Xfile1:30:I:msg3
+ Xfile1:40:N:msg4
+ Xfile1:50:R:msg5
+ END
+ cexpr lines
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:10 error: msg1',
+ \ ' 2 Xfile1:20 warning: msg2',
+ \ ' 3 Xfile1:30 info: msg3',
+ \ ' 4 Xfile1:40 note: msg4',
+ \ ' 5 Xfile1:50 R: msg5'], output)
+
+ " error type and a error number
+ set efm=%f:%l:%t:%n:%m
+ let lines =<< trim END
+ Xfile1:10:E:2:msg1
+ Xfile1:20:W:4:msg2
+ Xfile1:30:I:6:msg3
+ Xfile1:40:N:8:msg4
+ Xfile1:50:R:3:msg5
+ END
+ cexpr lines
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:10 error 2: msg1',
+ \ ' 2 Xfile1:20 warning 4: msg2',
+ \ ' 3 Xfile1:30 info 6: msg3',
+ \ ' 4 Xfile1:40 note 8: msg4',
+ \ ' 5 Xfile1:50 R 3: msg5'], output)
+ let &efm = save_efm
+endfunc
+
+" Test for end_lnum ('%e') and end_col ('%k') fields in 'efm'
+func Test_efm_end_lnum_col()
+ let save_efm = &efm
+
+ " single line
+ set efm=%f:%l-%e:%c-%k:%t:%m
+ cexpr ["Xfile1:10-20:1-2:E:msg1", "Xfile1:20-30:2-3:W:msg2",]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:10-20 col 1-2 error: msg1',
+ \ ' 2 Xfile1:20-30 col 2-3 warning: msg2'], output)
+
+ " multiple lines
+ set efm=%A%n)%m,%Z%f:%l-%e:%c-%k
+ let lines =<< trim END
+ 1)msg1
+ Xfile1:14-24:1-2
+ 2)msg2
+ Xfile1:24-34:3-4
+ END
+ cexpr lines
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1',
+ \ ' 2 Xfile1:24-34 col 3-4 error 2: msg2'], output)
+ let &efm = save_efm
+endfunc
+
+func XquickfixChangedByAutocmd(cchar)
+ call s:setup_commands(a:cchar)
+ if a:cchar == 'c'
+ let ErrorNr = 'E925'
+ func! ReadFunc()
+ colder
+ cgetexpr []
+ endfunc
+ else
+ let ErrorNr = 'E926'
+ func! ReadFunc()
+ lolder
+ lgetexpr []
+ endfunc
+ endif
+
+ augroup QF_Test
+ au!
+ autocmd BufReadCmd test_changed.txt call ReadFunc()
+ augroup END
+
+ new | only
+ let words = [ "a", "b" ]
+ let qflist = []
+ for word in words
+ call add(qflist, {'filename': 'test_changed.txt'})
+ call g:Xsetlist(qflist, ' ')
+ endfor
+ call assert_fails('Xrewind', ErrorNr . ':')
+
+ augroup QF_Test
+ au!
+ augroup END
+
+ if a:cchar == 'c'
+ cexpr ["Xtest1:1:Line"]
+ cwindow
+ only
+ augroup QF_Test
+ au!
+ autocmd WinEnter * call setqflist([], 'f')
+ augroup END
+ call assert_fails('exe "normal \<CR>"', 'E925:')
+ augroup QF_Test
+ au!
+ augroup END
+ endif
+ %bw!
+endfunc
+
+func Test_quickfix_was_changed_by_autocmd()
+ call XquickfixChangedByAutocmd('c')
+ call XquickfixChangedByAutocmd('l')
+endfunc
+
+func Test_setloclist_in_autocommand()
+ call writefile(['test1', 'test2'], 'Xfile', 'D')
+ edit Xfile
+ let s:bufnr = bufnr()
+ call setloclist(1,
+ \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
+ \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}])
+
+ augroup Test_LocList
+ au!
+ autocmd BufEnter * call setloclist(1,
+ \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
+ \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}], 'r')
+ augroup END
+
+ lopen
+ call assert_fails('exe "normal j\<CR>"', 'E926:')
+
+ augroup Test_LocList
+ au!
+ augroup END
+endfunc
+
+func Test_caddbuffer_to_empty()
+ helpgr quickfix
+ call setqflist([], 'r')
+ cad
+ try
+ cn
+ catch
+ " number of matches is unknown
+ call assert_true(v:exception =~ 'E553:')
+ endtry
+ quit!
+endfunc
+
+func Test_cgetexpr_works()
+ " this must not crash Vim
+ cgetexpr [$x]
+ lgetexpr [$x]
+endfunc
+
+" Tests for the setqflist() and setloclist() functions
+func SetXlistTests(cchar, bnum)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1},
+ \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5}])
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(2, l[1].lnum)
+ call assert_equal(3, l[1].end_lnum)
+ call assert_equal(4, l[1].col)
+ call assert_equal(5, l[1].end_col)
+
+ Xnext
+ call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 3}], 'a')
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ Xnext
+ call assert_equal(3, line('.'))
+
+ " Appending entries to the list should not change the cursor position
+ " in the quickfix window
+ Xwindow
+ 1
+ call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 4},
+ \ {'bufnr': a:bnum, 'lnum': 5}], 'a')
+ call assert_equal(1, line('.'))
+ close
+
+ call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 3},
+ \ {'bufnr': a:bnum, 'lnum': 4},
+ \ {'bufnr': a:bnum, 'lnum': 5}], 'r')
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ call assert_equal(5, l[2].lnum)
+
+ call g:Xsetlist([])
+ let l = g:Xgetlist()
+ call assert_equal(0, len(l))
+
+ " Tests for setting the 'valid' flag
+ call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}])
+ Xwindow
+ call assert_equal(1, winnr('$'))
+ let l = g:Xgetlist()
+ call g:Xsetlist(l)
+ call assert_equal(0, g:Xgetlist()[0].valid)
+ " Adding a non-valid entry should not mark the list as having valid entries
+ call g:Xsetlist([{'bufnr':a:bnum, 'lnum':5, 'valid':0}], 'a')
+ Xwindow
+ call assert_equal(1, winnr('$'))
+
+ " :cnext/:cprev should still work even with invalid entries in the list
+ let l = [{'bufnr' : a:bnum, 'lnum' : 1, 'text' : '1', 'valid' : 0},
+ \ {'bufnr' : a:bnum, 'lnum' : 2, 'text' : '2', 'valid' : 0}]
+ call g:Xsetlist(l)
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+ " :cnext/:cprev should still work after appending invalid entries to an
+ " empty list
+ call g:Xsetlist([])
+ call g:Xsetlist(l, 'a')
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+
+ call g:Xsetlist([{'text':'Text1', 'valid':1}])
+ Xwindow
+ call assert_equal(2, winnr('$'))
+ Xclose
+ let save_efm = &efm
+ set efm=%m
+ Xgetexpr 'TestMessage'
+ let l = g:Xgetlist()
+ call g:Xsetlist(l)
+ call assert_equal(1, g:Xgetlist()[0].valid)
+ let &efm = save_efm
+
+ " Error cases:
+ " Refer to a non-existing buffer and pass a non-dictionary type
+ call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," .
+ \ " {'bufnr':999, 'lnum':5}])", 'E92:')
+ call g:Xsetlist([[1, 2,3]])
+ call assert_equal(0, len(g:Xgetlist()))
+ call assert_fails('call g:Xsetlist([], [])', 'E928:')
+ call g:Xsetlist([test_null_dict()])
+ call assert_equal([], g:Xgetlist())
+endfunc
+
+func Test_setqflist()
+ new Xtestfile | only
+ let bnum = bufnr('%')
+ call setline(1, range(1,5))
+
+ call SetXlistTests('c', bnum)
+ call SetXlistTests('l', bnum)
+
+ enew!
+ call delete('Xtestfile')
+endfunc
+
+func Xlist_empty_middle(cchar)
+ call s:setup_commands(a:cchar)
+
+ " create three quickfix lists
+ let @/ = 'Test_'
+ Xvimgrep // test_quickfix.vim
+ let testlen = len(g:Xgetlist())
+ call assert_true(testlen > 0)
+ Xvimgrep empty test_quickfix.vim
+ call assert_true(len(g:Xgetlist()) > 0)
+ Xvimgrep matches test_quickfix.vim
+ let matchlen = len(g:Xgetlist())
+ call assert_true(matchlen > 0)
+ Xolder
+ " make the middle list empty
+ call g:Xsetlist([], 'r')
+ call assert_true(len(g:Xgetlist()) == 0)
+ Xolder
+ call assert_equal(testlen, len(g:Xgetlist()))
+ Xnewer
+ Xnewer
+ call assert_equal(matchlen, len(g:Xgetlist()))
+endfunc
+
+func Test_setqflist_empty_middle()
+ call Xlist_empty_middle('c')
+ call Xlist_empty_middle('l')
+endfunc
+
+func Xlist_empty_older(cchar)
+ call s:setup_commands(a:cchar)
+
+ " create three quickfix lists
+ Xvimgrep one test_quickfix.vim
+ let onelen = len(g:Xgetlist())
+ call assert_true(onelen > 0)
+ Xvimgrep two test_quickfix.vim
+ let twolen = len(g:Xgetlist())
+ call assert_true(twolen > 0)
+ Xvimgrep three test_quickfix.vim
+ let threelen = len(g:Xgetlist())
+ call assert_true(threelen > 0)
+ Xolder 2
+ " make the first list empty, check the others didn't change
+ call g:Xsetlist([], 'r')
+ call assert_true(len(g:Xgetlist()) == 0)
+ Xnewer
+ call assert_equal(twolen, len(g:Xgetlist()))
+ Xnewer
+ call assert_equal(threelen, len(g:Xgetlist()))
+endfunc
+
+func Test_setqflist_empty_older()
+ call Xlist_empty_older('c')
+ call Xlist_empty_older('l')
+endfunc
+
+func XquickfixSetListWithAct(cchar)
+ call s:setup_commands(a:cchar)
+
+ let list1 = [{'filename': 'fnameA', 'text': 'A'},
+ \ {'filename': 'fnameB', 'text': 'B'}]
+ let list2 = [{'filename': 'fnameC', 'text': 'C'},
+ \ {'filename': 'fnameD', 'text': 'D'},
+ \ {'filename': 'fnameE', 'text': 'E'}]
+
+ " {action} is unspecified. Same as specifying ' '.
+ new | only
+ silent! Xnewer 99
+ call g:Xsetlist(list1)
+ call g:Xsetlist(list2)
+ let li = g:Xgetlist()
+ call assert_equal(3, len(li))
+ call assert_equal('C', li[0]['text'])
+ call assert_equal('D', li[1]['text'])
+ call assert_equal('E', li[2]['text'])
+ silent! Xolder
+ let li = g:Xgetlist()
+ call assert_equal(2, len(li))
+ call assert_equal('A', li[0]['text'])
+ call assert_equal('B', li[1]['text'])
+
+ " {action} is specified ' '.
+ new | only
+ silent! Xnewer 99
+ call g:Xsetlist(list1)
+ call g:Xsetlist(list2, ' ')
+ let li = g:Xgetlist()
+ call assert_equal(3, len(li))
+ call assert_equal('C', li[0]['text'])
+ call assert_equal('D', li[1]['text'])
+ call assert_equal('E', li[2]['text'])
+ silent! Xolder
+ let li = g:Xgetlist()
+ call assert_equal(2, len(li))
+ call assert_equal('A', li[0]['text'])
+ call assert_equal('B', li[1]['text'])
+
+ " {action} is specified 'a'.
+ new | only
+ silent! Xnewer 99
+ call g:Xsetlist(list1)
+ call g:Xsetlist(list2, 'a')
+ let li = g:Xgetlist()
+ call assert_equal(5, len(li))
+ call assert_equal('A', li[0]['text'])
+ call assert_equal('B', li[1]['text'])
+ call assert_equal('C', li[2]['text'])
+ call assert_equal('D', li[3]['text'])
+ call assert_equal('E', li[4]['text'])
+
+ " {action} is specified 'r'.
+ new | only
+ silent! Xnewer 99
+ call g:Xsetlist(list1)
+ call g:Xsetlist(list2, 'r')
+ let li = g:Xgetlist()
+ call assert_equal(3, len(li))
+ call assert_equal('C', li[0]['text'])
+ call assert_equal('D', li[1]['text'])
+ call assert_equal('E', li[2]['text'])
+
+ " Test for wrong value.
+ new | only
+ call assert_fails("call g:Xsetlist(0)", 'E714:')
+ call assert_fails("call g:Xsetlist(list1, '')", 'E927:')
+ call assert_fails("call g:Xsetlist(list1, 'aa')", 'E927:')
+ call assert_fails("call g:Xsetlist(list1, ' a')", 'E927:')
+ call assert_fails("call g:Xsetlist(list1, 0)", 'E928:')
+endfunc
+
+func Test_setqflist_invalid_nr()
+ " The following command used to crash Vim
+ eval []->setqflist(' ', {'nr' : $XXX_DOES_NOT_EXIST})
+endfunc
+
+func Test_setqflist_user_sets_buftype()
+ call setqflist([{'text': 'foo'}, {'text': 'bar'}])
+ set buftype=quickfix
+ call setqflist([], 'a')
+ enew
+endfunc
+
+func Test_quickfix_set_list_with_act()
+ call XquickfixSetListWithAct('c')
+ call XquickfixSetListWithAct('l')
+endfunc
+
+func XLongLinesTests(cchar)
+ let l = g:Xgetlist()
+
+ call assert_equal(4, len(l))
+ call assert_equal(1, l[0].lnum)
+ call assert_equal(1, l[0].col)
+ call assert_equal(1975, len(l[0].text))
+ call assert_equal(2, l[1].lnum)
+ call assert_equal(1, l[1].col)
+ call assert_equal(4070, len(l[1].text))
+ call assert_equal(3, l[2].lnum)
+ call assert_equal(1, l[2].col)
+ call assert_equal(4070, len(l[2].text))
+ call assert_equal(4, l[3].lnum)
+ call assert_equal(1, l[3].col)
+ call assert_equal(10, len(l[3].text))
+
+ call g:Xsetlist([], 'r')
+endfunc
+
+func s:long_lines_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ let testfile = 'samples/quickfix.txt'
+
+ " file
+ exe 'Xgetfile' testfile
+ call XLongLinesTests(a:cchar)
+
+ " list
+ Xexpr readfile(testfile)
+ call XLongLinesTests(a:cchar)
+
+ " string
+ Xexpr join(readfile(testfile), "\n")
+ call XLongLinesTests(a:cchar)
+
+ " buffer
+ exe 'edit' testfile
+ exe 'Xbuffer' bufnr('%')
+ call XLongLinesTests(a:cchar)
+endfunc
+
+func Test_long_lines()
+ call s:long_lines_tests('c')
+ call s:long_lines_tests('l')
+endfunc
+
+func Test_cgetfile_on_long_lines()
+ " Problematic values if the line is longer than 4096 bytes. Then 1024 bytes
+ " are read at a time.
+ for len in [4078, 4079, 4080, 5102, 5103, 5104, 6126, 6127, 6128, 7150, 7151, 7152]
+ let lines =<< trim END
+ /tmp/file1:1:1:aaa
+ /tmp/file2:1:1:%s
+ /tmp/file3:1:1:bbb
+ /tmp/file4:1:1:ccc
+ END
+ let lines[1] = substitute(lines[1], '%s', repeat('x', len), '')
+ call writefile(lines, 'Xcqetfile.txt', 'D')
+ cgetfile Xcqetfile.txt
+ call assert_equal(4, getqflist(#{size: v:true}).size, 'with length ' .. len)
+ endfor
+endfunc
+
+func s:create_test_file(filename)
+ let l = []
+ for i in range(1, 20)
+ call add(l, 'Line' . i)
+ endfor
+ call writefile(l, a:filename)
+endfunc
+
+func Test_switchbuf()
+ call s:create_test_file('Xqftestfile1')
+ call s:create_test_file('Xqftestfile2')
+ call s:create_test_file('Xqftestfile3')
+
+ new | only
+ edit Xqftestfile1
+ let file1_winid = win_getid()
+ new Xqftestfile2
+ let file2_winid = win_getid()
+ let lines =<< trim END
+ Xqftestfile1:5:Line5
+ Xqftestfile1:6:Line6
+ Xqftestfile2:10:Line10
+ Xqftestfile2:11:Line11
+ Xqftestfile3:15:Line15
+ Xqftestfile3:16:Line16
+ END
+ cgetexpr lines
+
+ new
+ let winid = win_getid()
+ cfirst | cnext
+ call assert_equal(winid, win_getid())
+ 2cnext
+ call assert_equal(winid, win_getid())
+ 2cnext
+ call assert_equal(winid, win_getid())
+
+ " Test for 'switchbuf' set to search for files in windows in the current
+ " tabpage and jump to an existing window (if present)
+ set switchbuf=useopen
+ enew
+ cfirst | cnext
+ call assert_equal(file1_winid, win_getid())
+ 2cnext
+ call assert_equal(file2_winid, win_getid())
+ 2cnext
+ call assert_equal(file2_winid, win_getid())
+
+ " Test for 'switchbuf' set to search for files in tabpages and jump to an
+ " existing tabpage (if present)
+ enew | only
+ set switchbuf=usetab
+ tabedit Xqftestfile1
+ tabedit Xqftestfile2
+ tabedit Xqftestfile3
+ tabfirst
+ cfirst | cnext
+ call assert_equal(2, tabpagenr())
+ 2cnext
+ call assert_equal(3, tabpagenr())
+ 6cnext
+ call assert_equal(4, tabpagenr())
+ 2cpfile
+ call assert_equal(2, tabpagenr())
+ 2cnfile
+ call assert_equal(4, tabpagenr())
+ tabfirst | tabonly | enew
+
+ " Test for 'switchbuf' set to open a new window for every file
+ set switchbuf=split
+ cfirst | cnext
+ call assert_equal(1, winnr('$'))
+ cnext | cnext
+ call assert_equal(2, winnr('$'))
+ cnext | cnext
+ call assert_equal(3, winnr('$'))
+
+ " Test for 'switchbuf' set to open a new tabpage for every file
+ set switchbuf=newtab
+ enew | only
+ cfirst | cnext
+ call assert_equal(1, tabpagenr('$'))
+ cnext | cnext
+ call assert_equal(2, tabpagenr('$'))
+ cnext | cnext
+ call assert_equal(3, tabpagenr('$'))
+ tabfirst | enew | tabonly | only
+
+ set switchbuf=uselast
+ split
+ let last_winid = win_getid()
+ copen
+ exe "normal 1G\<CR>"
+ call assert_equal(last_winid, win_getid())
+ enew | only
+
+ " With an empty 'switchbuf', jumping to a quickfix entry should open the
+ " file in an existing window (if present)
+ set switchbuf=
+ edit Xqftestfile1
+ let file1_winid = win_getid()
+ new Xqftestfile2
+ let file2_winid = win_getid()
+ copen
+ exe "normal 1G\<CR>"
+ call assert_equal(file1_winid, win_getid())
+ copen
+ exe "normal 3G\<CR>"
+ call assert_equal(file2_winid, win_getid())
+ copen | only
+ exe "normal 5G\<CR>"
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, bufwinnr('Xqftestfile3'))
+
+ " If only quickfix window is open in the current tabpage, jumping to an
+ " entry with 'switchbuf' set to 'usetab' should search in other tabpages.
+ enew | only
+ set switchbuf=usetab
+ tabedit Xqftestfile1
+ tabedit Xqftestfile2
+ tabedit Xqftestfile3
+ tabfirst
+ copen | only
+ clast
+ call assert_equal(4, tabpagenr())
+ tabfirst | tabonly | enew | only
+
+ " Jumping to a file that is not present in any of the tabpages and the
+ " current tabpage doesn't have any usable windows, should open it in a new
+ " window in the current tabpage.
+ copen | only
+ cfirst
+ call assert_equal(1, tabpagenr())
+ call assert_equal('Xqftestfile1', @%)
+
+ " If opening a file changes 'switchbuf', then the new value should be
+ " retained.
+ set modeline&vim
+ call writefile(["vim: switchbuf=split"], 'Xqftestfile1', 'D')
+ enew | only
+ set switchbuf&vim
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('split', &switchbuf)
+ call writefile(["vim: switchbuf=usetab"], 'Xqftestfile1')
+ enew | only
+ set switchbuf=useopen
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('usetab', &switchbuf)
+ call writefile(["vim: switchbuf&vim"], 'Xqftestfile1')
+ enew | only
+ set switchbuf=useopen
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('', &switchbuf)
+
+ call delete('Xqftestfile2')
+ call delete('Xqftestfile3')
+ set switchbuf&vim
+
+ enew | only
+endfunc
+
+func Xadjust_qflnum(cchar)
+ call s:setup_commands(a:cchar)
+
+ enew | only
+
+ let fname = 'Xqftestfile' . a:cchar
+ call s:create_test_file(fname)
+ exe 'edit ' . fname
+
+ Xgetexpr [fname . ':5:Line5',
+ \ fname . ':10:Line10',
+ \ fname . ':15:Line15',
+ \ fname . ':20:Line20']
+
+ 6,14delete
+ call append(6, ['Buffer', 'Window'])
+
+ let l = g:Xgetlist()
+ call assert_equal(5, l[0].lnum)
+ call assert_equal(6, l[2].lnum)
+ call assert_equal(13, l[3].lnum)
+
+ " If a file doesn't have any quickfix entries, then deleting lines in the
+ " file should not update the quickfix list
+ call g:Xsetlist([], 'f')
+ 1,2delete
+ call assert_equal([], g:Xgetlist())
+
+ enew!
+ call delete(fname)
+endfunc
+
+func Test_adjust_lnum()
+ call setloclist(0, [])
+ call Xadjust_qflnum('c')
+ call setqflist([])
+ call Xadjust_qflnum('l')
+endfunc
+
+" Tests for the :grep/:lgrep and :grepadd/:lgrepadd commands
+func s:test_xgrep(cchar)
+ call s:setup_commands(a:cchar)
+
+ " The following lines are used for the grep test. Don't remove.
+ " Grep_Test_Text: Match 1
+ " Grep_Test_Text: Match 2
+ " GrepAdd_Test_Text: Match 1
+ " GrepAdd_Test_Text: Match 2
+ enew! | only
+ set makeef&vim
+ silent Xgrep Grep_Test_Text: test_quickfix.vim
+ call assert_true(len(g:Xgetlist()) == 5)
+ Xopen
+ call assert_true(w:quickfix_title =~ '^:grep')
+ Xclose
+ enew
+ set makeef=Temp_File_##
+ silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim
+ call assert_true(len(g:Xgetlist()) == 9)
+
+ " Try with 'grepprg' set to 'internal'
+ set grepprg=internal
+ silent Xgrep Grep_Test_Text: test_quickfix.vim
+ silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim
+ call assert_true(len(g:Xgetlist()) == 9)
+ set grepprg&vim
+
+ call writefile(['Vim'], 'XtestTempFile')
+ set makeef=XtestTempFile
+ silent Xgrep Grep_Test_Text: test_quickfix.vim
+ call assert_equal(5, len(g:Xgetlist()))
+ call assert_false(filereadable('XtestTempFile'))
+ set makeef&vim
+endfunc
+
+func Test_grep()
+ " The grepprg may not be set on non-Unix systems
+ CheckUnix
+
+ call s:test_xgrep('c')
+ call s:test_xgrep('l')
+endfunc
+
+func Test_two_windows()
+ " Use one 'errorformat' for two windows. Add an expression to each of them,
+ " make sure they each keep their own state.
+ set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
+ call mkdir('Xone/a', 'pR')
+ call mkdir('Xtwo/a', 'pR')
+ let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
+ call writefile(lines, 'Xone/a/one.txt')
+ call writefile(lines, 'Xtwo/a/two.txt')
+
+ new one
+ let one_id = win_getid()
+ lexpr ""
+ new two
+ let two_id = win_getid()
+ lexpr ""
+
+ laddexpr "Entering dir 'Xtwo/a'"
+ call win_gotoid(one_id)
+ laddexpr "Entering dir 'Xone/a'"
+ call win_gotoid(two_id)
+ laddexpr 'two.txt:5:two two two'
+ call win_gotoid(one_id)
+ laddexpr 'one.txt:3:one one one'
+
+ let loc_one = getloclist(one_id)
+ call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr))
+ call assert_equal(3, loc_one[1].lnum)
+
+ let loc_two = getloclist(two_id)
+ call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr))
+ call assert_equal(5, loc_two[1].lnum)
+
+ call win_gotoid(one_id)
+ bwipe!
+ call win_gotoid(two_id)
+ bwipe!
+endfunc
+
+func XbottomTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Calling lbottom without any errors should fail
+ if a:cchar == 'l'
+ call assert_fails('lbottom', 'E776:')
+ endif
+
+ call g:Xsetlist([{'filename': 'foo', 'lnum': 42}])
+ Xopen
+ let wid = win_getid()
+ call assert_equal(1, line('.'))
+ wincmd w
+ call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a')
+ Xbottom
+ call win_gotoid(wid)
+ call assert_equal(2, line('.'))
+ Xclose
+endfunc
+
+" Tests for the :cbottom and :lbottom commands
+func Test_cbottom()
+ call XbottomTests('c')
+ call XbottomTests('l')
+endfunc
+
+func HistoryTest(cchar)
+ call s:setup_commands(a:cchar)
+
+ " clear all lists after the first one, then replace the first one.
+ call g:Xsetlist([])
+ call assert_fails('Xolder 99', 'E380:')
+ let entry = {'filename': 'foo', 'lnum': 42}
+ call g:Xsetlist([entry], 'r')
+ call g:Xsetlist([entry, entry])
+ call g:Xsetlist([entry, entry, entry])
+ let res = split(execute(a:cchar . 'hist'), "\n")
+ call assert_equal(3, len(res))
+ let common = 'errors :set' . (a:cchar == 'c' ? 'qf' : 'loc') . 'list()'
+ call assert_equal(' error list 1 of 3; 1 ' . common, res[0])
+ call assert_equal(' error list 2 of 3; 2 ' . common, res[1])
+ call assert_equal('> error list 3 of 3; 3 ' . common, res[2])
+
+ " Test for changing the quickfix lists
+ call assert_equal(3, g:Xgetlist({'nr' : 0}).nr)
+ exe '1' . a:cchar . 'hist'
+ call assert_equal(1, g:Xgetlist({'nr' : 0}).nr)
+ exe '3' . a:cchar . 'hist'
+ call assert_equal(3, g:Xgetlist({'nr' : 0}).nr)
+ call assert_fails('-2' . a:cchar . 'hist', 'E16:')
+ call assert_fails('4' . a:cchar . 'hist', 'E16:')
+
+ call g:Xsetlist([], 'f')
+ let l = split(execute(a:cchar . 'hist'), "\n")
+ call assert_equal('No entries', l[0])
+ if a:cchar == 'c'
+ call assert_fails('4chist', 'E16:')
+ else
+ call assert_fails('4lhist', 'E776:')
+ endif
+
+ " An empty list should still show the stack history
+ call g:Xsetlist([])
+ let res = split(execute(a:cchar . 'hist'), "\n")
+ call assert_equal('> error list 1 of 1; 0 ' . common, res[0])
+
+ call g:Xsetlist([], 'f')
+endfunc
+
+func Test_history()
+ call HistoryTest('c')
+ call HistoryTest('l')
+endfunc
+
+func Test_duplicate_buf()
+ " make sure we can get the highest buffer number
+ edit DoesNotExist
+ edit DoesNotExist2
+ let last_buffer = bufnr("$")
+
+ " make sure only one buffer is created
+ call writefile(['this one', 'that one'], 'Xgrepthis', 'D')
+ vimgrep one Xgrepthis
+ vimgrep one Xgrepthis
+ call assert_equal(last_buffer + 1, bufnr("$"))
+endfunc
+
+" Quickfix/Location list set/get properties tests
+func Xproperty_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Error cases
+ call assert_fails('call g:Xgetlist(99)', 'E715:')
+ call assert_fails('call g:Xsetlist(99)', 'E714:')
+ call assert_fails('call g:Xsetlist([], "a", [])', 'E715:')
+
+ " Set and get the title
+ call g:Xsetlist([])
+ Xopen
+ wincmd p
+ call g:Xsetlist([{'filename':'foo', 'lnum':27}])
+ let s = g:Xsetlist([], 'a', {'title' : 'Sample'})
+ call assert_equal(0, s)
+ let d = g:Xgetlist({"title":1})
+ call assert_equal('Sample', d.title)
+ " Try setting title to a non-string value
+ call assert_equal(-1, g:Xsetlist([], 'a', {'title' : ['Test']}))
+ call assert_equal('Sample', g:Xgetlist({"title":1}).title)
+
+ Xopen
+ call assert_equal('Sample', w:quickfix_title)
+ Xclose
+
+ " Tests for action argument
+ silent! Xolder 999
+ let qfnr = g:Xgetlist({'all':1}).nr
+ call g:Xsetlist([], 'r', {'title' : 'N1'})
+ call assert_equal('N1', g:Xgetlist({'all':1}).title)
+ call g:Xsetlist([], ' ', {'title' : 'N2'})
+ call assert_equal(qfnr + 1, g:Xgetlist({'all':1}).nr)
+
+ let res = g:Xgetlist({'nr': 0})
+ call assert_equal(qfnr + 1, res.nr)
+ call assert_equal(['nr'], keys(res))
+
+ call g:Xsetlist([], ' ', {'title' : 'N3'})
+ call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title)
+
+ " Changing the title of an earlier quickfix list
+ call g:Xsetlist([], 'r', {'title' : 'NewTitle', 'nr' : 2})
+ call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title)
+
+ " Changing the title of an invalid quickfix list
+ call assert_equal(-1, g:Xsetlist([], ' ',
+ \ {'title' : 'SomeTitle', 'nr' : 99}))
+ call assert_equal(-1, g:Xsetlist([], ' ',
+ \ {'title' : 'SomeTitle', 'nr' : 'abc'}))
+
+ if a:cchar == 'c'
+ copen
+ call assert_equal({'winid':win_getid()}, getqflist({'winid':1}))
+ cclose
+ endif
+
+ " Invalid arguments
+ call assert_fails('call g:Xgetlist([])', 'E715:')
+ call assert_fails('call g:Xsetlist([], "a", [])', 'E715:')
+ let s = g:Xsetlist([], 'a', {'abc':1})
+ call assert_equal(-1, s)
+
+ call assert_equal({}, g:Xgetlist({'abc':1}))
+ call assert_equal('', g:Xgetlist({'nr':99, 'title':1}).title)
+ call assert_equal('', g:Xgetlist({'nr':[], 'title':1}).title)
+
+ if a:cchar == 'l'
+ call assert_equal({}, getloclist(99, {'title': 1}))
+ endif
+
+ " Context related tests
+ let s = g:Xsetlist([], 'a', {'context':[1,2,3]})
+ call assert_equal(0, s)
+ call test_garbagecollect_now()
+ let d = g:Xgetlist({'context':1})
+ call assert_equal([1,2,3], d.context)
+ call g:Xsetlist([], 'a', {'context':{'color':'green'}})
+ let d = g:Xgetlist({'context':1})
+ call assert_equal({'color':'green'}, d.context)
+ call g:Xsetlist([], 'a', {'context':"Context info"})
+ let d = g:Xgetlist({'context':1})
+ call assert_equal("Context info", d.context)
+ call g:Xsetlist([], 'a', {'context':246})
+ let d = g:Xgetlist({'context':1})
+ call assert_equal(246, d.context)
+ " set other Vim data types as context
+ call g:Xsetlist([], 'a', {'context' : test_null_blob()})
+ if has('channel')
+ call g:Xsetlist([], 'a', {'context' : test_null_channel()})
+ endif
+ if has('job')
+ call g:Xsetlist([], 'a', {'context' : test_null_job()})
+ endif
+ call g:Xsetlist([], 'a', {'context' : test_null_function()})
+ call g:Xsetlist([], 'a', {'context' : test_null_partial()})
+ call g:Xsetlist([], 'a', {'context' : ''})
+ call test_garbagecollect_now()
+ if a:cchar == 'l'
+ " Test for copying context across two different location lists
+ new | only
+ let w1_id = win_getid()
+ let l = [1]
+ call setloclist(0, [], 'a', {'context':l})
+ new
+ let w2_id = win_getid()
+ call add(l, 2)
+ call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context)
+ call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
+ unlet! l
+ call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
+ only
+ call setloclist(0, [], 'f')
+ call assert_equal('', getloclist(0, {'context':1}).context)
+ endif
+
+ " Test for changing the context of previous quickfix lists
+ call g:Xsetlist([], 'f')
+ Xexpr "One"
+ Xexpr "Two"
+ Xexpr "Three"
+ call g:Xsetlist([], 'r', {'context' : [1], 'nr' : 1})
+ call g:Xsetlist([], 'a', {'context' : [2], 'nr' : 2})
+ " Also, check for setting the context using quickfix list number zero.
+ call g:Xsetlist([], 'r', {'context' : [3], 'nr' : 0})
+ call test_garbagecollect_now()
+ let l = g:Xgetlist({'nr' : 1, 'context' : 1})
+ call assert_equal([1], l.context)
+ let l = g:Xgetlist({'nr' : 2, 'context' : 1})
+ call assert_equal([2], l.context)
+ let l = g:Xgetlist({'nr' : 3, 'context' : 1})
+ call assert_equal([3], l.context)
+
+ " Test for changing the context through reference and for garbage
+ " collection of quickfix context
+ let l = ["red"]
+ call g:Xsetlist([], ' ', {'context' : l})
+ call add(l, "blue")
+ let x = g:Xgetlist({'context' : 1})
+ call add(x.context, "green")
+ call assert_equal(["red", "blue", "green"], l)
+ call assert_equal(["red", "blue", "green"], x.context)
+ unlet l
+ call test_garbagecollect_now()
+ let m = g:Xgetlist({'context' : 1})
+ call assert_equal(["red", "blue", "green"], m.context)
+
+ " Test for setting/getting items
+ Xexpr ""
+ let qfprev = g:Xgetlist({'nr':0})
+ let s = g:Xsetlist([], ' ', {'title':'Green',
+ \ 'items' : [{'filename':'F1', 'lnum':10}]})
+ call assert_equal(0, s)
+ let qfcur = g:Xgetlist({'nr':0})
+ call assert_true(qfcur.nr == qfprev.nr + 1)
+ let l = g:Xgetlist({'items':1})
+ call assert_equal('F1', bufname(l.items[0].bufnr))
+ call assert_equal(10, l.items[0].lnum)
+ call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20},
+ \ {'filename':'F2', 'lnum':30}]})
+ let l = g:Xgetlist({'items':1})
+ call assert_equal('F2', bufname(l.items[2].bufnr))
+ call assert_equal(30, l.items[2].lnum)
+ call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]})
+ let l = g:Xgetlist({'items':1})
+ call assert_equal('F3', bufname(l.items[0].bufnr))
+ call assert_equal(40, l.items[0].lnum)
+ call g:Xsetlist([], 'r', {'items' : []})
+ let l = g:Xgetlist({'items':1})
+ call assert_equal(0, len(l.items))
+
+ call g:Xsetlist([], 'r', {'title' : 'TestTitle'})
+ call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]})
+ call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]})
+ call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title)
+
+ " Test for getting id of window associated with a location list window
+ if a:cchar == 'l'
+ only
+ call assert_equal(0, g:Xgetlist({'all' : 1}).filewinid)
+ let wid = win_getid()
+ Xopen
+ call assert_equal(wid, g:Xgetlist({'filewinid' : 1}).filewinid)
+ wincmd w
+ call assert_equal(0, g:Xgetlist({'filewinid' : 1}).filewinid)
+ only
+ endif
+
+ " The following used to crash Vim with address sanitizer
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]})
+ call assert_equal(10, g:Xgetlist({'items':1}).items[0].lnum)
+
+ " Try setting the items using a string
+ call assert_equal(-1, g:Xsetlist([], ' ', {'items' : 'Test'}))
+
+ " Save and restore the quickfix stack
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
+ Xexpr "File1:10:Line1"
+ Xexpr "File2:20:Line2"
+ Xexpr "File3:30:Line3"
+ let last_qf = g:Xgetlist({'nr':'$'}).nr
+ call assert_equal(3, last_qf)
+ let qstack = []
+ for i in range(1, last_qf)
+ let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1}))
+ endfor
+ call g:Xsetlist([], 'f')
+ for i in range(len(qstack))
+ call g:Xsetlist([], ' ', qstack[i])
+ endfor
+ call assert_equal(3, g:Xgetlist({'nr':'$'}).nr)
+ call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum)
+ call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum)
+ call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum)
+ call g:Xsetlist([], 'f')
+
+ " Swap two quickfix lists
+ Xexpr "File1:10:Line10"
+ Xexpr "File2:20:Line20"
+ Xexpr "File3:30:Line30"
+ call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']})
+ call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']})
+ let l1=g:Xgetlist({'nr':1,'all':1})
+ let l2=g:Xgetlist({'nr':2,'all':1})
+ let save_id = l1.id
+ let l1.id=l2.id
+ let l2.id=save_id
+ call g:Xsetlist([], 'r', l1)
+ call g:Xsetlist([], 'r', l2)
+ let newl1=g:Xgetlist({'nr':1,'all':1})
+ let newl2=g:Xgetlist({'nr':2,'all':1})
+ call assert_equal('Fruits', newl1.title)
+ call assert_equal(['Fruits'], newl1.context)
+ call assert_equal('Line20', newl1.items[0].text)
+ call assert_equal('Colors', newl2.title)
+ call assert_equal(['Colors'], newl2.context)
+ call assert_equal('Line10', newl2.items[0].text)
+ call g:Xsetlist([], 'f')
+
+ " Cannot specify both a non-empty list argument and a dict argument
+ call assert_fails("call g:Xsetlist([{}], ' ', {})", 'E475:')
+endfunc
+
+func Test_qf_property()
+ call Xproperty_tests('c')
+ call Xproperty_tests('l')
+endfunc
+
+" Test for setting the current index in the location/quickfix list
+func Xtest_setqfidx(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xgetexpr "F1:10:1:Line1\nF2:20:2:Line2\nF3:30:3:Line3"
+ Xgetexpr "F4:10:1:Line1\nF5:20:2:Line2\nF6:30:3:Line3"
+ Xgetexpr "F7:10:1:Line1\nF8:20:2:Line2\nF9:30:3:Line3"
+
+ call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 2})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'idx' : 2})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 3})
+ Xolder 2
+ Xopen
+ call assert_equal(3, line('.'))
+ Xnewer
+ call assert_equal(2, line('.'))
+ Xnewer
+ call assert_equal(2, line('.'))
+ " Update the current index with the quickfix window open
+ wincmd w
+ call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 3})
+ Xopen
+ call assert_equal(3, line('.'))
+ Xclose
+
+ " Set the current index to the last entry
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : '$'})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ " A large value should set the index to the last index
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 1})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 999})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ " Invalid index values
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : -1})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 0})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 'xx'})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call assert_fails("call g:Xsetlist([], 'a', {'nr':1, 'idx':[]})", 'E745:')
+
+ call g:Xsetlist([], 'f')
+ new | only
+endfunc
+
+func Test_setqfidx()
+ call Xtest_setqfidx('c')
+ call Xtest_setqfidx('l')
+endfunc
+
+" Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands
+func QfAutoCmdHandler(loc, cmd)
+ call add(g:acmds, a:loc . a:cmd)
+endfunc
+
+func Test_Autocmd()
+ autocmd QuickFixCmdPre * call QfAutoCmdHandler('pre', expand('<amatch>'))
+ autocmd QuickFixCmdPost * call QfAutoCmdHandler('post', expand('<amatch>'))
+
+ let g:acmds = []
+ cexpr "F1:10:Line 10"
+ caddexpr "F1:20:Line 20"
+ cgetexpr "F1:30:Line 30"
+ cexpr ""
+ caddexpr ""
+ cgetexpr ""
+ silent! cexpr non_existing_func()
+ silent! caddexpr non_existing_func()
+ silent! cgetexpr non_existing_func()
+ let l =<< trim END
+ precexpr
+ postcexpr
+ precaddexpr
+ postcaddexpr
+ precgetexpr
+ postcgetexpr
+ precexpr
+ postcexpr
+ precaddexpr
+ postcaddexpr
+ precgetexpr
+ postcgetexpr
+ precexpr
+ precaddexpr
+ precgetexpr
+ END
+ call assert_equal(l, g:acmds)
+
+ let g:acmds = []
+ enew! | call append(0, "F2:10:Line 10")
+ cbuffer!
+ enew! | call append(0, "F2:20:Line 20")
+ cgetbuffer
+ enew! | call append(0, "F2:30:Line 30")
+ caddbuffer
+ new
+ let bnum = bufnr('%')
+ bunload
+ exe 'silent! cbuffer! ' . bnum
+ exe 'silent! cgetbuffer ' . bnum
+ exe 'silent! caddbuffer ' . bnum
+ enew!
+ let l =<< trim END
+ precbuffer
+ postcbuffer
+ precgetbuffer
+ postcgetbuffer
+ precaddbuffer
+ postcaddbuffer
+ precbuffer
+ precgetbuffer
+ precaddbuffer
+ END
+ call assert_equal(l, g:acmds)
+
+ call writefile(['Xtest:1:Line1'], 'Xtest', 'D')
+ call writefile([], 'Xempty', 'D')
+ let g:acmds = []
+ cfile Xtest
+ caddfile Xtest
+ cgetfile Xtest
+ cfile Xempty
+ caddfile Xempty
+ cgetfile Xempty
+ silent! cfile do_not_exist
+ silent! caddfile do_not_exist
+ silent! cgetfile do_not_exist
+ let l =<< trim END
+ precfile
+ postcfile
+ precaddfile
+ postcaddfile
+ precgetfile
+ postcgetfile
+ precfile
+ postcfile
+ precaddfile
+ postcaddfile
+ precgetfile
+ postcgetfile
+ precfile
+ postcfile
+ precaddfile
+ postcaddfile
+ precgetfile
+ postcgetfile
+ END
+ call assert_equal(l, g:acmds)
+
+ let g:acmds = []
+ helpgrep quickfix
+ silent! helpgrep non_existing_help_topic
+ vimgrep test Xtest
+ vimgrepadd test Xtest
+ silent! vimgrep non_existing_test Xtest
+ silent! vimgrepadd non_existing_test Xtest
+ set makeprg=
+ silent! make
+ set makeprg&
+ let l =<< trim END
+ prehelpgrep
+ posthelpgrep
+ prehelpgrep
+ posthelpgrep
+ previmgrep
+ postvimgrep
+ previmgrepadd
+ postvimgrepadd
+ previmgrep
+ postvimgrep
+ previmgrepadd
+ postvimgrepadd
+ premake
+ postmake
+ END
+ call assert_equal(l, g:acmds)
+
+ if has('unix')
+ " Run this test only on Unix-like systems. The grepprg may not be set on
+ " non-Unix systems.
+ " The following lines are used for the grep test. Don't remove.
+ " Grep_Autocmd_Text: Match 1
+ " GrepAdd_Autocmd_Text: Match 2
+ let g:acmds = []
+ silent grep Grep_Autocmd_Text test_quickfix.vim
+ silent grepadd GrepAdd_Autocmd_Text test_quickfix.vim
+ silent grep abc123def Xtest
+ silent grepadd abc123def Xtest
+ set grepprg=internal
+ silent grep Grep_Autocmd_Text test_quickfix.vim
+ silent grepadd GrepAdd_Autocmd_Text test_quickfix.vim
+ silent lgrep Grep_Autocmd_Text test_quickfix.vim
+ silent lgrepadd GrepAdd_Autocmd_Text test_quickfix.vim
+ set grepprg&vim
+ let l =<< trim END
+ pregrep
+ postgrep
+ pregrepadd
+ postgrepadd
+ pregrep
+ postgrep
+ pregrepadd
+ postgrepadd
+ pregrep
+ postgrep
+ pregrepadd
+ postgrepadd
+ prelgrep
+ postlgrep
+ prelgrepadd
+ postlgrepadd
+ END
+ call assert_equal(l, g:acmds)
+ endif
+
+ au! QuickFixCmdPre
+ au! QuickFixCmdPost
+endfunc
+
+func Test_Autocmd_Exception()
+ set efm=%m
+ lgetexpr '?'
+
+ try
+ call DoesNotExit()
+ catch
+ lgetexpr '1'
+ finally
+ lgetexpr '1'
+ endtry
+
+ call assert_equal('1', getloclist(0)[0].text)
+
+ set efm&vim
+endfunc
+
+func Test_caddbuffer_wrong()
+ " This used to cause a memory access in freed memory.
+ let save_efm = &efm
+ set efm=%EEEE%m,%WWWW,%+CCCC%>%#,%GGGG%.#
+ cgetexpr ['WWWW', 'EEEE', 'CCCC']
+ let &efm = save_efm
+ caddbuffer
+ bwipe!
+endfunc
+
+func Test_caddexpr_wrong()
+ " This used to cause a memory access in freed memory.
+ cbuffer
+ cbuffer
+ copen
+ let save_efm = &efm
+ set efm=%
+ call assert_fails('caddexpr ""', 'E376:')
+ let &efm = save_efm
+endfunc
+
+func Test_dirstack_cleanup()
+ " This used to cause a memory access in freed memory.
+ let save_efm = &efm
+ lexpr '0'
+ lopen
+ fun X(c)
+ let save_efm=&efm
+ set efm=%D%f
+ if a:c == 'c'
+ caddexpr '::'
+ else
+ laddexpr ':0:0'
+ endif
+ let &efm=save_efm
+ endfun
+ call X('c')
+ call X('l')
+ call setqflist([], 'r')
+ caddbuffer
+ let &efm = save_efm
+endfunc
+
+" Tests for jumping to entries from the location list window and quickfix
+" window
+func Test_cwindow_jump()
+ set efm=%f%%%l%%%m
+ lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ lopen | only
+ lfirst
+ call assert_true(winnr('$') == 2)
+ call assert_true(winnr() == 1)
+ " Location list for the new window should be set
+ call assert_true(getloclist(0)[2].text == 'Line 30')
+
+ " Open a scratch buffer
+ " Open a new window and create a location list
+ " Open the location list window and close the other window
+ " Jump to an entry.
+ " Should create a new window and jump to the entry. The scratch buffer
+ " should not be used.
+ enew | only
+ set buftype=nofile
+ below new
+ lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ lopen
+ 2wincmd c
+ lnext
+ call assert_true(winnr('$') == 3)
+ call assert_true(winnr() == 2)
+
+ " Open two windows with two different location lists
+ " Open the location list window and close the previous window
+ " Jump to an entry in the location list window
+ " Should open the file in the first window and not set the location list.
+ enew | only
+ lgetexpr ["F1%5%Line 5"]
+ below new
+ lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ lopen
+ 2wincmd c
+ lnext
+ call assert_true(winnr() == 1)
+ call assert_true(getloclist(0)[0].text == 'Line 5')
+
+ enew | only
+ cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ copen
+ cnext
+ call assert_true(winnr('$') == 2)
+ call assert_true(winnr() == 1)
+
+ " open the quickfix buffer in two windows and jump to an entry. Should open
+ " the file in the first quickfix window.
+ enew | only
+ copen
+ let bnum = bufnr('')
+ exe 'sbuffer ' . bnum
+ wincmd b
+ cfirst
+ call assert_equal(2, winnr())
+ call assert_equal('F1', @%)
+ enew | only
+ exe 'sb' bnum
+ exe 'botright sb' bnum
+ wincmd t
+ clast
+ call assert_equal(2, winnr())
+ call assert_equal('quickfix', getwinvar(1, '&buftype'))
+ call assert_equal('quickfix', getwinvar(3, '&buftype'))
+
+ " Jumping to a file from the location list window should find a usable
+ " window by wrapping around the window list.
+ enew | only
+ call setloclist(0, [], 'f')
+ new | new
+ lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"]
+ lopen
+ 1close
+ call assert_equal(0, getloclist(3, {'id' : 0}).id)
+ lnext
+ call assert_equal(3, winnr())
+ call assert_equal(getloclist(1, {'id' : 0}).id, getloclist(3, {'id' : 0}).id)
+
+ enew | only
+ set efm&vim
+endfunc
+
+func Test_cwindow_highlight()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['some', 'text', 'with', 'matches'])
+ write XCwindow
+ vimgrep e XCwindow
+ redraw
+ cwindow 4
+ END
+ call writefile(lines, 'XtestCwindow', 'D')
+ let buf = RunVimInTerminal('-S XtestCwindow', #{rows: 12})
+ call VerifyScreenDump(buf, 'Test_quickfix_cwindow_1', {})
+
+ call term_sendkeys(buf, ":cnext\<CR>")
+ call VerifyScreenDump(buf, 'Test_quickfix_cwindow_2', {})
+
+ call term_sendkeys(buf, "\<C-W>j:set cursorline\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_quickfix_cwindow_3', {})
+
+ call term_sendkeys(buf, ":set cursorlineopt=number,screenline\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_quickfix_cwindow_3', {})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_quickfix_cwindow_4', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XCwindow')
+endfunc
+
+func XvimgrepTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ let lines =<< trim END
+ Editor:VIM vim
+ Editor:Emacs EmAcS
+ Editor:Notepad NOTEPAD
+ END
+ call writefile(lines, 'Xtestfile1', 'D')
+ call writefile(['Linux', 'macOS', 'MS-Windows'], 'Xtestfile2', 'D')
+
+ " Error cases
+ call assert_fails('Xvimgrep /abc *', 'E682:')
+
+ let @/=''
+ call assert_fails('Xvimgrep // *', 'E35:')
+
+ call assert_fails('Xvimgrep abc', 'E683:')
+ call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:')
+ call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:')
+
+ Xexpr ""
+ Xvimgrepadd Notepad Xtestfile1
+ Xvimgrepadd macOS Xtestfile2
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal('Editor:Notepad NOTEPAD', l[0].text)
+
+ 10Xvimgrep #\cvim#g Xtestfile?
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(8, l[0].col)
+ call assert_equal(11, l[0].end_col)
+ call assert_equal(12, l[1].col)
+ call assert_equal(15, l[1].end_col)
+
+ 1Xvimgrep ?Editor? Xtestfile*
+ let l = g:Xgetlist()
+ call assert_equal(1, len(l))
+ call assert_equal('Editor:VIM vim', l[0].text)
+
+ edit +3 Xtestfile2
+ Xvimgrep +\cemacs+j Xtestfile1
+ let l = g:Xgetlist()
+ call assert_equal('Xtestfile2', @%)
+ call assert_equal('Editor:Emacs EmAcS', l[0].text)
+
+ " Test for unloading a buffer after vimgrep searched the buffer
+ %bwipe
+ Xvimgrep /Editor/j Xtestfile*
+ call assert_equal(0, getbufinfo('Xtestfile1')[0].loaded)
+ call assert_equal([], getbufinfo('Xtestfile2'))
+
+ " Test for opening the dummy buffer used by vimgrep in a window. The new
+ " window should be closed
+ %bw!
+ augroup QF_Test
+ au!
+ autocmd BufReadPre * exe "sb " .. expand("<abuf>")
+ augroup END
+ call assert_fails("Xvimgrep /sublime/ Xtestfile1", 'E480:')
+ call assert_equal(1, winnr('$'))
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" Tests for the :vimgrep command
+func Test_vimgrep()
+ call XvimgrepTests('c')
+ call XvimgrepTests('l')
+endfunc
+
+func Test_vimgrep_wildcards_expanded_once()
+ new X[id-01] file.txt
+ call setline(1, 'some text to search for')
+ vimgrep text %
+ bwipe!
+endfunc
+
+" Test for incsearch highlighting of the :vimgrep pattern
+" This test used to cause "E315: ml_get: invalid lnum" errors.
+func Test_vimgrep_incsearch()
+ enew
+ set incsearch
+ call test_override("char_avail", 1)
+
+ call feedkeys(":2vimgrep assert test_quickfix.vim test_cdo.vim\<CR>", "ntx")
+ let l = getqflist()
+ call assert_equal(2, len(l))
+
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+" Test vimgrep with the last search pattern not set
+func Test_vimgrep_with_no_last_search_pat()
+ let lines =<< trim [SCRIPT]
+ call assert_fails('vimgrep // *', 'E35:')
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test vimgrep without swap file
+func Test_vimgrep_without_swap_file()
+ let lines =<< trim [SCRIPT]
+ vimgrep grep test_c*
+ call writefile(['done'], 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -n -S Xscript Xscript')
+ call assert_equal(['done'], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+func Test_vimgrep_existing_swapfile()
+ call writefile(['match apple with apple'], 'Xapple', 'D')
+ call writefile(['swapfile'], '.Xapple.swp', 'D')
+ let g:foundSwap = 0
+ let g:ignoreSwapExists = 1
+ augroup grep
+ au SwapExists * let foundSwap = 1 | let v:swapchoice = 'e'
+ augroup END
+ vimgrep apple Xapple
+ call assert_equal(1, g:foundSwap)
+ call assert_match('.Xapple.swo', swapname(''))
+
+ augroup grep
+ au! SwapExists
+ augroup END
+ unlet g:ignoreSwapExists
+endfunc
+
+func XfreeTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ enew | only
+
+ " Deleting the quickfix stack should work even When the current list is
+ " somewhere in the middle of the stack
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
+ Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
+ Xolder
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, len(g:Xgetlist()))
+
+ " After deleting the stack, adding a new list should create a stack with a
+ " single list.
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ call assert_equal(1, g:Xgetlist({'all':1}).nr)
+
+ " Deleting the stack from a quickfix window should update/clear the
+ " quickfix/location list window.
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
+ Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
+ Xolder
+ Xwindow
+ call g:Xsetlist([], 'f')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, line('$'))
+ Xclose
+
+ " Deleting the stack from a non-quickfix window should update/clear the
+ " quickfix/location list window.
+ Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25']
+ Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35']
+ Xolder
+ Xwindow
+ wincmd p
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, len(g:Xgetlist()))
+ wincmd p
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, line('$'))
+
+ " After deleting the location list stack, if the location list window is
+ " opened, then a new location list should be created. So opening the
+ " location list window again should not create a new window.
+ if a:cchar == 'l'
+ lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15']
+ wincmd p
+ lopen
+ call assert_equal(2, winnr('$'))
+ endif
+ Xclose
+endfunc
+
+" Tests for the quickfix free functionality
+func Test_qf_free()
+ call XfreeTests('c')
+ call XfreeTests('l')
+endfunc
+
+" Test for buffer overflow when parsing lines and adding new entries to
+" the quickfix list.
+func Test_bufoverflow()
+ set efm=%f:%l:%m
+ cgetexpr ['File1:100:' . repeat('x', 1025)]
+
+ set efm=%+GCompiler:\ %.%#,%f:%l:%m
+ cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World']
+
+ set efm=%DEntering\ directory\ %f,%f:%l:%m
+ let lines =<< trim eval END
+ Entering directory $"{repeat('a', 1006)}"
+ File1:10:Hello World
+ END
+ cgetexpr lines
+ set efm&vim
+endfunc
+
+" Tests for getting the quickfix stack size
+func XsizeTests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
+ call assert_equal('', g:Xgetlist({'nr':'$', 'all':1}).title)
+ call assert_equal(0, g:Xgetlist({'nr':0}).nr)
+
+ Xexpr "File1:10:Line1"
+ Xexpr "File2:20:Line2"
+ Xexpr "File3:30:Line3"
+ Xolder | Xolder
+ call assert_equal(3, g:Xgetlist({'nr':'$'}).nr)
+ call g:Xsetlist([], 'f')
+
+ Xexpr "File1:10:Line1"
+ Xexpr "File2:20:Line2"
+ Xexpr "File3:30:Line3"
+ Xolder | Xolder
+ call g:Xsetlist([], 'a', {'nr':'$', 'title':'Compiler'})
+ call assert_equal('Compiler', g:Xgetlist({'nr':3, 'all':1}).title)
+endfunc
+
+func Test_Qf_Size()
+ call XsizeTests('c')
+ call XsizeTests('l')
+endfunc
+
+func Test_cclose_from_copen()
+ augroup QF_Test
+ au!
+ au FileType qf :call assert_fails(':cclose', 'E788:')
+ augroup END
+ copen
+ augroup QF_Test
+ au!
+ augroup END
+ augroup! QF_Test
+endfunc
+
+func Test_cclose_in_autocmd()
+ " Problem is only triggered if "starting" is zero, so that the OptionSet
+ " event will be triggered.
+ call test_override('starting', 1)
+ augroup QF_Test
+ au!
+ au FileType qf :call assert_fails(':cclose', 'E788:')
+ augroup END
+ copen
+ augroup QF_Test
+ au!
+ augroup END
+ augroup! QF_Test
+ call test_override('starting', 0)
+endfunc
+
+" Check that ":file" without an argument is possible even when "curbuf_lock"
+" is set.
+func Test_file_from_copen()
+ " Works without argument.
+ augroup QF_Test
+ au!
+ au FileType qf file
+ augroup END
+ copen
+
+ augroup QF_Test
+ au!
+ augroup END
+ cclose
+
+ " Fails with argument.
+ augroup QF_Test
+ au!
+ au FileType qf call assert_fails(':file foo', 'E788:')
+ augroup END
+ copen
+ augroup QF_Test
+ au!
+ augroup END
+ cclose
+
+ augroup! QF_Test
+endfunc
+
+func Test_resize_from_copen()
+ augroup QF_Test
+ au!
+ au FileType qf resize 5
+ augroup END
+ try
+ " This should succeed without any exception. No other buffers are
+ " involved in the autocmd.
+ copen
+ finally
+ augroup QF_Test
+ au!
+ augroup END
+ augroup! QF_Test
+ endtry
+endfunc
+
+func Test_filetype_autocmd()
+ " this changes the location list while it is in use to fill a buffer
+ lexpr ''
+ lopen
+ augroup FT_loclist
+ au FileType * call setloclist(0, [], 'f')
+ augroup END
+ silent! lolder
+ lexpr ''
+
+ augroup FT_loclist
+ au! FileType
+ augroup END
+endfunc
+
+func Test_vimgrep_with_textlock()
+ new
+
+ " Simple way to execute something with "textlock" set.
+ " Check that vimgrep without jumping can be executed.
+ au InsertCharPre * vimgrep /RunTheTest/j runtest.vim
+ normal ax
+ let qflist = getqflist()
+ call assert_true(len(qflist) > 0)
+ call assert_match('RunTheTest', qflist[0].text)
+ call setqflist([], 'r')
+ au! InsertCharPre
+
+ " Check that vimgrepadd without jumping can be executed.
+ au InsertCharPre * vimgrepadd /RunTheTest/j runtest.vim
+ normal ax
+ let qflist = getqflist()
+ call assert_true(len(qflist) > 0)
+ call assert_match('RunTheTest', qflist[0].text)
+ call setqflist([], 'r')
+ au! InsertCharPre
+
+ " Check that lvimgrep without jumping can be executed.
+ au InsertCharPre * lvimgrep /RunTheTest/j runtest.vim
+ normal ax
+ let qflist = getloclist(0)
+ call assert_true(len(qflist) > 0)
+ call assert_match('RunTheTest', qflist[0].text)
+ call setloclist(0, [], 'r')
+ au! InsertCharPre
+
+ " Check that lvimgrepadd without jumping can be executed.
+ au InsertCharPre * lvimgrepadd /RunTheTest/j runtest.vim
+ normal ax
+ let qflist = getloclist(0)
+ call assert_true(len(qflist) > 0)
+ call assert_match('RunTheTest', qflist[0].text)
+ call setloclist(0, [], 'r')
+ au! InsertCharPre
+
+ " trying to jump will give an error
+ au InsertCharPre * vimgrep /RunTheTest/ runtest.vim
+ call assert_fails('normal ax', 'E565:')
+ au! InsertCharPre
+
+ au InsertCharPre * vimgrepadd /RunTheTest/ runtest.vim
+ call assert_fails('normal ax', 'E565:')
+ au! InsertCharPre
+
+ au InsertCharPre * lvimgrep /RunTheTest/ runtest.vim
+ call assert_fails('normal ax', 'E565:')
+ au! InsertCharPre
+
+ au InsertCharPre * lvimgrepadd /RunTheTest/ runtest.vim
+ call assert_fails('normal ax', 'E565:')
+ au! InsertCharPre
+
+ bwipe!
+endfunc
+
+" Tests for the quickfix buffer b:changedtick variable
+func Xchangedtick_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ new | only
+
+ Xexpr "" | Xexpr "" | Xexpr ""
+
+ Xopen
+ Xolder
+ Xolder
+ Xaddexpr "F1:10:Line10"
+ Xaddexpr "F2:20:Line20"
+ call g:Xsetlist([{"filename":"F3", "lnum":30, "text":"Line30"}], 'a')
+ call g:Xsetlist([], 'f')
+ call assert_equal(8, getbufvar('%', 'changedtick'))
+ Xclose
+endfunc
+
+func Test_changedtick()
+ call Xchangedtick_tests('c')
+ call Xchangedtick_tests('l')
+endfunc
+
+" Tests for parsing an expression using setqflist()
+func Xsetexpr_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ let t = ["File1:10:Line10", "File1:20:Line20"]
+ call g:Xsetlist([], ' ', {'lines' : t})
+ call g:Xsetlist([], 'a', {'lines' : ["File1:30:Line30"]})
+
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ call assert_equal(20, l[1].lnum)
+ call assert_equal('Line30', l[2].text)
+ call g:Xsetlist([], 'r', {'lines' : ["File2:5:Line5"]})
+ let l = g:Xgetlist()
+ call assert_equal(1, len(l))
+ call assert_equal('Line5', l[0].text)
+ call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : 10}))
+ call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : "F1:10:L10"}))
+
+ call g:Xsetlist([], 'f')
+ " Add entries to multiple lists
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:10:Line10"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:20:Line20"]})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:15:Line15"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:25:Line25"]})
+ call assert_equal('Line15', g:Xgetlist({'nr':1, 'items':1}).items[1].text)
+ call assert_equal('Line25', g:Xgetlist({'nr':2, 'items':1}).items[1].text)
+
+ " Adding entries using a custom efm
+ set efm&
+ call g:Xsetlist([], ' ', {'efm' : '%f#%l#%m',
+ \ 'lines' : ["F1#10#L10", "F2#20#L20"]})
+ call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum)
+ call g:Xsetlist([], 'a', {'efm' : '%f#%l#%m', 'lines' : ["F3:30:L30"]})
+ call assert_equal('F3:30:L30', g:Xgetlist({'items':1}).items[2].text)
+ call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum)
+ call assert_equal(-1, g:Xsetlist([], 'a', {'efm' : [],
+ \ 'lines' : ['F1:10:L10']}))
+endfunc
+
+func Test_setexpr()
+ call Xsetexpr_tests('c')
+ call Xsetexpr_tests('l')
+endfunc
+
+" Tests for per quickfix/location list directory stack
+func Xmultidirstack_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ Xexpr "" | Xexpr ""
+
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["Entering dir 'Xone/a'"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["Entering dir 'Xtwo/a'"]})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["one.txt:3:one one one"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["two.txt:5:two two two"]})
+
+ let l1 = g:Xgetlist({'nr':1, 'items':1})
+ let l2 = g:Xgetlist({'nr':2, 'items':1})
+ call assert_equal('Xone/a/one.txt', bufname(l1.items[1].bufnr))
+ call assert_equal(3, l1.items[1].lnum)
+ call assert_equal('Xtwo/a/two.txt', bufname(l2.items[1].bufnr))
+ call assert_equal(5, l2.items[1].lnum)
+endfunc
+
+func Test_multidirstack()
+ call mkdir('Xone/a', 'pR')
+ call mkdir('Xtwo/a', 'pR')
+ let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
+ call writefile(lines, 'Xone/a/one.txt')
+ call writefile(lines, 'Xtwo/a/two.txt')
+ let save_efm = &efm
+ set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
+
+ call Xmultidirstack_tests('c')
+ call Xmultidirstack_tests('l')
+
+ let &efm = save_efm
+endfunc
+
+" Tests for per quickfix/location list file stack
+func Xmultifilestack_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ Xexpr "" | Xexpr ""
+
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["[one.txt]"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["[two.txt]"]})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["(3,5) one one one"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["(5,9) two two two"]})
+
+ let l1 = g:Xgetlist({'nr':1, 'items':1})
+ let l2 = g:Xgetlist({'nr':2, 'items':1})
+ call assert_equal('one.txt', bufname(l1.items[1].bufnr))
+ call assert_equal(3, l1.items[1].lnum)
+ call assert_equal('two.txt', bufname(l2.items[1].bufnr))
+ call assert_equal(5, l2.items[1].lnum)
+
+ " Test for start of a new error line in the same line where a previous
+ " error line ends with a file stack.
+ let efm_val = 'Error\ l%l\ in\ %f,'
+ let efm_val .= '%-P%>(%f%r,Error\ l%l\ in\ %m,%-Q)%r'
+ let lines =<< trim END
+ (one.txt
+ Error l4 in one.txt
+ ) (two.txt
+ Error l6 in two.txt
+ )
+ Error l8 in one.txt
+ END
+ let l = g:Xgetlist({'lines': lines, 'efm' : efm_val})
+ call assert_equal(3, len(l.items))
+ call assert_equal('one.txt', bufname(l.items[0].bufnr))
+ call assert_equal(4, l.items[0].lnum)
+ call assert_equal('one.txt', l.items[0].text)
+ call assert_equal('two.txt', bufname(l.items[1].bufnr))
+ call assert_equal(6, l.items[1].lnum)
+ call assert_equal('two.txt', l.items[1].text)
+ call assert_equal('one.txt', bufname(l.items[2].bufnr))
+ call assert_equal(8, l.items[2].lnum)
+ call assert_equal('', l.items[2].text)
+endfunc
+
+func Test_multifilestack()
+ let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
+ call writefile(lines, 'one.txt', 'D')
+ call writefile(lines, 'two.txt', 'D')
+ let save_efm = &efm
+ set efm=%+P[%f],(%l\\,%c)\ %m,%-Q
+
+ call Xmultifilestack_tests('c')
+ call Xmultifilestack_tests('l')
+
+ let &efm = save_efm
+endfunc
+
+" Tests for per buffer 'efm' setting
+func Test_perbuf_efm()
+ call writefile(["File1-10-Line10"], 'one.txt', 'D')
+ call writefile(["File2#20#Line20"], 'two.txt', 'D')
+ set efm=%f#%l#%m
+ new | only
+ new
+ setlocal efm=%f-%l-%m
+ cfile one.txt
+ wincmd w
+ caddfile two.txt
+
+ let l = getqflist()
+ call assert_equal(10, l[0].lnum)
+ call assert_equal('Line20', l[1].text)
+
+ set efm&
+ new | only
+endfunc
+
+" Open multiple help windows using ":lhelpgrep
+" This test used to crash Vim
+func Test_Multi_LL_Help()
+ new | only
+ lhelpgrep window
+ lopen
+ e#
+ lhelpgrep buffer
+ call assert_equal(3, winnr('$'))
+ call assert_true(len(getloclist(1)) != 0)
+ call assert_true(len(getloclist(2)) != 0)
+ new | only
+endfunc
+
+" Tests for adding new quickfix lists using setqflist()
+func XaddQf_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Create a new list using ' ' for action
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], ' ', {'title' : 'Test1'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test1', l.title)
+
+ " Create a new list using ' ' for action and '$' for 'nr'
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], ' ', {'title' : 'Test2', 'nr' : '$'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test2', l.title)
+
+ " Create a new list using 'a' for action
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], 'a', {'title' : 'Test3'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test3', l.title)
+
+ " Create a new list using 'a' for action and '$' for 'nr'
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], 'a', {'title' : 'Test3', 'nr' : '$'})
+ call g:Xsetlist([], 'a', {'title' : 'Test4'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test4', l.title)
+
+ " Adding a quickfix list should remove all the lists following the current
+ " list.
+ Xexpr "" | Xexpr "" | Xexpr ""
+ silent! 10Xolder
+ call g:Xsetlist([], ' ', {'title' : 'Test5'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(2, l.nr)
+ call assert_equal('Test5', l.title)
+
+ " Add a quickfix list using '$' as the list number.
+ let lastqf = g:Xgetlist({'nr':'$'}).nr
+ silent! 99Xolder
+ call g:Xsetlist([], ' ', {'nr' : '$', 'title' : 'Test6'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(lastqf + 1, l.nr)
+ call assert_equal('Test6', l.title)
+
+ " Add a quickfix list using 'nr' set to one more than the quickfix
+ " list size.
+ let lastqf = g:Xgetlist({'nr':'$'}).nr
+ silent! 99Xolder
+ call g:Xsetlist([], ' ', {'nr' : lastqf + 1, 'title' : 'Test7'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(lastqf + 1, l.nr)
+ call assert_equal('Test7', l.title)
+
+ " Add a quickfix list to a stack with 10 lists using 'nr' set to '$'
+ exe repeat('Xexpr "" |', 9) . 'Xexpr ""'
+ silent! 99Xolder
+ call g:Xsetlist([], ' ', {'nr' : '$', 'title' : 'Test8'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(10, l.nr)
+ call assert_equal('Test8', l.title)
+
+ " Add a quickfix list using 'nr' set to a value greater than 10
+ call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : 12, 'title' : 'Test9'}))
+
+ " Try adding a quickfix list with 'nr' set to a value greater than the
+ " quickfix list size but less than 10.
+ call g:Xsetlist([], 'f')
+ Xexpr "" | Xexpr "" | Xexpr ""
+ silent! 99Xolder
+ call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : 8, 'title' : 'Test10'}))
+
+ " Add a quickfix list using 'nr' set to a some string or list
+ call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : [1,2], 'title' : 'Test11'}))
+endfunc
+
+func Test_add_qf()
+ call XaddQf_tests('c')
+ call XaddQf_tests('l')
+endfunc
+
+" Test for getting the quickfix list items from some text without modifying
+" the quickfix stack
+func XgetListFromLines(cchar)
+ call s:setup_commands(a:cchar)
+ call g:Xsetlist([], 'f')
+
+ let l = g:Xgetlist({'lines' : ["File2:20:Line20", "File2:30:Line30"]}).items
+ call assert_equal(2, len(l))
+ call assert_equal(30, l[1].lnum)
+
+ call assert_equal({}, g:Xgetlist({'lines' : 10}))
+ call assert_equal({}, g:Xgetlist({'lines' : 'File1:10:Line10'}))
+ call assert_equal([], g:Xgetlist({'lines' : []}).items)
+ call assert_equal([], g:Xgetlist({'lines' : [10, 20]}).items)
+
+ " Parse text using a custom efm
+ set efm&
+ let l = g:Xgetlist({'lines':['File3#30#Line30'], 'efm' : '%f#%l#%m'}).items
+ call assert_equal('Line30', l[0].text)
+ let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : '%f-%l-%m'}).items
+ call assert_equal('File3:30:Line30', l[0].text)
+ let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : [1,2]})
+ call assert_equal({}, l)
+ call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':'%2'})", 'E376:')
+ call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':''})", 'E378:')
+
+ " Make sure that the quickfix stack is not modified
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+endfunc
+
+func Test_get_list_from_lines()
+ call XgetListFromLines('c')
+ call XgetListFromLines('l')
+endfunc
+
+" Tests for the quickfix list id
+func Xqfid_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, g:Xgetlist({'id':0}).id)
+ Xexpr ''
+ let start_id = g:Xgetlist({'id' : 0}).id
+ Xexpr '' | Xexpr ''
+ Xolder
+ call assert_equal(start_id, g:Xgetlist({'id':0, 'nr':1}).id)
+ call assert_equal(start_id + 1, g:Xgetlist({'id':0, 'nr':0}).id)
+ call assert_equal(start_id + 2, g:Xgetlist({'id':0, 'nr':'$'}).id)
+ call assert_equal(0, g:Xgetlist({'id':0, 'nr':99}).id)
+ call assert_equal(2, g:Xgetlist({'id':start_id + 1, 'nr':0}).nr)
+ call assert_equal(0, g:Xgetlist({'id':99, 'nr':0}).id)
+ call assert_equal(0, g:Xgetlist({'id':"abc", 'nr':0}).id)
+
+ call g:Xsetlist([], 'a', {'id':start_id, 'context':[1,2]})
+ call assert_equal([1,2], g:Xgetlist({'nr':1, 'context':1}).context)
+ call g:Xsetlist([], 'a', {'id':start_id+1, 'lines':['F1:10:L10']})
+ call assert_equal('L10', g:Xgetlist({'nr':2, 'items':1}).items[0].text)
+ call assert_equal(-1, g:Xsetlist([], 'a', {'id':999, 'title':'Vim'}))
+ call assert_equal(-1, g:Xsetlist([], 'a', {'id':'abc', 'title':'Vim'}))
+
+ let qfid = g:Xgetlist({'id':0, 'nr':0})
+ call g:Xsetlist([], 'f')
+ call assert_equal(0, g:Xgetlist({'id':qfid, 'nr':0}).id)
+endfunc
+
+func Test_qf_id()
+ call Xqfid_tests('c')
+ call Xqfid_tests('l')
+endfunc
+
+func Xqfjump_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call writefile(["Line1\tFoo", "Line2"], 'F1', 'D')
+ call writefile(["Line1\tBar", "Line2"], 'F2', 'D')
+ call writefile(["Line1\tBaz", "Line2"], 'F3', 'D')
+
+ call g:Xsetlist([], 'f')
+
+ " Tests for
+ " Jumping to a line using a pattern
+ " Jumping to a column greater than the last column in a line
+ " Jumping to a line greater than the last line in the file
+ let l = []
+ for i in range(1, 7)
+ call add(l, {})
+ endfor
+ let l[0].filename='F1'
+ let l[0].pattern='Line1'
+ let l[1].filename='F2'
+ let l[1].pattern='Line1'
+ let l[2].filename='F3'
+ let l[2].pattern='Line1'
+ let l[3].filename='F3'
+ let l[3].lnum=1
+ let l[3].col=9
+ let l[3].vcol=1
+ let l[4].filename='F3'
+ let l[4].lnum=99
+ let l[5].filename='F3'
+ let l[5].lnum=1
+ let l[5].col=99
+ let l[5].vcol=1
+ let l[6].filename='F3'
+ let l[6].pattern='abcxyz'
+
+ call g:Xsetlist([], ' ', {'items' : l})
+ Xopen | only
+ 2Xnext
+ call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal('F3', @%)
+ Xnext
+ call assert_equal(7, col('.'))
+ Xnext
+ call assert_equal(2, line('.'))
+ Xnext
+ call assert_equal(9, col('.'))
+ 2
+ Xnext
+ call assert_equal(2, line('.'))
+
+ if a:cchar == 'l'
+ " When jumping to a location list entry in the location list window and
+ " no usable windows are available, then a new window should be opened.
+ enew! | new | only
+ call g:Xsetlist([], 'f')
+ setlocal buftype=nofile
+ new
+ let lines =<< trim END
+ F1:1:1:Line1
+ F1:2:2:Line2
+ F2:1:1:Line1
+ F2:2:2:Line2
+ F3:1:1:Line1
+ F3:2:2:Line2
+ END
+ call g:Xsetlist([], ' ', {'lines': lines})
+ Xopen
+ let winid = win_getid()
+ wincmd p
+ close
+ call win_gotoid(winid)
+ Xnext
+ call assert_equal(3, winnr('$'))
+ call assert_equal(1, winnr())
+ call assert_equal(2, line('.'))
+
+ " When jumping to an entry in the location list window and the window
+ " associated with the location list is not present and a window containing
+ " the file is already present, then that window should be used.
+ close
+ belowright new
+ call g:Xsetlist([], 'f')
+ edit F3
+ call win_gotoid(winid)
+ Xlast
+ call assert_equal(3, winnr())
+ call assert_equal(6, g:Xgetlist({'size' : 1}).size)
+ call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid)
+ endif
+
+ " Cleanup
+ enew!
+ new | only
+endfunc
+
+func Test_qfjump()
+ call Xqfjump_tests('c')
+ call Xqfjump_tests('l')
+endfunc
+
+" Tests for the getqflist() and getloclist() functions when the list is not
+" present or is empty
+func Xgetlist_empty_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Empty quickfix stack
+ call g:Xsetlist([], 'f')
+ call assert_equal('', g:Xgetlist({'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick)
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
+ \ 'items' : [], 'nr' : 0, 'size' : 0, 'qfbufnr' : 0,
+ \ 'title' : '', 'winid' : 0, 'changedtick': 0,
+ \ 'quickfixtextfunc' : ''}, g:Xgetlist({'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0,
+ \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '',
+ \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0,
+ \ 'qfbufnr' : 0, 'quickfixtextfunc' : ''},
+ \ g:Xgetlist({'all' : 0}))
+ endif
+
+ " Quickfix window with empty stack
+ silent! Xopen
+ let qfwinid = (a:cchar == 'c') ? win_getid() : 0
+ let qfbufnr = (a:cchar == 'c') ? bufnr('') : 0
+ call assert_equal(qfwinid, g:Xgetlist({'winid' : 0}).winid)
+ Xclose
+
+ " Empty quickfix list
+ Xexpr ""
+ call assert_equal('', g:Xgetlist({'context' : 0}).context)
+ call assert_notequal(0, g:Xgetlist({'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'items' : 0}).items)
+ call assert_notequal(0, g:Xgetlist({'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'size' : 0}).size)
+ call assert_notequal('', g:Xgetlist({'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ let qfid = g:Xgetlist({'id' : 0}).id
+ call g:Xsetlist([], 'f')
+
+ " Non-existing quickfix identifier
+ call assert_equal('', g:Xgetlist({'id' : qfid, 'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'id' : qfid}).id)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'id' : qfid, 'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'id' : qfid, 'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'changedtick' : 0}).changedtick)
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'qfbufnr' : qfbufnr, 'quickfixtextfunc' : '',
+ \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0,
+ \ 'quickfixtextfunc' : ''},
+ \ g:Xgetlist({'id' : qfid, 'all' : 0}))
+ endif
+
+ " Non-existing quickfix list number
+ call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'nr' : 5}).nr)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'nr' : 5, 'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'nr' : 5, 'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'changedtick' : 0}).changedtick)
+ if a:cchar == 'c'
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0, 'qfbufnr' : qfbufnr,
+ \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+ else
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [],
+ \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0,
+ \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0,
+ \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+ endif
+endfunc
+
+func Test_empty_list_quickfixtextfunc()
+ " This was crashing. Can only reproduce by running it in a separate Vim
+ " instance.
+ let lines =<< trim END
+ func s:Func(o)
+ cgetexpr '0'
+ endfunc
+ cope
+ let &quickfixtextfunc = 's:Func'
+ cgetfile [ex
+ END
+ call writefile(lines, 'Xquickfixtextfunc', 'D')
+ call RunVim([], [], '-e -s -S Xquickfixtextfunc -c qa')
+endfunc
+
+func Test_getqflist()
+ call Xgetlist_empty_tests('c')
+ call Xgetlist_empty_tests('l')
+endfunc
+
+func Test_getqflist_invalid_nr()
+ " The following commands used to crash Vim
+ cexpr ""
+ call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
+
+ " Cleanup
+ call setqflist([], 'r')
+endfunc
+
+" Tests for the quickfix/location list changedtick
+func Xqftick_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+
+ Xexpr "F1:10:Line10"
+ let qfid = g:Xgetlist({'id' : 0}).id
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ Xaddexpr "F2:20:Line20\nF2:21:Line21"
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'a', {'lines' : ["F3:30:Line30", "F3:31:Line31"]})
+ call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'r', {'lines' : ["F4:40:Line40"]})
+ call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'a', {'title' : 'New Title'})
+ call assert_equal(5, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ enew!
+ call append(0, ["F5:50:L50", "F6:60:L60"])
+ Xaddbuffer
+ call assert_equal(6, g:Xgetlist({'changedtick' : 0}).changedtick)
+ enew!
+
+ call g:Xsetlist([], 'a', {'context' : {'bus' : 'pci'}})
+ call assert_equal(7, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'a')
+ call assert_equal(8, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], ' ')
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r')
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ call writefile(["F8:80:L80", "F8:81:L81"], "Xone", 'D')
+ Xfile Xone
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ Xaddfile Xone
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ " Test case for updating a non-current quickfix list
+ call g:Xsetlist([], 'f')
+ Xexpr "F1:1:L1"
+ Xexpr "F2:2:L2"
+ call g:Xsetlist([], 'a', {'nr' : 1, "lines" : ["F10:10:L10"]})
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call assert_equal(2, g:Xgetlist({'nr' : 1, 'changedtick' : 0}).changedtick)
+endfunc
+
+func Test_qf_tick()
+ call Xqftick_tests('c')
+ call Xqftick_tests('l')
+endfunc
+
+" Test helpgrep with lang specifier
+func Xtest_helpgrep_with_lang_specifier(cchar)
+ call s:setup_commands(a:cchar)
+ Xhelpgrep Vim@en
+ call assert_equal('help', &filetype)
+ call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr)
+ new | only
+endfunc
+
+func Test_helpgrep_with_lang_specifier()
+ call Xtest_helpgrep_with_lang_specifier('c')
+ call Xtest_helpgrep_with_lang_specifier('l')
+endfunc
+
+" The following test used to crash Vim.
+" Open the location list window and close the regular window associated with
+" the location list. When the garbage collection runs now, it incorrectly
+" marks the location list context as not in use and frees the context.
+func Test_ll_window_ctx()
+ call setloclist(0, [], 'f')
+ call setloclist(0, [], 'a', {'context' : []})
+ lopen | only
+ call test_garbagecollect_now()
+ echo getloclist(0, {'context' : 1}).context
+ enew | only
+endfunc
+
+" The following test used to crash vim
+func Test_lfile_crash()
+ sp Xtest
+ au QuickFixCmdPre * bw
+ call assert_fails('lfile', 'E40:')
+ au! QuickFixCmdPre
+endfunc
+
+" The following test used to crash vim
+func Test_lbuffer_crash()
+ sv Xtest
+ augroup QF_Test
+ au!
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bw
+ augroup END
+ lbuffer
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" The following test used to crash vim
+func Test_lexpr_crash()
+ augroup QF_Test
+ au!
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f')
+ augroup END
+ lexpr ""
+ augroup QF_Test
+ au!
+ augroup END
+
+ enew | only
+ augroup QF_Test
+ au!
+ au BufNew * call setloclist(0, [], 'f')
+ augroup END
+ lexpr 'x:1:x'
+ augroup QF_Test
+ au!
+ augroup END
+
+ enew | only
+ lexpr ''
+ lopen
+ augroup QF_Test
+ au!
+ au FileType * call setloclist(0, [], 'f')
+ augroup END
+ lexpr ''
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" The following test used to crash Vim
+func Test_lvimgrep_crash()
+ sv Xtest
+ augroup QF_Test
+ au!
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call setloclist(0, [], 'f')
+ augroup END
+ lvimgrep quickfix test_quickfix.vim
+ augroup QF_Test
+ au!
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setloclist(0, [], 'r')
+ augroup END
+ call assert_fails('lvimgrep Test_lvimgrep_crash *', 'E926:')
+ augroup QF_Test
+ au!
+ augroup END
+
+ enew | only
+endfunc
+
+func Test_lvimgrep_crash2()
+ au BufNewFile x sfind
+ call assert_fails('lvimgrep x x', 'E471:')
+ call assert_fails('lvimgrep x x x', 'E471:')
+
+ au! BufNewFile
+endfunc
+
+" Test for the position of the quickfix and location list window
+func Test_qfwin_pos()
+ " Open two windows
+ new | only
+ new
+ cexpr ['F1:10:L10']
+ copen
+ " Quickfix window should be the bottom most window
+ call assert_equal(3, winnr())
+ close
+ " Open at the very top
+ wincmd t
+ topleft copen
+ call assert_equal(1, winnr())
+ close
+ " open left of the current window
+ wincmd t
+ below new
+ leftabove copen
+ call assert_equal(2, winnr())
+ close
+ " open right of the current window
+ rightbelow copen
+ call assert_equal(3, winnr())
+ close
+endfunc
+
+" Tests for quickfix/location lists changed by autocommands when
+" :vimgrep/:lvimgrep commands are running.
+func Test_vimgrep_autocmd()
+ call setqflist([], 'f')
+ call writefile(['stars'], 'Xtest1.txt', 'D')
+ call writefile(['stars'], 'Xtest2.txt', 'D')
+
+ " Test 1:
+ " When searching for a pattern using :vimgrep, if the quickfix list is
+ " changed by an autocmd, the results should be added to the correct quickfix
+ " list.
+ autocmd BufRead Xtest2.txt cexpr '' | cexpr ''
+ silent vimgrep stars Xtest*.txt
+ call assert_equal(1, getqflist({'nr' : 0}).nr)
+ call assert_equal(3, getqflist({'nr' : '$'}).nr)
+ call assert_equal('Xtest2.txt', bufname(getqflist()[1].bufnr))
+ au! BufRead Xtest2.txt
+
+ " Test 2:
+ " When searching for a pattern using :vimgrep, if the quickfix list is
+ " freed, then a error should be given.
+ silent! %bwipe!
+ call setqflist([], 'f')
+ autocmd BufRead Xtest2.txt for i in range(10) | cexpr '' | endfor
+ call assert_fails('vimgrep stars Xtest*.txt', 'E925:')
+ au! BufRead Xtest2.txt
+
+ " Test 3:
+ " When searching for a pattern using :lvimgrep, if the location list is
+ " freed, then the command should error out.
+ silent! %bwipe!
+ let g:save_winid = win_getid()
+ autocmd BufRead Xtest2.txt call setloclist(g:save_winid, [], 'f')
+ call assert_fails('lvimgrep stars Xtest*.txt', 'E926:')
+ au! BufRead Xtest2.txt
+
+ call setqflist([], 'f')
+endfunc
+
+" Test for an autocmd changing the current directory when running vimgrep
+func Xvimgrep_autocmd_cd(cchar)
+ call s:setup_commands(a:cchar)
+
+ %bwipe
+ let save_cwd = getcwd()
+
+ augroup QF_Test
+ au!
+ autocmd BufRead * silent cd %:p:h
+ augroup END
+
+ 10Xvimgrep /vim/ Xgrepdir/**
+ let l = g:Xgetlist()
+ call assert_equal('f1.txt', bufname(l[0].bufnr))
+ call assert_equal('f2.txt', fnamemodify(bufname(l[2].bufnr), ':t'))
+
+ augroup QF_Test
+ au!
+ augroup END
+
+ exe 'cd ' . save_cwd
+endfunc
+
+func Test_vimgrep_autocmd_cd()
+ call mkdir('Xgrepdir/a', 'pR')
+ call mkdir('Xgrepdir/b', 'pR')
+ call writefile(['a_L1_vim', 'a_L2_vim'], 'Xgrepdir/a/f1.txt')
+ call writefile(['b_L1_vim', 'b_L2_vim'], 'Xgrepdir/b/f2.txt')
+ call Xvimgrep_autocmd_cd('c')
+ call Xvimgrep_autocmd_cd('l')
+ %bwipe
+endfunc
+
+" The following test used to crash Vim
+func Test_lhelpgrep_autocmd()
+ lhelpgrep quickfix
+ augroup QF_Test
+ au!
+ autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
+ augroup END
+ lhelpgrep buffer
+ call assert_equal('help', &filetype)
+ call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
+ lhelpgrep tabpage
+ call assert_equal('help', &filetype)
+ call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
+ augroup QF_Test
+ au!
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setqflist([], 'f')
+ augroup END
+ call assert_fails('helpgrep quickfix', 'E925:')
+ " run the test with a help window already open
+ help
+ wincmd w
+ call assert_fails('helpgrep quickfix', 'E925:')
+ augroup QF_Test
+ au!
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setqflist([], 'r')
+ augroup END
+ call assert_fails('helpgrep quickfix', 'E925:')
+ augroup QF_Test
+ au!
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setloclist(0, [], 'r')
+ augroup END
+ call assert_fails('lhelpgrep quickfix', 'E926:')
+ augroup QF_Test
+ au!
+ augroup END
+
+ " Replace the contents of a help window location list when it is still in
+ " use.
+ new | only
+ lhelpgrep quickfix
+ wincmd w
+ augroup QF_Test
+ au!
+ autocmd WinEnter * call setloclist(0, [], 'r')
+ augroup END
+ call assert_fails('lhelpgrep win_getid', 'E926:')
+ augroup QF_Test
+ au!
+ augroup END
+
+ %bw!
+endfunc
+
+" The following test used to crash Vim
+func Test_lhelpgrep_autocmd_free_loclist()
+ %bw!
+ lhelpgrep quickfix
+ wincmd w
+ augroup QF_Test
+ au!
+ autocmd WinEnter * call setloclist(0, [], 'f')
+ augroup END
+ lhelpgrep win_getid
+ wincmd w
+ wincmd w
+ wincmd w
+ augroup QF_Test
+ au!
+ augroup END
+ %bw!
+endfunc
+
+" Test for shortening/simplifying the file name when opening the
+" quickfix window or when displaying the quickfix list
+func Test_shorten_fname()
+ CheckUnix
+ %bwipe
+ " Create a quickfix list with an absolute path filename
+ let fname = getcwd() . '/test_quickfix.vim'
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Opening the quickfix window should simplify the file path
+ cwindow
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+ cclose
+ %bwipe
+ " Create a quickfix list with an absolute path filename
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Displaying the quickfix list should simplify the file path
+ silent! clist
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+ " Add a few entries for the same file with different paths and check whether
+ " the buffer name is shortened
+ %bwipe
+ call setqflist([], 'f')
+ call setqflist([{'filename' : 'test_quickfix.vim', 'lnum' : 10},
+ \ {'filename' : '../testdir/test_quickfix.vim', 'lnum' : 20},
+ \ {'filename' : fname, 'lnum' : 30}], ' ')
+ copen
+ call assert_equal(['test_quickfix.vim|10| ',
+ \ 'test_quickfix.vim|20| ',
+ \ 'test_quickfix.vim|30| '], getline(1, '$'))
+ cclose
+endfunc
+
+" Quickfix title tests
+" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
+" Otherwise due to indentation, the title is set with spaces at the beginning
+" of the command.
+func Test_qftitle()
+ call writefile(["F1:1:Line1"], 'Xerr', 'D')
+
+ " :cexpr
+ exe "cexpr readfile('Xerr')"
+ call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
+
+ " :cgetexpr
+ exe "cgetexpr readfile('Xerr')"
+ call assert_equal(":cgetexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :caddexpr
+ call setqflist([], 'f')
+ exe "caddexpr readfile('Xerr')"
+ call assert_equal(":caddexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :cbuffer
+ new Xerr
+ exe "cbuffer"
+ call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cgetbuffer
+ edit Xerr
+ exe "cgetbuffer"
+ call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :caddbuffer
+ call setqflist([], 'f')
+ edit Xerr
+ exe "caddbuffer"
+ call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cfile
+ exe "cfile Xerr"
+ call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
+
+ " :cgetfile
+ exe "cgetfile Xerr"
+ call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
+
+ " :caddfile
+ call setqflist([], 'f')
+ exe "caddfile Xerr"
+ call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
+
+ " :grep
+ set grepprg=internal
+ exe "grep F1 Xerr"
+ call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :grepadd
+ call setqflist([], 'f')
+ exe "grepadd F1 Xerr"
+ call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
+ set grepprg&vim
+
+ " :vimgrep
+ exe "vimgrep F1 Xerr"
+ call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :vimgrepadd
+ call setqflist([], 'f')
+ exe "vimgrepadd F1 Xerr"
+ call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+
+ call setqflist(['F1:10:L10'], ' ')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'a')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'r')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ close
+
+ call setqflist([], ' ', {'title' : 'Errors'})
+ copen
+ call assert_equal('Errors', w:quickfix_title)
+ call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
+ call assert_equal('Errors', w:quickfix_title)
+ cclose
+
+ " Switching to another quickfix list in one tab page should update the
+ " quickfix window title and statusline in all the other tab pages also
+ call setqflist([], 'f')
+ %bw!
+ cgetexpr ['file_one:1:1: error in the first quickfix list']
+ call setqflist([], 'a', {'title': 'first quickfix list'})
+ cgetexpr ['file_two:2:1: error in the second quickfix list']
+ call setqflist([], 'a', {'title': 'second quickfix list'})
+ copen
+ wincmd t
+ tabnew two
+ copen
+ wincmd t
+ colder
+ call assert_equal('first quickfix list', gettabwinvar(1, 2, 'quickfix_title'))
+ call assert_equal('first quickfix list', gettabwinvar(2, 2, 'quickfix_title'))
+ call assert_equal(1, tabpagewinnr(1))
+ call assert_equal(1, tabpagewinnr(2))
+ tabnew
+ call setqflist([], 'a', {'title': 'new quickfix title'})
+ call assert_equal('new quickfix title', gettabwinvar(1, 2, 'quickfix_title'))
+ call assert_equal('new quickfix title', gettabwinvar(2, 2, 'quickfix_title'))
+ %bw!
+endfunc
+
+func Test_lbuffer_with_bwipe()
+ new
+ new
+ augroup nasty
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * bwipe
+ augroup END
+ lbuffer
+ augroup nasty
+ au!
+ augroup END
+endfunc
+
+" Test for an autocmd freeing the quickfix/location list when cexpr/lexpr is
+" running
+func Xexpr_acmd_freelist(cchar)
+ call s:setup_commands(a:cchar)
+
+ " This was using freed memory (but with what events?)
+ augroup nasty
+ au QuickFixCmdPre,QuickFixCmdPost,BufEnter,BufLeave * call g:Xsetlist([], 'f')
+ augroup END
+ Xexpr "x"
+ augroup nasty
+ au!
+ augroup END
+endfunc
+
+func Test_cexpr_acmd_freelist()
+ call Xexpr_acmd_freelist('c')
+ call Xexpr_acmd_freelist('l')
+endfunc
+
+" Test for commands that create a new quickfix/location list and jump to the
+" first error automatically.
+func Xjumpto_first_error_test(cchar)
+ call s:setup_commands(a:cchar)
+
+ call s:create_test_file('Xtestfile1')
+ call s:create_test_file('Xtestfile2')
+ let l = ['Xtestfile1:2:Line2', 'Xtestfile2:4:Line4']
+
+ " Test for cexpr/lexpr
+ enew
+ Xexpr l
+ call assert_equal('Xtestfile1', @%)
+ call assert_equal(2, line('.'))
+
+ " Test for cfile/lfile
+ enew
+ call writefile(l, 'Xerr', 'D')
+ Xfile Xerr
+ call assert_equal('Xtestfile1', @%)
+ call assert_equal(2, line('.'))
+
+ " Test for cbuffer/lbuffer
+ edit Xerr
+ Xbuffer
+ call assert_equal('Xtestfile1', @%)
+ call assert_equal(2, line('.'))
+
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+endfunc
+
+func Test_jumpto_first_error()
+ call Xjumpto_first_error_test('c')
+ call Xjumpto_first_error_test('l')
+endfunc
+
+" Test for a quickfix autocmd changing the quickfix/location list before
+" jumping to the first error in the new list.
+func Xautocmd_changelist(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Test for cfile/lfile
+ call s:create_test_file('Xtestfile1')
+ call s:create_test_file('Xtestfile2')
+ Xexpr 'Xtestfile1:2:Line2'
+ autocmd QuickFixCmdPost * Xolder
+ call writefile(['Xtestfile2:4:Line4'], 'Xerr', 'D')
+ Xfile Xerr
+ call assert_equal('Xtestfile2', @%)
+ call assert_equal(4, line('.'))
+ autocmd! QuickFixCmdPost
+
+ " Test for cbuffer/lbuffer
+ call g:Xsetlist([], 'f')
+ Xexpr 'Xtestfile1:2:Line2'
+ autocmd QuickFixCmdPost * Xolder
+ call writefile(['Xtestfile2:4:Line4'], 'Xerr')
+ edit Xerr
+ Xbuffer
+ call assert_equal('Xtestfile2', @%)
+ call assert_equal(4, line('.'))
+ autocmd! QuickFixCmdPost
+
+ " Test for cexpr/lexpr
+ call g:Xsetlist([], 'f')
+ Xexpr 'Xtestfile1:2:Line2'
+ autocmd QuickFixCmdPost * Xolder
+ Xexpr 'Xtestfile2:4:Line4'
+ call assert_equal('Xtestfile2', @%)
+ call assert_equal(4, line('.'))
+ autocmd! QuickFixCmdPost
+
+ " The grepprg may not be set on non-Unix systems
+ if has('unix')
+ " Test for grep/lgrep
+ call g:Xsetlist([], 'f')
+ Xexpr 'Xtestfile1:2:Line2'
+ autocmd QuickFixCmdPost * Xolder
+ silent Xgrep Line5 Xtestfile2
+ call assert_equal('Xtestfile2', @%)
+ call assert_equal(5, line('.'))
+ autocmd! QuickFixCmdPost
+ endif
+
+ " Test for vimgrep/lvimgrep
+ call g:Xsetlist([], 'f')
+ Xexpr 'Xtestfile1:2:Line2'
+ autocmd QuickFixCmdPost * Xolder
+ silent Xvimgrep Line5 Xtestfile2
+ call assert_equal('Xtestfile2', @%)
+ call assert_equal(5, line('.'))
+ autocmd! QuickFixCmdPost
+
+ " Test for autocommands clearing the quickfix list before jumping to the
+ " first error. This should not result in an error
+ autocmd QuickFixCmdPost * call g:Xsetlist([], 'r')
+ let v:errmsg = ''
+ " Test for cfile/lfile
+ Xfile Xerr
+ call assert_true(v:errmsg !~# 'E42:')
+ " Test for cbuffer/lbuffer
+ edit Xerr
+ Xbuffer
+ call assert_true(v:errmsg !~# 'E42:')
+ " Test for cexpr/lexpr
+ Xexpr 'Xtestfile2:4:Line4'
+ call assert_true(v:errmsg !~# 'E42:')
+ " Test for grep/lgrep
+ " The grepprg may not be set on non-Unix systems
+ if has('unix')
+ silent Xgrep Line5 Xtestfile2
+ call assert_true(v:errmsg !~# 'E42:')
+ endif
+ " Test for vimgrep/lvimgrep
+ call assert_fails('silent Xvimgrep Line5 Xtestfile2', 'E480:')
+ autocmd! QuickFixCmdPost
+
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+endfunc
+
+func Test_autocmd_changelist()
+ call Xautocmd_changelist('c')
+ call Xautocmd_changelist('l')
+endfunc
+
+" Tests for the ':filter /pat/ clist' command
+func Test_filter_clist()
+ cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
+ call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
+ \ split(execute('filter /Line 15/ clist'), "\n"))
+ call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
+ \ split(execute('filter /Xfile1/ clist'), "\n"))
+ call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
+
+ call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
+ \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
+ call assert_equal([' 2 pqr:pat2: '],
+ \ split(execute('filter /pqr/ clist'), "\n"))
+ call assert_equal([' 1 abc:pat1: '],
+ \ split(execute('filter /pat1/ clist'), "\n"))
+endfunc
+
+" Tests for the "CTRL-W <CR>" command.
+func Xview_result_split_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Test that "CTRL-W <CR>" in a qf/ll window fails with empty list.
+ call g:Xsetlist([])
+ Xopen
+ let l:win_count = winnr('$')
+ call assert_fails('execute "normal! \<C-W>\<CR>"', 'E42:')
+ call assert_equal(l:win_count, winnr('$'))
+ Xclose
+endfunc
+
+func Test_view_result_split()
+ call Xview_result_split_tests('c')
+ call Xview_result_split_tests('l')
+endfunc
+
+" Test that :cc sets curswant
+func Test_curswant()
+ helpgrep quickfix
+ normal! llll
+ 1cc
+ call assert_equal(getcurpos()[4], virtcol('.'))
+ cclose | helpclose
+endfunc
+
+" Test for opening a file from the quickfix window using CTRL-W <Enter>
+" doesn't leave an empty buffer around.
+func Test_splitview()
+ call s:create_test_file('Xtestfile1')
+ call s:create_test_file('Xtestfile2')
+ new | only
+ let last_bufnr = bufnr('Test_sv_1', 1)
+ let l = ['Xtestfile1:2:Line2', 'Xtestfile2:4:Line4']
+ cgetexpr l
+ copen
+ let numbufs = len(getbufinfo())
+ exe "normal \<C-W>\<CR>"
+ copen
+ exe "normal j\<C-W>\<CR>"
+ " Make sure new empty buffers are not created
+ call assert_equal(numbufs, len(getbufinfo()))
+ " Creating a new buffer should use the next available buffer number
+ call assert_equal(last_bufnr + 4, bufnr("Test_sv_2", 1))
+ bwipe Test_sv_1
+ bwipe Test_sv_2
+ new | only
+
+ " When split opening files from location list window, make sure that two
+ " windows doesn't refer to the same location list
+ lgetexpr l
+ let locid = getloclist(0, {'id' : 0}).id
+ lopen
+ exe "normal \<C-W>\<CR>"
+ call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
+ call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
+ new | only
+
+ " When split opening files from a helpgrep location list window, a new help
+ " window should be opened with a copy of the location list.
+ lhelpgrep window
+ let locid = getloclist(0, {'id' : 0}).id
+ lwindow
+ exe "normal j\<C-W>\<CR>"
+ call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
+ call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
+ new | only
+
+ " Using :split or :vsplit from a quickfix window should behave like a :new
+ " or a :vnew command
+ copen
+ split
+ call assert_equal(3, winnr('$'))
+ let l = getwininfo()
+ call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix])
+ close
+ copen
+ vsplit
+ let l = getwininfo()
+ call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix])
+ new | only
+
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+endfunc
+
+" Test for parsing entries using visual screen column
+func Test_viscol()
+ enew
+ call writefile(["Col1\tCol2\tCol3"], 'Xfile1', 'D')
+ edit Xfile1
+
+ " Use byte offset for column number
+ set efm&
+ cexpr "Xfile1:1:5:XX\nXfile1:1:9:YY\nXfile1:1:20:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([9, 12], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+
+ " Use screen column offset for column number
+ set efm=%f:%l:%v:%m
+ cexpr "Xfile1:1:8:XX\nXfile1:1:12:YY\nXfile1:1:20:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([9, 12], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+ cexpr "Xfile1:1:6:XX\nXfile1:1:15:YY\nXfile1:1:24:ZZ"
+ call assert_equal([5, 8], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([10, 16], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([14, 20], [col('.'), virtcol('.')])
+
+ enew
+ call writefile(["Col1\täü\töß\tCol4"], 'Xfile1')
+
+ " Use byte offset for column number
+ set efm&
+ cexpr "Xfile1:1:8:XX\nXfile1:1:11:YY\nXfile1:1:16:ZZ"
+ call assert_equal([8, 10], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([11, 17], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([16, 25], [col('.'), virtcol('.')])
+
+ " Use screen column offset for column number
+ set efm=%f:%l:%v:%m
+ cexpr "Xfile1:1:10:XX\nXfile1:1:17:YY\nXfile1:1:25:ZZ"
+ call assert_equal([8, 10], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([11, 17], [col('.'), virtcol('.')])
+ cnext
+ call assert_equal([16, 25], [col('.'), virtcol('.')])
+
+ " Use screen column number with a multi-line error message
+ enew
+ call writefile(["à test"], 'Xfile1')
+ set efm=%E===\ %f\ ===,%C%l:%v,%Z%m
+ cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
+ call assert_equal('Xfile1', @%)
+ call assert_equal([0, 1, 4, 0], getpos('.'))
+
+ " Repeat previous test with byte offset %c: ensure that fix to issue #7145
+ " does not break this
+ set efm=%E===\ %f\ ===,%C%l:%c,%Z%m
+ cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
+ call assert_equal('Xfile1', @%)
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
+ enew | only
+ set efm&
+endfunc
+
+" Test for the quickfix window buffer
+func Xqfbuf_test(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Quickfix buffer should be reused across closing and opening a quickfix
+ " window
+ Xexpr "F1:10:Line10"
+ Xopen
+ let qfbnum = bufnr('')
+ Xclose
+ " Even after the quickfix window is closed, the buffer should be loaded
+ call assert_true(bufloaded(qfbnum))
+ call assert_true(qfbnum, g:Xgetlist({'qfbufnr' : 0}).qfbufnr)
+ Xopen
+ " Buffer should be reused when opening the window again
+ call assert_equal(qfbnum, bufnr(''))
+ Xclose
+
+ " When quickfix buffer is wiped out, getqflist() should return 0
+ %bw!
+ Xexpr ""
+ Xopen
+ bw!
+ call assert_equal(0, g:Xgetlist({'qfbufnr': 0}).qfbufnr)
+
+ if a:cchar == 'l'
+ %bwipe
+ " For a location list, when both the file window and the location list
+ " window for the list are closed, then the buffer should be freed.
+ new | only
+ lexpr "F1:10:Line10"
+ let wid = win_getid()
+ lopen
+ let qfbnum = bufnr('')
+ call assert_match(qfbnum . ' %a- "\[Location List]"', execute('ls'))
+ close
+ " When the location list window is closed, the buffer name should not
+ " change to 'Quickfix List'
+ call assert_match(qfbnum . 'u h- "\[Location List]"', execute('ls!'))
+ call assert_true(bufloaded(qfbnum))
+
+ " After deleting a location list buffer using ":bdelete", opening the
+ " location list window should mark the buffer as a location list buffer.
+ exe "bdelete " . qfbnum
+ lopen
+ call assert_equal("quickfix", &buftype)
+ call assert_equal(1, getwininfo(win_getid(winnr()))[0].loclist)
+ call assert_equal(wid, getloclist(0, {'filewinid' : 0}).filewinid)
+ call assert_false(&swapfile)
+ lclose
+
+ " When the location list is cleared for the window, the buffer should be
+ " removed
+ call setloclist(0, [], 'f')
+ call assert_false(bufexists(qfbnum))
+ call assert_equal(0, getloclist(0, {'qfbufnr' : 0}).qfbufnr)
+
+ " When the location list is freed with the location list window open, the
+ " location list buffer should not be lost. It should be reused when the
+ " location list is again populated.
+ lexpr "F1:10:Line10"
+ lopen
+ let wid = win_getid()
+ let qfbnum = bufnr('')
+ wincmd p
+ call setloclist(0, [], 'f')
+ lexpr "F1:10:Line10"
+ lopen
+ call assert_equal(wid, win_getid())
+ call assert_equal(qfbnum, bufnr(''))
+ lclose
+
+ " When the window with the location list is closed, the buffer should be
+ " removed
+ new | only
+ call assert_false(bufexists(qfbnum))
+ endif
+endfunc
+
+func Test_qfbuf()
+ call Xqfbuf_test('c')
+ call Xqfbuf_test('l')
+endfunc
+
+" If there is an autocmd to use only one window, then opening the location
+" list window used to crash Vim.
+func Test_winonly_autocmd()
+ call s:create_test_file('Xtest1')
+ " Autocmd to show only one Vim window at a time
+ autocmd WinEnter * only
+ new
+ " Load the location list
+ lexpr "Xtest1:5:Line5\nXtest1:10:Line10\nXtest1:15:Line15"
+ let loclistid = getloclist(0, {'id' : 0}).id
+ " Open the location list window. Only this window will be shown and the file
+ " window is closed.
+ lopen
+ call assert_equal(loclistid, getloclist(0, {'id' : 0}).id)
+ " Jump to an entry in the location list and make sure that the cursor is
+ " positioned correctly.
+ ll 3
+ call assert_equal(loclistid, getloclist(0, {'id' : 0}).id)
+ call assert_equal('Xtest1', @%)
+ call assert_equal(15, line('.'))
+ " Cleanup
+ autocmd! WinEnter
+ new | only
+ call delete('Xtest1')
+endfunc
+
+" Test to make sure that an empty quickfix buffer is not reused for loading
+" a normal buffer.
+func Test_empty_qfbuf()
+ enew | only
+ call writefile(["Test"], 'Xfile1', 'D')
+ call setqflist([], 'f')
+ copen | only
+ let qfbuf = bufnr('')
+ edit Xfile1
+ call assert_notequal(qfbuf, bufnr(''))
+ enew
+endfunc
+
+" Test for the :cbelow, :cabove, :lbelow and :labove commands.
+" And for the :cafter, :cbefore, :lafter and :lbefore commands.
+func Xtest_below(cchar)
+ call s:setup_commands(a:cchar)
+
+ " No quickfix/location list
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('Xbefore', 'E42:')
+ call assert_fails('Xafter', 'E42:')
+
+ " Empty quickfix/location list
+ call g:Xsetlist([])
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('Xbefore', 'E42:')
+ call assert_fails('Xafter', 'E42:')
+
+ call s:create_test_file('X1')
+ call s:create_test_file('X2')
+ call s:create_test_file('X3')
+ call s:create_test_file('X4')
+
+ " Invalid entries
+ edit X1
+ call g:Xsetlist(["E1", "E2"])
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('3Xbelow', 'E42:')
+ call assert_fails('4Xabove', 'E42:')
+ call assert_fails('Xbefore', 'E42:')
+ call assert_fails('Xafter', 'E42:')
+ call assert_fails('3Xbefore', 'E42:')
+ call assert_fails('4Xafter', 'E42:')
+
+ " Test the commands with various arguments
+ Xexpr ["X1:5:3:L5", "X2:5:2:L5", "X2:10:3:L10", "X2:15:4:L15", "X3:3:5:L3"]
+ edit +7 X2
+ Xabove
+ call assert_equal(['X2', 5], [@%, line('.')])
+ call assert_fails('Xabove', 'E553:')
+ normal 7G
+ Xbefore
+ call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')])
+ call assert_fails('Xbefore', 'E553:')
+
+ normal 2j
+ Xbelow
+ call assert_equal(['X2', 10], [@%, line('.')])
+ normal 7G
+ Xafter
+ call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')])
+
+ " Last error in this file
+ Xbelow 99
+ call assert_equal(['X2', 15], [@%, line('.')])
+ call assert_fails('Xbelow', 'E553:')
+ normal gg
+ Xafter 99
+ call assert_equal(['X2', 15, 4], [@%, line('.'), col('.')])
+ call assert_fails('Xafter', 'E553:')
+
+ " First error in this file
+ Xabove 99
+ call assert_equal(['X2', 5], [@%, line('.')])
+ call assert_fails('Xabove', 'E553:')
+ normal G
+ Xbefore 99
+ call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')])
+ call assert_fails('Xbefore', 'E553:')
+
+ normal gg
+ Xbelow 2
+ call assert_equal(['X2', 10], [@%, line('.')])
+ normal gg
+ Xafter 2
+ call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')])
+
+ normal G
+ Xabove 2
+ call assert_equal(['X2', 10], [@%, line('.')])
+ normal G
+ Xbefore 2
+ call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')])
+
+ edit X4
+ call assert_fails('Xabove', 'E42:')
+ call assert_fails('Xbelow', 'E42:')
+ call assert_fails('Xbefore', 'E42:')
+ call assert_fails('Xafter', 'E42:')
+ if a:cchar == 'l'
+ " If a buffer has location list entries from some other window but not
+ " from the current window, then the commands should fail.
+ edit X1 | split | call setloclist(0, [], 'f')
+ call assert_fails('Xabove', 'E776:')
+ call assert_fails('Xbelow', 'E776:')
+ call assert_fails('Xbefore', 'E776:')
+ call assert_fails('Xafter', 'E776:')
+ close
+ endif
+
+ " Test for lines with multiple quickfix entries
+ let lines =<< trim END
+ X1:5:L5
+ X2:5:1:L5_1
+ X2:5:2:L5_2
+ X2:5:3:L5_3
+ X2:10:1:L10_1
+ X2:10:2:L10_2
+ X2:10:3:L10_3
+ X2:15:1:L15_1
+ X2:15:2:L15_2
+ X2:15:3:L15_3
+ X3:3:L3
+ END
+ Xexpr lines
+ edit +1 X2
+ Xbelow 2
+ call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')])
+ normal 1G
+ Xafter 2
+ call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')])
+
+ normal gg
+ Xbelow 99
+ call assert_equal(['X2', 15, 1], [@%, line('.'), col('.')])
+ normal gg
+ Xafter 99
+ call assert_equal(['X2', 15, 3], [@%, line('.'), col('.')])
+
+ normal G
+ Xabove 2
+ call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')])
+ normal G
+ Xbefore 2
+ call assert_equal(['X2', 15, 2], [@%, line('.'), col('.')])
+
+ normal G
+ Xabove 99
+ call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')])
+ normal G
+ Xbefore 99
+ call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')])
+
+ normal 10G
+ Xabove
+ call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')])
+ normal 10G$
+ 2Xbefore
+ call assert_equal(['X2', 10, 2], [@%, line('.'), col('.')])
+
+ normal 10G
+ Xbelow
+ call assert_equal(['X2', 15, 1], [@%, line('.'), col('.')])
+ normal 9G
+ 5Xafter
+ call assert_equal(['X2', 15, 2], [@%, line('.'), col('.')])
+
+ " Invalid range
+ if a:cchar == 'c'
+ call assert_fails('-2cbelow', 'E16:')
+ call assert_fails('-2cafter', 'E16:')
+ else
+ call assert_fails('-2lbelow', 'E16:')
+ call assert_fails('-2lafter', 'E16:')
+ endif
+
+ call delete('X1')
+ call delete('X2')
+ call delete('X3')
+ call delete('X4')
+endfunc
+
+func Test_cbelow()
+ call Xtest_below('c')
+ call Xtest_below('l')
+endfunc
+
+func Test_quickfix_count()
+ let commands =<< trim END
+ cNext
+ cNfile
+ cabove
+ cbelow
+ cfirst
+ clast
+ cnewer
+ cnext
+ cnfile
+ colder
+ cprevious
+ crewind
+ lNext
+ lNfile
+ labove
+ lbelow
+ lfirst
+ llast
+ lnewer
+ lnext
+ lnfile
+ lolder
+ lprevious
+ lrewind
+ END
+ for cmd in commands
+ call assert_fails('-1' .. cmd, 'E16:')
+ call assert_fails('.' .. cmd, 'E16:')
+ call assert_fails('%' .. cmd, 'E16:')
+ call assert_fails('$' .. cmd, 'E16:')
+ endfor
+endfunc
+
+" Test for aborting quickfix commands using QuickFixCmdPre
+func Xtest_qfcmd_abort(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+
+ " cexpr/lexpr
+ let e = ''
+ try
+ Xexpr ["F1:10:Line10", "F2:20:Line20"]
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " cfile/lfile
+ call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1', 'D')
+ let e = ''
+ try
+ Xfile Xfile1
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " cgetbuffer/lgetbuffer
+ enew!
+ call append(0, ["F1:10:Line10", "F2:20:Line20"])
+ let e = ''
+ try
+ Xgetbuffer
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ enew!
+
+ " vimgrep/lvimgrep
+ let e = ''
+ try
+ Xvimgrep /func/ test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " helpgrep/lhelpgrep
+ let e = ''
+ try
+ Xhelpgrep quickfix
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " grep/lgrep
+ if has('unix')
+ let e = ''
+ try
+ silent Xgrep func test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ endif
+endfunc
+
+func Test_qfcmd_abort()
+ augroup QF_Test
+ au!
+ autocmd QuickFixCmdPre * throw "AbortCmd"
+ augroup END
+
+ call Xtest_qfcmd_abort('c')
+ call Xtest_qfcmd_abort('l')
+
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" Test for using a file in one of the parent directories.
+func Test_search_in_dirstack()
+ call mkdir('Xtestdir/a/b/c', 'pR')
+ let save_cwd = getcwd()
+ call writefile(["X1_L1", "X1_L2"], 'Xtestdir/Xfile1')
+ call writefile(["X2_L1", "X2_L2"], 'Xtestdir/a/Xfile2')
+ call writefile(["X3_L1", "X3_L2"], 'Xtestdir/a/b/Xfile3')
+ call writefile(["X4_L1", "X4_L2"], 'Xtestdir/a/b/c/Xfile4')
+
+ let lines = "Entering dir Xtestdir\n" .
+ \ "Entering dir a\n" .
+ \ "Entering dir b\n" .
+ \ "Xfile2:2:X2_L2\n" .
+ \ "Leaving dir a\n" .
+ \ "Xfile1:2:X1_L2\n" .
+ \ "Xfile3:1:X3_L1\n" .
+ \ "Entering dir c\n" .
+ \ "Xfile4:2:X4_L2\n" .
+ \ "Leaving dir c\n"
+ set efm=%DEntering\ dir\ %f,%XLeaving\ dir\ %f,%f:%l:%m
+ cexpr lines .. "Leaving dir Xtestdir|\n" | let next = 1
+ call assert_equal(11, getqflist({'size' : 0}).size)
+ call assert_equal(4, getqflist({'idx' : 0}).idx)
+ call assert_equal('X2_L2', getline('.'))
+ call assert_equal(1, next)
+ cnext
+ call assert_equal(6, getqflist({'idx' : 0}).idx)
+ call assert_equal('X1_L2', getline('.'))
+ cnext
+ call assert_equal(7, getqflist({'idx' : 0}).idx)
+ call assert_equal(1, line('$'))
+ call assert_equal('', getline(1))
+ cnext
+ call assert_equal(9, getqflist({'idx' : 0}).idx)
+ call assert_equal(1, line('$'))
+ call assert_equal('', getline(1))
+
+ set efm&
+ exe 'cd ' . save_cwd
+endfunc
+
+" Test for :cquit
+func Test_cquit()
+ " Exit Vim with a non-zero value
+ if RunVim([], ["cquit 7"], '')
+ call assert_equal(7, v:shell_error)
+ endif
+
+ if RunVim([], ["50cquit"], '')
+ call assert_equal(50, v:shell_error)
+ endif
+
+ " Exit Vim with default value
+ if RunVim([], ["cquit"], '')
+ call assert_equal(1, v:shell_error)
+ endif
+
+ " Exit Vim with zero value
+ if RunVim([], ["cquit 0"], '')
+ call assert_equal(0, v:shell_error)
+ endif
+
+ " Exit Vim with negative value
+ call assert_fails('-3cquit', 'E16:')
+endfunc
+
+" Test for getting a specific item from a quickfix list
+func Xtest_getqflist_by_idx(cchar)
+ call s:setup_commands(a:cchar)
+ " Empty list
+ call assert_equal([], g:Xgetlist({'idx' : 1, 'items' : 0}).items)
+ Xexpr ['F1:10:L10', 'F1:20:L20']
+ let l = g:Xgetlist({'idx' : 2, 'items' : 0}).items
+ call assert_equal(bufnr('F1'), l[0].bufnr)
+ call assert_equal(20, l[0].lnum)
+ call assert_equal('L20', l[0].text)
+ call assert_equal([], g:Xgetlist({'idx' : -1, 'items' : 0}).items)
+ call assert_equal([], g:Xgetlist({'idx' : 3, 'items' : 0}).items)
+ call assert_equal({}, g:Xgetlist(#{idx: "abc"}))
+ %bwipe!
+endfunc
+
+func Test_getqflist_by_idx()
+ call Xtest_getqflist_by_idx('c')
+ call Xtest_getqflist_by_idx('l')
+endfunc
+
+" Test for the 'quickfixtextfunc' setting
+func Tqfexpr(info)
+ if a:info.quickfix
+ let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
+ else
+ let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
+ endif
+
+ let l = []
+ for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
+ let e = qfl[idx]
+ let s = ''
+ if e.bufnr != 0
+ let bname = bufname(e.bufnr)
+ let s ..= fnamemodify(bname, ':.')
+ endif
+ let s ..= '-'
+ let s ..= 'L' .. string(e.lnum) .. 'C' .. string(e.col) .. '-'
+ let s ..= e.text
+ call add(l, s)
+ endfor
+
+ return l
+endfunc
+
+func Xtest_qftextfunc(cchar)
+ call s:setup_commands(a:cchar)
+
+ set efm=%f:%l:%c:%m
+ set quickfixtextfunc=Tqfexpr
+ call assert_equal('Tqfexpr', &quickfixtextfunc)
+ call assert_equal('',
+ \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
+ call g:Xsetlist([
+ \ { 'filename': 'F1', 'lnum': 10, 'col': 2,
+ \ 'end_col': 7, 'text': 'green'},
+ \ { 'filename': 'F1', 'lnum': 20, 'end_lnum': 25, 'col': 4,
+ \ 'end_col': 8, 'text': 'blue'},
+ \ ])
+
+ Xwindow
+ call assert_equal('F1-L10C2-green', getline(1))
+ call assert_equal('F1-L20C4-blue', getline(2))
+ Xclose
+ set quickfixtextfunc&vim
+ Xwindow
+ call assert_equal('F1|10 col 2-7| green', getline(1))
+ call assert_equal('F1|20-25 col 4-8| blue', getline(2))
+ Xclose
+
+ set efm=%f:%l:%c:%m
+ set quickfixtextfunc=Tqfexpr
+ " Update the list with only the cwindow
+ Xwindow
+ only
+ call g:Xsetlist([
+ \ { 'filename': 'F2', 'lnum': 20, 'col': 2,
+ \ 'end_col': 7, 'text': 'red'}
+ \ ])
+ call assert_equal(['F2-L20C2-red'], getline(1, '$'))
+ new
+ Xclose
+ set efm&
+ set quickfixtextfunc&
+
+ " Test for per list 'quickfixtextfunc' setting
+ func PerQfText(info)
+ if a:info.quickfix
+ let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
+ else
+ let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
+ endif
+ if empty(qfl)
+ return []
+ endif
+ let l = []
+ for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
+ call add(l, 'Line ' .. qfl[idx].lnum .. ', Col ' .. qfl[idx].col)
+ endfor
+ return l
+ endfunc
+ set quickfixtextfunc=Tqfexpr
+ call g:Xsetlist([], ' ', {'quickfixtextfunc' : "PerQfText"})
+ Xaddexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal('Line 10, Col 2', getline(1))
+ call assert_equal('Line 20, Col 4', getline(2))
+ Xclose
+ call assert_equal(function('PerQfText'),
+ \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
+ " Add entries to the list when the quickfix buffer is hidden
+ Xaddexpr ['F1:30:6:red']
+ Xwindow
+ call assert_equal('Line 30, Col 6', getline(3))
+ Xclose
+ call g:Xsetlist([], 'r', {'quickfixtextfunc' : ''})
+ call assert_equal('', g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)
+ set quickfixtextfunc&
+ delfunc PerQfText
+
+ " Non-existing function
+ set quickfixtextfunc=Tabc
+ call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
+ call assert_fails("Xwindow", 'E117:')
+ Xclose
+ set quickfixtextfunc&
+
+ " set option to a non-function
+ set quickfixtextfunc=[10,\ 20]
+ call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
+ call assert_fails("Xwindow", 'E117:')
+ Xclose
+ set quickfixtextfunc&
+
+ " set option to a function with different set of arguments
+ func Xqftext(a, b, c)
+ return a:a .. a:b .. a:c
+ endfunc
+ set quickfixtextfunc=Xqftext
+ call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:')
+ call assert_fails("Xwindow", 'E119:')
+ Xclose
+
+ " set option to a function that returns a list with non-strings
+ func Xqftext2(d)
+ return ['one', [], 'two']
+ endfunc
+ set quickfixtextfunc=Xqftext2
+ call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']",
+ \ 'E730:')
+ call assert_fails('Xwindow', 'E730:')
+ call assert_equal(['one', 'F1|20 col 4| blue', 'F1|30 col 6| red'],
+ \ getline(1, '$'))
+ Xclose
+
+ set quickfixtextfunc&
+ delfunc Xqftext
+ delfunc Xqftext2
+
+ " set the global option to a lambda function
+ set quickfixtextfunc={d\ ->\ map(g:Xgetlist({'id'\ :\ d.id,\ 'items'\ :\ 1}).items[d.start_idx-1:d.end_idx-1],\ 'v:val.text')}
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal(['green', 'blue'], getline(1, '$'))
+ Xclose
+ call assert_equal("{d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1], 'v:val.text')}", &quickfixtextfunc)
+ set quickfixtextfunc&
+
+ " use a lambda function that returns an empty list
+ set quickfixtextfunc={d\ ->\ []}
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'],
+ \ getline(1, '$'))
+ Xclose
+ set quickfixtextfunc&
+
+ " use a lambda function that returns a list with empty strings
+ set quickfixtextfunc={d\ ->\ ['',\ '']}
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'],
+ \ getline(1, '$'))
+ Xclose
+ set quickfixtextfunc&
+
+ " set the per-quickfix list text function to a lambda function
+ call g:Xsetlist([], ' ',
+ \ {'quickfixtextfunc' :
+ \ {d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
+ \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}})
+ Xaddexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal('Line 10, Col 2', getline(1))
+ call assert_equal('Line 20, Col 4', getline(2))
+ Xclose
+ call assert_match("function('<lambda>\\d\\+')", string(g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc))
+ call g:Xsetlist([], 'f')
+endfunc
+
+func Test_qftextfunc()
+ call Xtest_qftextfunc('c')
+ call Xtest_qftextfunc('l')
+endfunc
+
+func Test_qftextfunc_callback()
+ let lines =<< trim END
+ set efm=%f:%l:%c:%m
+
+ #" Test for using a function name
+ LET &qftf = 'g:Tqfexpr'
+ cexpr "F0:0:0:L0"
+ copen
+ call assert_equal('F0-L0C0-L0', getline(1))
+ cclose
+
+ #" Test for using a function()
+ set qftf=function('g:Tqfexpr')
+ cexpr "F1:1:1:L1"
+ copen
+ call assert_equal('F1-L1C1-L1', getline(1))
+ cclose
+
+ #" Using a funcref variable to set 'quickfixtextfunc'
+ VAR Fn = function('g:Tqfexpr')
+ LET &qftf = Fn
+ cexpr "F2:2:2:L2"
+ copen
+ call assert_equal('F2-L2C2-L2', getline(1))
+ cclose
+
+ #" Using string(funcref_variable) to set 'quickfixtextfunc'
+ LET Fn = function('g:Tqfexpr')
+ LET &qftf = string(Fn)
+ cexpr "F3:3:3:L3"
+ copen
+ call assert_equal('F3-L3C3-L3', getline(1))
+ cclose
+
+ #" Test for using a funcref()
+ set qftf=funcref('g:Tqfexpr')
+ cexpr "F4:4:4:L4"
+ copen
+ call assert_equal('F4-L4C4-L4', getline(1))
+ cclose
+
+ #" Using a funcref variable to set 'quickfixtextfunc'
+ LET Fn = funcref('g:Tqfexpr')
+ LET &qftf = Fn
+ cexpr "F5:5:5:L5"
+ copen
+ call assert_equal('F5-L5C5-L5', getline(1))
+ cclose
+
+ #" Using a string(funcref_variable) to set 'quickfixtextfunc'
+ LET Fn = funcref('g:Tqfexpr')
+ LET &qftf = string(Fn)
+ cexpr "F5:5:5:L5"
+ copen
+ call assert_equal('F5-L5C5-L5', getline(1))
+ cclose
+
+ #" Test for using a lambda function with set
+ VAR optval = "LSTART a LMIDDLE g:Tqfexpr(a) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set qftf=" .. optval
+ cexpr "F6:6:6:L6"
+ copen
+ call assert_equal('F6-L6C6-L6', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to a lambda expression
+ LET &qftf = LSTART a LMIDDLE g:Tqfexpr(a) LEND
+ cexpr "F7:7:7:L7"
+ copen
+ call assert_equal('F7-L7C7-L7', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to string(lambda_expression)
+ LET &qftf = "LSTART a LMIDDLE g:Tqfexpr(a) LEND"
+ cexpr "F8:8:8:L8"
+ copen
+ call assert_equal('F8-L8C8-L8', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a LMIDDLE g:Tqfexpr(a) LEND
+ LET &qftf = Lambda
+ cexpr "F9:9:9:L9"
+ copen
+ call assert_equal('F9-L9C9-L9', getline(1))
+ cclose
+
+ #" Set 'quickfixtextfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a LMIDDLE g:Tqfexpr(a) LEND
+ LET &qftf = string(Lambda)
+ cexpr "F9:9:9:L9"
+ copen
+ call assert_equal('F9-L9C9-L9', getline(1))
+ cclose
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for using a script-local function name
+ func s:TqfFunc2(info)
+ let g:TqfFunc2Args = [a:info.start_idx, a:info.end_idx]
+ return ''
+ endfunc
+ let g:TqfFunc2Args = []
+ set quickfixtextfunc=s:TqfFunc2
+ cexpr "F10:10:10:L10"
+ cclose
+ call assert_equal([1, 1], g:TqfFunc2Args)
+
+ let &quickfixtextfunc = 's:TqfFunc2'
+ cexpr "F11:11:11:L11"
+ cclose
+ call assert_equal([1, 1], g:TqfFunc2Args)
+ delfunc s:TqfFunc2
+
+ " set 'quickfixtextfunc' to a partial with dict. This used to cause a crash.
+ func SetQftfFunc()
+ let params = {'qftf': function('g:DictQftfFunc')}
+ let &quickfixtextfunc = params.qftf
+ endfunc
+ func g:DictQftfFunc(_) dict
+ endfunc
+ call SetQftfFunc()
+ new
+ call SetQftfFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set qftf=
+ wincmd w
+ set qftf=
+ :%bw!
+
+ " set per-quickfix list 'quickfixtextfunc' to a partial with dict. This used
+ " to cause a crash.
+ let &qftf = ''
+ func SetLocalQftfFunc()
+ let params = {'qftf': function('g:DictQftfFunc')}
+ call setqflist([], 'a', {'quickfixtextfunc' : params.qftf})
+ endfunc
+ call SetLocalQftfFunc()
+ call test_garbagecollect_now()
+ call setqflist([], 'a', {'quickfixtextfunc' : ''})
+ delfunc g:DictQftfFunc
+ delfunc SetQftfFunc
+ delfunc SetLocalQftfFunc
+ set efm&
+endfunc
+
+" Test for updating a location list for some other window and check that
+" 'qftextfunc' uses the correct location list.
+func Test_qftextfunc_other_loclist()
+ %bw!
+ call setloclist(0, [], 'f')
+
+ " create a window and a location list for it and open the location list
+ " window
+ lexpr ['F1:10:12:one', 'F1:20:14:two']
+ let w1_id = win_getid()
+ call setloclist(0, [], ' ',
+ \ {'lines': ['F1:10:12:one', 'F1:20:14:two'],
+ \ 'quickfixtextfunc':
+ \ {d -> map(getloclist(d.winid, {'id' : d.id,
+ \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
+ \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}})
+ lwindow
+ let w2_id = win_getid()
+
+ " create another window and a location list for it and open the location
+ " list window
+ topleft new
+ let w3_id = win_getid()
+ call setloclist(0, [], ' ',
+ \ {'lines': ['F2:30:32:eleven', 'F2:40:34:twelve'],
+ \ 'quickfixtextfunc':
+ \ {d -> map(getloclist(d.winid, {'id' : d.id,
+ \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1],
+ \ "'Ligne ' .. v:val.lnum .. ', Colonne ' .. v:val.col")}})
+ lwindow
+ let w4_id = win_getid()
+
+ topleft new
+ lexpr ['F3:50:52:green', 'F3:60:54:blue']
+ let w5_id = win_getid()
+
+ " change the location list for some other window
+ call setloclist(0, [], 'r', {'lines': ['F3:55:56:aaa', 'F3:57:58:bbb']})
+ call setloclist(w1_id, [], 'r', {'lines': ['F1:62:63:bbb', 'F1:64:65:ccc']})
+ call setloclist(w3_id, [], 'r', {'lines': ['F2:76:77:ddd', 'F2:78:79:eee']})
+ call assert_equal(['Line 62, Col 63', 'Line 64, Col 65'],
+ \ getbufline(winbufnr(w2_id), 1, '$'))
+ call assert_equal(['Ligne 76, Colonne 77', 'Ligne 78, Colonne 79'],
+ \ getbufline(winbufnr(w4_id), 1, '$'))
+ call setloclist(w2_id, [], 'r', {'lines': ['F1:32:33:fff', 'F1:34:35:ggg']})
+ call setloclist(w4_id, [], 'r', {'lines': ['F2:46:47:hhh', 'F2:48:49:jjj']})
+ call assert_equal(['Line 32, Col 33', 'Line 34, Col 35'],
+ \ getbufline(winbufnr(w2_id), 1, '$'))
+ call assert_equal(['Ligne 46, Colonne 47', 'Ligne 48, Colonne 49'],
+ \ getbufline(winbufnr(w4_id), 1, '$'))
+
+ call win_gotoid(w5_id)
+ lwindow
+ call assert_equal(['F3|55 col 56| aaa', 'F3|57 col 58| bbb'],
+ \ getline(1, '$'))
+ %bw!
+endfunc
+
+" Running :lhelpgrep command more than once in a help window, doesn't jump to
+" the help topic
+func Test_lhelpgrep_from_help_window()
+ call mkdir('Xtestdir/doc', 'pR')
+ call writefile(['window'], 'Xtestdir/doc/a.txt')
+ call writefile(['buffer'], 'Xtestdir/doc/b.txt')
+ let save_rtp = &rtp
+ let &rtp = 'Xtestdir'
+ lhelpgrep window
+ lhelpgrep buffer
+ call assert_equal('b.txt', fnamemodify(@%, ":p:t"))
+ lhelpgrep window
+ call assert_equal('a.txt', fnamemodify(@%, ":p:t"))
+ let &rtp = save_rtp
+ new | only!
+endfunc
+
+" Test for the crash fixed by 7.3.715
+func Test_setloclist_crash()
+ %bw!
+ let g:BufNum = bufnr()
+ augroup QF_Test
+ au!
+ au BufUnload * call setloclist(0, [{'bufnr':g:BufNum, 'lnum':1, 'col':1, 'text': 'tango down'}])
+ augroup END
+
+ try
+ lvimgrep /.*/ *.mak
+ catch /E926:/
+ endtry
+ call assert_equal('tango down', getloclist(0, {'items' : 0}).items[0].text)
+ call assert_equal(1, getloclist(0, {'size' : 0}).size)
+
+ augroup QF_Test
+ au!
+ augroup END
+ unlet g:BufNum
+ %bw!
+endfunc
+
+" Test for adding an invalid entry with the quickfix window open and making
+" sure that the window contents are not changed
+func Test_add_invalid_entry_with_qf_window()
+ call setqflist([], 'f')
+ cexpr "Xfile1:10:aa"
+ copen
+ call setqflist(['bb'], 'a')
+ call assert_equal(1, line('$'))
+ call assert_equal(['Xfile1|10| aa'], getline(1, '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10 col 666-222| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10-6 col 666-222| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+ cclose
+endfunc
+
+" Test for very weird problem: autocommand causes a failure, resulting opening
+" the quickfix window to fail. This still splits the window, but otherwise
+" should not mess up buffers.
+func Test_quickfix_window_fails_to_open()
+ CheckScreendump
+
+ let lines =<< trim END
+ anything
+ try
+ anything
+ endtry
+ END
+ call writefile(lines, 'XquickfixFails', 'D')
+
+ let lines =<< trim END
+ split XquickfixFails
+ silent vimgrep anything %
+ normal o
+ au BufLeave * ++once source XquickfixFails
+ " This will trigger the autocommand, which causes an error, what follows
+ " is aborted but the window was already split.
+ silent! cwindow
+ END
+ call writefile(lines, 'XtestWinFails', 'D')
+ let buf = RunVimInTerminal('-S XtestWinFails', #{rows: 13})
+ call VerifyScreenDump(buf, 'Test_quickfix_window_fails', {})
+
+ " clean up
+ call term_sendkeys(buf, ":bwipe!\<CR>")
+ call term_wait(buf)
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for updating the quickfix buffer whenever the associated quickfix list
+" is changed.
+func Xqfbuf_update(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xexpr "F1:1:line1"
+ Xopen
+ call assert_equal(['F1|1| line1'], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ " Test setqflist() using the 'lines' key in 'what'
+ " add a new entry
+ call g:Xsetlist([], 'a', {'lines' : ['F2:2: line2']})
+ call assert_equal(['F1|1| line1', 'F2|2| line2'], getline(1, '$'))
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " replace all the entries with a single entry
+ call g:Xsetlist([], 'r', {'lines' : ['F3:3: line3']})
+ call assert_equal(['F3|3| line3'], getline(1, '$'))
+ call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " remove all the entries
+ call g:Xsetlist([], 'r', {'lines' : []})
+ call assert_equal([''], getline(1, '$'))
+ call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " add a new list
+ call g:Xsetlist([], ' ', {'lines' : ['F4:4: line4']})
+ call assert_equal(['F4|4| line4'], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ " Test setqflist() using the 'items' key in 'what'
+ " add a new entry
+ call g:Xsetlist([], 'a', {'items' : [{'filename' : 'F5', 'lnum' : 5, 'text' : 'line5'}]})
+ call assert_equal(['F4|4| line4', 'F5|5| line5'], getline(1, '$'))
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " replace all the entries with a single entry
+ call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F6', 'lnum' : 6, 'text' : 'line6'}]})
+ call assert_equal(['F6|6| line6'], getline(1, '$'))
+ call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " remove all the entries
+ call g:Xsetlist([], 'r', {'items' : []})
+ call assert_equal([''], getline(1, '$'))
+ call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " add a new list
+ call g:Xsetlist([], ' ', {'items' : [{'filename' : 'F7', 'lnum' : 7, 'text' : 'line7'}]})
+ call assert_equal(['F7|7| line7'], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ call g:Xsetlist([], ' ', {})
+ call assert_equal([''], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ Xclose
+endfunc
+
+func Test_qfbuf_update()
+ call Xqfbuf_update('c')
+ call Xqfbuf_update('l')
+endfunc
+
+func Test_vimgrep_noswapfile()
+ set noswapfile
+ call writefile(['one', 'two', 'three'], 'Xgreppie', 'D')
+ vimgrep two Xgreppie
+ call assert_equal('two', getline('.'))
+
+ set swapfile
+endfunc
+
+" Test for the :vimgrep 'f' flag (fuzzy match)
+func Xvimgrep_fuzzy_match(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xvimgrep /three one/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+
+ Xvimgrep /the/f Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ call assert_equal(['Xfile1', 1, 9, 'one two three'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile2', 2, 1, 'three one two'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 4, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+
+ Xvimgrep /aaa/fg Xfile*
+ let l = g:Xgetlist()
+ call assert_equal(4, len(l))
+ call assert_equal(['Xfile1', 2, 1, 'aaaaaa'],
+ \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text])
+ call assert_equal(['Xfile1', 2, 4, 'aaaaaa'],
+ \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text])
+ call assert_equal(['Xfile2', 4, 1, 'aaathreeaaa'],
+ \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text])
+ call assert_equal(['Xfile2', 4, 9, 'aaathreeaaa'],
+ \ [bufname(l[3].bufnr), l[3].lnum, l[3].col, l[3].text])
+
+ call assert_fails('Xvimgrep /xyz/fg Xfile*', 'E480:')
+endfunc
+
+func Test_vimgrep_fuzzy_match()
+ call writefile(['one two three', 'aaaaaa'], 'Xfile1', 'D')
+ call writefile(['one', 'three one two', 'two', 'aaathreeaaa'], 'Xfile2', 'D')
+ call Xvimgrep_fuzzy_match('c')
+ call Xvimgrep_fuzzy_match('l')
+endfunc
+
+func Test_locationlist_open_in_newtab()
+ call s:create_test_file('Xqftestfile1')
+ call s:create_test_file('Xqftestfile2')
+ call s:create_test_file('Xqftestfile3')
+
+ %bwipe!
+
+ let lines =<< trim END
+ Xqftestfile1:5:Line5
+ Xqftestfile2:10:Line10
+ Xqftestfile3:16:Line16
+ END
+ lgetexpr lines
+
+ silent! llast
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('Xqftestfile3', bufname())
+
+ set switchbuf=newtab
+
+ silent! lfirst
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal('Xqftestfile1', bufname())
+
+ silent! lnext
+ call assert_equal(3, tabpagenr('$'))
+ call assert_equal('Xqftestfile2', bufname())
+
+ call delete('Xqftestfile1')
+ call delete('Xqftestfile2')
+ call delete('Xqftestfile3')
+ set switchbuf&vim
+
+ %bwipe!
+endfunc
+
+" Test for win_gettype() in quickfix and location list windows
+func Test_win_gettype()
+ copen
+ call assert_equal("quickfix", win_gettype())
+ let wid = win_getid()
+ wincmd p
+ call assert_equal("quickfix", win_gettype(wid))
+ cclose
+ lexpr ''
+ lopen
+ call assert_equal("loclist", win_gettype())
+ let wid = win_getid()
+ wincmd p
+ call assert_equal("loclist", win_gettype(wid))
+ lclose
+endfunc
+
+fun Test_vimgrep_nomatch()
+ call XexprTests('c')
+ call g:Xsetlist([{'lnum':10,'text':'Line1'}])
+ copen
+ if has("win32")
+ call assert_fails('vimgrep foo *.zzz', 'E479:')
+ let expected = [{'lnum': 10, 'bufnr': 0, 'end_lnum': 0, 'pattern': '', 'valid': 0, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 0, 'text': 'Line1'}]
+ else
+ call assert_fails('vimgrep foo *.zzz', 'E480:')
+ let expected = []
+ endif
+ call assert_equal(expected, getqflist())
+ cclose
+endfunc
+
+" Test for opening the quickfix window in two tab pages and then closing one
+" of the quickfix windows. This should not make the quickfix buffer unlisted.
+" (github issue #9300).
+func Test_two_qf_windows()
+ cexpr "F1:1:line1"
+ copen
+ tabnew
+ copen
+ call assert_true(&buflisted)
+ cclose
+ tabfirst
+ call assert_true(&buflisted)
+ let bnum = bufnr()
+ cclose
+ " if all the quickfix windows are closed, then buffer should be unlisted.
+ call assert_false(buflisted(bnum))
+ %bw!
+
+ " Repeat the test for a location list
+ lexpr "F2:2:line2"
+ lopen
+ let bnum = bufnr()
+ tabnew
+ exe "buffer" bnum
+ tabfirst
+ lclose
+ tablast
+ call assert_true(buflisted(bnum))
+ tabclose
+ lopen
+ call assert_true(buflisted(bnum))
+ lclose
+ call assert_false(buflisted(bnum))
+ %bw!
+endfunc
+
+" Weird sequence of commands that caused entering a wiped-out buffer
+func Test_lopen_bwipe()
+ func R()
+ silent! tab lopen
+ e x
+ silent! lfile
+ endfunc
+
+ cal R()
+ cal R()
+ cal R()
+ bw!
+ delfunc R
+endfunc
+
+" Another sequence of commands that caused all buffers to be wiped out
+func Test_lopen_bwipe_all()
+ let lines =<< trim END
+ func R()
+ silent! tab lopen
+ e foo
+ silent! lfile
+ endfunc
+ cal R()
+ exe "norm \<C-W>\<C-V>0"
+ cal R()
+ bwipe
+
+ call writefile(['done'], 'Xresult')
+ qall!
+ END
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '-u NONE -n -X -Z -e -m -s -S Xscript')
+ call assert_equal(['done'], readfile('Xresult'))
+ endif
+
+ call delete('Xresult')
+endfunc
+
+" Test for calling setqflist() function recursively
+func Test_recursive_setqflist()
+ augroup QF_Test
+ au!
+ autocmd BufWinEnter quickfix call setqflist([], 'r')
+ augroup END
+
+ copen
+ call assert_fails("call setqflist([], 'a')", 'E952:')
+
+ augroup QF_Test
+ au!
+ augroup END
+ %bw!
+endfunc
+
+" Test for failure to create a new window when selecting a file from the
+" quickfix window
+func Test_cwindow_newwin_fails()
+ cgetexpr ["Xfile1:10:L10", "Xfile1:20:L20"]
+ cwindow
+ only
+ let qf_wid = win_getid()
+ " create the maximum number of scratch windows
+ let hor_win_count = (&lines - 1)/2
+ let hor_split_count = hor_win_count - 1
+ for s in range(1, hor_split_count) | new | set buftype=nofile | endfor
+ call win_gotoid(qf_wid)
+ call assert_fails('exe "normal \<CR>"', 'E36:')
+ %bw!
+endfunc
+
+" Test for updating the location list when only the location list window is
+" present and the corresponding file window is closed.
+func Test_loclist_update_with_llwin_only()
+ %bw!
+ new
+ wincmd w
+ lexpr ["Xfile1:1:Line1"]
+ lopen
+ wincmd p
+ close
+ call setloclist(2, [], 'r', {'lines': ["Xtest2:2:Line2"]})
+ call assert_equal(['Xtest2|2| Line2'], getbufline(winbufnr(2), 1, '$'))
+ %bw!
+endfunc
+
+" Test for getting the quickfix list after a buffer with an error is wiped out
+func Test_getqflist_wiped_out_buffer()
+ %bw!
+ cexpr ["Xtest1:34:Wiped out"]
+ let bnum = bufnr('Xtest1')
+ call assert_equal(bnum, getqflist()[0].bufnr)
+ bw Xtest1
+ call assert_equal(0, getqflist()[0].bufnr)
+ %bw!
+endfunc
+
+" Test for the status message that is displayed when opening a new quickfix
+" list
+func Test_qflist_statusmsg()
+ cexpr "1\n2"
+ cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"
+ call assert_equal('(4 of 4): msg', v:statusmsg)
+ call setqflist([], 'f')
+ %bw!
+
+ " When creating a new quickfix list, if an autocmd changes the quickfix list
+ " in the stack, then an error message should be displayed.
+ augroup QF_Test
+ au!
+ au BufEnter test_quickfix.vim colder
+ augroup END
+ cexpr "1\n2"
+ call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:')
+ call setqflist([], 'f')
+ augroup QF_Test
+ au!
+ augroup END
+ %bw!
+
+ augroup QF_Test
+ au!
+ au BufEnter test_quickfix.vim caddexpr "4"
+ augroup END
+ call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:')
+ call setqflist([], 'f')
+ augroup QF_Test
+ au!
+ augroup END
+ %bw!
+endfunc
+
+func Test_quickfixtextfunc_recursive()
+ func s:QFTfunc(o)
+ cgete '0'
+ endfunc
+ copen
+ let &quickfixtextfunc = 's:QFTfunc'
+ cex ""
+
+ let &quickfixtextfunc = ''
+ cclose
+endfunc
+
+" Test for replacing the location list from an autocmd. This used to cause a
+" read from freed memory.
+func Test_loclist_replace_autocmd()
+ %bw!
+ call setloclist(0, [], 'f')
+ let s:bufnr = bufnr()
+ cal setloclist(0, [{'0': 0, '': ''}])
+ au BufEnter * cal setloclist(1, [{'t': ''}, {'bufnr': s:bufnr}], 'r')
+ lopen
+ try
+ exe "norm j\<CR>"
+ catch
+ endtry
+ lnext
+ %bw!
+ call setloclist(0, [], 'f')
+endfunc
+
+" Test for a very long error line and a very long information line
+func Test_very_long_error_line()
+ let msg = repeat('abcdefghijklmn', 146)
+ let emsg = 'Xlonglines.c:1:' . msg
+ call writefile([msg, emsg], 'Xerror', 'D')
+ cfile Xerror
+ cwindow
+ call assert_equal($'|| {msg}', getline(1))
+ call assert_equal($'Xlonglines.c|1| {msg}', getline(2))
+ cclose
+
+ let l = execute('clist!')->split("\n")
+ call assert_equal([$' 1: {msg}', $' 2 Xlonglines.c:1: {msg}'], l)
+
+ let l = execute('cc')->split("\n")
+ call assert_equal([$'(2 of 2): {msg}'], l)
+
+ call setqflist([], 'f')
+endfunc
+
+" In the quickfix window, spaces at the beginning of an informational line
+" should not be removed but should be removed from an error line.
+func Test_info_line_with_space()
+ cexpr ["a.c:20:12: error: expected ';' before ':' token",
+ \ ' 20 | Afunc():', '', ' | ^']
+ copen
+ call assert_equal(["a.c|20 col 12| error: expected ';' before ':' token",
+ \ '|| 20 | Afunc():', '|| ',
+ \ '|| | ^'], getline(1, '$'))
+ cclose
+
+ let l = execute('clist!')->split("\n")
+ call assert_equal([" 1 a.c:20 col 12: error: expected ';' before ':' token",
+ \ ' 2: 20 | Afunc():', ' 3: ', ' 4: | ^'], l)
+
+ call setqflist([], 'f')
+endfunc
+
+func s:QfTf(_)
+endfunc
+
+func Test_setqflist_cb_arg()
+ " This was changing the callback name in the dictionary.
+ let d = #{quickfixtextfunc: 's:QfTf'}
+ call setqflist([], 'a', d)
+ call assert_equal('s:QfTf', d.quickfixtextfunc)
+
+ call setqflist([], 'f')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_quotestar.vim b/src/testdir/test_quotestar.vim
new file mode 100644
index 0000000..1d26942
--- /dev/null
+++ b/src/testdir/test_quotestar.vim
@@ -0,0 +1,158 @@
+" *-register (quotestar) tests
+
+source shared.vim
+source check.vim
+
+CheckFeature clipboard_working
+
+func Do_test_quotestar_for_macunix()
+ if empty(exepath('pbcopy')) || empty(exepath('pbpaste'))
+ return 'Test requires pbcopy(1) and pbpaste(1)'
+ endif
+
+ let @* = ''
+
+ " Test #1: Pasteboard to Vim
+ let test_msg = "text from pasteboard to vim via quotestar"
+ " Write a piece of text to the pasteboard.
+ call system('/bin/echo -n "' . test_msg . '" | pbcopy')
+ " See if the *-register is changed as expected.
+ call assert_equal(test_msg, @*)
+
+ " Test #2: Vim to Pasteboard
+ let test_msg = "text from vim to pasteboard via quotestar"
+ " Write a piece of text to the *-register.
+ let @* = test_msg
+ " See if the pasteboard is changed as expected.
+ call assert_equal(test_msg, system('pbpaste'))
+
+ return ''
+endfunc
+
+func Do_test_quotestar_for_x11()
+ if !has('clientserver') || !has('job')
+ return 'Test requires the client-server and job features'
+ endif
+
+ let cmd = GetVimCommand()
+ if cmd == ''
+ throw 'GetVimCommand() failed'
+ endif
+ try
+ call remote_send('xxx', '')
+ catch
+ if v:exception =~ 'E240:'
+ " No connection to the X server, give up.
+ return
+ endif
+ " ignore other errors
+ endtry
+
+ let name = 'XVIMCLIPBOARD'
+
+ " Make sure a previous server has exited
+ try
+ call remote_send(name, ":qa!\<CR>")
+ catch /E241:/
+ endtry
+ call WaitForAssert({-> assert_notmatch(name, serverlist())})
+
+ let cmd .= ' --servername ' . name
+ let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'})
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+
+ " Takes a short while for the server to be active.
+ call WaitForAssert({-> assert_match(name, serverlist())})
+
+ " Wait for the server to be up and answering requests. One second is not
+ " always sufficient.
+ call WaitForAssert({-> assert_notequal('', remote_expr(name, "v:version", "", 2))})
+
+ " Clear the *-register of this vim instance and wait for it to be picked up
+ " by the server.
+ let @* = 'no'
+ call remote_foreground(name)
+ call WaitForAssert({-> assert_equal("no", remote_expr(name, "@*", "", 1))})
+
+ " Set the * register on the server.
+ call remote_send(name, ":let @* = 'yes'\<CR>")
+ call WaitForAssert({-> assert_equal("yes", remote_expr(name, "@*", "", 1))})
+
+ " Check that the *-register of this vim instance is changed as expected.
+ call WaitForAssert({-> assert_equal("yes", @*)})
+
+ " Handle the large selection over 262040 byte.
+ let length = 262044
+ let sample = 'a' . repeat('b', length - 2) . 'c'
+ let @* = sample
+ call WaitFor('remote_expr("' . name . '", "len(@*) >= ' . length . '", "", 1)')
+ let res = remote_expr(name, "@*", "", 2)
+ call assert_equal(length, len(res))
+ " Check length to prevent a large amount of output at assertion failure.
+ if length == len(res)
+ call assert_equal(sample, res)
+ endif
+
+ if has('unix') && has('gui') && !has('gui_running')
+ let @* = ''
+
+ " Running in a terminal and the GUI is available: Tell the server to open
+ " the GUI and check that the remote command still works.
+ if has('gui_motif')
+ " For those GUIs, ignore the 'failed to create input context' error.
+ call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>")
+ else
+ call remote_send(name, ":gui -f\<CR>")
+ endif
+ " Wait for the server in the GUI to be up and answering requests.
+ " First need to wait for the GUI to start up, otherwise the send hangs in
+ " trying to send to the terminal window.
+ " On some systems and with valgrind this can be very slow.
+ sleep 1
+ call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000)
+
+ call remote_send(name, ":let @* = 'maybe'\<CR>")
+ call WaitForAssert({-> assert_equal("maybe", remote_expr(name, "@*", "", 2))})
+
+ call assert_equal('maybe', @*)
+ endif
+
+ call remote_send(name, ":qa!\<CR>")
+ try
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ finally
+ if job_status(job) != 'dead'
+ call assert_report('Server did not exit')
+ call job_stop(job, 'kill')
+ endif
+ endtry
+
+ return ''
+endfunc
+
+func Test_quotestar()
+ let g:test_is_flaky = 1
+ let skipped = ''
+
+ let quotestar_saved = @*
+
+ if has('macunix')
+ let skipped = Do_test_quotestar_for_macunix()
+ elseif has('x11')
+ if empty($DISPLAY)
+ let skipped = "Test can only run when $DISPLAY is set."
+ else
+ let skipped = Do_test_quotestar_for_x11()
+ endif
+ else
+ let skipped = "Test is not implemented yet for this platform."
+ endif
+
+ let @* = quotestar_saved
+
+ if !empty(skipped)
+ throw 'Skipped: ' . skipped
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_random.vim b/src/testdir/test_random.vim
new file mode 100644
index 0000000..be8473f
--- /dev/null
+++ b/src/testdir/test_random.vim
@@ -0,0 +1,56 @@
+" Tests for srand() and rand()
+
+source check.vim
+source shared.vim
+
+func Test_Rand()
+ let r = srand(123456789)
+ call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r)
+ call assert_equal(4284103975, rand(r))
+ call assert_equal(1001954530, rand(r))
+ call assert_equal(2701803082, rand(r))
+ call assert_equal(2658065534, rand(r))
+ call assert_equal(3104308804, rand(r))
+
+ let s = srand()
+ " using /dev/urandom or used time, result is different each time
+ call assert_notequal(s, srand())
+
+ call test_srand_seed(123456789)
+ call assert_equal(4284103975, rand())
+ call assert_equal(1001954530, rand())
+ call test_srand_seed()
+
+ call assert_fails('echo srand(1.2)', 'E805:')
+ call assert_fails('echo srand([1])', 'E745:')
+ call assert_fails('echo rand("burp")', 'E475:')
+ call assert_fails('echo rand([1, 2, 3])', 'E730:')
+ call assert_fails('echo rand([[1], 2, 3, 4])', 'E730:')
+ call assert_fails('echo rand([1, [2], 3, 4])', 'E730:')
+ call assert_fails('echo rand([1, 2, [3], 4])', 'E730:')
+ call assert_fails('echo rand([1, 2, 3, [4]])', 'E730:')
+endfunc
+
+func Test_issue_5587()
+ call rand()
+ call garbagecollect()
+ call rand()
+endfunc
+
+func Test_srand()
+ CheckNotGui
+
+ let cmd = GetVimCommand() .. ' -V -es -c "echo rand()" -c qa!'
+ let bad = 0
+ for _ in range(10)
+ echo cmd
+ let result1 = system(cmd)
+ let result2 = system(cmd)
+ if result1 ==# result2
+ let bad += 1
+ endif
+ endfor
+ call assert_inrange(0, 4, bad)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_recover.vim b/src/testdir/test_recover.vim
new file mode 100644
index 0000000..395eb06
--- /dev/null
+++ b/src/testdir/test_recover.vim
@@ -0,0 +1,451 @@
+" Test :recover
+
+source check.vim
+
+func Test_recover_root_dir()
+ " This used to access invalid memory.
+ split Xtest
+ set dir=/
+ call assert_fails('recover', 'E305:')
+ close!
+
+ if has('win32') || filewritable('/') == 2
+ " can write in / directory on MS-Windows
+ set dir=/notexist/
+ endif
+ call assert_fails('split Xtest', 'E303:')
+
+ " No error with empty 'directory' setting.
+ set directory=
+ split XtestOK
+ close!
+
+ set dir&
+endfunc
+
+" Make a copy of the current swap file to "Xswap".
+" Return the name of the swap file.
+func CopySwapfile()
+ preserve
+ " get the name of the swap file
+ let swname = split(execute("swapname"))[0]
+ let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '')
+ " make a copy of the swap file in Xswap
+ set binary
+ exe 'sp ' . swname
+ w! Xswap
+ set nobinary
+ return swname
+endfunc
+
+" Inserts 10000 lines with text to fill the swap file with two levels of pointer
+" blocks. Then recovers from the swap file and checks all text is restored.
+"
+" We need about 10000 lines of 100 characters to get two levels of pointer
+" blocks.
+func Test_swap_file()
+ set fileformat=unix undolevels=-1
+ edit! Xtest
+ let text = "\tabcdefghijklmnoparstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnoparstuvwxyz0123456789"
+ let i = 1
+ let linecount = 10000
+ while i <= linecount
+ call append(i - 1, i . text)
+ let i += 1
+ endwhile
+ $delete
+
+ let swname = CopySwapfile()
+
+ new
+ only!
+ bwipe! Xtest
+ call rename('Xswap', swname)
+ recover Xtest
+ call delete(swname)
+ let linedollar = line('$')
+ call assert_equal(linecount, linedollar)
+ if linedollar < linecount
+ let linecount = linedollar
+ endif
+ let i = 1
+ while i <= linecount
+ call assert_equal(i . text, getline(i))
+ let i += 1
+ endwhile
+
+ set undolevels&
+ enew! | only
+endfunc
+
+func Test_nocatch_process_still_running()
+ " sysinfo.uptime probably only works on Linux
+ if !has('linux')
+ let g:skipped_reason = 'only works on Linux'
+ return
+ endif
+ " the GUI dialog can't be handled
+ if has('gui_running')
+ let g:skipped_reason = 'only works in the terminal'
+ return
+ endif
+
+ " don't intercept existing swap file here
+ au! SwapExists
+
+ " Edit a file and grab its swapfile.
+ edit Xswaptest
+ call setline(1, ['a', 'b', 'c'])
+ let swname = CopySwapfile()
+
+ " Forget we edited this file
+ new
+ only!
+ bwipe! Xswaptest
+
+ call rename('Xswap', swname)
+ call feedkeys('e', 'tL')
+ redir => editOutput
+ edit Xswaptest
+ redir END
+ call assert_match('E325: ATTENTION', editOutput)
+ call assert_match('file name: .*Xswaptest', editOutput)
+ call assert_match('process ID: \d* (STILL RUNNING)', editOutput)
+
+ " Forget we edited this file
+ new
+ only!
+ bwipe! Xswaptest
+
+ " pretend we rebooted
+ call test_override("uptime", 0)
+ sleep 1
+
+ call feedkeys('e', 'tL')
+ redir => editOutput
+ edit Xswaptest
+ redir END
+ call assert_match('E325: ATTENTION', editOutput)
+ call assert_notmatch('(STILL RUNNING)', editOutput)
+
+ call test_override("ALL", 0)
+ call delete(swname)
+endfunc
+
+" Test for :recover with multiple swap files
+func Test_recover_multiple_swap_files()
+ CheckUnix
+ new Xfile1
+ call setline(1, ['a', 'b', 'c'])
+ preserve
+ let b = readblob(swapname(''))
+ call writefile(b, '.Xfile1.swm', 'D')
+ call writefile(b, '.Xfile1.swn', 'D')
+ call writefile(b, '.Xfile1.swo', 'D')
+ %bw!
+ call feedkeys(":recover Xfile1\<CR>3\<CR>q", 'xt')
+ call assert_equal(['a', 'b', 'c'], getline(1, '$'))
+ " try using out-of-range number to select a swap file
+ bw!
+ call feedkeys(":recover Xfile1\<CR>4\<CR>q", 'xt')
+ call assert_equal('Xfile1', @%)
+ call assert_equal([''], getline(1, '$'))
+ bw!
+ call feedkeys(":recover Xfile1\<CR>0\<CR>q", 'xt')
+ call assert_equal('Xfile1', @%)
+ call assert_equal([''], getline(1, '$'))
+ bw!
+endfunc
+
+" Test for :recover using an empty swap file
+func Test_recover_empty_swap_file()
+ CheckUnix
+ call writefile([], '.Xfile1.swp', 'D')
+ let msg = execute('recover Xfile1')
+ call assert_match('Unable to read block 0 from .Xfile1.swp', msg)
+ call assert_equal('Xfile1', @%)
+ bw!
+
+ " make sure there are no old swap files laying around
+ for f in glob('.sw?', 0, 1)
+ call delete(f)
+ endfor
+
+ " :recover from an empty buffer
+ call assert_fails('recover', 'E305:')
+endfunc
+
+" Test for :recover using a corrupted swap file
+" Refer to the comments in the memline.c file for the swap file headers
+" definition.
+func Test_recover_corrupted_swap_file()
+ CheckUnix
+
+ " recover using a partial swap file
+ call writefile(0z1234, '.Xfile1.swp')
+ call assert_fails('recover Xfile1', 'E295:')
+ bw!
+
+ " recover using invalid content in the swap file
+ call writefile([repeat('1', 2*1024)], '.Xfile1.swp')
+ call assert_fails('recover Xfile1', 'E307:')
+ call delete('.Xfile1.swp')
+
+ " :recover using a swap file with a corrupted header
+ edit Xfile1
+ preserve
+ let sn = swapname('')
+ let b = readblob(sn)
+ let save_b = copy(b)
+ bw!
+
+ " Not all fields are written in a system-independent manner. Detect whether
+ " the test is running on a little or big-endian system, so the correct
+ " corruption values can be set.
+ " The B0_MAGIC_LONG field may be 32-bit or 64-bit, depending on the system,
+ " even though the value stored is only 32-bits. Therefore, need to check
+ " both the high and low 32-bits to compute these values.
+ let little_endian = (b[1008:1011] == 0z33323130) || (b[1012:1015] == 0z33323130)
+ let system_64bit = little_endian ? (b[1012:1015] == 0z00000000) : (b[1008:1011] == 0z00000000)
+
+ " clear the B0_MAGIC_LONG field
+ if system_64bit
+ let b[1008:1015] = 0z00000000.00000000
+ else
+ let b[1008:1011] = 0z00000000
+ endif
+ call writefile(b, sn)
+ let msg = execute('recover Xfile1')
+ call assert_match('the file has been damaged', msg)
+ call assert_equal('Xfile1', @%)
+ call assert_equal([''], getline(1, '$'))
+ bw!
+
+ " reduce the page size
+ let b = copy(save_b)
+ let b[12:15] = 0z00010000
+ call writefile(b, sn)
+ let msg = execute('recover Xfile1')
+ call assert_match('page size is smaller than minimum value', msg)
+ call assert_equal('Xfile1', @%)
+ call assert_equal([''], getline(1, '$'))
+ bw!
+
+ " clear the pointer ID
+ let b = copy(save_b)
+ let b[4096:4097] = 0z0000
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E310:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal([''], getline(1, '$'))
+ bw!
+
+ " set the number of pointers in a pointer block to zero
+ let b = copy(save_b)
+ let b[4098:4099] = 0z0000
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['???EMPTY BLOCK'], getline(1, '$'))
+ bw!
+
+ " set the block number in a pointer entry to a negative number
+ let b = copy(save_b)
+ if system_64bit
+ let b[4104:4111] = little_endian ? 0z00000000.00000080 : 0z80000000.00000000
+ else
+ let b[4104:4107] = little_endian ? 0z00000080 : 0z80000000
+ endif
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['???LINES MISSING'], getline(1, '$'))
+ bw!
+
+ " clear the data block ID
+ let b = copy(save_b)
+ let b[8192:8193] = 0z0000
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['???BLOCK MISSING'], getline(1, '$'))
+ bw!
+
+ " set the number of lines in the data block to zero
+ let b = copy(save_b)
+ if system_64bit
+ let b[8208:8215] = 0z00000000.00000000
+ else
+ let b[8208:8211] = 0z00000000
+ endif
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['??? from here until ???END lines may have been inserted/deleted',
+ \ '???END'], getline(1, '$'))
+ bw!
+
+ " use an invalid text start for the lines in a data block
+ let b = copy(save_b)
+ if system_64bit
+ let b[8216:8219] = 0z00000000
+ else
+ let b[8212:8215] = 0z00000000
+ endif
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['???'], getline(1, '$'))
+ bw!
+
+ " use an incorrect text end (db_txt_end) for the data block
+ let b = copy(save_b)
+ let b[8204:8207] = little_endian ? 0z80000000 : 0z00000080
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['??? from here until ???END lines may be messed up', '',
+ \ '???END'], getline(1, '$'))
+ bw!
+
+ " remove the data block
+ let b = copy(save_b)
+ call writefile(b[:8191], sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['???MANY LINES MISSING'], getline(1, '$'))
+
+ bw!
+ call delete(sn)
+endfunc
+
+" Test for :recover using an encrypted swap file
+func Test_recover_encrypted_swap_file()
+ CheckFeature cryptv
+ CheckUnix
+
+ " Recover an encrypted file from the swap file without the original file
+ new Xfile1
+ call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt')
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ preserve
+ let b = readblob('.Xfile1.swp')
+ call writefile(b, '.Xfile1.swm')
+ bw!
+ call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt')
+ call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
+ bw!
+ call delete('.Xfile1.swm')
+
+ " Recover an encrypted file from the swap file with the original file
+ new Xfile1
+ call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt')
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ update
+ call setline(1, ['111', '222', '333'])
+ preserve
+ let b = readblob('.Xfile1.swp')
+ call writefile(b, '.Xfile1.swm')
+ bw!
+ call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt')
+ call assert_equal(['111', '222', '333'], getline(1, '$'))
+ call assert_true(&modified)
+ bw!
+ call delete('.Xfile1.swm')
+ call delete('Xfile1')
+endfunc
+
+" Test for :recover using a unreadable swap file
+func Test_recover_unreadable_swap_file()
+ CheckUnix
+ CheckNotRoot
+ new Xfile1
+ let b = readblob('.Xfile1.swp')
+ call writefile(b, '.Xfile1.swm', 'D')
+ bw!
+ call setfperm('.Xfile1.swm', '-w-------')
+ call assert_fails('recover Xfile1', 'E306:')
+endfunc
+
+" Test for using :recover when the original file and the swap file have the
+" same contents.
+func Test_recover_unmodified_file()
+ CheckUnix
+ call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1')
+ edit Xfile1
+ preserve
+ let b = readblob('.Xfile1.swp')
+ %bw!
+ call writefile(b, '.Xfile1.swz', 'D')
+ let msg = execute('recover Xfile1')
+ call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
+ call assert_false(&modified)
+ call assert_match('Buffer contents equals file contents', msg)
+ bw!
+ call delete('Xfile1')
+endfunc
+
+" Test for recovering a file when editing a symbolically linked file
+func Test_recover_symbolic_link()
+ CheckUnix
+ call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1', 'D')
+ silent !ln -s Xfile1 Xfile2
+ edit Xfile2
+ call assert_equal('.Xfile1.swp', fnamemodify(swapname(''), ':t'))
+ preserve
+ let b = readblob('.Xfile1.swp')
+ %bw!
+ call writefile([], 'Xfile1')
+ call writefile(b, '.Xfile1.swp')
+ silent! recover Xfile2
+ call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
+ call assert_true(&modified)
+ update
+ %bw!
+ call assert_equal(['aaa', 'bbb', 'ccc'], readfile('Xfile1'))
+ call delete('Xfile2')
+ call delete('.Xfile1.swp')
+endfunc
+
+" Test for recovering a file when an autocmd moves the cursor to an invalid
+" line. This used to result in an internal error (E315) which is fixed
+" by 8.2.2966.
+func Test_recover_invalid_cursor_pos()
+ call writefile([], 'Xfile1', 'D')
+ edit Xfile1
+ preserve
+ let b = readblob('.Xfile1.swp')
+ bw!
+ augroup Test
+ au!
+ au BufReadPost Xfile1 normal! 3G
+ augroup END
+ call writefile(range(1, 3), 'Xfile1')
+ call writefile(b, '.Xfile1.swp', 'D')
+ try
+ recover Xfile1
+ catch /E308:/
+ " this test is for the :E315 internal error.
+ " ignore the 'E308: Original file may have been changed' error
+ endtry
+ redraw!
+ augroup Test
+ au!
+ augroup END
+ augroup! Test
+endfunc
+
+" Test for recovering a buffer without a name
+func Test_noname_buffer()
+ new
+ call setline(1, ['one', 'two'])
+ preserve
+ let sn = swapname('')
+ let b = readblob(sn)
+ bw!
+ call writefile(b, sn, 'D')
+ exe "recover " .. sn
+ call assert_equal(['one', 'two'], getline(1, '$'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_regex_char_classes.vim b/src/testdir/test_regex_char_classes.vim
new file mode 100644
index 0000000..db16f05
--- /dev/null
+++ b/src/testdir/test_regex_char_classes.vim
@@ -0,0 +1,297 @@
+" Tests for regexp with backslash and other special characters inside []
+" Also test backslash for hex/octal numbered character.
+"
+
+scriptencoding utf-8
+
+function RunSTest(value, calls, expected)
+ new
+ call feedkeys("i" . a:value, "mx")
+ exec a:calls
+ call assert_equal(a:expected, getline(1), printf("wrong result for %s", a:calls))
+ quit!
+endfunction
+
+function RunXTest(value, search_exp, expected)
+ new
+ call feedkeys("i" . a:value, "mx")
+ call feedkeys("gg" . a:search_exp . "\nx", "mx")
+ call assert_equal(a:expected, getline(1), printf("wrong result for %s", a:search_exp))
+ quit!
+endfunction
+
+
+function Test_x_search()
+ let res = "test text test text"
+ call RunXTest("test \\text test text", "/[\\x]", res)
+ call RunXTest("test \ttext test text", "/[\\t\\]]", res)
+ call RunXTest("test text ]test text", "/[]y]", res)
+ call RunXTest("test ]text test text", "/[\\]]", res)
+ call RunXTest("test text te^st text", "/[y^]", res)
+ call RunXTest("test te$xt test text", "/[$y]", res)
+ call RunXTest("test taext test text", "/[\\x61]", res)
+ call RunXTest("test tbext test text","/[\\x60-\\x64]", res)
+ call RunXTest("test 5text test text","/[\\x785]", res)
+ call RunXTest("testc text test text","/[\\o143]", res)
+ call RunXTest("tesdt text test text","/[\\o140-\\o144]", res)
+ call RunXTest("test7 text test text", "/[\\o417]", res)
+ call RunXTest("test text tBest text", "/\\%x42", res)
+ call RunXTest("test text teCst text", "/\\%o103", res)
+ call RunXTest("test text \<C-V>x00test text", "/[\\x00]", res)
+endfunction
+
+function Test_s_search()
+ let res = "test text test text"
+ call RunSTest("test te\<C-V>x00xt t\<C-V>x04est t\<C-V>x10ext", "s/[\\x00-\\x10]//g", res)
+ call RunSTest("test \\xyztext test text", "s/[\\x-z]\\+//", res)
+ call RunSTest("test text tev\\uyst text", "s/[\\u-z]\\{2,}//", res)
+ call RunSTest("xx aaaaa xx a", "s/\\(a\\)\\+//", "xx xx a")
+ call RunSTest("xx aaaaa xx a", "s/\\(a*\\)\\+//", "xx aaaaa xx a")
+ call RunSTest("xx aaaaa xx a", "s/\\(a*\\)*//", "xx aaaaa xx a")
+ call RunSTest("xx aaaaa xx", "s/\\(a\\)\\{2,3}/A/", "xx Aaa xx")
+ call RunSTest("xx aaaaa xx", "s/\\(a\\)\\{-2,3}/A/", "xx Aaaa xx")
+ call RunSTest("xx aaa12aa xx", "s/\\(a\\)*\\(12\\)\\@>/A/", "xx Aaa xx")
+ call RunSTest("xx foobar xbar xx", "s/\\(foo\\)\\@<!bar/A/", "xx foobar xA xx")
+ call RunSTest("xx an file xx", "s/\\(an\\_s\\+\\)\\@<=file/A/", "xx an A xx")
+ call RunSTest("x= 9;", "s/^\\(\\h\\w*\\%(->\\|\\.\\)\\=\\)\\+=/XX/", "XX 9;")
+ call RunSTest("hh= 77;", "s/^\\(\\h\\w*\\%(->\\|\\.\\)\\=\\)\\+=/YY/", "YY 77;")
+ call RunSTest(" aaa ", "s/aaa/xyz/", " xyz ")
+ call RunSTest(" xyz", "s/~/bcd/", " bcd")
+ call RunSTest(" bcdbcdbcd", "s/~\\+/BB/", " BB")
+endfunction
+
+" Test character classes in regexp using regexpengine 0, 1, 2.
+func Test_regex_char_classes()
+ new
+ let save_enc = &encoding
+ set encoding=utf-8
+
+ let input = "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"
+
+ " Format is [cmd_to_run, expected_output]
+ let tests = [
+ \ [':s/\%#=0\d//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\d//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\d//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[0-9]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[0-9]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[0-9]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\D//g',
+ \ "0123456789"],
+ \ [':s/\%#=1\D//g',
+ \ "0123456789"],
+ \ [':s/\%#=2\D//g',
+ \ "0123456789"],
+ \ [':s/\%#=0[^0-9]//g',
+ \ "0123456789"],
+ \ [':s/\%#=1[^0-9]//g',
+ \ "0123456789"],
+ \ [':s/\%#=2[^0-9]//g',
+ \ "0123456789"],
+ \ [':s/\%#=0\o//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\o//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\o//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[0-7]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[0-7]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[0-7]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\O//g',
+ \ "01234567"],
+ \ [':s/\%#=1\O//g',
+ \ "01234567"],
+ \ [':s/\%#=2\O//g',
+ \ "01234567"],
+ \ [':s/\%#=0[^0-7]//g',
+ \ "01234567"],
+ \ [':s/\%#=1[^0-7]//g',
+ \ "01234567"],
+ \ [':s/\%#=2[^0-7]//g',
+ \ "01234567"],
+ \ [':s/\%#=0\x//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\x//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\x//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[0-9A-Fa-f]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[0-9A-Fa-f]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[0-9A-Fa-f]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\X//g',
+ \ "0123456789ABCDEFabcdef"],
+ \ [':s/\%#=1\X//g',
+ \ "0123456789ABCDEFabcdef"],
+ \ [':s/\%#=2\X//g',
+ \ "0123456789ABCDEFabcdef"],
+ \ [':s/\%#=0[^0-9A-Fa-f]//g',
+ \ "0123456789ABCDEFabcdef"],
+ \ [':s/\%#=1[^0-9A-Fa-f]//g',
+ \ "0123456789ABCDEFabcdef"],
+ \ [':s/\%#=2[^0-9A-Fa-f]//g',
+ \ "0123456789ABCDEFabcdef"],
+ \ [':s/\%#=0\w//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\w//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\w//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[0-9A-Za-z_]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[0-9A-Za-z_]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[0-9A-Za-z_]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\W//g',
+ \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=1\W//g',
+ \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=2\W//g',
+ \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=0[^0-9A-Za-z_]//g',
+ \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=1[^0-9A-Za-z_]//g',
+ \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=2[^0-9A-Za-z_]//g',
+ \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=0\h//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\h//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\h//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[A-Za-z_]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[A-Za-z_]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[A-Za-z_]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\H//g',
+ \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=1\H//g',
+ \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=2\H//g',
+ \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=0[^A-Za-z_]//g',
+ \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=1[^A-Za-z_]//g',
+ \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=2[^A-Za-z_]//g',
+ \ "ABCDEFGHIXYZ_abcdefghiwxyz"],
+ \ [':s/\%#=0\a//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\a//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\a//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[A-Za-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[A-Za-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[A-Za-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\A//g',
+ \ "ABCDEFGHIXYZabcdefghiwxyz"],
+ \ [':s/\%#=1\A//g',
+ \ "ABCDEFGHIXYZabcdefghiwxyz"],
+ \ [':s/\%#=2\A//g',
+ \ "ABCDEFGHIXYZabcdefghiwxyz"],
+ \ [':s/\%#=0[^A-Za-z]//g',
+ \ "ABCDEFGHIXYZabcdefghiwxyz"],
+ \ [':s/\%#=1[^A-Za-z]//g',
+ \ "ABCDEFGHIXYZabcdefghiwxyz"],
+ \ [':s/\%#=2[^A-Za-z]//g',
+ \ "ABCDEFGHIXYZabcdefghiwxyz"],
+ \ [':s/\%#=0\l//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\l//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\l//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[a-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[a-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[a-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\L//g',
+ \ "abcdefghiwxyz"],
+ \ [':s/\%#=1\L//g',
+ \ "abcdefghiwxyz"],
+ \ [':s/\%#=2\L//g',
+ \ "abcdefghiwxyz"],
+ \ [':s/\%#=0[^a-z]//g',
+ \ "abcdefghiwxyz"],
+ \ [':s/\%#=1[^a-z]//g',
+ \ "abcdefghiwxyz"],
+ \ [':s/\%#=2[^a-z]//g',
+ \ "abcdefghiwxyz"],
+ \ [':s/\%#=0\u//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\u//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\u//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[A-Z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[A-Z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[A-Z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0\U//g',
+ \ "ABCDEFGHIXYZ"],
+ \ [':s/\%#=1\U//g',
+ \ "ABCDEFGHIXYZ"],
+ \ [':s/\%#=2\U//g',
+ \ "ABCDEFGHIXYZ"],
+ \ [':s/\%#=0[^A-Z]//g',
+ \ "ABCDEFGHIXYZ"],
+ \ [':s/\%#=1[^A-Z]//g',
+ \ "ABCDEFGHIXYZ"],
+ \ [':s/\%#=2[^A-Z]//g',
+ \ "ABCDEFGHIXYZ"],
+ \ [':s/\%#=0\%' . line('.') . 'l^\t...//g',
+ \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1\%' . line('.') . 'l^\t...//g',
+ \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2\%' . line('.') . 'l^\t...//g',
+ \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[0-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=1[0-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=2[0-z]//g',
+ \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ [':s/\%#=0[^0-z]//g',
+ \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"],
+ \ [':s/\%#=1[^0-z]//g',
+ \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"],
+ \ [':s/\%#=2[^0-z]//g',
+ \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"]
+ \]
+
+ for [cmd, expected] in tests
+ call append(0, input)
+ call cursor(1, 1)
+ exe cmd
+ call assert_equal(expected, getline(1), cmd)
+ endfor
+
+ let &encoding = save_enc
+ enew!
+ close
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_regexp_latin.vim b/src/testdir/test_regexp_latin.vim
new file mode 100644
index 0000000..9acb12b
--- /dev/null
+++ b/src/testdir/test_regexp_latin.vim
@@ -0,0 +1,1163 @@
+" Tests for regexp in latin1 encoding
+
+set encoding=latin1
+scriptencoding latin1
+
+source check.vim
+
+func s:equivalence_test()
+ let str = 'AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z '
+ \ .. 'aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z '
+ \ .. "0 1 2 3 4 5 6 7 8 9 "
+ \ .. "` ~ ! ? ; : . , / \\ ' \" | < > [ ] { } ( ) @ # $ % ^ & * _ - + \b \e \f \n \r \t"
+ let groups = split(str)
+ for group1 in groups
+ for c in split(group1, '\zs')
+ " next statement confirms that equivalence class matches every
+ " character in group
+ call assert_match('^[[=' . c . '=]]*$', group1)
+ for group2 in groups
+ if group2 != group1
+ " next statement converts that equivalence class doesn't match
+ " a character in any other group
+ call assert_equal(-1, match(group2, '[[=' . c . '=]]'))
+ endif
+ endfor
+ endfor
+ endfor
+endfunc
+
+func Test_equivalence_re1()
+ set re=1
+ call s:equivalence_test()
+ set re=0
+endfunc
+
+func Test_equivalence_re2()
+ set re=2
+ call s:equivalence_test()
+ set re=0
+endfunc
+
+func Test_recursive_substitute()
+ new
+ s/^/\=execute("s#^##gn")
+ " check we are now not in the sandbox
+ call setwinvar(1, 'myvar', 1)
+ bwipe!
+endfunc
+
+func Test_nested_backrefs()
+ " Check example in change.txt.
+ new
+ for re in range(0, 2)
+ exe 'set re=' . re
+ call setline(1, 'aa ab x')
+ 1s/\(\(a[a-d] \)*\)\(x\)/-\1- -\2- -\3-/
+ call assert_equal('-aa ab - -ab - -x-', getline(1))
+
+ call assert_equal('-aa ab - -ab - -x-', substitute('aa ab x', '\(\(a[a-d] \)*\)\(x\)', '-\1- -\2- -\3-', ''))
+ endfor
+ bwipe!
+ set re=0
+endfunc
+
+func Test_eow_with_optional()
+ let expected = ['abc def', 'abc', 'def', '', '', '', '', '', '', '']
+ for re in range(0, 2)
+ exe 'set re=' . re
+ let actual = matchlist('abc def', '\(abc\>\)\?\s*\(def\)')
+ call assert_equal(expected, actual)
+ endfor
+ set re=0
+endfunc
+
+func Test_backref()
+ new
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ call assert_equal(3, search('\%#=1\(e\)\1'))
+ call assert_equal(3, search('\%#=2\(e\)\1'))
+ call assert_fails('call search("\\%#=1\\(e\\1\\)")', 'E65:')
+ call assert_fails('call search("\\%#=2\\(e\\1\\)")', 'E65:')
+ bwipe!
+endfunc
+
+func Test_multi_failure()
+ set re=1
+ call assert_fails('/a**', 'E61:')
+ call assert_fails('/a*\+', 'E62:')
+ call assert_fails('/a\{a}', 'E554:')
+ set re=2
+ call assert_fails('/a**', 'E871:')
+ call assert_fails('/a*\+', 'E871:')
+ call assert_fails('/a\{a}', 'E554:')
+ set re=0
+endfunc
+
+func Test_column_success_failure()
+ new
+ call setline(1, 'xbar')
+
+ set re=1
+ %s/\%>0v./A/
+ call assert_equal('Abar', getline(1))
+ call assert_fails('/\%v', 'E71:')
+ call assert_fails('/\%>v', 'E71:')
+ call assert_fails('/\%c', 'E71:')
+ call assert_fails('/\%<c', 'E71:')
+ call assert_fails('/\%l', 'E71:')
+ set re=2
+ %s/\%>0v./B/
+ call assert_equal('Bbar', getline(1))
+ call assert_fails('/\%v', 'E1273:')
+ call assert_fails('/\%>v', 'E1273:')
+ call assert_fails('/\%c', 'E1273:')
+ call assert_fails('/\%<c', 'E1273:')
+ call assert_fails('/\%l', 'E1273:')
+
+ set re=0
+ bwipe!
+endfunc
+
+func Test_recursive_addstate()
+ " This will call addstate() recursively until it runs into the limit.
+ let lnum = search('\v((){328}){389}')
+ call assert_equal(0, lnum)
+endfunc
+
+func Test_out_of_memory()
+ new
+ s/^/,n
+ " This will be slow...
+ call assert_fails('call search("\\v((n||<)+);")', 'E363:')
+endfunc
+
+func Test_get_equi_class()
+ new
+ " Incomplete equivalence class caused invalid memory access
+ s/^/[[=
+ call assert_equal(1, search(getline(1)))
+ s/.*/[[.
+ call assert_equal(1, search(getline(1)))
+endfunc
+
+func Test_rex_init()
+ set noincsearch
+ set re=1
+ new
+ setlocal iskeyword=a-z
+ call setline(1, ['abc', 'ABC'])
+ call assert_equal(1, search('[[:keyword:]]'))
+ new
+ setlocal iskeyword=A-Z
+ call setline(1, ['abc', 'ABC'])
+ call assert_equal(2, search('[[:keyword:]]'))
+ bwipe!
+ bwipe!
+ set re=0
+endfunc
+
+func Test_range_with_newline()
+ new
+ call setline(1, "a")
+ call assert_equal(0, search("[ -*\\n- ]"))
+ call assert_equal(0, search("[ -*\\t-\\n]"))
+ bwipe!
+endfunc
+
+func Test_pattern_compile_speed()
+ CheckOption spellcapcheck
+ CheckFunction reltimefloat
+
+ let start = reltime()
+ " this used to be very slow, not it should be about a second
+ set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179}
+ call assert_inrange(0.01, 10.0, reltimefloat(reltime(start)))
+ set spc=
+endfunc
+
+" Tests for regexp patterns without multi-byte support.
+func Test_regexp_single_line_pat()
+ " tl is a List of Lists with:
+ " regexp engines to test
+ " 0 - test with 'regexpengine' values 0 and 1
+ " 1 - test with 'regexpengine' values 0 and 2
+ " 2 - test with 'regexpengine' values 0, 1 and 2
+ " regexp pattern
+ " text to test the pattern on
+ " expected match (optional)
+ " expected submatch 1 (optional)
+ " expected submatch 2 (optional)
+ " etc.
+ " When there is no match use only the first two items.
+ let tl = []
+
+ call add(tl, [2, 'ab', 'aab', 'ab'])
+ call add(tl, [2, 'b', 'abcdef', 'b'])
+ call add(tl, [2, 'bc*', 'abccccdef', 'bcccc'])
+ call add(tl, [2, 'bc\{-}', 'abccccdef', 'b'])
+ call add(tl, [2, 'bc\{-}\(d\)', 'abccccdef', 'bccccd', 'd'])
+ call add(tl, [2, 'bc*', 'abbdef', 'b'])
+ call add(tl, [2, 'c*', 'ccc', 'ccc'])
+ call add(tl, [2, 'bc*', 'abdef', 'b'])
+ call add(tl, [2, 'c*', 'abdef', ''])
+ call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc'])
+ call add(tl, [2, 'bc\+', 'abdef']) " no match
+ " match escape character in a string
+ call add(tl, [2, '.\e.', "one\<Esc>two", "e\<Esc>t"])
+ " match backspace character in a string
+ call add(tl, [2, '.\b.', "one\<C-H>two", "e\<C-H>t"])
+ " match newline character in a string
+ call add(tl, [2, 'o\nb', "foo\nbar", "o\nb"])
+
+ " operator \|
+ call add(tl, [2, 'a\|ab', 'cabd', 'a']) " alternation is ordered
+
+ call add(tl, [2, 'c\?', 'ccb', 'c'])
+ call add(tl, [2, 'bc\?', 'abd', 'b'])
+ call add(tl, [2, 'bc\?', 'abccd', 'bc'])
+
+ call add(tl, [2, '\va{1}', 'ab', 'a'])
+
+ call add(tl, [2, '\va{2}', 'aa', 'aa'])
+ call add(tl, [2, '\va{2}', 'caad', 'aa'])
+ call add(tl, [2, '\va{2}', 'aba'])
+ call add(tl, [2, '\va{2}', 'ab'])
+ call add(tl, [2, '\va{2}', 'abaa', 'aa'])
+ call add(tl, [2, '\va{2}', 'aaa', 'aa'])
+
+ call add(tl, [2, '\vb{1}', 'abca', 'b'])
+ call add(tl, [2, '\vba{2}', 'abaa', 'baa'])
+ call add(tl, [2, '\vba{3}', 'aabaac'])
+
+ call add(tl, [2, '\v(ab){1}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1}', 'dabc', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1}', 'acb'])
+
+ call add(tl, [2, '\v(ab){0,2}', 'acb', "", ""])
+ call add(tl, [2, '\v(ab){0,2}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1,2}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1,2}', 'ababc', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2,4}', 'ababcab', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2,4}', 'abcababa', 'abab', 'ab'])
+
+ call add(tl, [2, '\v(ab){2}', 'abab', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2}', 'cdababe', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2}', 'abac'])
+ call add(tl, [2, '\v(ab){2}', 'abacabab', 'abab', 'ab'])
+ call add(tl, [2, '\v((ab){2}){2}', 'abababab', 'abababab', 'abab', 'ab'])
+ call add(tl, [2, '\v((ab){2}){2}', 'abacabababab', 'abababab', 'abab', 'ab'])
+
+ call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
+ call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'aaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'daaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{1}){2}', 'daaac', 'aa', 'a'])
+ call add(tl, [2, '\v(a{1}){2}', 'aaa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{2})+', 'adaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2})+', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{1}){2}', 'aa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
+ call add(tl, [2, '\v(a{2}){2}', 'aaaa', 'aaaa', 'aa'])
+ call add(tl, [2, '\v(a{2}){2}', 'aaabaaaa', 'aaaa', 'aa'])
+
+ call add(tl, [2, '\v(a+){2}', 'dadaac', 'aa', 'a'])
+ call add(tl, [2, '\v(a{3}){2}', 'aaaaaaa', 'aaaaaa', 'aaa'])
+
+ call add(tl, [2, '\v(a{1,2}){2}', 'daaac', 'aaa', 'a'])
+ call add(tl, [2, '\v(a{1,3}){2}', 'daaaac', 'aaaa', 'a'])
+ call add(tl, [2, '\v(a{1,3}){2}', 'daaaaac', 'aaaaa', 'aa'])
+ call add(tl, [2, '\v(a{1,3}){3}', 'daac'])
+ call add(tl, [2, '\v(a{1,2}){2}', 'dac'])
+ call add(tl, [2, '\v(a+)+', 'daac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a+)+', 'aaa', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a+){1,2}', 'aaa', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a+)(a+)', 'aaa', 'aaa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{3})+', 'daaaac', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a|b|c)+', 'aacb', 'aacb', 'b'])
+ call add(tl, [2, '\v(a|b|c){2}', 'abcb', 'ab', 'b'])
+ call add(tl, [2, '\v(abc){2}', 'abcabd', ])
+ call add(tl, [2, '\v(abc){2}', 'abdabcabc','abcabc', 'abc'])
+
+ call add(tl, [2, 'a*', 'cc', ''])
+ call add(tl, [2, '\v(a*)+', 'cc', ''])
+ call add(tl, [2, '\v((ab)+)+', 'ab', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(((ab)+)+)+', 'ab', 'ab', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(((ab)+)+)+', 'dababc', 'abab', 'abab', 'abab', 'ab'])
+ call add(tl, [2, '\v(a{0,2})+', 'cc', ''])
+ call add(tl, [2, '\v(a*)+', '', ''])
+ call add(tl, [2, '\v((a*)+)+', '', ''])
+ call add(tl, [2, '\v((ab)*)+', '', ''])
+ call add(tl, [2, '\va{1,3}', 'aab', 'aa'])
+ call add(tl, [2, '\va{2,3}', 'abaa', 'aa'])
+
+ call add(tl, [2, '\v((ab)+|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
+ call add(tl, [2, '\v(a{2})|(b{3})', 'bbabbbb', 'bbb', '', 'bbb'])
+ call add(tl, [2, '\va{2}|b{2}', 'abab'])
+ call add(tl, [2, '\v(a)+|(c)+', 'bbacbaacbbb', 'a', 'a'])
+ call add(tl, [2, '\vab{2,3}c', 'aabbccccccccccccc', 'abbc'])
+ call add(tl, [2, '\vab{2,3}c', 'aabbbccccccccccccc', 'abbbc'])
+ call add(tl, [2, '\vab{2,3}cd{2,3}e', 'aabbbcddee', 'abbbcdde'])
+ call add(tl, [2, '\va(bc){2}d', 'aabcbfbc' ])
+ call add(tl, [2, '\va*a{2}', 'a', ])
+ call add(tl, [2, '\va*a{2}', 'aa', 'aa' ])
+ call add(tl, [2, '\va*a{2}', 'aaa', 'aaa' ])
+ call add(tl, [2, '\va*a{2}', 'bbbabcc', ])
+ call add(tl, [2, '\va*b*|a*c*', 'a', 'a'])
+ call add(tl, [2, '\va{1}b{1}|a{1}b{1}', ''])
+
+ " submatches
+ call add(tl, [2, '\v(a)', 'ab', 'a', 'a'])
+ call add(tl, [2, '\v(a)(b)', 'ab', 'ab', 'a', 'b'])
+ call add(tl, [2, '\v(ab)(b)(c)', 'abbc', 'abbc', 'ab', 'b', 'c'])
+ call add(tl, [2, '\v((a)(b))', 'ab', 'ab', 'ab', 'a', 'b'])
+ call add(tl, [2, '\v(a)|(b)', 'ab', 'a', 'a'])
+
+ call add(tl, [2, '\v(a*)+', 'aaaa', 'aaaa', ''])
+ call add(tl, [2, 'x', 'abcdef'])
+
+ "
+ " Simple tests
+ "
+
+ " Search single groups
+ call add(tl, [2, 'ab', 'aab', 'ab'])
+ call add(tl, [2, 'ab', 'baced'])
+ call add(tl, [2, 'ab', ' ab ', 'ab'])
+
+ " Search multi-modifiers
+ call add(tl, [2, 'x*', 'xcd', 'x'])
+ call add(tl, [2, 'x*', 'xxxxxxxxxxxxxxxxsofijiojgf', 'xxxxxxxxxxxxxxxx'])
+ " empty match is good
+ call add(tl, [2, 'x*', 'abcdoij', ''])
+ " no match here
+ call add(tl, [2, 'x\+', 'abcdoin'])
+ call add(tl, [2, 'x\+', 'abcdeoijdfxxiuhfij', 'xx'])
+ call add(tl, [2, 'x\+', 'xxxxx', 'xxxxx'])
+ call add(tl, [2, 'x\+', 'abc x siufhiush xxxxxxxxx', 'x'])
+ call add(tl, [2, 'x\=', 'x sdfoij', 'x'])
+ call add(tl, [2, 'x\=', 'abc sfoij', '']) " empty match is good
+ call add(tl, [2, 'x\=', 'xxxxxxxxx c', 'x'])
+ call add(tl, [2, 'x\?', 'x sdfoij', 'x'])
+ " empty match is good
+ call add(tl, [2, 'x\?', 'abc sfoij', ''])
+ call add(tl, [2, 'x\?', 'xxxxxxxxxx c', 'x'])
+
+ call add(tl, [2, 'a\{0,0}', 'abcdfdoij', ''])
+ " same thing as 'a?'
+ call add(tl, [2, 'a\{0,1}', 'asiubid axxxaaa', 'a'])
+ " same thing as 'a\{0,1}'
+ call add(tl, [2, 'a\{1,0}', 'asiubid axxxaaa', 'a'])
+ call add(tl, [2, 'a\{3,6}', 'aa siofuh'])
+ call add(tl, [2, 'a\{3,6}', 'aaaaa asfoij afaa', 'aaaaa'])
+ call add(tl, [2, 'a\{3,6}', 'aaaaaaaa', 'aaaaaa'])
+ call add(tl, [2, 'a\{0}', 'asoiuj', ''])
+ call add(tl, [2, 'a\{2}', 'aaaa', 'aa'])
+ call add(tl, [2, 'a\{2}', 'iuash fiusahfliusah fiushfilushfi uhsaifuh askfj nasfvius afg aaaa sfiuhuhiushf', 'aa'])
+ call add(tl, [2, 'a\{2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
+ " same thing as 'a*'
+ call add(tl, [2, 'a\{0,}', 'oij sdigfusnf', ''])
+ call add(tl, [2, 'a\{0,}', 'aaaaa aa', 'aaaaa'])
+ call add(tl, [2, 'a\{2,}', 'sdfiougjdsafg'])
+ call add(tl, [2, 'a\{2,}', 'aaaaasfoij ', 'aaaaa'])
+ call add(tl, [2, 'a\{5,}', 'xxaaaaxxx '])
+ call add(tl, [2, 'a\{5,}', 'xxaaaaaxxx ', 'aaaaa'])
+ call add(tl, [2, 'a\{,0}', 'oidfguih iuhi hiu aaaa', ''])
+ call add(tl, [2, 'a\{,5}', 'abcd', 'a'])
+ call add(tl, [2, 'a\{,5}', 'aaaaaaaaaa', 'aaaaa'])
+ " leading star as normal char when \{} follows
+ call add(tl, [2, '^*\{4,}$', '***'])
+ call add(tl, [2, '^*\{4,}$', '****', '****'])
+ call add(tl, [2, '^*\{4,}$', '*****', '*****'])
+ " same thing as 'a*'
+ call add(tl, [2, 'a\{}', 'bbbcddiuhfcd', ''])
+ call add(tl, [2, 'a\{}', 'aaaaioudfh coisf jda', 'aaaa'])
+
+ call add(tl, [2, 'a\{-0,0}', 'abcdfdoij', ''])
+ " anti-greedy version of 'a?'
+ call add(tl, [2, 'a\{-0,1}', 'asiubid axxxaaa', ''])
+ call add(tl, [2, 'a\{-3,6}', 'aa siofuh'])
+ call add(tl, [2, 'a\{-3,6}', 'aaaaa asfoij afaa', 'aaa'])
+ call add(tl, [2, 'a\{-3,6}', 'aaaaaaaa', 'aaa'])
+ call add(tl, [2, 'a\{-0}', 'asoiuj', ''])
+ call add(tl, [2, 'a\{-2}', 'aaaa', 'aa'])
+ call add(tl, [2, 'a\{-2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
+ call add(tl, [2, 'a\{-0,}', 'oij sdigfusnf', ''])
+ call add(tl, [2, 'a\{-0,}', 'aaaaa aa', ''])
+ call add(tl, [2, 'a\{-2,}', 'sdfiougjdsafg'])
+ call add(tl, [2, 'a\{-2,}', 'aaaaasfoij ', 'aa'])
+ call add(tl, [2, 'a\{-,0}', 'oidfguih iuhi hiu aaaa', ''])
+ call add(tl, [2, 'a\{-,5}', 'abcd', ''])
+ call add(tl, [2, 'a\{-,5}', 'aaaaaaaaaa', ''])
+ " anti-greedy version of 'a*'
+ call add(tl, [2, 'a\{-}', 'bbbcddiuhfcd', ''])
+ call add(tl, [2, 'a\{-}', 'aaaaioudfh coisf jda', ''])
+
+ " Test groups of characters and submatches
+ call add(tl, [2, '\(abc\)*', 'abcabcabc', 'abcabcabc', 'abc'])
+ call add(tl, [2, '\(ab\)\+', 'abababaaaaa', 'ababab', 'ab'])
+ call add(tl, [2, '\(abaaaaa\)*cd', 'cd', 'cd', ''])
+ call add(tl, [2, '\(test1\)\? \(test2\)\?', 'test1 test3', 'test1 ', 'test1', ''])
+ call add(tl, [2, '\(test1\)\= \(test2\) \(test4443\)\=', ' test2 test4443 yupiiiiiiiiiii', ' test2 test4443', '', 'test2', 'test4443'])
+ call add(tl, [2, '\(\(sub1\) hello \(sub 2\)\)', 'asterix sub1 hello sub 2 obelix', 'sub1 hello sub 2', 'sub1 hello sub 2', 'sub1', 'sub 2'])
+ call add(tl, [2, '\(\(\(yyxxzz\)\)\)', 'abcdddsfiusfyyzzxxyyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz'])
+ call add(tl, [2, '\v((ab)+|c+)+', 'abcccaba', 'abcccab', 'ab', 'ab'])
+ call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
+ call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', ''])
+ call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', ''])
+ call add(tl, [2, '\p*', 'aá ', 'aá '])
+
+ " Test greedy-ness and lazy-ness
+ call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa'])
+ call add(tl, [2, 'a\{-2,7}x','aaaaaaaaax', 'aaaaaaax'])
+ call add(tl, [2, 'a\{2,7}','aaaaaaaaaaaaaaaaaaaa', 'aaaaaaa'])
+ call add(tl, [2, 'a\{2,7}x','aaaaaaaaax', 'aaaaaaax'])
+ call add(tl, [2, '\vx(.{-,8})yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz','ayxa','xayzxayz'])
+ call add(tl, [2, '\vx(.*)yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz', 'ayxayzxayzxa',''])
+ call add(tl, [2, '\v(a{1,2}){-2,3}','aaaaaaa','aaaa','aa'])
+ call add(tl, [2, '\v(a{-1,3})+', 'aa', 'aa', 'a'])
+ call add(tl, [2, '^\s\{-}\zs\( x\|x$\)', ' x', ' x', ' x'])
+ call add(tl, [2, '^\s\{-}\zs\(x\| x$\)', ' x', ' x', ' x'])
+ call add(tl, [2, '^\s\{-}\ze\(x\| x$\)', ' x', '', ' x'])
+ call add(tl, [2, '^\(\s\{-}\)\(x\| x$\)', ' x', ' x', '', ' x'])
+
+ " Test Character classes
+ call add(tl, [2, '\d\+e\d\d','test 10e23 fd','10e23'])
+
+ " Test collections and character range []
+ call add(tl, [2, '\v[a]', 'abcd', 'a'])
+ call add(tl, [2, 'a[bcd]', 'abcd', 'ab'])
+ call add(tl, [2, 'a[b-d]', 'acbd', 'ac'])
+ call add(tl, [2, '[a-d][e-f][x-x]d', 'cexdxx', 'cexd'])
+ call add(tl, [2, '\v[[:alpha:]]+', 'abcdefghijklmnopqrstuvwxyz6','abcdefghijklmnopqrstuvwxyz'])
+ call add(tl, [2, '[[:alpha:]\+]', '6x8','x'])
+ call add(tl, [2, '[^abc]\+','abcabcabc'])
+ call add(tl, [2, '[^abc]','defghiasijvoinasoiunbvb','d'])
+ call add(tl, [2, '[^abc]\+','ddddddda','ddddddd'])
+ call add(tl, [2, '[^a-d]\+','aaaAAAZIHFNCddd','AAAZIHFNC'])
+ call add(tl, [2, '[a-f]*','iiiiiiii',''])
+ call add(tl, [2, '[a-f]*','abcdefgh','abcdef'])
+ call add(tl, [2, '[^a-f]\+','abcdefgh','gh'])
+ call add(tl, [2, '[a-c]\{-3,6}','abcabc','abc'])
+ call add(tl, [2, '[^[:alpha:]]\+','abcccadfoij7787ysf287yrnccdu','7787'])
+ call add(tl, [2, '[-a]', '-', '-'])
+ call add(tl, [2, '[a-]', '-', '-'])
+ call add(tl, [2, '[a-f]*\c','ABCDEFGH','ABCDEF'])
+ call add(tl, [2, '[abc][xyz]\c','-af-AF-BY--','BY'])
+ " filename regexp
+ call add(tl, [2, '[-./[:alnum:]_~]\+', 'log13.file', 'log13.file'])
+ " special chars
+ call add(tl, [2, '[\]\^\-\\]\+', '\^\\\-\---^', '\^\\\-\---^'])
+ " collation elem
+ call add(tl, [2, '[[.a.]]\+', 'aa', 'aa'])
+ " middle of regexp
+ call add(tl, [2, 'abc[0-9]*ddd', 'siuhabc ii'])
+ call add(tl, [2, 'abc[0-9]*ddd', 'adf abc44482ddd oijs', 'abc44482ddd'])
+ call add(tl, [2, '\_[0-9]\+', 'asfi9888u', '9888'])
+ call add(tl, [2, '[0-9\n]\+', 'asfi9888u', '9888'])
+ call add(tl, [2, '\_[0-9]\+', "asfi\n9888u", "\n9888"])
+ call add(tl, [2, '\_f', " \na ", "\n"])
+ call add(tl, [2, '\_f\+', " \na ", "\na"])
+ call add(tl, [2, '[0-9A-Za-z-_.]\+', " @0_a.A-{ ", "0_a.A-"])
+
+ " Test start/end of line, start/end of file
+ call add(tl, [2, '^a.', "a_\nb ", "a_"])
+ call add(tl, [2, '^a.', "b a \na_"])
+ call add(tl, [2, '.a$', " a\n "])
+ call add(tl, [2, '.a$', " a b\n_a", "_a"])
+ call add(tl, [2, '\%^a.', "a a\na", "a "])
+ call add(tl, [2, '\%^a', " a \na "])
+ call add(tl, [2, '.a\%$', " a\n "])
+ call add(tl, [2, '.a\%$', " a\n_a", "_a"])
+
+ " Test recognition of character classes
+ call add(tl, [2, '[0-7]\+', 'x0123456789x', '01234567'])
+ call add(tl, [2, '[^0-7]\+', '0a;X+% 897', 'a;X+% 89'])
+ call add(tl, [2, '[0-9]\+', 'x0123456789x', '0123456789'])
+ call add(tl, [2, '[^0-9]\+', '0a;X+% 9', 'a;X+% '])
+ call add(tl, [2, '[0-9a-fA-F]\+', 'x0189abcdefg', '0189abcdef'])
+ call add(tl, [2, '[^0-9A-Fa-f]\+', '0189g;X+% ab', 'g;X+% '])
+ call add(tl, [2, '[a-z_A-Z0-9]\+', ';+aso_SfOij ', 'aso_SfOij'])
+ call add(tl, [2, '[^a-z_A-Z0-9]\+', 'aSo_;+% sfOij', ';+% '])
+ call add(tl, [2, '[a-z_A-Z]\+', '0abyz_ABYZ;', 'abyz_ABYZ'])
+ call add(tl, [2, '[^a-z_A-Z]\+', 'abAB_09;+% yzYZ', '09;+% '])
+ call add(tl, [2, '[a-z]\+', '0abcxyz1', 'abcxyz'])
+ call add(tl, [2, '[a-z]\+', 'AabxyzZ', 'abxyz'])
+ call add(tl, [2, '[^a-z]\+', 'a;X09+% x', ';X09+% '])
+ call add(tl, [2, '[^a-z]\+', 'abX0;%yz', 'X0;%'])
+ call add(tl, [2, '[a-zA-Z]\+', '0abABxzXZ9', 'abABxzXZ'])
+ call add(tl, [2, '[^a-zA-Z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '[A-Z]\+', 'aABXYZz', 'ABXYZ'])
+ call add(tl, [2, '[^A-Z]\+', 'ABx0;%YZ', 'x0;%'])
+ call add(tl, [2, '[a-z]\+\c', '0abxyzABXYZ;', 'abxyzABXYZ'])
+ call add(tl, [2, '[A-Z]\+\c', '0abABxzXZ9', 'abABxzXZ'])
+ call add(tl, [2, '\c[^a-z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '\c[^A-Z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '\C[^A-Z]\+', 'ABCOIJDEOIFNSD jsfoij sa', ' jsfoij sa'])
+
+ " Tests for \z features
+ " match ends at \ze
+ call add(tl, [2, 'xx \ze test', 'xx '])
+ call add(tl, [2, 'abc\zeend', 'oij abcend', 'abc'])
+ call add(tl, [2, 'aa\zebb\|aaxx', ' aabb ', 'aa'])
+ call add(tl, [2, 'aa\zebb\|aaxx', ' aaxx ', 'aaxx'])
+ call add(tl, [2, 'aabb\|aa\zebb', ' aabb ', 'aabb'])
+ call add(tl, [2, 'aa\zebb\|aaebb', ' aabb ', 'aa'])
+ " match starts at \zs
+ call add(tl, [2, 'abc\zsdd', 'ddabcddxyzt', 'dd'])
+ call add(tl, [2, 'aa \zsax', ' ax'])
+ call add(tl, [2, 'abc \zsmatch\ze abc', 'abc abc abc match abc abc', 'match'])
+ call add(tl, [2, '\v(a \zsif .*){2}', 'a if then a if last', 'if last', 'a if last'])
+ call add(tl, [2, '\>\zs.', 'aword. ', '.'])
+ call add(tl, [2, '\s\+\ze\[/\|\s\zs\s\+', 'is [a t', ' '])
+
+ " Tests for \@= and \& features
+ call add(tl, [2, 'abc\@=', 'abc', 'ab'])
+ call add(tl, [2, 'abc\@=cd', 'abcd', 'abcd'])
+ call add(tl, [2, 'abc\@=', 'ababc', 'ab'])
+ " will never match, no matter the input text
+ call add(tl, [2, 'abcd\@=e', 'abcd'])
+ " will never match
+ call add(tl, [2, 'abcd\@=e', 'any text in here ... '])
+ call add(tl, [2, '\v(abc)@=..', 'xabcd', 'ab', 'abc'])
+ call add(tl, [2, '\(.*John\)\@=.*Bob', 'here is John, and here is B'])
+ call add(tl, [2, '\(John.*\)\@=.*Bob', 'John is Bobs friend', 'John is Bob', 'John is Bobs friend'])
+ call add(tl, [2, '\<\S\+\())\)\@=', '$((i=i+1))', 'i=i+1', '))'])
+ call add(tl, [2, '.*John\&.*Bob', 'here is John, and here is B'])
+ call add(tl, [2, '.*John\&.*Bob', 'John is Bobs friend', 'John is Bob'])
+ call add(tl, [2, '\v(test1)@=.*yep', 'this is a test1, yep it is', 'test1, yep', 'test1'])
+ call add(tl, [2, 'foo\(bar\)\@!', 'foobar'])
+ call add(tl, [2, 'foo\(bar\)\@!', 'foo bar', 'foo'])
+ call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if then else'])
+ call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if else ', 'if else ', ' '])
+ call add(tl, [2, '\(foo\)\@!bar', 'foobar', 'bar'])
+ call add(tl, [2, '\(foo\)\@!...bar', 'foobar'])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' bar foo '])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo bar '])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo xxx ', 'foo'])
+ call add(tl, [2, '[ ]\@!\p\%([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:'])
+ call add(tl, [2, '[ ]\@!\p\([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:', 's'])
+ call add(tl, [2, 'm\k\+_\@=\%(_\@!\k\)\@<=\k\+e', 'mx__xe', 'mx__xe'])
+ call add(tl, [2, '\%(\U\@<=S\k*\|S\l\)R', 'SuR', 'SuR'])
+
+ " Combining different tests and features
+ call add(tl, [2, '[[:alpha:]]\{-2,6}', '787abcdiuhsasiuhb4', 'ab'])
+ call add(tl, [2, '', 'abcd', ''])
+ call add(tl, [2, '\v(())', 'any possible text', ''])
+ call add(tl, [2, '\v%(ab(xyz)c)', ' abxyzc ', 'abxyzc', 'xyz'])
+ call add(tl, [2, '\v(test|)empty', 'tesempty', 'empty', ''])
+ call add(tl, [2, '\v(a|aa)(a|aa)', 'aaa', 'aa', 'a', 'a'])
+
+ " \%u and friends
+ call add(tl, [2, '\%d32', 'yes no', ' '])
+ call add(tl, [2, '\%o40', 'yes no', ' '])
+ call add(tl, [2, '\%x20', 'yes no', ' '])
+ call add(tl, [2, '\%u0020', 'yes no', ' '])
+ call add(tl, [2, '\%U00000020', 'yes no', ' '])
+ call add(tl, [2, '\%d0', "yes\x0ano", "\x0a"])
+
+ "" \%[abc]
+ call add(tl, [2, 'foo\%[bar]', 'fobar'])
+ call add(tl, [2, 'foo\%[bar]', 'foobar', 'foobar'])
+ call add(tl, [2, 'foo\%[bar]', 'fooxx', 'foo'])
+ call add(tl, [2, 'foo\%[bar]', 'foobxx', 'foob'])
+ call add(tl, [2, 'foo\%[bar]', 'foobaxx', 'fooba'])
+ call add(tl, [2, 'foo\%[bar]', 'foobarxx', 'foobar'])
+ call add(tl, [2, 'foo\%[bar]x', 'foobxx', 'foobx'])
+ call add(tl, [2, 'foo\%[bar]x', 'foobarxx', 'foobarx'])
+ call add(tl, [2, '\%[bar]x', 'barxx', 'barx'])
+ call add(tl, [2, '\%[bar]x', 'bxx', 'bx'])
+ call add(tl, [2, '\%[bar]x', 'xxx', 'x'])
+ call add(tl, [2, 'b\%[[ao]r]', 'bar bor', 'bar'])
+ call add(tl, [2, 'b\%[[]]r]', 'b]r bor', 'b]r'])
+ call add(tl, [2, '@\%[\w\-]*', '<http://john.net/pandoc/>[@pandoc]', '@pandoc'])
+
+ " Alternatives, must use first longest match
+ call add(tl, [2, 'goo\|go', 'google', 'goo'])
+ call add(tl, [2, '\<goo\|\<go', 'google', 'goo'])
+ call add(tl, [2, '\<goo\|go', 'google', 'goo'])
+
+ " Back references
+ call add(tl, [2, '\(\i\+\) \1', ' abc abc', 'abc abc', 'abc'])
+ call add(tl, [2, '\(\i\+\) \1', 'xgoo goox', 'goo goo', 'goo'])
+ call add(tl, [2, '\(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9', 'xabcddefghiabcddefghix', 'abcddefghiabcddefghi', 'a', 'b', 'c', 'dd', 'e', 'f', 'g', 'h', 'i'])
+ call add(tl, [2, '\(\d*\)a \1b', ' a b ', 'a b', ''])
+ call add(tl, [2, '^.\(.\).\_..\1.', "aaa\naaa\nb", "aaa\naaa", 'a'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.com', 'foo.bat/foo.com', 'bat'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.bat'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<=$', 'foo.bat/foo.bat', 'foo.bat/foo.bat', 'bat', 'bat'])
+ call add(tl, [2, '\\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}', '2013-06-27${0}', '${0}', '0'])
+ call add(tl, [2, '^\(a*\)\1$', 'aaaaaaaa', 'aaaaaaaa', 'aaaa'])
+ call add(tl, [2, '^\(a\{-2,}\)\1\+$', 'aaaaaaaaa', 'aaaaaaaaa', 'aaa'])
+
+ " Look-behind with limit
+ call add(tl, [2, '<\@<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '<\@1<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '<\@2<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '\(<<\)\@<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
+ call add(tl, [2, '\(<<\)\@1<=span.', 'xxspanxxxx<spanxx<<spanyyy'])
+ call add(tl, [2, '\(<<\)\@2<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
+ call add(tl, [2, '\(foo\)\@<!bar.', 'xx foobar1 xbar2 xx', 'bar2'])
+
+ " look-behind match in front of a zero-width item
+ call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" test header'])
+ call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" Last Changed: 1970', '1970'])
+ call add(tl, [2, '\(foo\)\@<=\>', 'foobar'])
+ call add(tl, [2, '\(foo\)\@<=\>', 'barfoo', '', 'foo'])
+ call add(tl, [2, '\(foo\)\@<=.*', 'foobar', 'bar', 'foo'])
+
+ " complicated look-behind match
+ call add(tl, [2, '\(r\@<=\|\w\@<!\)\/', 'x = /word/;', '/'])
+ call add(tl, [2, '^[a-z]\+\ze \&\(asdf\)\@<!', 'foo bar', 'foo'])
+
+ "" \@>
+ call add(tl, [2, '\(a*\)\@>a', 'aaaa'])
+ call add(tl, [2, '\(a*\)\@>b', 'aaab', 'aaab', 'aaa'])
+ call add(tl, [2, '^\(.\{-}b\)\@>.', ' abcbd', ' abc', ' ab'])
+ call add(tl, [2, '\(.\{-}\)\(\)\@>$', 'abc', 'abc', 'abc', ''])
+ " TODO: BT engine does not restore submatch after failure
+ call add(tl, [1, '\(a*\)\@>a\|a\+', 'aaaa', 'aaaa'])
+
+ " "\_" prepended negated collection matches EOL
+ call add(tl, [2, '\_[^8-9]\+', "asfi\n9888", "asfi\n"])
+ call add(tl, [2, '\_[^a]\+', "asfi\n9888", "sfi\n9888"])
+
+ " Requiring lots of states.
+ call add(tl, [2, '[0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}', " 12345678-1234-1234-1234-123456789012 ", "12345678-1234-1234-1234-123456789012", "1234-"])
+
+ " Skip adding state twice
+ call add(tl, [2, '^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=', "#if FOO", "#if", ' FOO'])
+
+ " Test \%V atom
+ call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt'])
+
+ " Test for ignoring case and matching repeated characters
+ call add(tl, [2, '\cb\+', 'aAbBbBcC', 'bBbB'])
+
+ " Run the tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let text = t[2]
+ let matchidx = 3
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re == 1
+ continue
+ endif
+ let &regexpengine = engine
+ try
+ let l = matchlist(text, pat)
+ catch
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", caused an exception: \"'
+ \ . v:exception . '\"')
+ endtry
+ " check the match itself
+ if len(l) == 0 && len(t) > matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", did not match, expected: \"'
+ \ . t[matchidx] . '\"')
+ elseif len(l) > 0 && len(t) == matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", match: \"' . l[0]
+ \ . '\", expected no match')
+ elseif len(t) > matchidx && l[0] != t[matchidx]
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", match: \"' . l[0]
+ \ . '\", expected: \"' . t[matchidx] . '\"')
+ else
+ " Test passed
+ endif
+
+ " check all the nine submatches
+ if len(l) > 0
+ for i in range(1, 9)
+ if len(t) <= matchidx + i
+ let e = ''
+ else
+ let e = t[matchidx + i]
+ endif
+ if l[i] != e
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", submatch ' . i . ': \"'
+ \ . l[i] . '\", expected: \"' . e . '\"')
+ endif
+ endfor
+ unlet i
+ endif
+ endfor
+ endfor
+
+ unlet t tl e l
+endfunc
+
+" Tests for multi-line regexp patterns without multi-byte support.
+func Test_regexp_multiline_pat()
+ " tl is a List of Lists with:
+ " regexp engines to test
+ " 0 - test with 'regexpengine' values 0 and 1
+ " 1 - test with 'regexpengine' values 0 and 2
+ " 2 - test with 'regexpengine' values 0, 1 and 2
+ " regexp pattern
+ " List with text to test the pattern on
+ " List with the expected match
+ let tl = []
+
+ " back references
+ call add(tl, [2, '^.\(.\).\_..\1.', ['aaa', 'aaa', 'b'], ['XX', 'b']])
+ call add(tl, [2, '\v.*\/(.*)\n.*\/\1$', ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', './Dir1/Dir2/file1.txt', './OtherDir1/OtherDir2/file1.txt'], ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', 'XX']])
+
+ " line breaks
+ call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']])
+
+ " Any single character or end-of-line
+ call add(tl, [2, '\_.\+', ['a', 'b', 'c'], ['XX']])
+ " Any identifier or end-of-line
+ call add(tl, [2, '\_i\+', ['a', 'b', ';', '2'], ['XX;XX']])
+ " Any identifier but excluding digits or end-of-line
+ call add(tl, [2, '\_I\+', ['a', 'b', ';', '2'], ['XX;XX2XX']])
+ " Any keyword or end-of-line
+ call add(tl, [2, '\_k\+', ['a', 'b', '=', '2'], ['XX=XX']])
+ " Any keyword but excluding digits or end-of-line
+ call add(tl, [2, '\_K\+', ['a', 'b', '=', '2'], ['XX=XX2XX']])
+ " Any filename character or end-of-line
+ call add(tl, [2, '\_f\+', ['a', 'b', '.', '5'], ['XX']])
+ " Any filename character but excluding digits or end-of-line
+ call add(tl, [2, '\_F\+', ['a', 'b', '.', '5'], ['XX5XX']])
+ " Any printable character or end-of-line
+ call add(tl, [2, '\_p\+', ['a', 'b', '=', '4'], ['XX']])
+ " Any printable character excluding digits or end-of-line
+ call add(tl, [2, '\_P\+', ['a', 'b', '=', '4'], ['XX4XX']])
+ " Any whitespace character or end-of-line
+ call add(tl, [2, '\_s\+', [' ', ' ', 'a', 'b'], ['XXaXXbXX']])
+ " Any non-whitespace character or end-of-line
+ call add(tl, [2, '\_S\+', [' ', ' ', 'a', 'b'], [' XX XX']])
+ " Any decimal digit or end-of-line
+ call add(tl, [2, '\_d\+', ['1', 'a', '2', 'b', '3'], ['XXaXXbXX']])
+ " Any non-decimal digit or end-of-line
+ call add(tl, [2, '\_D\+', ['1', 'a', '2', 'b', '3'], ['1XX2XX3XX']])
+ " Any hexadecimal digit or end-of-line
+ call add(tl, [2, '\_x\+', ['1', 'a', 'g', '9', '8'], ['XXgXX']])
+ " Any non-hexadecimal digit or end-of-line
+ call add(tl, [2, '\_X\+', ['1', 'a', 'g', '9', '8'], ['1XXaXX9XX8XX']])
+ " Any octal digit or end-of-line
+ call add(tl, [2, '\_o\+', ['0', '7', '8', '9', '0'], ['XX8XX9XX']])
+ " Any non-octal digit or end-of-line
+ call add(tl, [2, '\_O\+', ['0', '7', '8', '9', '0'], ['0XX7XX0XX']])
+ " Any word character or end-of-line
+ call add(tl, [2, '\_w\+', ['A', 'B', '=', 'C', 'D'], ['XX=XX']])
+ " Any non-word character or end-of-line
+ call add(tl, [2, '\_W\+', ['A', 'B', '=', 'C', 'D'], ['AXXBXXCXXDXX']])
+ " Any head-of-word character or end-of-line
+ call add(tl, [2, '\_h\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']])
+ " Any non-head-of-word character or end-of-line
+ call add(tl, [2, '\_H\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']])
+ " Any alphabetic character or end-of-line
+ call add(tl, [2, '\_a\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']])
+ " Any non-alphabetic character or end-of-line
+ call add(tl, [2, '\_A\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']])
+ " Any lowercase character or end-of-line
+ call add(tl, [2, '\_l\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']])
+ " Any non-lowercase character or end-of-line
+ call add(tl, [2, '\_L\+', ['a', 'A', 'b', 'B'], ['aXXbXX']])
+ " Any uppercase character or end-of-line
+ call add(tl, [2, '\_u\+', ['a', 'A', 'b', 'B'], ['aXXbXX']])
+ " Any non-uppercase character or end-of-line
+ call add(tl, [2, '\_U\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']])
+ " Collection or end-of-line
+ call add(tl, [2, '\_[a-z]\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']])
+ " start of line anywhere in the text
+ call add(tl, [2, 'one\zs\_s*\_^\zetwo',
+ \ ['', 'one', ' two', 'one', '', 'two'],
+ \ ['', 'one', ' two', 'oneXXtwo']])
+ " end of line anywhere in the text
+ call add(tl, [2, 'one\zs\_$\_s*two',
+ \ ['', 'one', ' two', 'one', '', 'two'], ['', 'oneXX', 'oneXX']])
+
+ " Check that \_[0-9] matching EOL does not break a following \>
+ call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']])
+
+ " Check a pattern with a line break and ^ and $
+ call add(tl, [2, 'a\n^b$\n^c', ['a', 'b', 'c'], ['XX']])
+
+ call add(tl, [2, '\(^.\+\n\)\1', [' dog', ' dog', 'asdf'], ['XXasdf']])
+
+ " Run the multi-line tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let before = t[2]
+ let after = t[3]
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re == 1
+ continue
+ endif
+ let &regexpengine = engine
+ new
+ call setline(1, before)
+ exe '%s/' . pat . '/XX/'
+ let result = getline(1, '$')
+ q!
+ if result != after
+ call assert_report('Error: pat: \"' . pat . '\", text: \"'
+ \ . string(before) . '\", expected: \"' . string(after)
+ \ . '\", got: \"' . string(result) . '\"')
+ else
+ " Test passed
+ endif
+ endfor
+ endfor
+ unlet t tl
+endfunc
+
+" Check that using a pattern on two lines doesn't get messed up by using
+" matchstr() with \ze in between.
+func Test_matchstr_with_ze()
+ new
+ call append(0, ['Substitute here:', '<T="">Ta 5</Title>',
+ \ '<T="">Ac 7</Title>'])
+ call cursor(1, 1)
+ set re=0
+
+ .+1,.+2s/""/\='"' . matchstr(getline("."), '\d\+\ze<') . '"'
+ call assert_equal(['Substitute here:', '<T="5">Ta 5</Title>',
+ \ '<T="7">Ac 7</Title>', ''], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Check a pattern with a look behind crossing a line boundary
+func Test_lookbehind_across_line()
+ new
+ call append(0, ['Behind:', 'asdfasd<yyy', 'xxstart1', 'asdfasd<yy',
+ \ 'xxxstart2', 'asdfasd<yy', 'xxstart3'])
+ call cursor(1, 1)
+ call search('\(<\_[xy]\+\)\@3<=start')
+ call assert_equal([0, 7, 3, 0], getpos('.'))
+ bwipe!
+endfunc
+
+" Test for the \%V atom (match inside the visual area)
+func Regex_Match_Visual_Area()
+ call append(0, ['Visual:', 'thexe the thexethe', 'andaxand andaxand',
+ \ 'oooxofor foroxooo', 'oooxofor foroxooo'])
+ call cursor(1, 1)
+ exe "normal jfxvfx:s/\\%Ve/E/g\<CR>"
+ exe "normal jV:s/\\%Va/A/g\<CR>"
+ exe "normal jfx\<C-V>fxj:s/\\%Vo/O/g\<CR>"
+ call assert_equal(['Visual:', 'thexE thE thExethe', 'AndAxAnd AndAxAnd',
+ \ 'oooxOfOr fOrOxooo', 'oooxOfOr fOrOxooo', ''], getline(1, '$'))
+ %d
+endfunc
+
+" Check matching Visual area
+func Test_matching_visual_area()
+ new
+ set regexpengine=1
+ call Regex_Match_Visual_Area()
+ set regexpengine=2
+ call Regex_Match_Visual_Area()
+ set regexpengine&
+ bwipe!
+endfunc
+
+" Check matching marks
+func Regex_Mark()
+ call append(0, ['', '', '', 'Marks:', 'asdfSasdfsadfEasdf', 'asdfSas',
+ \ 'dfsadfEasdf', '', '', '', '', ''])
+ call cursor(4, 1)
+ exe "normal jfSmsfEme:.-4,.+6s/.\\%>'s.*\\%<'e../here/\<CR>"
+ exe "normal jfSmsj0fEme:.-4,.+6s/.\\%>'s\\_.*\\%<'e../again/\<CR>"
+ call assert_equal(['', '', '', 'Marks:', 'asdfhereasdf', 'asdfagainasdf',
+ \ '', '', '', '', '', ''], getline(1, '$'))
+ %d
+endfunc
+
+func Test_matching_marks()
+ new
+ set regexpengine=1
+ call Regex_Mark()
+ set regexpengine=2
+ call Regex_Mark()
+ bwipe!
+endfunc
+
+" Check patterns matching cursor position.
+func s:curpos_test()
+ new
+ call setline(1, ['ffooooo', 'boboooo', 'zoooooo', 'koooooo', 'moooooo',
+ \ "\t\t\tfoo", 'abababababababfoo', 'bababababababafoo', '********_',
+ \ ' xxxxxxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'])
+ call setpos('.', [0, 1, 0, 0])
+ s/\%>3c.//g
+ call setpos('.', [0, 2, 4, 0])
+ s/\%#.*$//g
+ call setpos('.', [0, 3, 0, 0])
+ s/\%<3c./_/g
+ %s/\%4l\%>5c./_/g
+ %s/\%6l\%>25v./_/g
+ %s/\%>6l\%3c./!/g
+ %s/\%>7l\%12c./?/g
+ %s/\%>7l\%<9l\%>5v\%<8v./#/g
+ $s/\%(|\u.*\)\@<=[^|\t]\+$//ge
+ call assert_equal(['ffo', 'bob', '__ooooo', 'koooo__', 'moooooo',
+ \ ' f__', 'ab!babababababfoo',
+ \ 'ba!ab##abab?bafoo', '**!*****_',
+ \ ' ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'],
+ \ getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_matching_curpos()
+ set re=0
+ call s:curpos_test()
+ set re=1
+ call s:curpos_test()
+ set re=2
+ call s:curpos_test()
+ set re&
+endfunc
+
+" Test for matching the start and end of a buffer
+func Regex_start_end_buffer()
+ call setline(1, repeat(['vim edit'], 20))
+ /\%^
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ exe "normal 50%/\\%^..\<CR>"
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ exe "normal 50%/\\%$\<CR>"
+ call assert_equal([0, 20, 8, 0], getpos('.'))
+ exe "normal 6gg/..\\%$\<CR>"
+ call assert_equal([0, 20, 7, 0], getpos('.'))
+ %d
+endfunc
+
+func Test_start_end_of_buffer_match()
+ new
+ set regexpengine=1
+ call Regex_start_end_buffer()
+ set regexpengine=2
+ call Regex_start_end_buffer()
+ bwipe!
+endfunc
+
+func Test_ze_before_zs()
+ call assert_equal('', matchstr(' ', '\%#=1\ze \zs'))
+ call assert_equal('', matchstr(' ', '\%#=2\ze \zs'))
+ call assert_equal(repeat([''], 10), matchlist(' ', '\%#=1\ze \zs'))
+ call assert_equal(repeat([''], 10), matchlist(' ', '\%#=2\ze \zs'))
+endfunc
+
+" Check for detecting error
+func Test_regexp_error()
+ call assert_fails("call matchlist('x x', '\\%#=1 \\zs*')", 'E888:')
+ call assert_fails("call matchlist('x x', '\\%#=1 \\ze*')", 'E888:')
+ call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:')
+ call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:')
+ call assert_fails("call matchstr('abcd', '\\%o841\\%o142')", 'E678:')
+ call assert_fails("call matchstr('abcd', '\\%#=2\\%2147483647c')", 'E951:')
+ call assert_fails("call matchstr('abcd', '\\%#=2\\%2147483647l')", 'E951:')
+ call assert_fails("call matchstr('abcd', '\\%#=2\\%2147483647v')", 'E951:')
+ call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:')
+ call assert_fails('exe "normal /\\%#=2\\%2147483647l\<CR>"', 'E951:')
+ call assert_fails('exe "normal /\\%#=2\\%2147483647c\<CR>"', 'E951:')
+ call assert_fails('exe "normal /\\%#=2\\%102261126v\<CR>"', 'E951:')
+ call assert_fails('exe "normal /\\%#=2\\%2147483646l\<CR>"', 'E486:')
+ call assert_fails('exe "normal /\\%#=2\\%2147483646c\<CR>"', 'E486:')
+ call assert_fails('exe "normal /\\%#=2\\%102261125v\<CR>"', 'E486:')
+ call assert_equal('', matchstr('abcd', '\%o181\%o142'))
+endfunc
+
+" Test for using the last substitute string pattern (~)
+func Test_regexp_last_subst_string()
+ new
+ s/bar/baz/e
+ call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=1\~"), "baz")
+ call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=2\~"), "baz")
+ close!
+endfunc
+
+" Check patterns matching cursor position.
+func s:curpos_test2()
+ new
+ call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
+ \ '3 foobar eins zwei drei vier fünf sechse',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'])
+ call setpos('.', [0, 2, 10, 0])
+ s/\%.c.*//g
+ call setpos('.', [0, 3, 15, 0])
+ s/\%.l.*//g
+ call setpos('.', [0, 5, 3, 0])
+ s/\%.v.*/_/g
+ call assert_equal(['1',
+ \ '2 foobar ',
+ \ '',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 _',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'],
+ \ getline(1, '$'))
+ call assert_fails('call search("\\%.1l")', 'E1204:')
+ call assert_fails('call search("\\%.1c")', 'E1204:')
+ call assert_fails('call search("\\%.1v")', 'E1204:')
+ bwipe!
+endfunc
+
+" Check patterns matching before or after cursor position.
+func s:curpos_test3()
+ new
+ call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
+ \ '3 foobar eins zwei drei vier fünf sechse',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'])
+ call setpos('.', [0, 2, 10, 0])
+ " Note: This removes all columns, except for the column directly in front of
+ " the cursor. Bug????
+ :s/^.*\%<.c//
+ call setpos('.', [0, 3, 10, 0])
+ :s/\%>.c.*$//
+ call setpos('.', [0, 5, 4, 0])
+ " Note: This removes all columns, except for the column directly in front of
+ " the cursor. Bug????
+ :s/^.*\%<.v/_/
+ call setpos('.', [0, 6, 4, 0])
+ :s/\%>.v.*$/_/
+ call assert_equal(['1',
+ \ ' eins zwei drei vier fünf sechse',
+ \ '3 foobar e',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '_foobar eins zwei drei vier fünf sechse',
+ \ '6 fo_',
+ \ '7 foobar eins zwei drei vier fünf sechse'],
+ \ getline(1, '$'))
+ sil %d
+ call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
+ \ '3 foobar eins zwei drei vier fünf sechse',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'])
+ call setpos('.', [0, 4, 4, 0])
+ %s/\%<.l.*//
+ call setpos('.', [0, 5, 4, 0])
+ %s/\%>.l.*//
+ call assert_equal(['', '', '',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '', ''],
+ \ getline(1, '$'))
+ bwipe!
+endfunc
+
+" Test that matching below, at or after the
+" cursor position work
+func Test_matching_pos()
+ for val in range(3)
+ exe "set re=" .. val
+ " Match at cursor position
+ call s:curpos_test2()
+ " Match before or after cursor position
+ call s:curpos_test3()
+ endfor
+ set re&
+endfunc
+
+func Test_using_mark_position()
+ " this was using freed memory
+ " new engine
+ new
+ norm O0
+ call assert_fails("s/\\%')", 'E486:')
+ bwipe!
+
+ " old engine
+ new
+ norm O0
+ call assert_fails("s/\\%#=1\\%')", 'E486:')
+ bwipe!
+endfunc
+
+func Test_using_visual_position()
+ " this was using freed memory
+ new
+ exe "norm 0o\<Esc>\<C-V>k\<C-X>o0"
+ /\%V
+ bwipe!
+endfunc
+
+func Test_using_invalid_visual_position()
+ " this was going beyond the end of the line
+ new
+ exe "norm 0o000\<Esc>0\<C-V>$s0"
+ /\%V
+ bwipe!
+endfunc
+
+func Test_using_two_engines_pattern()
+ new
+ call setline(1, ['foobar=0', 'foobar=1', 'foobar=2'])
+ " \%#= at the end of the pattern
+ for i in range(0, 2)
+ for j in range(0, 2)
+ exe "set re=" .. i
+ call cursor(j + 1, 7)
+ call assert_fails("%s/foobar\\%#=" .. j, 'E1281:')
+ endfor
+ endfor
+ set re=0
+
+ " \%#= at the start of the pattern
+ for i in range(0, 2)
+ call cursor(i + 1, 7)
+ exe ":%s/\\%#=" .. i .. "foobar=" .. i .. "/xx"
+ endfor
+ call assert_equal(['xx', 'xx', 'xx'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_recursive_substitute_expr()
+ new
+ func Repl()
+ s
+ endfunc
+ silent! s/\%')/~\=Repl()
+
+ bwipe!
+ delfunc Repl
+endfunc
+
+def Test_compare_columns()
+ # this was using a line below the last line
+ enew
+ setline(1, ['', ''])
+ prop_type_add('name', {highlight: 'ErrorMsg'})
+ prop_add(1, 1, {length: 1, type: 'name'})
+ search('\%#=1\%>.l\n.*\%<2v', 'nW')
+ search('\%#=2\%>.l\n.*\%<2v', 'nW')
+ bwipe!
+ prop_type_delete('name')
+enddef
+
+def Test_compare_column_matchstr()
+ # do some search in text to set the line number, it should be ignored in
+ # matchstr().
+ enew
+ setline(1, ['one', 'two', 'three'])
+ :3
+ :/ee
+ bwipe!
+ set re=1
+ call assert_equal('aaa', matchstr('aaaaaaaaaaaaaaaaaaaa', '.*\%<5v'))
+ set re=2
+ call assert_equal('aaa', matchstr('aaaaaaaaaaaaaaaaaaaa', '.*\%<5v'))
+ set re=0
+enddef
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_regexp_utf8.vim b/src/testdir/test_regexp_utf8.vim
new file mode 100644
index 0000000..b591aed
--- /dev/null
+++ b/src/testdir/test_regexp_utf8.vim
@@ -0,0 +1,579 @@
+" Tests for regexp in utf8 encoding
+
+source shared.vim
+
+func s:equivalence_test()
+ let str = "AÀÃÂÃÄÅĀĂĄÇǞǠǺȂȦȺḀẠẢẤẦẨẪẬẮẰẲẴẶ BÆɃḂḄḆ CÇĆĈĊČƇȻḈꞒ DÄŽÄƊḊḌḎá¸á¸’ EÈÉÊËĒĔĖĘĚȄȆȨɆḔḖḘḚḜẸẺẼẾỀỂỄỆ FƑḞꞘ GĜĞĠĢƓǤǦǴḠꞠ HĤĦȞḢḤḦḨḪⱧ IÃŒÃÃŽÃĨĪĬĮİƗÇȈȊḬḮỈỊ JĴɈ KÄ¶Æ˜Ç¨á¸°á¸²á¸´â±©ê€ LĹĻĽĿÅȽḶḸḺḼⱠ MḾṀṂ NÑŃŅŇǸṄṆṈṊꞤ OÒÓÔÕÖØŌŎÅƟƠǑǪǬǾȌȎȪȬȮȰṌṎá¹á¹’ỌỎá»á»’ỔỖỘỚỜỞỠỢ PƤṔṖⱣ QÉŠ RŔŖŘÈȒɌṘṚṜṞⱤꞦ SŚŜŞŠȘṠṢṤṦṨⱾꞨ TŢŤŦƬƮȚȾṪṬṮṰ UÙÚÛÜŨŪŬŮŰƯǕǙǛǓǗȔȖɄṲṴṶṸṺỤỦỨỪỬỮỰ VƲṼṾ WŴẀẂẄẆẈ XẊẌ YÃŶŸƳȲɎẎỲỴỶỸ ZŹŻŽƵáºáº’ẔⱫ aàáâãäåÄăąǎǟǡǻȃȧá¶á¸áºšáº¡áº£áº¥áº§áº©áº«áº­áº¯áº±áº³áºµáº·â±¥ bƀɓᵬᶀḃḅḇ cçćĉċÄƈȼḉꞓꞔ dÄÄ‘É—áµ­á¶á¶‘ḋá¸á¸á¸‘ḓ eèéêëēĕėęěȅȇȩɇᶒḕḗḙḛá¸áº¹áº»áº½áº¿á»á»ƒá»…ệ fƒᵮᶂḟꞙ gÄğġģǥǧǵɠᶃḡꞡ hĥħȟḣḥḧḩḫẖⱨꞕ iìíîïĩīĭįÇȉȋɨᶖḭḯỉị jĵǰɉ kķƙǩᶄḱḳḵⱪê lĺļľŀłƚḷḹḻḽⱡ mᵯḿá¹á¹ƒ nñńņňʼnǹᵰᶇṅṇṉṋꞥ oòóôõöøÅÅÅ‘Æ¡Ç’Ç«Ç­Ç¿ÈÈȫȭȯȱɵá¹á¹á¹‘ṓá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£ pƥᵱᵽᶈṕṗ qÉ‹Ê  rŕŗřȑȓÉɽᵲᵳᶉṛá¹á¹Ÿêž§ sÅ›Åşšșȿᵴᶊṡṣṥṧṩꞩ tţťŧƫƭțʈᵵṫṭṯṱẗⱦ uùúûüũūŭůűųǚǖưǔǘǜȕȗʉᵾᶙṳṵṷṹṻụủứừửữự vʋᶌṽṿ wŵáºáºƒáº…ẇẉẘ xẋẠyýÿŷƴȳÉáºáº™á»³á»µá»·á»¹ zźżžƶᵶᶎẑẓẕⱬ"
+ let groups = split(str)
+ for group1 in groups
+ for c in split(group1, '\zs')
+ " next statement confirms that equivalence class matches every
+ " character in group
+ call assert_match('^[[=' .. c .. '=]]*$', group1)
+ for group2 in groups
+ if group2 != group1
+ " next statement converts that equivalence class doesn't match
+ " character in any other group
+ call assert_equal(-1, match(group2, '[[=' .. c .. '=]]'), c)
+ endif
+ endfor
+ endfor
+ endfor
+endfunc
+
+func Test_equivalence_re1()
+ set re=1
+ call s:equivalence_test()
+ set re=0
+endfunc
+
+func Test_equivalence_re2()
+ set re=2
+ call s:equivalence_test()
+ set re=0
+endfunc
+
+func s:classes_test()
+ if has('win32')
+ set iskeyword=@,48-57,_,192-255
+ endif
+ set isprint=@,161-255
+ call assert_equal('Motörhead', matchstr('Motörhead', '[[:print:]]\+'))
+
+ let alnumchars = ''
+ let alphachars = ''
+ let backspacechar = ''
+ let blankchars = ''
+ let cntrlchars = ''
+ let digitchars = ''
+ let escapechar = ''
+ let graphchars = ''
+ let lowerchars = ''
+ let printchars = ''
+ let punctchars = ''
+ let returnchar = ''
+ let spacechars = ''
+ let tabchar = ''
+ let upperchars = ''
+ let xdigitchars = ''
+ let identchars = ''
+ let identchars1 = ''
+ let kwordchars = ''
+ let kwordchars1 = ''
+ let fnamechars = ''
+ let fnamechars1 = ''
+ let i = 1
+ while i <= 255
+ let c = nr2char(i)
+ if c =~ '[[:alpha:]]'
+ let alphachars .= c
+ endif
+ if c =~ '[[:alnum:]]'
+ let alnumchars .= c
+ endif
+ if c =~ '[[:backspace:]]'
+ let backspacechar .= c
+ endif
+ if c =~ '[[:blank:]]'
+ let blankchars .= c
+ endif
+ if c =~ '[[:cntrl:]]'
+ let cntrlchars .= c
+ endif
+ if c =~ '[[:digit:]]'
+ let digitchars .= c
+ endif
+ if c =~ '[[:escape:]]'
+ let escapechar .= c
+ endif
+ if c =~ '[[:graph:]]'
+ let graphchars .= c
+ endif
+ if c =~ '[[:lower:]]'
+ let lowerchars .= c
+ endif
+ if c =~ '[[:print:]]'
+ let printchars .= c
+ endif
+ if c =~ '[[:punct:]]'
+ let punctchars .= c
+ endif
+ if c =~ '[[:return:]]'
+ let returnchar .= c
+ endif
+ if c =~ '[[:space:]]'
+ let spacechars .= c
+ endif
+ if c =~ '[[:tab:]]'
+ let tabchar .= c
+ endif
+ if c =~ '[[:upper:]]'
+ let upperchars .= c
+ endif
+ if c =~ '[[:xdigit:]]'
+ let xdigitchars .= c
+ endif
+ if c =~ '[[:ident:]]'
+ let identchars .= c
+ endif
+ if c =~ '\i'
+ let identchars1 .= c
+ endif
+ if c =~ '[[:keyword:]]'
+ let kwordchars .= c
+ endif
+ if c =~ '\k'
+ let kwordchars1 .= c
+ endif
+ if c =~ '[[:fname:]]'
+ let fnamechars .= c
+ endif
+ if c =~ '\f'
+ let fnamechars1 .= c
+ endif
+ let i += 1
+ endwhile
+
+ call assert_equal('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', alphachars)
+ call assert_equal('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', alnumchars)
+ call assert_equal("\b", backspacechar)
+ call assert_equal("\t ", blankchars)
+ call assert_equal("\x01\x02\x03\x04\x05\x06\x07\b\t\n\x0b\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f\x7f", cntrlchars)
+ call assert_equal("0123456789", digitchars)
+ call assert_equal("\<Esc>", escapechar)
+ call assert_equal('!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~', graphchars)
+ call assert_equal('abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ', lowerchars)
+ call assert_equal(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ', printchars)
+ call assert_equal('!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~', punctchars)
+ call assert_equal('ABCDEFGHIJKLMNOPQRSTUVWXYZÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ', upperchars)
+ call assert_equal("\r", returnchar)
+ call assert_equal("\t\n\x0b\f\r ", spacechars)
+ call assert_equal("\t", tabchar)
+ call assert_equal('0123456789ABCDEFabcdef', xdigitchars)
+
+ if has('win32')
+ let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€Â‚ƒ„…†‡ˆ‰Š‹ŒÂŽ‘’“”•–—˜™š›œÂžŸ ¡¢£¤¥¦§µÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
+ let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ else
+ let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ endif
+
+ if has('win32')
+ let fnamechars_ok = '!#$%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_abcdefghijklmnopqrstuvwxyz{}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('amiga')
+ let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('vms')
+ let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ else
+ let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ endif
+
+ call assert_equal(identchars_ok, identchars)
+ call assert_equal(kwordchars_ok, kwordchars)
+ call assert_equal(fnamechars_ok, fnamechars)
+
+ call assert_equal(identchars1, identchars)
+ call assert_equal(kwordchars1, kwordchars)
+ call assert_equal(fnamechars1, fnamechars)
+endfunc
+
+func Test_classes_re1()
+ set re=1
+ call s:classes_test()
+ set re=0
+endfunc
+
+func Test_classes_re2()
+ set re=2
+ call s:classes_test()
+ set re=0
+endfunc
+
+func Test_reversed_range()
+ for re in range(0, 2)
+ exe 'set re=' . re
+ call assert_fails('call match("abc def", "[c-a]")', 'E944:', re)
+ endfor
+ set re=0
+endfunc
+
+func Test_large_class()
+ set re=1
+ call assert_fails('call match("abc def", "[\u3000-\u4000]")', 'E945:')
+ set re=2
+ call assert_equal(0, 'abc def' =~# '[\u3000-\u4000]')
+ call assert_equal(1, "\u3042" =~# '[\u3000-\u4000]')
+ set re=0
+endfunc
+
+func Test_optmatch_toolong()
+ set re=1
+ " Can only handle about 8000 characters.
+ let pat = '\\%[' .. repeat('x', 9000) .. ']'
+ call assert_fails('call match("abc def", "' .. pat .. '")', 'E339:')
+ set re=0
+endfunc
+
+" Test for regexp patterns with multi-byte support, using utf-8.
+func Test_multibyte_chars()
+ " tl is a List of Lists with:
+ " 2: test auto/old/new 0: test auto/old 1: test auto/new
+ " regexp pattern
+ " text to test the pattern on
+ " expected match (optional)
+ " expected submatch 1 (optional)
+ " expected submatch 2 (optional)
+ " etc.
+ " When there is no match use only the first two items.
+ let tl = []
+
+ " Multi-byte character tests.
+ call add(tl, [2, '[[:alpha:][=a=]]\+', '879 aiaãâaiuvna ', 'aiaãâaiuvna'])
+ call add(tl, [2, '[[=a=]]\+', 'ddaãâbcd', 'aãâ']) " equivalence classes
+ call add(tl, [2, '[^ม ]\+', 'มม oijasoifjos ifjoisj f osij j มมมมม abcd', 'oijasoifjos'])
+ call add(tl, [2, ' [^ ]\+', 'start มabcdม ', ' มabcdม'])
+ call add(tl, [2, '[ม[:alpha:][=a=]]\+', '879 aiaãมâมaiuvna ', 'aiaãมâมaiuvna'])
+
+ " this is not a normal "i" but 0xec
+ call add(tl, [2, '\p\+', 'ìa', 'ìa'])
+ call add(tl, [2, '\p*', 'aã‚', 'aã‚'])
+
+ " Test recognition of some character classes
+ call add(tl, [2, '\i\+', '&*¨xx ', 'xx'])
+ call add(tl, [2, '\f\+', '&*Ÿfname ', 'fname'])
+
+ " Test composing character matching
+ call add(tl, [2, '.ม', 'xม่x yมy', 'yม'])
+ call add(tl, [2, '.ม่', 'xม่x yมy', 'xม่'])
+ call add(tl, [2, "\u05b9", " x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, ".\u05b9", " x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, "\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, ".\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, "\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"])
+ call add(tl, [2, ".\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"])
+ call add(tl, [1, "\u05b9\u05bb", " y\u05b9 x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05b9\u05bb", " y\u05bb x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "a", "ca\u0300t"])
+ call add(tl, [2, "ca", "ca\u0300t"])
+ call add(tl, [2, "a\u0300", "ca\u0300t", "a\u0300"])
+ call add(tl, [2, 'a\%C', "ca\u0300t", "a\u0300"])
+ call add(tl, [2, 'ca\%C', "ca\u0300t", "ca\u0300"])
+ call add(tl, [2, 'ca\%Ct', "ca\u0300t", "ca\u0300t"])
+
+ " Test \Z
+ call add(tl, [2, 'ú\Z', 'x'])
+ call add(tl, [2, 'יהוה\Z', 'יהוה', 'יהוה'])
+ call add(tl, [2, 'יְהוָה\Z', 'יהוה', 'יהוה'])
+ call add(tl, [2, 'יהוה\Z', 'יְהוָה', 'יְהוָה'])
+ call add(tl, [2, 'יְהוָה\Z', 'יְהוָה', 'יְהוָה'])
+ call add(tl, [2, 'יְ\Z', 'וְיַ', 'יַ'])
+ call add(tl, [2, "ק\u200d\u05b9x\\Z", "xק\u200d\u05b9xy", "ק\u200d\u05b9x"])
+ call add(tl, [2, "ק\u200d\u05b9x\\Z", "xק\u200dxy", "ק\u200dx"])
+ call add(tl, [2, "ק\u200dx\\Z", "xק\u200d\u05b9xy", "ק\u200d\u05b9x"])
+ call add(tl, [2, "ק\u200dx\\Z", "xק\u200dxy", "ק\u200dx"])
+ call add(tl, [2, "\u05b9\\Z", "xyz"])
+ call add(tl, [2, "\\Z\u05b9", "xyz"])
+ call add(tl, [2, "\u05b9\\Z", "xy\u05b9z", "y\u05b9"])
+ call add(tl, [2, "\\Z\u05b9", "xy\u05b9z", "y\u05b9"])
+ call add(tl, [1, "\u05b9\\+\\Z", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"])
+ call add(tl, [1, "\\Z\u05b9\\+", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"])
+
+ " Combining different tests and features
+ call add(tl, [2, '[^[=a=]]\+', 'ddaãâbcd', 'dd'])
+
+ " Run the tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let text = t[2]
+ let matchidx = 3
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re == 1
+ continue
+ endif
+ let &regexpengine = engine
+ try
+ let l = matchlist(text, pat)
+ catch
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text .
+ \ '\", caused an exception: \"' . v:exception . '\"')
+ endtry
+ " check the match itself
+ if len(l) == 0 && len(t) > matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text .
+ \ '\", did not match, expected: \"' . t[matchidx] . '\"')
+ elseif len(l) > 0 && len(t) == matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", match: \"' . l[0] .
+ \ '\", expected no match')
+ elseif len(t) > matchidx && l[0] != t[matchidx]
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", match: \"' . l[0] .
+ \ '\", expected: \"' . t[matchidx] . '\"')
+ else
+ " Test passed
+ endif
+ if len(l) > 0
+ " check all the nine submatches
+ for i in range(1, 9)
+ if len(t) <= matchidx + i
+ let e = ''
+ else
+ let e = t[matchidx + i]
+ endif
+ if l[i] != e
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", submatch ' . i .
+ \ ': \"' . l[i] . '\", expected: \"' . e . '\"')
+ endif
+ endfor
+ unlet i
+ endif
+ endfor
+ endfor
+ set regexpengine&
+endfunc
+
+" check that 'ambiwidth' does not change the meaning of \p
+func Test_ambiwidth()
+ set regexpengine=1 ambiwidth=single
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=1 ambiwidth=double
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=2 ambiwidth=single
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=2 ambiwidth=double
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine& ambiwidth&
+endfunc
+
+func Run_regexp_ignore_case()
+ call assert_equal('iIÄ°', substitute('iIÄ°', '\([iIÄ°]\)', '\1', 'g'))
+
+ call assert_equal('iIx', substitute('iIÄ°', '\c\([Ä°]\)', 'x', 'g'))
+ call assert_equal('xxÄ°', substitute('iIÄ°', '\(i\c\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIÄ°', '\(Ä°\c\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIÄ°', '\c\(\%u0130\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIÄ°', '\c\([\u0130]\)', 'x', 'g'))
+ call assert_equal('iIx', substitute('iIÄ°', '\c\([\u012f-\u0131]\)', 'x', 'g'))
+endfunc
+
+func Test_regexp_ignore_case()
+ set regexpengine=1
+ call Run_regexp_ignore_case()
+ set regexpengine=2
+ call Run_regexp_ignore_case()
+ set regexpengine&
+endfunc
+
+" Tests for regexp with multi-byte encoding and various magic settings
+func Run_regexp_multibyte_magic()
+ let text =<< trim END
+ 1 a aa abb abbccc
+ 2 d dd dee deefff
+ 3 g gg ghh ghhiii
+ 4 j jj jkk jkklll
+ 5 m mm mnn mnnooo
+ 6 x ^aa$ x
+ 7 (a)(b) abbaa
+ 8 axx [ab]xx
+ 9 หม่x อมx
+ a อมx หม่x
+ b ã¡ã‚«ãƒ¨ã¯
+ c x ¬€x
+ d 天使x
+ e ü’…™¸y
+ f ü’Š¯z
+ g aå•·bb
+ j 0123â¤x
+ k combinations
+ l äö üᾱ̆Ì
+ END
+
+ new
+ call setline(1, text)
+ exe 'normal /a*b\{2}c\+/e' .. "\<CR>x"
+ call assert_equal('1 a aa abb abbcc', getline('.'))
+ exe 'normal /\Md\*e\{2}f\+/e' .. "\<CR>x"
+ call assert_equal('2 d dd dee deeff', getline('.'))
+ set nomagic
+ exe 'normal /g\*h\{2}i\+/e' .. "\<CR>x"
+ call assert_equal('3 g gg ghh ghhii', getline('.'))
+ exe 'normal /\mj*k\{2}l\+/e' .. "\<CR>x"
+ call assert_equal('4 j jj jkk jkkll', getline('.'))
+ exe 'normal /\vm*n{2}o+/e' .. "\<CR>x"
+ call assert_equal('5 m mm mnn mnnoo', getline('.'))
+ exe 'normal /\V^aa$/' .. "\<CR>x"
+ call assert_equal('6 x aa$ x', getline('.'))
+ set magic
+ exe 'normal /\v(a)(b)\2\1\1/e' .. "\<CR>x"
+ call assert_equal('7 (a)(b) abba', getline('.'))
+ exe 'normal /\V[ab]\(\[xy]\)\1' .. "\<CR>x"
+ call assert_equal('8 axx ab]xx', getline('.'))
+
+ " search for multi-byte without composing char
+ exe 'normal /ม' .. "\<CR>x"
+ call assert_equal('9 หม่x อx', getline('.'))
+
+ " search for multi-byte with composing char
+ exe 'normal /ม่' .. "\<CR>x"
+ call assert_equal('a อมx หx', getline('.'))
+
+ " find word by change of word class
+ exe 'normal /ã¡\<カヨ\>ã¯' .. "\<CR>x"
+ call assert_equal('b カヨã¯', getline('.'))
+
+ " Test \%u, [\u] and friends
+ " c
+ exe 'normal /\%u20ac' .. "\<CR>x"
+ call assert_equal('c x ¬x', getline('.'))
+ " d
+ exe 'normal /[\u4f7f\u5929]\+' .. "\<CR>x"
+ call assert_equal('d 使x', getline('.'))
+ " e
+ exe 'normal /\%U12345678' .. "\<CR>x"
+ call assert_equal('e y', getline('.'))
+ " f
+ exe 'normal /[\U1234abcd\u1234\uabcd]' .. "\<CR>x"
+ call assert_equal('f z', getline('.'))
+ " g
+ exe 'normal /\%d21879b' .. "\<CR>x"
+ call assert_equal('g abb', getline('.'))
+
+ " j Test backwards search from a multi-byte char
+ exe "normal /x\<CR>x?.\<CR>x"
+ call assert_equal('j 012â¤', getline('.'))
+ " k
+ let @w=':%s#comb[i]nations#Å“Ì„á¹£Ìm̥̄ᾱ̆Ì#g'
+ @w
+ call assert_equal('k Å“Ì„á¹£Ìm̥̄ᾱ̆Ì', getline(18))
+
+ close!
+endfunc
+
+func Test_regexp_multibyte_magic()
+ set regexpengine=1
+ call Run_regexp_multibyte_magic()
+ set regexpengine=2
+ call Run_regexp_multibyte_magic()
+ set regexpengine&
+endfunc
+
+" Test for 7.3.192
+" command ":s/ \?/ /g" splits multi-byte characters into bytes
+func Test_split_multibyte_to_bytes()
+ new
+ call setline(1, 'l äö üᾱ̆Ì')
+ s/ \?/ /g
+ call assert_equal(' l ä ö ü ᾱ̆Ì', getline(1))
+ close!
+endfunc
+
+" Test for matchstr() with multibyte characters
+func Test_matchstr_multibyte()
+ new
+ call assert_equal('ב', matchstr("×בגד", ".", 0, 2))
+ call assert_equal('בג', matchstr("×בגד", "..", 0, 2))
+ call assert_equal('×', matchstr("×בגד", ".", 0, 0))
+ call assert_equal('×’', matchstr("×בגד", ".", 4, -1))
+ close!
+endfunc
+
+" Test for 7.4.636
+" A search with end offset gets stuck at end of file.
+func Test_search_with_end_offset()
+ new
+ call setline(1, ['', 'dog(a', 'cat('])
+ exe "normal /(/e+\<CR>"
+ normal n"ayn
+ call assert_equal("a\ncat(", @a)
+ close!
+endfunc
+
+" Check that "^" matches even when the line starts with a combining char
+func Test_match_start_of_line_combining()
+ new
+ call setline(1, ['', "\u05ae", ''])
+ exe "normal gg/^\<CR>"
+ call assert_equal(2, getcurpos()[1])
+ bwipe!
+endfunc
+
+" Check that [[:upper:]] matches for automatic engine
+func Test_match_char_class_upper()
+ new
+
+ " Test 1: [[:upper:]]\{2,\}
+ set regexpengine=0
+ call setline(1, ['05. ПЕСÐЯ О ГЕРОЯХ муз. Ð. Давиденко, Ðœ. ÐšÐ¾Ð²Ð°Ð»Ñ Ð¸ Б. Шехтера ...', '05. PJESNJA O GJEROJAKH mus. A. Davidjenko, M. Kovalja i B. Shjekhtjera ...'])
+ call cursor(1,1)
+ let search_cmd='norm /\<[[:upper:]]\{2,\}\>' .. "\<CR>"
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 1')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+
+ " Test 2: [[:upper:]].\+
+ let search_cmd='norm /\<[[:upper:]].\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(1, searchcount().total, 'TEST 2')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+
+ " Test 3: [[:lower:]]\+
+ let search_cmd='norm /\<[[:lower:]]\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 3 lower')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+
+ " clean up
+ set regexpengine=0
+ bwipe!
+endfunc
+
+func Test_match_invalid_byte()
+ call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid', 'D')
+ new
+ source Xinvalid
+ bwipe!
+endfunc
+
+func Test_match_illegal_byte()
+ " Text has illegal bytes which need to be set explicitly
+ let lines = ["norm :set no\x01\<CR>", "silent n\xff", "silent norm :b\xff\<CR>"]
+ call writefile(lines, 'Xregexp', 'D')
+ call system(GetVimCommand() .. ' -X -Z -e -s -S Xregexp -c qa!')
+endfunc
+
+func Test_match_too_complicated()
+ set regexpengine=1
+ exe "noswapfile vsplit \xeb\xdb\x99"
+ silent! buf \&\zs*\zs*0
+ bwipe!
+ set regexpengine=0
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_registers.vim b/src/testdir/test_registers.vim
new file mode 100644
index 0000000..e966932
--- /dev/null
+++ b/src/testdir/test_registers.vim
@@ -0,0 +1,890 @@
+" Tests for register operations
+
+source check.vim
+source view_util.vim
+
+" This test must be executed first to check for empty and unset registers.
+func Test_aaa_empty_reg_test()
+ call assert_fails('normal @@', 'E748:')
+ call assert_fails('normal @%', 'E354:')
+ call assert_fails('normal @#', 'E354:')
+ call assert_fails('normal @!', 'E354:')
+ call assert_fails('normal @:', 'E30:')
+ call assert_fails('normal @.', 'E29:')
+ call assert_fails('put /', 'E35:')
+ call assert_fails('put .', 'E29:')
+endfunc
+
+func Test_yank_shows_register()
+ enew
+ set report=0
+ call setline(1, ['foo', 'bar'])
+ " Line-wise
+ exe 'norm! yy'
+ call assert_equal('1 line yanked', v:statusmsg)
+ exe 'norm! "zyy'
+ call assert_equal('1 line yanked into "z', v:statusmsg)
+ exe 'norm! yj'
+ call assert_equal('2 lines yanked', v:statusmsg)
+ exe 'norm! "zyj'
+ call assert_equal('2 lines yanked into "z', v:statusmsg)
+
+ " Block-wise
+ exe "norm! \<C-V>y"
+ call assert_equal('block of 1 line yanked', v:statusmsg)
+ exe "norm! \<C-V>\"zy"
+ call assert_equal('block of 1 line yanked into "z', v:statusmsg)
+ exe "norm! \<C-V>jy"
+ call assert_equal('block of 2 lines yanked', v:statusmsg)
+ exe "norm! \<C-V>j\"zy"
+ call assert_equal('block of 2 lines yanked into "z', v:statusmsg)
+
+ bwipe!
+endfunc
+
+func Test_display_registers()
+ e file1
+ e file2
+ call setline(1, ['foo', 'bar'])
+ /bar
+ exe 'norm! y2l"axx'
+ call feedkeys("i\<C-R>=2*4\n\<esc>")
+ call feedkeys(":ls\n", 'xt')
+
+ let a = execute('display')
+ let b = execute('registers')
+
+ call assert_equal(a, b)
+ call assert_match('^\nType Name Content\n'
+ \ . ' c "" a\n'
+ \ . ' c "0 ba\n'
+ \ . ' c "a b\n'
+ \ . '.*'
+ \ . ' c "- a\n'
+ \ . '.*'
+ \ . ' c ": ls\n'
+ \ . ' c "% file2\n'
+ \ . ' c "# file1\n'
+ \ . ' c "/ bar\n'
+ \ . ' c "= 2\*4', a)
+
+ let a = execute('registers a')
+ call assert_match('^\nType Name Content\n'
+ \ . ' c "a b', a)
+
+ let a = execute('registers :')
+ call assert_match('^\nType Name Content\n'
+ \ . ' c ": ls', a)
+
+ bwipe!
+endfunc
+
+func Test_register_one()
+ " delete a line goes into register one
+ new
+ call setline(1, "one")
+ normal dd
+ call assert_equal("one\n", @1)
+
+ " delete a word does not change register one, does change "-
+ call setline(1, "two")
+ normal de
+ call assert_equal("one\n", @1)
+ call assert_equal("two", @-)
+
+ " delete a word with a register does not change register one
+ call setline(1, "three")
+ normal "ade
+ call assert_equal("three", @a)
+ call assert_equal("one\n", @1)
+
+ " delete a word with register DOES change register one with one of a list of
+ " operators
+ " %
+ call setline(1, ["(12)3"])
+ normal "ad%
+ call assert_equal("(12)", @a)
+ call assert_equal("(12)", @1)
+
+ " (
+ call setline(1, ["first second"])
+ normal $"ad(
+ call assert_equal("first secon", @a)
+ call assert_equal("first secon", @1)
+
+ " )
+ call setline(1, ["First Second."])
+ normal gg0"ad)
+ call assert_equal("First Second.", @a)
+ call assert_equal("First Second.", @1)
+
+ " `
+ call setline(1, ["start here."])
+ normal gg0fhmx0"ad`x
+ call assert_equal("start ", @a)
+ call assert_equal("start ", @1)
+
+ " /
+ call setline(1, ["searchX"])
+ exe "normal gg0\"ad/X\<CR>"
+ call assert_equal("search", @a)
+ call assert_equal("search", @1)
+
+ " ?
+ call setline(1, ["Ysearch"])
+ exe "normal gg$\"ad?Y\<CR>"
+ call assert_equal("Ysearc", @a)
+ call assert_equal("Ysearc", @1)
+
+ " n
+ call setline(1, ["Ynext"])
+ normal gg$"adn
+ call assert_equal("Ynex", @a)
+ call assert_equal("Ynex", @1)
+
+ " N
+ call setline(1, ["prevY"])
+ normal gg0"adN
+ call assert_equal("prev", @a)
+ call assert_equal("prev", @1)
+
+ " }
+ call setline(1, ["one", ""])
+ normal gg0"ad}
+ call assert_equal("one\n", @a)
+ call assert_equal("one\n", @1)
+
+ " {
+ call setline(1, ["", "two"])
+ normal 2G$"ad{
+ call assert_equal("\ntw", @a)
+ call assert_equal("\ntw", @1)
+
+ bwipe!
+endfunc
+
+func Test_recording_status_in_ex_line()
+ norm qx
+ redraw!
+ call assert_equal('recording @x', Screenline(&lines))
+ set shortmess=q
+ redraw!
+ call assert_equal('recording', Screenline(&lines))
+ set shortmess&
+ norm q
+ redraw!
+ call assert_equal('', Screenline(&lines))
+endfunc
+
+" Check that replaying a typed sequence does not use an Esc and following
+" characters as an escape sequence.
+func Test_recording_esc_sequence()
+ new
+ try
+ let save_F2 = &t_F2
+ catch
+ endtry
+ let t_F2 = "\<Esc>OQ"
+ call feedkeys("qqiTest\<Esc>", "xt")
+ call feedkeys("OQuirk\<Esc>q", "xt")
+ call feedkeys("Go\<Esc>@q", "xt")
+ call assert_equal(['Quirk', 'Test', 'Quirk', 'Test'], getline(1, 4))
+ bwipe!
+ if exists('save_F2')
+ let &t_F2 = save_F2
+ else
+ set t_F2=
+ endif
+endfunc
+
+func Test_recording_with_select_mode()
+ new
+ call feedkeys("qacc12345\<Esc>gH98765\<Esc>q", "tx")
+ call assert_equal("98765", getline(1))
+ call assert_equal("cc12345\<Esc>gH98765\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("98765", getline(1))
+ bwipe!
+endfunc
+
+" Test for executing the last used register (@)
+func Test_last_used_exec_reg()
+ " Test for the @: command
+ let a = ''
+ call feedkeys(":let a ..= 'Vim'\<CR>", 'xt')
+ normal @:
+ call assert_equal('VimVim', a)
+
+ " Test for the @= command
+ let x = ''
+ let a = ":let x ..= 'Vim'\<CR>"
+ exe "normal @=a\<CR>"
+ normal @@
+ call assert_equal('VimVim', x)
+
+ " Test for the @. command
+ let a = ''
+ call feedkeys("i:let a ..= 'Edit'\<CR>", 'xt')
+ normal @.
+ normal @@
+ call assert_equal('EditEdit', a)
+
+ " Test for repeating the last command-line in visual mode
+ call append(0, 'register')
+ normal gg
+ let @r = ''
+ call feedkeys("v:yank R\<CR>", 'xt')
+ call feedkeys("v@:", 'xt')
+ call assert_equal("\nregister\nregister\n", @r)
+
+ enew!
+endfunc
+
+func Test_get_register()
+ enew
+ edit Xfile1
+ edit Xfile2
+ call assert_equal('Xfile2', getreg('%'))
+ call assert_equal('Xfile1', getreg('#'))
+
+ call feedkeys("iTwo\<Esc>", 'xt')
+ call assert_equal('Two', getreg('.'))
+ call assert_equal('', getreg('_'))
+ call assert_beeps('normal ":yy')
+ call assert_beeps('normal "%yy')
+ call assert_beeps('normal ".yy')
+
+ call assert_equal('', getreg("\<C-F>"))
+ call assert_equal('', getreg("\<C-W>"))
+ call assert_equal('', getreg("\<C-L>"))
+ " Change the last used register to '"' for the next test
+ normal! ""yy
+ let @" = 'happy'
+ call assert_equal('happy', getreg())
+ call assert_equal('happy', getreg(''))
+
+ call assert_equal('', getregtype('!'))
+ call assert_fails('echo getregtype([])', 'E730:')
+ call assert_equal('v', getregtype())
+ call assert_equal('v', getregtype(''))
+
+ " Test for inserting an invalid register content
+ call assert_beeps('exe "normal i\<C-R>!"')
+
+ " Test for inserting a register with multiple lines
+ call deletebufline('', 1, '$')
+ call setreg('r', ['a', 'b'])
+ exe "normal i\<C-R>r"
+ call assert_equal(['a', 'b', ''], getline(1, '$'))
+
+ " Test for inserting a multi-line register in the command line
+ call feedkeys(":\<C-R>r\<Esc>", 'xt')
+ call assert_equal("a\rb\r", histget(':', -1))
+
+ call assert_fails('let r = getreg("=", [])', 'E745:')
+ call assert_fails('let r = getreg("=", 1, [])', 'E745:')
+ enew!
+
+ " Using a register in operator-pending mode should fail
+ call assert_beeps('norm! c"')
+endfunc
+
+func Test_set_register()
+ call assert_fails("call setreg('#', 200)", 'E86:')
+ call assert_fails("call setreg('a', test_unknown())", 'E908:')
+
+ edit Xfile_alt_1
+ let b1 = bufnr('')
+ edit Xfile_alt_2
+ let b2 = bufnr('')
+ edit Xfile_alt_3
+ let b3 = bufnr('')
+ call setreg('#', 'alt_1')
+ call assert_equal('Xfile_alt_1', getreg('#'))
+ call setreg('#', b2)
+ call assert_equal('Xfile_alt_2', getreg('#'))
+
+ let ab = 'regwrite'
+ call setreg('=', '')
+ call setreg('=', 'a', 'a')
+ call setreg('=', 'b', 'a')
+ call assert_equal('regwrite', getreg('='))
+
+ " Test for setting a list of lines to special registers
+ call setreg('/', [])
+ call assert_equal('', @/)
+ call setreg('=', [])
+ call assert_equal('', @=)
+ call assert_fails("call setreg('/', ['a', 'b'])", 'E883:')
+ call assert_fails("call setreg('=', ['a', 'b'])", 'E883:')
+ call assert_equal(0, setreg('_', ['a', 'b']))
+
+ " Test for recording to a invalid register
+ call assert_beeps('normal q$')
+
+ " Appending to a register when recording
+ call append(0, "text for clipboard test")
+ normal gg
+ call feedkeys('qrllq', 'xt')
+ call feedkeys('qRhhq', 'xt')
+ call assert_equal('llhh', getreg('r'))
+
+ " Appending a list of characters to a register from different lines
+ let @r = ''
+ call append(0, ['abcdef', '123456'])
+ normal gg"ry3l
+ call cursor(2, 4)
+ normal "Ry3l
+ call assert_equal('abc456', @r)
+
+ " Test for gP with multiple lines selected using characterwise motion
+ %delete
+ call append(0, ['vim editor', 'vim editor'])
+ let @r = ''
+ exe "normal ggwy/vim /e\<CR>gP"
+ call assert_equal(['vim editor', 'vim editor', 'vim editor'], getline(1, 3))
+
+ " Test for gP with . register
+ %delete
+ normal iabc
+ normal ".gp
+ call assert_equal('abcabc', getline(1))
+ normal 0".gP
+ call assert_equal('abcabcabc', getline(1))
+
+ let @"=''
+ call setreg('', '1')
+ call assert_equal('1', @")
+ call setreg('@', '2')
+ call assert_equal('2', @")
+
+ enew!
+endfunc
+
+" Test for clipboard registers (* and +)
+func Test_clipboard_regs()
+ CheckNotGui
+ CheckFeature clipboard_working
+
+ new
+ call append(0, "text for clipboard test")
+ normal gg"*yiw
+ call assert_equal('text', getreg('*'))
+ normal gg2w"+yiw
+ call assert_equal('clipboard', getreg('+'))
+
+ " Test for replacing the clipboard register contents
+ set clipboard=unnamed
+ let @* = 'food'
+ normal ggviw"*p
+ call assert_equal('text', getreg('*'))
+ call assert_equal('food for clipboard test', getline(1))
+ normal ggviw"*p
+ call assert_equal('food', getreg('*'))
+ call assert_equal('text for clipboard test', getline(1))
+
+ " Test for replacing the selection register contents
+ set clipboard=unnamedplus
+ let @+ = 'food'
+ normal ggviw"+p
+ call assert_equal('text', getreg('+'))
+ call assert_equal('food for clipboard test', getline(1))
+ normal ggviw"+p
+ call assert_equal('food', getreg('+'))
+ call assert_equal('text for clipboard test', getline(1))
+
+ " Test for auto copying visually selected text to clipboard register
+ call setline(1, "text for clipboard test")
+ let @* = ''
+ set clipboard=autoselect
+ normal ggwwviwy
+ call assert_equal('clipboard', @*)
+
+ " Test for auto copying visually selected text to selection register
+ let @+ = ''
+ set clipboard=autoselectplus
+ normal ggwviwy
+ call assert_equal('for', @+)
+
+ set clipboard&vim
+ bwipe!
+endfunc
+
+" Test unnamed for both clipboard registers (* and +)
+func Test_clipboard_regs_both_unnamed()
+ CheckNotGui
+ CheckFeature clipboard_working
+ CheckTwoClipboards
+
+ let @* = 'xxx'
+ let @+ = 'xxx'
+
+ new
+
+ set clipboard=unnamed,unnamedplus
+ call setline(1, ['foo', 'bar'])
+
+ " op_yank copies to both
+ :1
+ :normal yw
+ call assert_equal('foo', getreg('*'))
+ call assert_equal('foo', getreg('+'))
+
+ " op_delete only copies to '+'
+ :2
+ :normal dw
+ call assert_equal('foo', getreg('*'))
+ call assert_equal('bar', getreg('+'))
+
+ set clipboard&vim
+ bwipe!
+endfunc
+
+" Test for restarting the current mode (insert or virtual replace) after
+" executing the contents of a register
+func Test_put_reg_restart_mode()
+ new
+ call append(0, 'editor')
+ normal gg
+ let @r = "ivim \<Esc>"
+ call feedkeys("i\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
+ call assert_equal('vimi editor', getline(1))
+
+ call setline(1, 'editor')
+ normal gg
+ call feedkeys("gR\<C-O>@r\<C-R>=mode()\<CR>", 'xt')
+ call assert_equal('vimReditor', getline(1))
+
+ bwipe!
+endfunc
+
+" Test for executing a register using :@ command
+func Test_execute_register()
+ call setreg('r', [])
+ call assert_beeps('@r')
+ let i = 1
+ let @q = 'let i+= 1'
+ @q
+ @
+ call assert_equal(3, i)
+
+ " try to execute expression register and use a backspace to cancel it
+ new
+ call feedkeys("@=\<BS>ax\<CR>y", 'xt')
+ call assert_equal(['x', 'y'], getline(1, '$'))
+ close!
+
+ " cannot execute a register in operator pending mode
+ call assert_beeps('normal! c@r')
+endfunc
+
+" Test for getting register info
+func Test_get_reginfo()
+ enew
+ call setline(1, ['foo', 'bar'])
+
+ exe 'norm! "zyy'
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+ call setreg('y', 'baz')
+ call assert_equal('z', getreginfo('').points_to)
+ call setreg('y', { 'isunnamed': v:true })
+ call assert_equal('y', getreginfo('"').points_to)
+
+ exe '$put'
+ call assert_equal(getreg('y'), getline(3))
+ call setreg('', 'qux')
+ call assert_equal('0', getreginfo('').points_to)
+ call setreg('x', 'quux')
+ call assert_equal('0', getreginfo('').points_to)
+
+ let info = getreginfo('')
+ call assert_equal(getreg('', 1, 1), info.regcontents)
+ call assert_equal(getregtype(''), info.regtype)
+
+ exe "norm! 0\<c-v>e" .. '"zy'
+ let info = getreginfo('z')
+ call assert_equal(getreg('z', 1, 1), info.regcontents)
+ call assert_equal(getregtype('z'), info.regtype)
+ call assert_equal(1, +info.isunnamed)
+
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+
+ let @a="a1b2"
+ nnoremap <F2> <Cmd>let g:RegInfo = getreginfo()<CR>
+ exe "normal \"a\<F2>"
+ call assert_equal({'regcontents': ['a1b2'], 'isunnamed': v:false,
+ \ 'regtype': 'v'}, g:RegInfo)
+ nunmap <F2>
+ unlet g:RegInfo
+
+ " The type of "isunnamed" was VAR_SPECIAL but should be VAR_BOOL. Can only
+ " be noticed when using json_encod().
+ call setreg('a', 'foo')
+ let reginfo = getreginfo('a')
+ let expected = #{regcontents: ['foo'], isunnamed: v:false, regtype: 'v'}
+ call assert_equal(json_encode(expected), json_encode(reginfo))
+
+ bwipe!
+endfunc
+
+" Test for restoring register with dict from getreginfo
+func Test_set_register_dict()
+ enew!
+
+ call setreg('"', #{ regcontents: ['one', 'two'],
+ \ regtype: 'V', points_to: 'z' })
+ call assert_equal(['one', 'two'], getreg('"', 1, 1))
+ let info = getreginfo('"')
+ call assert_equal('z', info.points_to)
+ call assert_equal('V', info.regtype)
+ call assert_equal(1, +getreginfo('z').isunnamed)
+
+ call setreg('x', #{ regcontents: ['three', 'four'],
+ \ regtype: 'v', isunnamed: v:true })
+ call assert_equal(['three', 'four'], getreg('"', 1, 1))
+ let info = getreginfo('"')
+ call assert_equal('x', info.points_to)
+ call assert_equal('v', info.regtype)
+ call assert_equal(1, +getreginfo('x').isunnamed)
+
+ call setreg('y', #{ regcontents: 'five',
+ \ regtype: "\<c-v>", isunnamed: v:false })
+ call assert_equal("\<c-v>4", getreginfo('y').regtype)
+ call assert_equal(0, +getreginfo('y').isunnamed)
+ call assert_equal(['three', 'four'], getreg('"', 1, 1))
+ call assert_equal('x', getreginfo('"').points_to)
+
+ call setreg('"', #{ regcontents: 'six' })
+ call assert_equal('0', getreginfo('"').points_to)
+ call assert_equal(1, +getreginfo('0').isunnamed)
+ call assert_equal(['six'], getreginfo('0').regcontents)
+ call assert_equal(['six'], getreginfo('"').regcontents)
+
+ let @x = 'one'
+ call setreg('x', {})
+ call assert_equal(1, len(split(execute('reg x'), '\n')))
+
+ call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:')
+ call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:')
+ call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:')
+
+ bwipe!
+endfunc
+
+func Test_v_register()
+ enew
+ call setline(1, 'nothing')
+
+ func s:Put()
+ let s:register = v:register
+ exec 'normal! "' .. v:register .. 'P'
+ endfunc
+ nnoremap <buffer> <plug>(test) :<c-u>call s:Put()<cr>
+ nmap <buffer> S <plug>(test)
+
+ let @z = "testz\n"
+ let @" = "test@\n"
+
+ let s:register = ''
+ call feedkeys('"_ddS', 'mx')
+ call assert_equal('test@', getline('.')) " fails before 8.2.0929
+ call assert_equal('"', s:register) " fails before 8.2.0929
+
+ let s:register = ''
+ call feedkeys('"zS', 'mx')
+ call assert_equal('z', s:register)
+
+ let s:register = ''
+ call feedkeys('"zSS', 'mx')
+ call assert_equal('"', s:register)
+
+ let s:register = ''
+ call feedkeys('"_S', 'mx')
+ call assert_equal('_', s:register)
+
+ let s:register = ''
+ normal "_ddS
+ call assert_equal('"', s:register) " fails before 8.2.0929
+ call assert_equal('test@', getline('.')) " fails before 8.2.0929
+
+ let s:register = ''
+ execute 'normal "z:call' "s:Put()\n"
+ call assert_equal('z', s:register)
+ call assert_equal('testz', getline('.'))
+
+ " Test operator and omap
+ let @b = 'testb'
+ func s:OpFunc(...)
+ let s:register2 = v:register
+ endfunc
+ set opfunc=s:OpFunc
+
+ normal "bg@l
+ normal S
+ call assert_equal('"', s:register) " fails before 8.2.0929
+ call assert_equal('b', s:register2)
+
+ func s:Motion()
+ let s:register1 = v:register
+ normal! l
+ endfunc
+ onoremap <buffer> Q :<c-u>call s:Motion()<cr>
+
+ normal "bg@Q
+ normal S
+ call assert_equal('"', s:register)
+ call assert_equal('b', s:register1)
+ call assert_equal('"', s:register2)
+
+ set opfunc&
+ bwipe!
+endfunc
+
+" Test for executing the contents of a register as an Ex command with line
+" continuation.
+func Test_execute_reg_as_ex_cmd()
+ " Line continuation with just two lines
+ let code =<< trim END
+ let l = [
+ \ 1]
+ END
+ let @r = code->join("\n")
+ let l = []
+ @r
+ call assert_equal([1], l)
+
+ " Line continuation with more than two lines
+ let code =<< trim END
+ let l = [
+ \ 1,
+ \ 2,
+ \ 3]
+ END
+ let @r = code->join("\n")
+ let l = []
+ @r
+ call assert_equal([1, 2, 3], l)
+
+ " use comments interspersed with code
+ let code =<< trim END
+ let l = [
+ "\ one
+ \ 1,
+ "\ two
+ \ 2,
+ "\ three
+ \ 3]
+ END
+ let @r = code->join("\n")
+ let l = []
+ @r
+ call assert_equal([1, 2, 3], l)
+
+ " use line continuation in the middle
+ let code =<< trim END
+ let a = "one"
+ let l = [
+ \ 1,
+ \ 2]
+ let b = "two"
+ END
+ let @r = code->join("\n")
+ let l = []
+ @r
+ call assert_equal([1, 2], l)
+ call assert_equal("one", a)
+ call assert_equal("two", b)
+
+ " only one line with a \
+ let @r = "\\let l = 1"
+ call assert_fails('@r', 'E10:')
+
+ " only one line with a "\
+ let @r = ' "\ let i = 1'
+ @r
+ call assert_false(exists('i'))
+
+ " first line also begins with a \
+ let @r = "\\let l = [\n\\ 1]"
+ call assert_fails('@r', 'E10:')
+
+ " Test with a large number of lines
+ let @r = "let str = \n"
+ let @r ..= repeat(" \\ 'abcdefghijklmnopqrstuvwxyz' ..\n", 312)
+ let @r ..= ' \ ""'
+ @r
+ call assert_equal(repeat('abcdefghijklmnopqrstuvwxyz', 312), str)
+endfunc
+
+" Test for clipboard registers with ASCII NUL
+func Test_clipboard_nul()
+ CheckFeature clipboard_working
+ new
+
+ " Test for putting ASCII NUL into the clipboard
+ set clipboard=unnamed
+ call append(0, "\ntest")
+ normal ggyyp
+ call assert_equal("^@test^@", strtrans(getreg('*')))
+ call assert_equal(getline(1), getline(2))
+ let b = split(execute(":reg *"), "\n")
+ call assert_match('"\*\s*\^@test\^J',b[1])
+
+ set clipboard&vim
+ bwipe!
+endfunc
+
+func Test_ve_blockpaste()
+ new
+ set ve=all
+ 0put =['QWERTZ','ASDFGH']
+ call cursor(1,1)
+ exe ":norm! \<C-V>3ljdP"
+ call assert_equal(1, col('.'))
+ call assert_equal(getline(1, 2), ['QWERTZ', 'ASDFGH'])
+ call cursor(1,1)
+ exe ":norm! \<C-V>3ljd"
+ call cursor(1,1)
+ norm! $3lP
+ call assert_equal(5, col('.'))
+ call assert_equal(getline(1, 2), ['TZ QWER', 'GH ASDF'])
+ set ve&vim
+ bwipe!
+endfunc
+
+func Test_insert_small_delete()
+ new
+ call setline(1, ['foo foobar bar'])
+ call cursor(1,1)
+ exe ":norm! ciw'\<C-R>-'"
+ call assert_equal("'foo' foobar bar", getline(1))
+ exe ":norm! w.w."
+ call assert_equal("'foo' 'foobar' 'bar'", getline(1))
+ bwipe!
+endfunc
+
+" Record in insert mode using CTRL-O
+func Test_record_in_insert_mode()
+ new
+ let @r = ''
+ call setline(1, ['foo'])
+ call feedkeys("i\<C-O>qrbaz\<C-O>q", 'xt')
+ call assert_equal('baz', @r)
+ bwipe!
+endfunc
+
+func Test_record_in_select_mode()
+ new
+ call setline(1, 'text')
+ sil norm q00
+ sil norm q
+ call assert_equal('0ext', getline(1))
+
+ %delete
+ let @r = ''
+ call setline(1, ['abc', 'abc', 'abc'])
+ smap <F2> <Right><Right>,
+ call feedkeys("qrgh\<F2>Dk\<Esc>q", 'xt')
+ call assert_equal("gh\<F2>Dk\<Esc>", @r)
+ norm j0@rj0@@
+ call assert_equal([',Dk', ',Dk', ',Dk'], getline(1, 3))
+ sunmap <F2>
+
+ bwipe!
+endfunc
+
+" mapping that ends macro recording should be removed from recorded macro
+func Test_end_record_using_mapping()
+ call setline(1, 'aaa')
+ nnoremap s q
+ call feedkeys('safas', 'tx')
+ call assert_equal('fa', @a)
+ nunmap s
+
+ nnoremap xx q
+ call feedkeys('0xxafaxx', 'tx')
+ call assert_equal('fa', @a)
+ nunmap xx
+
+ nnoremap xsx q
+ call feedkeys('0qafaxsx', 'tx')
+ call assert_equal('fa', @a)
+ nunmap xsx
+
+ bwipe!
+endfunc
+
+func Test_end_reg_executing()
+ nnoremap s <Nop>
+ let @a = 's'
+ call feedkeys("@aqaq\<Esc>", 'tx')
+ call assert_equal('', @a)
+ call assert_equal('', getline(1))
+
+ call setline(1, 'aaa')
+ nnoremap s qa
+ let @a = 'fa'
+ call feedkeys("@asq\<Esc>", 'tx')
+ call assert_equal('', @a)
+ call assert_equal('aaa', getline(1))
+
+ nunmap s
+ bwipe!
+endfunc
+
+" Make sure that y_append is correctly reset
+" and the previous register is working as expected
+func Test_register_y_append_reset()
+ new
+ call setline(1, ['1',
+ \ '2 ----------------------------------------------------',
+ \ '3',
+ \ '4',
+ \ '5 ----------------------------------------------------',
+ \ '6',
+ \ '7',
+ \ '8 ----------------------------------------------------',
+ \ '9',
+ \ '10 aaaaaaa 4.',
+ \ '11 Game Dbl-Figures Leaders:',
+ \ '12 Player Pts FG% 3P% FT% RB AS BL ST TO PF EFF',
+ \ '13 bbbbbbbbb 12 (50 /0 /67 )/ 7/ 3/ 0/ 2/ 3/ 4/+15',
+ \ '14 cccccc 12 (57 /67 /100)/ 2/ 1/ 1/ 0/ 1/ 3/+12',
+ \ '15 ddddddd 10 (63 /0 /0 )/ 1/ 3/ 0/ 3/ 5/ 3/ +9',
+ \ '16 4 5-15 0-3 2-2 5-12 1-1 3-4 33.3 0.0 100 41.7 100 75 12 14',
+ \ '17 F 23-55 2-10 9-11 23-52 3-13 26-29 41.8 20 81.8 44.2 23.1 89.7 57 75',
+ \ '18 4 3 6 3 2 3 3 4 3 3 7 3 1 4 6 -1 -1 +2 -1 -2',
+ \ '19 F 13 19 5 10 4 17 22 9 14 32 13 4 20 17 -1 -13 -4 -3 -3 +5'])
+ 11
+ exe "norm! \"a5dd"
+ norm! j
+ exe "norm! \"bY"
+ norm! 2j
+ exe "norm! \"BY"
+ norm! 4k
+ norm! 5dd
+ norm! 3k
+ " The next put should put the content of the unnamed register, not of
+ " register b!
+ norm! p
+ call assert_equal(['1',
+ \ '2 ----------------------------------------------------',
+ \ '3',
+ \ '4',
+ \ '5 ----------------------------------------------------',
+ \ '6',
+ \ '10 aaaaaaa 4.',
+ \ '16 4 5-15 0-3 2-2 5-12 1-1 3-4 33.3 0.0 100 41.7 100 75 12 14',
+ \ '17 F 23-55 2-10 9-11 23-52 3-13 26-29 41.8 20 81.8 44.2 23.1 89.7 57 75',
+ \ '18 4 3 6 3 2 3 3 4 3 3 7 3 1 4 6 -1 -1 +2 -1 -2',
+ \ '19 F 13 19 5 10 4 17 22 9 14 32 13 4 20 17 -1 -13 -4 -3 -3 +5',
+ \ '7',
+ \ '8 ----------------------------------------------------',
+ \ '9'], getline(1,'$'))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_reltime.vim b/src/testdir/test_reltime.vim
new file mode 100644
index 0000000..54104d5
--- /dev/null
+++ b/src/testdir/test_reltime.vim
@@ -0,0 +1,31 @@
+" Tests for reltime()
+
+source check.vim
+CheckFeature reltime
+
+func Test_reltime()
+ let g:test_is_flaky = 1
+ let now = reltime()
+ sleep 10m
+ let later = reltime()
+ let elapsed = now->reltime()
+ call assert_true(reltimestr(elapsed) =~ '0\.0')
+ call assert_true(elapsed->reltimestr() != '0.0')
+ call assert_true(reltimefloat(elapsed) < 0.1)
+ call assert_true(elapsed->reltimefloat() > 0.0)
+
+ let same = reltime(now, now)
+ call assert_equal('0.000', split(reltimestr(same))[0][:4])
+ call assert_equal(0.0, reltimefloat(same))
+
+ let differs = reltime(now, later)
+ call assert_true(reltimestr(differs) =~ '0\.0')
+ call assert_true(reltimestr(differs) != '0.0')
+ call assert_true(reltimefloat(differs) < 0.1)
+ call assert_true(reltimefloat(differs) > 0.0)
+
+ call assert_equal([], reltime({}))
+ call assert_equal([], reltime({}, {}))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_rename.vim b/src/testdir/test_rename.vim
new file mode 100644
index 0000000..26efe5c
--- /dev/null
+++ b/src/testdir/test_rename.vim
@@ -0,0 +1,113 @@
+" Test rename()
+
+source shared.vim
+
+func Test_rename_file_to_file()
+ call writefile(['foo'], 'Xrename1')
+
+ call assert_equal(0, rename('Xrename1', 'Xrename2'))
+
+ call assert_equal('', glob('Xrename1'))
+ call assert_equal(['foo'], readfile('Xrename2'))
+
+ " When the destination file already exists, it should be overwritten.
+ call writefile(['foo'], 'Xrename1')
+ call writefile(['bar'], 'Xrename2', 'D')
+
+ call assert_equal(0, rename('Xrename1', 'Xrename2'))
+ call assert_equal('', glob('Xrename1'))
+ call assert_equal(['foo'], readfile('Xrename2'))
+
+ call delete('Xrename2')
+endfunc
+
+func Test_rename_file_ignore_case()
+ " With 'fileignorecase', renaming file will go through a temp file
+ " when the source and destination file only differ by case.
+ set fileignorecase
+ call writefile(['foo'], 'Xrename')
+
+ call assert_equal(0, 'Xrename'->rename('XRENAME'))
+
+ call assert_equal(['foo'], readfile('XRENAME'))
+
+ set fileignorecase&
+ call delete('XRENAME')
+endfunc
+
+func Test_rename_same_file()
+ call writefile(['foo'], 'Xrename', 'D')
+
+ " When the source and destination are the same file, nothing
+ " should be done. The source file should not be deleted.
+ call assert_equal(0, rename('Xrename', 'Xrename'))
+ call assert_equal(['foo'], readfile('Xrename'))
+
+ call assert_equal(0, rename('./Xrename', 'Xrename'))
+ call assert_equal(['foo'], readfile('Xrename'))
+endfunc
+
+func Test_rename_dir_to_dir()
+ call mkdir('Xrenamedir1')
+ call writefile(['foo'], 'Xrenamedir1/Xrenamefile')
+
+ call assert_equal(0, rename('Xrenamedir1', 'Xrenamedir2'))
+
+ call assert_equal('', glob('Xrenamedir1'))
+ call assert_equal(['foo'], readfile('Xrenamedir2/Xrenamefile'))
+
+ call delete('Xrenamedir2/Xrenamefile')
+ call delete('Xrenamedir2', 'd')
+endfunc
+
+func Test_rename_same_dir()
+ call mkdir('Xrenamedir', 'R')
+ call writefile(['foo'], 'Xrenamedir/Xrenamefile')
+
+ call assert_equal(0, rename('Xrenamedir', 'Xrenamedir'))
+
+ call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
+endfunc
+
+func Test_rename_copy()
+ " Check that when original file can't be deleted, rename()
+ " still succeeds but copies the file.
+ call mkdir('Xrenamedir', 'R')
+ call writefile(['foo'], 'Xrenamedir/Xrenamefile')
+ call setfperm('Xrenamedir', 'r-xr-xr-x')
+
+ call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile'))
+
+ if !has('win32') && !IsRoot()
+ " On Windows, the source file is removed despite
+ " its directory being made not writable.
+ call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile'))
+ endif
+ call assert_equal(['foo'], readfile('Xrenamefile'))
+
+ call setfperm('Xrenamedir', 'rwxrwxrwx')
+ call delete('Xrenamefile')
+endfunc
+
+func Test_rename_fails()
+ call writefile(['foo'], 'Xrenamefile', 'D')
+
+ " Can't rename into a non-existing directory.
+ call assert_notequal(0, rename('Xrenamefile', 'Xdoesnotexist/Xrenamefile'))
+
+ " Can't rename a non-existing file.
+ call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile2'))
+ call assert_equal('', glob('Xrenamefile2'))
+
+ " When rename() fails, the destination file should not be deleted.
+ call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile'))
+ call assert_equal(['foo'], readfile('Xrenamefile'))
+
+ " Can't rename to en empty file name.
+ call assert_notequal(0, rename('Xrenamefile', ''))
+
+ call assert_fails('call rename("Xrenamefile", [])', 'E730:')
+ call assert_fails('call rename(0z, "Xrenamefile")', 'E976:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_restricted.vim b/src/testdir/test_restricted.vim
new file mode 100644
index 0000000..d01406e
--- /dev/null
+++ b/src/testdir/test_restricted.vim
@@ -0,0 +1,120 @@
+" Test for "rvim" or "vim -Z"
+
+source shared.vim
+
+"if has('win32') && has('gui')
+" " Win32 GUI shows a dialog instead of displaying the error in the last line.
+" finish
+"endif
+
+func Test_restricted_mode()
+ let lines =<< trim END
+ if has('lua')
+ call assert_fails('lua print("Hello, Vim!")', 'E981:')
+ call assert_fails('luado return "hello"', 'E981:')
+ call assert_fails('luafile somefile', 'E981:')
+ call assert_fails('call luaeval("expression")', 'E145:')
+ endif
+
+ if has('mzscheme')
+ call assert_fails('mzscheme statement', 'E981:')
+ call assert_fails('mzfile somefile', 'E981:')
+ call assert_fails('call mzeval("expression")', 'E145:')
+ endif
+
+ if has('perl')
+ " TODO: how to make Safe mode fail?
+ " call assert_fails('perl system("ls")', 'E981:')
+ " call assert_fails('perldo system("hello")', 'E981:')
+ " call assert_fails('perlfile somefile', 'E981:')
+ " call assert_fails('call perleval("system(\"ls\")")', 'E145:')
+ endif
+
+ if has('python')
+ call assert_fails('python print "hello"', 'E981:')
+ call assert_fails('pydo return "hello"', 'E981:')
+ call assert_fails('pyfile somefile', 'E981:')
+ call assert_fails('call pyeval("expression")', 'E145:')
+ endif
+
+ if has('python3')
+ call assert_fails('py3 print "hello"', 'E981:')
+ call assert_fails('py3do return "hello"', 'E981:')
+ call assert_fails('py3file somefile', 'E981:')
+ call assert_fails('call py3eval("expression")', 'E145:')
+ endif
+
+ if has('ruby')
+ call assert_fails('ruby print "Hello"', 'E981:')
+ call assert_fails('rubydo print "Hello"', 'E981:')
+ call assert_fails('rubyfile somefile', 'E981:')
+ endif
+
+ if has('tcl')
+ call assert_fails('tcl puts "Hello"', 'E981:')
+ call assert_fails('tcldo puts "Hello"', 'E981:')
+ call assert_fails('tclfile somefile', 'E981:')
+ endif
+
+ if has('clientserver')
+ call assert_fails('let s=remote_peek(10)', 'E145:')
+ call assert_fails('let s=remote_read(10)', 'E145:')
+ call assert_fails('let s=remote_send("vim", "abc")', 'E145:')
+ call assert_fails('let s=server2client(10, "abc")', 'E145:')
+ endif
+
+ if has('terminal')
+ call assert_fails('terminal', 'E145:')
+ call assert_fails('call term_start("vim")', 'E145:')
+ call assert_fails('call term_dumpwrite(1, "Xfile")', 'E145:')
+ endif
+
+ if has('channel')
+ call assert_fails("call ch_logfile('Xlog')", 'E145:')
+ call assert_fails("call ch_open('localhost:8765')", 'E145:')
+ endif
+
+ if has('job')
+ call assert_fails("call job_start('vim')", 'E145:')
+ endif
+
+ if has('unix') && has('libcall')
+ call assert_fails("echo libcall('libc.so', 'getenv', 'HOME')", 'E145:')
+ endif
+ call assert_fails("call rename('a', 'b')", 'E145:')
+ call assert_fails("call delete('Xfile')", 'E145:')
+ call assert_fails("call mkdir('Xdir')", 'E145:')
+ call assert_fails('!ls', 'E145:')
+ call assert_fails('shell', 'E145:')
+ call assert_fails('stop', 'E145:')
+ call assert_fails('exe "normal \<C-Z>"', 'E145:')
+ set insertmode
+ call assert_fails('call feedkeys("\<C-Z>", "xt")', 'E145:')
+ set insertmode&
+ call assert_fails('suspend', 'E145:')
+ call assert_fails('call system("ls")', 'E145:')
+ call assert_fails('call systemlist("ls")', 'E145:')
+ if has('unix')
+ call assert_fails('cd `pwd`', 'E145:')
+ endif
+
+ call writefile(v:errors, 'Xresult')
+ qa!
+ END
+ call writefile(lines, 'Xrestricted', 'D')
+ if RunVim([], [], '-Z --clean -S Xrestricted')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+ if has('unix') && RunVimPiped([], [], '--clean -S Xrestricted', 'SHELL=/bin/false ')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+ if has('unix') && RunVimPiped([], [], '--clean -S Xrestricted', 'SHELL=/sbin/nologin')
+ call assert_equal([], readfile('Xresult'))
+ endif
+
+ call delete('Xresult')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_retab.vim b/src/testdir/test_retab.vim
new file mode 100644
index 0000000..a4f9505
--- /dev/null
+++ b/src/testdir/test_retab.vim
@@ -0,0 +1,117 @@
+" Test :retab
+
+source check.vim
+
+func SetUp()
+ new
+ call setline(1, "\ta \t b c ")
+endfunc
+
+func TearDown()
+ bwipe!
+endfunc
+
+func Retab(bang, n)
+ let l:old_tabstop = &tabstop
+ let l:old_line = getline(1)
+ exe "retab" . a:bang . a:n
+ let l:line = getline(1)
+ call setline(1, l:old_line)
+ if a:n > 0
+ " :retab changes 'tabstop' to n with argument n > 0.
+ call assert_equal(a:n, &tabstop)
+ exe 'set tabstop=' . l:old_tabstop
+ else
+ " :retab does not change 'tabstop' with empty or n <= 0.
+ call assert_equal(l:old_tabstop, &tabstop)
+ endif
+ return l:line
+endfunc
+
+func Test_retab()
+ set tabstop=8 noexpandtab
+ call assert_equal("\ta\t b c ", Retab('', ''))
+ call assert_equal("\ta\t b c ", Retab('', 0))
+ call assert_equal("\ta\t b c ", Retab('', 8))
+ call assert_equal("\ta\t b\t c\t ", Retab('!', ''))
+ call assert_equal("\ta\t b\t c\t ", Retab('!', 0))
+ call assert_equal("\ta\t b\t c\t ", Retab('!', 8))
+
+ call assert_equal("\t\ta\t\t\tb c ", Retab('', 4))
+ call assert_equal("\t\ta\t\t\tb\t\t c\t ", Retab('!', 4))
+
+ call assert_equal(" a\t\tb c ", Retab('', 10))
+ call assert_equal(" a\t\tb c ", Retab('!', 10))
+
+ set tabstop=8 expandtab
+ call assert_equal(" a b c ", Retab('', ''))
+ call assert_equal(" a b c ", Retab('', 0))
+ call assert_equal(" a b c ", Retab('', 8))
+ call assert_equal(" a b c ", Retab('!', ''))
+ call assert_equal(" a b c ", Retab('!', 0))
+ call assert_equal(" a b c ", Retab('!', 8))
+
+ call assert_equal(" a b c ", Retab(' ', 4))
+ call assert_equal(" a b c ", Retab('!', 4))
+
+ call assert_equal(" a b c ", Retab(' ', 10))
+ call assert_equal(" a b c ", Retab('!', 10))
+
+ set tabstop=4 noexpandtab
+ call assert_equal("\ta\t\tb c ", Retab('', ''))
+ call assert_equal("\ta\t\tb\t\t c\t ", Retab('!', ''))
+ call assert_equal("\t a\t\t\tb c ", Retab('', 3))
+ call assert_equal("\t a\t\t\tb\t\t\tc\t ", Retab('!', 3))
+ call assert_equal(" a\t b c ", Retab('', 5))
+ call assert_equal(" a\t b\t\t c\t ", Retab('!', 5))
+
+ set tabstop=4 expandtab
+ call assert_equal(" a b c ", Retab('', ''))
+ call assert_equal(" a b c ", Retab('!', ''))
+ call assert_equal(" a b c ", Retab('', 3))
+ call assert_equal(" a b c ", Retab('!', 3))
+ call assert_equal(" a b c ", Retab('', 5))
+ call assert_equal(" a b c ", Retab('!', 5))
+
+ set tabstop& expandtab&
+endfunc
+
+func Test_retab_error()
+ call assert_fails('retab -1', 'E487:')
+ call assert_fails('retab! -1', 'E487:')
+ call assert_fails('ret -1000', 'E487:')
+ call assert_fails('ret 10000', 'E475:')
+ call assert_fails('ret 80000000000000000000', 'E475:')
+endfunc
+
+func RetabLoop()
+ while 1
+ set ts=4000
+ retab 4
+ endwhile
+endfunc
+
+func Test_retab_endless()
+ " inside try/catch we can catch the error message
+ call setline(1, "\t0\t")
+ let caught = 'no'
+ try
+ call RetabLoop()
+ catch /E1240:/
+ let caught = v:exception
+ endtry
+ call assert_match('E1240:', caught)
+
+ set tabstop&
+endfunc
+
+func Test_nocatch_retab_endless()
+ " when not inside try/catch an interrupt is generated to get out of loops
+ call setline(1, "\t0\t")
+ call assert_fails('call RetabLoop()', ['E1240:', 'Interrupted'])
+
+ set tabstop&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_ruby.vim b/src/testdir/test_ruby.vim
new file mode 100644
index 0000000..224dffb
--- /dev/null
+++ b/src/testdir/test_ruby.vim
@@ -0,0 +1,437 @@
+" Tests for ruby interface
+
+source check.vim
+CheckFeature ruby
+
+func Test_ruby_change_buffer()
+ call setline(line('$'), ['1 line 1'])
+ ruby Vim.command("normal /^1\n")
+ ruby $curbuf.line = "1 changed line 1"
+ call assert_equal('1 changed line 1', getline('$'))
+endfunc
+
+func Test_rubydo()
+ " Check deleting lines does not trigger ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ rubydo Vim.command("%d_")
+ bwipe!
+
+ " Check switching to another buffer does not trigger ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ rubydo Vim.command("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ %bwipe!
+endfunc
+
+func Test_rubydo_dollar_underscore()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ 2,3rubydo $_ = '[' + $_ + ']'
+ call assert_equal(['one', '[two]', '[three]', 'four'], getline(1, '$'))
+ bwipe!
+
+ call assert_fails('rubydo $_ = 0', 'E265:')
+ call assert_fails('rubydo (')
+ bwipe!
+endfunc
+
+func Test_rubyfile()
+ " Check :rubyfile does not SEGV with Ruby level exception but just fails
+ let tempfile = tempname() . '.rb'
+ call writefile(['raise "vim!"'], tempfile)
+ call assert_fails('rubyfile ' . tempfile)
+ call delete(tempfile)
+endfunc
+
+func Test_ruby_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ rubydo $curwin.cursor = [1, 5]
+ call assert_equal([1, 6], [line('.'), col('.')])
+ call assert_equal([1, 5], rubyeval('$curwin.cursor'))
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 6], [line('.'), col('.')])
+ call assert_equal([2, 5], '$curwin.cursor'->rubyeval())
+
+ call assert_fails('ruby $curwin.cursor = [1]',
+ \ 'ArgumentError: array length must be 2')
+ bwipe!
+endfunc
+
+" Test buffer.count and buffer.length (number of lines in buffer)
+func Test_ruby_buffer_count()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ call assert_equal(3, rubyeval('$curbuf.count'))
+ call assert_equal(3, rubyeval('$curbuf.length'))
+ bwipe!
+endfunc
+
+" Test buffer.name (buffer name)
+func Test_ruby_buffer_name()
+ new Xfoo
+ call assert_equal(expand('%:p'), rubyeval('$curbuf.name'))
+ bwipe
+ call assert_equal(v:null, rubyeval('$curbuf.name'))
+endfunc
+
+" Test buffer.number (number of the buffer).
+func Test_ruby_buffer_number()
+ new
+ call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
+ new
+ call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
+
+ %bwipe
+endfunc
+
+" Test buffer.delete({n}) (delete line {n})
+func Test_ruby_buffer_delete()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ ruby $curbuf.delete(2)
+ call assert_equal(['one', 'three'], getline(1, '$'))
+
+ call assert_fails('ruby $curbuf.delete(0)', 'IndexError: line number 0 out of range')
+ call assert_fails('ruby $curbuf.delete(3)', 'IndexError: line number 3 out of range')
+
+ bwipe!
+endfunc
+
+" Test buffer.append({str}, str) (append line {str} after line {n})
+func Test_ruby_buffer_append()
+ new
+ ruby $curbuf.append(0, 'one')
+ ruby $curbuf.append(1, 'three')
+ ruby $curbuf.append(1, 'two')
+ ruby $curbuf.append(4, 'four')
+
+ call assert_equal(['one', 'two', 'three', '', 'four'], getline(1, '$'))
+
+ call assert_fails('ruby $curbuf.append(-1, "x")',
+ \ 'IndexError: line number -1 out of range')
+ call assert_fails('ruby $curbuf.append(6, "x")',
+ \ 'IndexError: line number 6 out of range')
+
+ bwipe!
+endfunc
+
+" Test buffer.line (get or set the current line)
+func Test_ruby_buffer_line()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ call assert_equal('two', rubyeval('$curbuf.line'))
+
+ ruby $curbuf.line = 'TWO'
+ call assert_equal(['one', 'TWO', 'three'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test buffer.line_number (get current line number)
+func Test_ruby_buffer_line_number()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ call assert_equal(2, rubyeval('$curbuf.line_number'))
+
+ bwipe!
+endfunc
+
+func Test_ruby_buffer_get()
+ new
+ call setline(1, ['one', 'two'])
+ call assert_equal('one', rubyeval('$curbuf[1]'))
+ call assert_equal('two', rubyeval('$curbuf[2]'))
+
+ call assert_fails('ruby $curbuf[0]',
+ \ 'IndexError: line number 0 out of range')
+ call assert_fails('ruby $curbuf[3]',
+ \ 'IndexError: line number 3 out of range')
+
+ bwipe!
+endfunc
+
+func Test_ruby_buffer_set()
+ new
+ call setline(1, ['one', 'two'])
+ ruby $curbuf[2] = 'TWO'
+ ruby $curbuf[1] = 'ONE'
+
+ call assert_fails('ruby $curbuf[0] = "ZERO"',
+ \ 'IndexError: line number 0 out of range')
+ call assert_fails('ruby $curbuf[3] = "THREE"',
+ \ 'IndexError: line number 3 out of range')
+ bwipe!
+endfunc
+
+" Test window.width (get or set window height).
+func Test_ruby_window_height()
+ new
+
+ " Test setting window height
+ ruby $curwin.height = 2
+ call assert_equal(2, winheight(0))
+
+ " Test getting window height
+ call assert_equal(2, rubyeval('$curwin.height'))
+
+ bwipe
+endfunc
+
+" Test window.width (get or set window width).
+func Test_ruby_window_width()
+ vnew
+
+ " Test setting window width
+ ruby $curwin.width = 2
+ call assert_equal(2, winwidth(0))
+
+ " Test getting window width
+ call assert_equal(2, rubyeval('$curwin.width'))
+
+ bwipe
+endfunc
+
+" Test window.buffer (get buffer object of a window object).
+func Test_ruby_window_buffer()
+ new Xfoo1
+ new Xfoo2
+ ruby $b2 = $curwin.buffer
+ ruby $w2 = $curwin
+ wincmd j
+ ruby $b1 = $curwin.buffer
+ ruby $w1 = $curwin
+
+ call assert_equal(rubyeval('$b1'), rubyeval('$w1.buffer'))
+ call assert_equal(rubyeval('$b2'), rubyeval('$w2.buffer'))
+ call assert_equal(bufnr('Xfoo1'), rubyeval('$w1.buffer.number'))
+ call assert_equal(bufnr('Xfoo2'), rubyeval('$w2.buffer.number'))
+
+ ruby $b1, $w1, $b2, $w2 = nil
+ %bwipe
+endfunc
+
+" Test Vim::Window.current (get current window object)
+func Test_ruby_Vim_window_current()
+ let cw = rubyeval('$curwin')
+ call assert_equal(cw, rubyeval('Vim::Window.current'))
+ call assert_match('^#<Vim::Window:0x\x\+>$', cw)
+endfunc
+
+" Test Vim::Window.count (number of windows)
+func Test_ruby_Vim_window_count()
+ new Xfoo1
+ new Xfoo2
+ split
+ call assert_equal(4, rubyeval('Vim::Window.count'))
+ %bwipe
+ call assert_equal(1, rubyeval('Vim::Window.count'))
+endfunc
+
+" Test Vim::Window[n] (get window object of window n)
+func Test_ruby_Vim_window_get()
+ new Xfoo1
+ new Xfoo2
+ call assert_match('Xfoo2$', rubyeval('Vim::Window[0].buffer.name'))
+ wincmd j
+ call assert_match('Xfoo1$', rubyeval('Vim::Window[1].buffer.name'))
+ wincmd j
+ call assert_equal(v:null, rubyeval('Vim::Window[2].buffer.name'))
+ %bwipe
+endfunc
+
+" Test Vim::Buffer.current (return the buffer object of current buffer)
+func Test_ruby_Vim_buffer_current()
+ let cb = rubyeval('$curbuf')
+ call assert_equal(cb, rubyeval('Vim::Buffer.current'))
+ call assert_match('^#<Vim::Buffer:0x\x\+>$', cb)
+endfunc
+
+" Test Vim::Buffer:.count (return the number of buffers)
+func Test_ruby_Vim_buffer_count()
+ new Xfoo1
+ new Xfoo2
+ call assert_equal(3, rubyeval('Vim::Buffer.count'))
+ %bwipe
+ call assert_equal(1, rubyeval('Vim::Buffer.count'))
+endfunc
+
+" Test Vim::buffer[n] (return the buffer object of buffer number n)
+func Test_ruby_Vim_buffer_get()
+ new Xfoo1
+ new Xfoo2
+
+ " Index of Vim::Buffer[n] goes from 0 to the number of buffers.
+ call assert_equal(v:null, rubyeval('Vim::Buffer[0].name'))
+ call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name'))
+ call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name'))
+ call assert_fails('ruby print Vim::Buffer[3].name',
+ \ "NoMethodError: undefined method `name' for nil:NilClass")
+ %bwipe
+endfunc
+
+" Test Vim::command({cmd}) (execute a Ex command))
+" Test Vim::command({cmd})
+func Test_ruby_Vim_command()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ ruby Vim::command('2,3d')
+ call assert_equal(['one', 'four'], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Test Vim::set_option (set a vim option)
+func Test_ruby_Vim_set_option()
+ call assert_equal(0, &number)
+ ruby Vim::set_option('number')
+ call assert_equal(1, &number)
+ ruby Vim::set_option('nonumber')
+ call assert_equal(0, &number)
+endfunc
+
+func Test_ruby_Vim_evaluate()
+ call assert_equal(123, rubyeval('Vim::evaluate("123")'))
+ " Vim::evaluate("123").class gives Integer or Fixnum depending
+ " on versions of Ruby.
+ call assert_match('^Integer\|Fixnum$', rubyeval('Vim::evaluate("123").class'))
+
+ call assert_equal(1.23, rubyeval('Vim::evaluate("1.23")'))
+ call assert_equal('Float', rubyeval('Vim::evaluate("1.23").class'))
+
+ call assert_equal('foo', rubyeval('Vim::evaluate("\"foo\"")'))
+ call assert_equal('String', rubyeval('Vim::evaluate("\"foo\"").class'))
+
+ call assert_equal(["\x01\xAB"], rubyeval('Vim::evaluate("0z01ab").unpack("M")'))
+ call assert_equal('String', rubyeval('Vim::evaluate("0z01ab").class'))
+
+ call assert_equal([1, 2], rubyeval('Vim::evaluate("[1, 2]")'))
+ call assert_equal('Array', rubyeval('Vim::evaluate("[1, 2]").class'))
+
+ call assert_equal({'1': 2}, rubyeval('Vim::evaluate("{1:2}")'))
+ call assert_equal('Hash', rubyeval('Vim::evaluate("{1:2}").class'))
+
+ call assert_equal(v:null, rubyeval('Vim::evaluate("v:null")'))
+ call assert_equal('NilClass', rubyeval('Vim::evaluate("v:null").class'))
+
+ call assert_equal(v:null, rubyeval('Vim::evaluate("v:none")'))
+ call assert_equal('NilClass', rubyeval('Vim::evaluate("v:none").class'))
+
+ call assert_equal(v:true, rubyeval('Vim::evaluate("v:true")'))
+ call assert_equal('TrueClass', rubyeval('Vim::evaluate("v:true").class'))
+ call assert_equal(v:false, rubyeval('Vim::evaluate("v:false")'))
+ call assert_equal('FalseClass',rubyeval('Vim::evaluate("v:false").class'))
+endfunc
+
+func Test_ruby_Vim_blob()
+ call assert_equal('0z', rubyeval('Vim::blob("")'))
+ call assert_equal('0z31326162', rubyeval('Vim::blob("12ab")'))
+ call assert_equal('0z00010203', rubyeval('Vim::blob("\x00\x01\x02\x03")'))
+ call assert_equal('0z8081FEFF', rubyeval('Vim::blob("\x80\x81\xfe\xff")'))
+endfunc
+
+func Test_ruby_Vim_evaluate_list()
+ call setline(line('$'), ['2 line 2'])
+ ruby Vim.command("normal /^2\n")
+ let l = ["abc", "def"]
+ ruby << trim EOF
+ curline = $curbuf.line_number
+ l = Vim.evaluate("l");
+ $curbuf.append(curline, l.join("\n"))
+ EOF
+ normal j
+ .rubydo $_ = $_.gsub(/\n/, '/')
+ call assert_equal('abc/def', getline('$'))
+endfunc
+
+func Test_ruby_Vim_evaluate_dict()
+ let d = {'a': 'foo', 'b': 123}
+ redir => l:out
+ ruby d = Vim.evaluate("d"); print d
+ redir END
+ call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n"))
+endfunc
+
+" Test Vim::message({msg}) (display message {msg})
+func Test_ruby_Vim_message()
+ ruby Vim::message('A message')
+ let messages = split(execute('message'), "\n")
+ call assert_equal('A message', messages[-1])
+endfunc
+
+func Test_ruby_print()
+ func RubyPrint(expr)
+ return trim(execute('ruby print ' . a:expr))
+ endfunc
+
+ call assert_equal('123', RubyPrint('123'))
+ call assert_equal('1.23', RubyPrint('1.23'))
+ call assert_equal('Hello World!', RubyPrint('"Hello World!"'))
+ call assert_equal('[1, 2]', RubyPrint('[1, 2]'))
+ call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})'))
+ call assert_equal('true', RubyPrint('true'))
+ call assert_equal('false', RubyPrint('false'))
+ call assert_equal('', RubyPrint('nil'))
+ call assert_match('Vim', RubyPrint('Vim'))
+ call assert_match('Module', RubyPrint('Vim.class'))
+
+ delfunc RubyPrint
+endfunc
+
+func Test_ruby_p()
+ ruby p 'Just a test'
+ let messages = GetMessages()
+ call assert_equal('"Just a test"', messages[-1])
+
+ " Check return values of p method
+
+ call assert_equal(123, rubyeval('p(123)'))
+ call assert_equal([1, 2, 3], rubyeval('p(1, 2, 3)'))
+
+ " Avoid the "message maintainer" line.
+ let $LANG = ''
+ messages clear
+ call assert_equal(v:true, rubyeval('p() == nil'))
+
+ let messages = GetMessages()
+ call assert_equal(0, len(messages))
+endfunc
+
+func Test_rubyeval_error()
+ " On Linux or Windows the error matches:
+ " "syntax error, unexpected end-of-input"
+ " whereas on macOS in CI, the error message makes less sense:
+ " "SyntaxError: array length must be 2"
+ " Unclear why. The test does not check the error message.
+ call assert_fails('call rubyeval("(")')
+endfunc
+
+" Test for various heredoc syntax
+func Test_ruby_heredoc()
+ ruby << END
+Vim.command('let s = "A"')
+END
+ ruby <<
+Vim.command('let s ..= "B"')
+.
+ ruby << trim END
+ Vim.command('let s ..= "C"')
+ END
+ ruby << trim
+ Vim.command('let s ..= "D"')
+ .
+ ruby << trim eof
+ Vim.command('let s ..= "E"')
+ eof
+ call assert_equal('ABCDE', s)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_scriptnames.vim b/src/testdir/test_scriptnames.vim
new file mode 100644
index 0000000..3f81349
--- /dev/null
+++ b/src/testdir/test_scriptnames.vim
@@ -0,0 +1,96 @@
+
+" Test for the :scriptnames command
+func Test_scriptnames()
+ call writefile(['let did_load_script = 123'], 'Xscripting', 'D')
+ source Xscripting
+ call assert_equal(123, g:did_load_script)
+
+ let scripts = split(execute('scriptnames'), "\n")
+ let last = scripts[-1]
+ call assert_match('\<Xscripting\>', last)
+ let lastnr = substitute(last, '\D*\(\d\+\):.*', '\1', '')
+ exe 'script ' . lastnr
+ call assert_equal('Xscripting', expand('%:t'))
+
+ call assert_fails('script ' . (lastnr + 1), 'E474:')
+ call assert_fails('script 0', 'E939:')
+
+ new
+ call setline(1, 'nothing')
+ call assert_fails('script ' . lastnr, 'E37:')
+ exe 'script! ' . lastnr
+ call assert_equal('Xscripting', expand('%:t'))
+
+ bwipe
+
+ let msgs = execute('messages')
+ scriptnames
+ call assert_equal(msgs, execute('messages'))
+endfunc
+
+" Test for the getscriptinfo() function
+func Test_getscriptinfo()
+ let lines =<< trim END
+ scriptversion 3
+ let g:loaded_script_id = expand("<SID>")
+ let s:XscriptVar = [1, #{v: 2}]
+ func s:XgetScriptVar()
+ return s:XscriptVar
+ endfunc
+ func s:Xscript_legacy_func1()
+ endfunc
+ def s:Xscript_def_func1()
+ enddef
+ func Xscript_legacy_func2()
+ endfunc
+ def Xscript_def_func2()
+ enddef
+ END
+ call writefile(lines, 'X22script91', 'D')
+ source X22script91
+ let l = getscriptinfo()
+ call assert_match('X22script91$', l[-1].name)
+ call assert_equal(g:loaded_script_id, $"<SNR>{l[-1].sid}_")
+ call assert_equal(3, l[-1].version)
+ call assert_equal(0, has_key(l[-1], 'variables'))
+ call assert_equal(0, has_key(l[-1], 'functions'))
+
+ " Get script information using script name
+ let l = getscriptinfo(#{name: '22script91'})
+ call assert_equal(1, len(l))
+ call assert_match('22script91$', l[0].name)
+ let sid = l[0].sid
+
+ " Get script information using script-ID
+ let l = getscriptinfo({'sid': sid})
+ call assert_equal(#{XscriptVar: [1, {'v': 2}]}, l[0].variables)
+ let funcs = ['Xscript_legacy_func2',
+ \ $"<SNR>{sid}_Xscript_legacy_func1",
+ \ $"<SNR>{sid}_Xscript_def_func1",
+ \ 'Xscript_def_func2',
+ \ $"<SNR>{sid}_XgetScriptVar"]
+ for f in funcs
+ call assert_true(index(l[0].functions, f) != -1)
+ endfor
+
+ " Verify that a script-local variable cannot be modified using the dict
+ " returned by getscriptinfo()
+ let l[0].variables.XscriptVar = ['n']
+ let funcname = $"<SNR>{sid}_XgetScriptVar"
+ call assert_equal([1, {'v': 2}], call(funcname, []))
+
+ let l = getscriptinfo({'name': 'foobar'})
+ call assert_equal(0, len(l))
+ let l = getscriptinfo({'name': ''})
+ call assert_true(len(l) > 1)
+
+ call assert_fails("echo getscriptinfo({'name': []})", 'E730:')
+ call assert_fails("echo getscriptinfo({'name': '\\@'})", 'E866:')
+ let l = getscriptinfo({'name': test_null_string()})
+ call assert_true(len(l) > 1)
+ call assert_fails("echo getscriptinfo('foobar')", 'E1206:')
+
+ call assert_fails("echo getscriptinfo({'sid': []})", 'E745:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_scroll_opt.vim b/src/testdir/test_scroll_opt.vim
new file mode 100644
index 0000000..01b1daf
--- /dev/null
+++ b/src/testdir/test_scroll_opt.vim
@@ -0,0 +1,608 @@
+" Test for reset 'scroll' and 'smoothscroll'
+
+source check.vim
+source screendump.vim
+source mouse.vim
+
+func Test_reset_scroll()
+ let scr = &l:scroll
+
+ setlocal scroll=1
+ setlocal scroll&
+ call assert_equal(scr, &l:scroll)
+
+ setlocal scroll=1
+ setlocal scroll=0
+ call assert_equal(scr, &l:scroll)
+
+ try
+ execute 'setlocal scroll=' . (winheight(0) + 1)
+ " not reached
+ call assert_false(1)
+ catch
+ call assert_exception('E49:')
+ endtry
+
+ split
+
+ let scr = &l:scroll
+
+ setlocal scroll=1
+ setlocal scroll&
+ call assert_equal(scr, &l:scroll)
+
+ setlocal scroll=1
+ setlocal scroll=0
+ call assert_equal(scr, &l:scroll)
+
+ quit!
+endfunc
+
+func Test_scolloff_even_line_count()
+ new
+ resize 6
+ setlocal scrolloff=3
+ call setline(1, range(20))
+ normal 2j
+ call assert_equal(1, getwininfo(win_getid())[0].topline)
+ normal j
+ call assert_equal(1, getwininfo(win_getid())[0].topline)
+ normal j
+ call assert_equal(2, getwininfo(win_getid())[0].topline)
+ normal j
+ call assert_equal(3, getwininfo(win_getid())[0].topline)
+
+ bwipe!
+endfunc
+
+func Test_CtrlE_CtrlY_stop_at_end()
+ enew
+ call setline(1, ['one', 'two'])
+ set number
+ exe "normal \<C-Y>"
+ call assert_equal([" 1 one "], ScreenLines(1, 10))
+ exe "normal \<C-E>\<C-E>\<C-E>"
+ call assert_equal([" 2 two "], ScreenLines(1, 10))
+
+ bwipe!
+ set nonumber
+endfunc
+
+func Test_smoothscroll_CtrlE_CtrlY()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, [
+ 'line one',
+ 'word '->repeat(20),
+ 'line three',
+ 'long word '->repeat(7),
+ 'line',
+ 'line',
+ 'line',
+ ])
+ set smoothscroll
+ :5
+ END
+ call writefile(lines, 'XSmoothScroll', 'D')
+ let buf = RunVimInTerminal('-S XSmoothScroll', #{rows: 12, cols: 40})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_2', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_3', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_4', {})
+
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_5', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_6', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_7', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_8', {})
+
+ if has('folding')
+ call term_sendkeys(buf, ":set foldmethod=indent\<CR>")
+ " move the cursor so we can reuse the same dumps
+ call term_sendkeys(buf, "5G")
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_2', {})
+ call term_sendkeys(buf, "7G")
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_7', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_8', {})
+ endif
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_number()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, [
+ 'one ' .. 'word '->repeat(20),
+ 'two ' .. 'long word '->repeat(7),
+ 'line',
+ 'line',
+ 'line',
+ ])
+ set smoothscroll
+ set number cpo+=n
+ :3
+
+ def g:DoRel()
+ set number relativenumber scrolloff=0
+ :%del
+ setline(1, [
+ 'one',
+ 'very long text '->repeat(12),
+ 'three',
+ ])
+ exe "normal 2Gzt\<C-E>"
+ enddef
+ END
+ call writefile(lines, 'XSmoothNumber', 'D')
+ let buf = RunVimInTerminal('-S XSmoothNumber', #{rows: 12, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_smooth_number_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_2', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_3', {})
+
+ call term_sendkeys(buf, ":set cpo-=n\<CR>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_4', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_5', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_6', {})
+
+ call term_sendkeys(buf, ":call DoRel()\<CR>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_7', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_list()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ set smoothscroll scrolloff=0
+ set list
+ setline(1, [
+ 'one',
+ 'very long text '->repeat(12),
+ 'three',
+ ])
+ exe "normal 2Gzt\<C-E>"
+ END
+ call writefile(lines, 'XSmoothList', 'D')
+ let buf = RunVimInTerminal('-S XSmoothList', #{rows: 8, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_smooth_list_1', {})
+
+ call term_sendkeys(buf, ":set listchars+=precedes:#\<CR>")
+ call VerifyScreenDump(buf, 'Test_smooth_list_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_diff_mode()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ var text = 'just some text here'
+ setline(1, text)
+ set smoothscroll
+ diffthis
+ new
+ setline(1, text)
+ set smoothscroll
+ diffthis
+ END
+ call writefile(lines, 'XSmoothDiff', 'D')
+ let buf = RunVimInTerminal('-S XSmoothDiff', #{rows: 8})
+
+ call VerifyScreenDump(buf, 'Test_smooth_diff_1', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_diff_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_diff_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_wrap_scrolloff_zero()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7))
+ set smoothscroll scrolloff=0
+ :3
+ END
+ call writefile(lines, 'XSmoothWrap', 'D')
+ let buf = RunVimInTerminal('-S XSmoothWrap', #{rows: 8, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_1', {})
+
+ " moving cursor down - whole bottom line shows
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_2', {})
+
+ call term_sendkeys(buf, "\<C-E>j")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_3', {})
+
+ call term_sendkeys(buf, "G")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_4', {})
+
+ " moving cursor up right after the >>> marker - no need to show whole line
+ call term_sendkeys(buf, "2gj3l2k")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_5', {})
+
+ " moving cursor up where the >>> marker is - whole top line shows
+ call term_sendkeys(buf, "2j02k")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_6', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_wrap_long_line()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(30)) .. ' end', 'four'])
+ set smoothscroll scrolloff=0
+ normal 3G10|zt
+ END
+ call writefile(lines, 'XSmoothWrap', 'D')
+ let buf = RunVimInTerminal('-S XSmoothWrap', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_long_1', {})
+
+ " scrolling up, cursor moves screen line down
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_2', {})
+ call term_sendkeys(buf, "5\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_3', {})
+
+ " scrolling down, cursor moves screen line up
+ call term_sendkeys(buf, "5\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_4', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_5', {})
+
+ " 'scrolloff' set to 1, scrolling up, cursor moves screen line down
+ call term_sendkeys(buf, ":set scrolloff=1\<CR>")
+ call term_sendkeys(buf, "10|\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_6', {})
+
+ " 'scrolloff' set to 1, scrolling down, cursor moves screen line up
+ call term_sendkeys(buf, "\<C-E>")
+ call term_sendkeys(buf, "gjgj")
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_7', {})
+
+ " 'scrolloff' set to 2, scrolling up, cursor moves screen line down
+ call term_sendkeys(buf, ":set scrolloff=2\<CR>")
+ call term_sendkeys(buf, "10|\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_8', {})
+
+ " 'scrolloff' set to 2, scrolling down, cursor moves screen line up
+ call term_sendkeys(buf, "\<C-E>")
+ call term_sendkeys(buf, "gj")
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_9', {})
+
+ " 'scrolloff' set to 0, move cursor down one line.
+ " Cursor should move properly, and since this is a really long line, it will
+ " be put on top of the screen.
+ call term_sendkeys(buf, ":set scrolloff=0\<CR>")
+ call term_sendkeys(buf, "0j")
+ call VerifyScreenDump(buf, 'Test_smooth_long_10', {})
+
+ " Test zt/zz/zb that they work properly when a long line is above it
+ call term_sendkeys(buf, "zb")
+ call VerifyScreenDump(buf, 'Test_smooth_long_11', {})
+ call term_sendkeys(buf, "zz")
+ call VerifyScreenDump(buf, 'Test_smooth_long_12', {})
+ call term_sendkeys(buf, "zt")
+ call VerifyScreenDump(buf, 'Test_smooth_long_13', {})
+
+ " Repeat the step and move the cursor down again.
+ " This time, use a shorter long line that is barely long enough to span more
+ " than one window. Note that the cursor is at the bottom this time because
+ " Vim prefers to do so if we are scrolling a few lines only.
+ call term_sendkeys(buf, ":call setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(10)) .. ' end', 'four'])\<CR>")
+ call term_sendkeys(buf, "3Gzt")
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_smooth_long_14', {})
+
+ " Repeat the step but this time start it when the line is smooth-scrolled by
+ " one line. This tests that the offset calculation is still correct and
+ " still end up scrolling down to the next line with cursor at bottom of
+ " screen.
+ call term_sendkeys(buf, "3Gzt")
+ call term_sendkeys(buf, "\<C-E>j")
+ call VerifyScreenDump(buf, 'Test_smooth_long_15', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_one_long_line()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, 'with lots of text '->repeat(7))
+ set smoothscroll scrolloff=0
+ END
+ call writefile(lines, 'XSmoothOneLong', 'D')
+ let buf = RunVimInTerminal('-S XSmoothOneLong', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_one_long_1', {})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_one_long_2', {})
+
+ call term_sendkeys(buf, "0")
+ call VerifyScreenDump(buf, 'Test_smooth_one_long_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_long_line_showbreak()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ # a line that spans four screen lines
+ setline(1, 'with lots of text in one line '->repeat(6))
+ set smoothscroll scrolloff=0 showbreak=+++\
+ END
+ call writefile(lines, 'XSmoothLongShowbreak', 'D')
+ let buf = RunVimInTerminal('-S XSmoothLongShowbreak', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_1', {})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_2', {})
+
+ call term_sendkeys(buf, "0")
+ call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func s:check_col_calc(win_col, win_line, buf_col)
+ call assert_equal(a:win_col, wincol())
+ call assert_equal(a:win_line, winline())
+ call assert_equal(a:buf_col, col('.'))
+endfunc
+
+" Test that if the current cursor is on a smooth scrolled line, we correctly
+" reposition it. Also check that we don't miscalculate the values by checking
+" the consistency between wincol() and col('.') as they are calculated
+" separately in code.
+func Test_smoothscroll_cursor_position()
+ call NewWindow(10, 20)
+ setl smoothscroll wrap
+ call setline(1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ call s:check_col_calc(1, 1, 1)
+ exe "normal \<C-E>"
+
+ " Move down another line to avoid blocking the <<< display
+ call s:check_col_calc(1, 2, 41)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(1, 3, 41)
+
+ normal gg3l
+ exe "normal \<C-E>"
+
+ " Move down only 1 line when we are out of the range of the <<< display
+ call s:check_col_calc(4, 1, 24)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(4, 2, 24)
+ normal ggg$
+ exe "normal \<C-E>"
+ call s:check_col_calc(20, 1, 40)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(20, 2, 40)
+ normal gg
+
+ " Test number, where we have indented lines
+ setl number
+ call s:check_col_calc(5, 1, 1)
+ exe "normal \<C-E>"
+
+ " Move down only 1 line when the <<< display is on the number column
+ call s:check_col_calc(5, 1, 17)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(5, 2, 17)
+ normal ggg$
+ exe "normal \<C-E>"
+ call s:check_col_calc(20, 1, 32)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(20, 2, 32)
+ normal gg
+
+ setl numberwidth=1
+
+ " Move down another line when numberwidth is too short to cover the whole
+ " <<< display
+ call s:check_col_calc(3, 1, 1)
+ exe "normal \<C-E>"
+ call s:check_col_calc(3, 2, 37)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(3, 3, 37)
+ normal ggl
+
+ " Only move 1 line down when we are just past the <<< display
+ call s:check_col_calc(4, 1, 2)
+ exe "normal \<C-E>"
+ call s:check_col_calc(4, 1, 20)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(4, 2, 20)
+ normal gg
+ setl numberwidth&
+
+ " Test number + showbreak, so test that the additional indentation works
+ setl number showbreak=+++
+ call s:check_col_calc(5, 1, 1)
+ exe "normal \<C-E>"
+ call s:check_col_calc(8, 1, 17)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(8, 2, 17)
+ normal gg
+
+ " Test number + cpo+=n mode, where wrapped lines aren't indented
+ setl number cpo+=n showbreak=
+ call s:check_col_calc(5, 1, 1)
+ exe "normal \<C-E>"
+ call s:check_col_calc(1, 2, 37)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(1, 3, 37)
+ normal gg
+
+ bwipe!
+endfunc
+
+func Test_smoothscroll_cursor_scrolloff()
+ call NewWindow(10, 20)
+ setl smoothscroll wrap
+ setl scrolloff=3
+
+ " 120 chars are 6 screen lines
+ call setline(1, "abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST")
+ call setline(2, "below")
+
+ call s:check_col_calc(1, 1, 1)
+
+ " CTRL-E shows "<<<DEFG...", cursor move four lines down
+ exe "normal \<C-E>"
+ call s:check_col_calc(1, 4, 81)
+
+ " cursor on start of second line, "gk" moves into first line, skipcol doesn't
+ " change
+ exe "normal G0gk"
+ call s:check_col_calc(1, 5, 101)
+
+ " move cursor left one window width worth, scrolls one screen line
+ exe "normal 20h"
+ call s:check_col_calc(1, 5, 81)
+
+ " move cursor left one window width worth, scrolls one screen line
+ exe "normal 20h"
+ call s:check_col_calc(1, 4, 61)
+
+ " cursor on last line, "gk" should not cause a scroll
+ set scrolloff=0
+ normal G0
+ call s:check_col_calc(1, 7, 1)
+ normal gk
+ call s:check_col_calc(1, 6, 101)
+
+ bwipe!
+endfunc
+
+
+" Test that mouse picking is still accurate when we have smooth scrolled lines
+func Test_smoothscroll_mouse_pos()
+ CheckNotGui
+ CheckUnix
+
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ set mouse=a term=xterm ttymouse=xterm2
+
+ call NewWindow(10, 20)
+ setl smoothscroll wrap
+ " First line will wrap to 3 physical lines. 2nd/3rd lines are short lines.
+ call setline(1, ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "line 2", "line 3"])
+
+ func s:check_mouse_click(row, col, buf_row, buf_col)
+ call MouseLeftClick(a:row, a:col)
+
+ call assert_equal(a:col, wincol())
+ call assert_equal(a:row, winline())
+ call assert_equal(a:buf_row, line('.'))
+ call assert_equal(a:buf_col, col('.'))
+ endfunc
+
+ " Check that clicking without scroll works first.
+ call s:check_mouse_click(3, 5, 1, 45)
+ call s:check_mouse_click(4, 1, 2, 1)
+ call s:check_mouse_click(4, 6, 2, 6)
+ call s:check_mouse_click(5, 1, 3, 1)
+ call s:check_mouse_click(5, 6, 3, 6)
+
+ " Smooth scroll, and checks that this didn't mess up mouse clicking
+ exe "normal \<C-E>"
+ call s:check_mouse_click(2, 5, 1, 45)
+ call s:check_mouse_click(3, 1, 2, 1)
+ call s:check_mouse_click(3, 6, 2, 6)
+ call s:check_mouse_click(4, 1, 3, 1)
+ call s:check_mouse_click(4, 6, 3, 6)
+
+ exe "normal \<C-E>"
+ call s:check_mouse_click(1, 5, 1, 45)
+ call s:check_mouse_click(2, 1, 2, 1)
+ call s:check_mouse_click(2, 6, 2, 6)
+ call s:check_mouse_click(3, 1, 3, 1)
+ call s:check_mouse_click(3, 6, 3, 6)
+
+ " Make a new first line 11 physical lines tall so it's taller than window
+ " height, to test overflow calculations with really long lines wrapping.
+ normal gg
+ call setline(1, "12345678901234567890"->repeat(11))
+ exe "normal 6\<C-E>"
+ call s:check_mouse_click(5, 1, 1, 201)
+ call s:check_mouse_click(6, 1, 2, 1)
+ call s:check_mouse_click(7, 1, 3, 1)
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+endfunc
+
+" this was dividing by zero
+func Test_smoothscrol_zero_width()
+ CheckScreendump
+
+ let lines =<< trim END
+ winsize 0 0
+ vsplit
+ vsplit
+ vsplit
+ vsplit
+ vsplit
+ sil norm H
+ set wrap
+ set smoothscroll
+ set number
+ END
+ call writefile(lines, 'XSmoothScrollZero', 'D')
+ let buf = RunVimInTerminal('-u NONE -i NONE -n -m -X -Z -e -s -S XSmoothScrollZero', #{rows: 6, cols: 60, wait_for_ruler: 0})
+ call TermWait(buf, 3000)
+ call VerifyScreenDump(buf, 'Test_smoothscroll_zero_1', {})
+
+ call term_sendkeys(buf, ":sil norm \<C-V>\<C-W>\<C-V>\<C-N>\<CR>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_zero_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_scrollbind.vim b/src/testdir/test_scrollbind.vim
new file mode 100644
index 0000000..211b05e
--- /dev/null
+++ b/src/testdir/test_scrollbind.vim
@@ -0,0 +1,275 @@
+" Test for 'scrollbind' causing an unexpected scroll of one of the windows.
+
+func Test_scrollbind()
+ " We don't want the status line to cause problems:
+ set laststatus=0
+ let totalLines = &lines * 20
+ let middle = totalLines / 2
+ new | only
+ for i in range(1, totalLines)
+ call setline(i, 'LINE ' . i)
+ endfor
+ exe string(middle)
+ normal zt
+ normal M
+ aboveleft vert new
+ for i in range(1, totalLines)
+ call setline(i, 'line ' . i)
+ endfor
+ exe string(middle)
+ normal zt
+ normal M
+ " Execute the following two commands at once to reproduce the problem.
+ setl scb | wincmd p
+ setl scb
+ wincmd w
+ let topLineLeft = line('w0')
+ wincmd p
+ let topLineRight = line('w0')
+ setl noscrollbind
+ wincmd p
+ setl noscrollbind
+ call assert_equal(0, topLineLeft - topLineRight)
+endfunc
+
+" Test for 'scrollbind'
+func Test_scrollbind_opt()
+ new | only
+ set noscrollbind
+ set scrollopt=ver,jump scrolloff=2 nowrap noequalalways splitbelow
+
+ " Insert the text used for the test
+ append
+
+
+start of window 1
+. line 01 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 01
+. line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02
+. line 03 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 03
+. line 04 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 04
+. line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05
+. line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06
+. line 07 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 07
+. line 08 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 08
+. line 09 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 09
+. line 10 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 10
+. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11
+. line 12 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 12
+. line 13 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 13
+. line 14 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 14
+. line 15 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 15
+end of window 1
+
+
+start of window 2
+. line 01 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 01
+. line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02
+. line 03 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 03
+. line 04 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 04
+. line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05
+. line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06
+. line 07 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 07
+. line 08 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 08
+. line 09 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 09
+. line 10 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 10
+. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11
+. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12
+. line 13 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 13
+. line 14 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 14
+. line 15 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 15
+. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16
+end of window 2
+
+.
+
+ " Test using two windows open to one buffer, one extra empty window
+ split
+ new
+ wincmd t
+ resize 8
+ call search('^start of window 1$')
+ normal zt
+ set scrollbind
+ wincmd j
+ resize 7
+ call search('^start of window 2$')
+ normal zt
+ set scrollbind
+
+ " -- start of tests --
+ " Test scrolling down
+ normal L5jHyy
+ wincmd b | normal pr0
+ wincmd t | normal Hyy
+ wincmd b | normal pr1
+ wincmd t | normal L6jHyy
+ wincmd b | normal pr2
+ wincmd k | normal Hyy
+ wincmd b | normal pr3
+
+ " Test scrolling up
+ wincmd t | normal H4k
+ wincmd j | normal H
+ wincmd t | normal Hyy
+ wincmd b | normal pr4
+ wincmd k | normal Hyy
+ wincmd b | normal pr5
+ wincmd k | normal 3k
+ wincmd t | normal H
+ wincmd j | normal Hyy
+ wincmd b | normal pr6
+ wincmd t | normal Hyy
+ wincmd b | normal pr7
+
+ " Test horizontal scrolling
+ set scrollopt+=hor
+ normal gg"zyyG"zpG
+ wincmd t | normal 015zly$
+ wincmd b | normal p"zpG
+ wincmd k | normal y$
+ wincmd b | normal p"zpG
+ wincmd k | normal 10jH7zhg0y$
+ wincmd b | normal p"zpG
+ wincmd t | normal Hg0y$
+ wincmd b | normal p"zpG
+ set scrollopt-=hor
+
+ wincmd b
+ call assert_equal([
+ \ '',
+ \ '0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05',
+ \ '1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05',
+ \ '2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
+ \ '3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
+ \ '4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06',
+ \ '5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06',
+ \ '6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02',
+ \ '7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
+ \ '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
+ \ 'UTSRQPONMLKJIHGREDCBA9876543210 02',
+ \ '. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
+ \ '. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
+ \ ''], getline(1, '$'))
+ enew!
+
+ " ****** tests using two different buffers *****
+ wincmd t | wincmd j | close
+ wincmd t | set noscrollbind
+ /start of window 2$/,/^end of window 2$/y
+ new
+ wincmd t | wincmd j | normal 4"zpGp
+ wincmd t
+ call search('^start of window 1$')
+ normal zt
+ set scrollbind
+ wincmd j
+ call search('^start of window 2$')
+ normal zt
+ set scrollbind
+
+ " -- start of tests --
+ " Test scrolling down
+ normal L5jHyy
+ wincmd b | normal pr0
+ wincmd t | normal Hyy
+ wincmd b | normal pr1
+ wincmd t | normal L6jHyy
+ wincmd b | normal pr2
+ wincmd k | normal Hyy
+ wincmd b | normal pr3
+
+ " Test scrolling up
+ wincmd t | normal H4k
+ wincmd j | normal H
+ wincmd t | normal Hyy
+ wincmd b | normal pr4
+ wincmd k | normal Hyy
+ wincmd b | normal pr5
+ wincmd k | normal 3k
+ wincmd t | normal H
+ wincmd j | normal Hyy
+ wincmd b | normal pr6
+ wincmd t | normal Hyy
+ wincmd b | normal pr7
+
+ " Test horizontal scrolling
+ set scrollopt+=hor
+ normal gg"zyyG"zpG
+ wincmd t | normal 015zly$
+ wincmd b | normal p"zpG
+ wincmd k | normal y$
+ wincmd b | normal p"zpG
+ wincmd k | normal 10jH7zhg0y$
+ wincmd b | normal p"zpG
+ wincmd t | normal Hg0y$
+ wincmd b | normal p"zpG
+ set scrollopt-=hor
+
+ wincmd b
+ call assert_equal([
+ \ '',
+ \ '0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05',
+ \ '1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05',
+ \ '2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
+ \ '3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
+ \ '4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06',
+ \ '5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06',
+ \ '6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02',
+ \ '7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
+ \ '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
+ \ 'UTSRQPONMLKJIHGREDCBA9876543210 02',
+ \ '. line 10 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 10',
+ \ '. line 10 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 10',
+ \ ''], getline(1, '$'))
+ enew!
+
+ " Test 'syncbind'
+ wincmd t | set noscrollbind | normal ggL
+ wincmd j | set noscrollbind | normal ggL
+ set scrollbind
+ wincmd t | set scrollbind | normal G
+ wincmd j | normal G
+ syncbind
+ normal Hk
+ wincmd t | normal H
+ wincmd j | normal Hyy
+ wincmd b | normal p
+ wincmd t | normal yy
+ wincmd b | normal p
+ wincmd t | set noscrollbind | normal ggL
+ wincmd j | set noscrollbind
+ normal ggL
+ set scrollbind
+ wincmd t | set scrollbind
+ wincmd t | normal G
+ wincmd j | normal G
+ wincmd t | syncbind | normal Hk
+ wincmd j | normal H
+ wincmd t | normal Hyy
+ wincmd b | normal p
+ wincmd t | wincmd j | normal yy
+ wincmd b | normal p
+ wincmd t | normal H3k
+ wincmd j | normal H
+ wincmd t | normal Hyy
+ wincmd b | normal p
+ wincmd t | wincmd j | normal yy
+ wincmd b | normal p
+
+ wincmd b
+ call assert_equal([
+ \ '',
+ \ '. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16',
+ \ 'start of window 2',
+ \ 'start of window 2',
+ \ '. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16',
+ \ '. line 15 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 15',
+ \ '. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12',
+ \ ], getline(1, '$'))
+ enew!
+
+ new | only!
+ set scrollbind& scrollopt& scrolloff& wrap& equalalways& splitbelow&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_search.vim b/src/testdir/test_search.vim
new file mode 100644
index 0000000..ef44751
--- /dev/null
+++ b/src/testdir/test_search.vim
@@ -0,0 +1,2174 @@
+" Test for the search command
+
+source shared.vim
+source screendump.vim
+source check.vim
+
+func Test_search_cmdline()
+ CheckOption incsearch
+
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar'])
+ " Test 1
+ " CTRL-N / CTRL-P skips through the previous search history
+ set noincsearch
+ :1
+ call feedkeys("/foobar\<cr>", 'tx')
+ call feedkeys("/the\<cr>", 'tx')
+ call assert_equal('the', @/)
+ call feedkeys("/thes\<C-P>\<C-P>\<cr>", 'tx')
+ call assert_equal('foobar', @/)
+
+ " Test 2
+ " Ctrl-G goes from one match to the next
+ " until the end of the buffer
+ set incsearch nowrapscan
+ :1
+ " first match
+ call feedkeys("/the\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ :1
+ " second match
+ call feedkeys("/the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the', getline('.'))
+ call assert_equal([0, 0, 0, 0], getpos('"'))
+ :1
+ " third match
+ call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
+ call assert_equal(' 4 their', getline('.'))
+ :1
+ " fourth match
+ call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
+ call assert_equal(' 5 there', getline('.'))
+ :1
+ " fifth match
+ call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
+ call assert_equal(' 6 their', getline('.'))
+ :1
+ " sixth match
+ call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
+ call assert_equal(' 7 the', getline('.'))
+ :1
+ " seventh match
+ call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ :1
+ " eighth match
+ call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ :1
+ " no further match
+ call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ call assert_equal([0, 0, 0, 0], getpos('"'))
+
+ " Test 3
+ " Ctrl-G goes from one match to the next
+ " and continues back at the top
+ set incsearch wrapscan
+ :1
+ " first match
+ call feedkeys("/the\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ :1
+ " second match
+ call feedkeys("/the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the', getline('.'))
+ :1
+ " third match
+ call feedkeys("/the".repeat("\<C-G>", 2)."\<cr>", 'tx')
+ call assert_equal(' 4 their', getline('.'))
+ :1
+ " fourth match
+ call feedkeys("/the".repeat("\<C-G>", 3)."\<cr>", 'tx')
+ call assert_equal(' 5 there', getline('.'))
+ :1
+ " fifth match
+ call feedkeys("/the".repeat("\<C-G>", 4)."\<cr>", 'tx')
+ call assert_equal(' 6 their', getline('.'))
+ :1
+ " sixth match
+ call feedkeys("/the".repeat("\<C-G>", 5)."\<cr>", 'tx')
+ call assert_equal(' 7 the', getline('.'))
+ :1
+ " seventh match
+ call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ :1
+ " eighth match
+ call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ :1
+ " back at first match
+ call feedkeys("/the".repeat("\<C-G>", 8)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+
+ " Test 4
+ " CTRL-T goes to the previous match
+ set incsearch nowrapscan
+ $
+ " first match
+ call feedkeys("?the\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ $
+ " first match
+ call feedkeys("?the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ $
+ " second match
+ call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ $
+ " last match
+ call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ $
+ " last match
+ call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+
+ " Test 5
+ " CTRL-T goes to the previous match
+ set incsearch wrapscan
+ $
+ " first match
+ call feedkeys("?the\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ $
+ " first match at the top
+ call feedkeys("?the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ $
+ " second match
+ call feedkeys("?the".repeat("\<C-T>", 1)."\<cr>", 'tx')
+ call assert_equal(' 8 them', getline('.'))
+ $
+ " last match
+ call feedkeys("?the".repeat("\<C-T>", 7)."\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ $
+ " back at the bottom of the buffer
+ call feedkeys("?the".repeat("\<C-T>", 8)."\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+
+ " Test 6
+ " CTRL-L adds to the search pattern
+ set incsearch wrapscan
+ 1
+ " first match
+ call feedkeys("/the\<c-l>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " go to next match of 'thes'
+ call feedkeys("/the\<c-l>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ 1
+ " wrap around
+ call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " wrap around
+ set nowrapscan
+ call feedkeys("/the\<c-l>\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+
+ " Test 7
+ " <bs> remove from match, but stay at current match
+ set incsearch wrapscan
+ 1
+ " first match
+ call feedkeys("/thei\<cr>", 'tx')
+ call assert_equal(' 4 their', getline('.'))
+ 1
+ " delete one char, add another
+ call feedkeys("/thei\<bs>s\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " delete one char, add another, go to previous match, add one char
+ call feedkeys("/thei\<bs>s\<bs>\<C-T>\<c-l>\<cr>", 'tx')
+ call assert_equal(' 9 these', getline('.'))
+ 1
+ " delete all chars, start from the beginning again
+ call feedkeys("/them". repeat("\<bs>",4).'the\>'."\<cr>", 'tx')
+ call assert_equal(' 3 the', getline('.'))
+
+ " clean up
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_search_cmdline2()
+ CheckOption incsearch
+
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, [' 1', ' 2 these', ' 3 the theother'])
+ " Test 1
+ " Ctrl-T goes correctly back and forth
+ set incsearch
+ 1
+ " first match
+ call feedkeys("/the\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " go to next match (on next line)
+ call feedkeys("/the\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to next match (still on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to next match (still on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to previous match (on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to previous match (on line 3)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline('.'))
+ 1
+ " go to previous match (on line 2)
+ call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<C-T>\<cr>", 'tx')
+ call assert_equal(' 2 these', getline('.'))
+ 1
+ " go to previous match (on line 2)
+ call feedkeys("/the\<C-G>\<C-R>\<C-W>\<cr>", 'tx')
+ call assert_equal('theother', @/)
+
+ " Test 2: keep the view,
+ " after deleting a character from the search cmd
+ call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar'])
+ resize 5
+ 1
+ call feedkeys("/foo\<bs>\<cr>", 'tx')
+ redraw
+ call assert_equal({'lnum': 10, 'leftcol': 0, 'col': 4, 'topfill': 0, 'topline': 6, 'coladd': 0, 'skipcol': 0, 'curswant': 4}, winsaveview())
+
+ " remove all history entries
+ for i in range(11)
+ call histdel('/')
+ endfor
+
+ " Test 3: reset the view,
+ " after deleting all characters from the search cmd
+ norm! 1gg0
+ " unfortunately, neither "/foo\<c-w>\<cr>", nor "/foo\<bs>\<bs>\<bs>\<cr>",
+ " nor "/foo\<c-u>\<cr>" works to delete the commandline.
+ " In that case Vim should return "E35 no previous regular expression",
+ " but it looks like Vim still sees /foo and therefore the test fails.
+ " Therefore, disabling this test
+ "call assert_fails(feedkeys("/foo\<c-w>\<cr>", 'tx'), 'E35:')
+ "call assert_equal({'lnum': 1, 'leftcol': 0, 'col': 0, 'topfill': 0, 'topline': 1, 'coladd': 0, 'skipcol': 0, 'curswant': 0}, winsaveview())
+
+ " clean up
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_use_sub_pat()
+ split
+ let @/ = ''
+ func X()
+ s/^/a/
+ /
+ endfunc
+ call X()
+ bwipe!
+endfunc
+
+func Test_searchpair()
+ new
+ call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
+
+ " should not give an error for using "42"
+ call assert_equal(0, searchpair('a', 'b', 'c', '', 42))
+
+ 4
+ call assert_equal(3, searchpair('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ 4
+ call assert_equal(2, searchpair('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4
+ call assert_equal(1, searchpair('\[', '', ']', 'bWm'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ 4|norm ^
+ call assert_equal(5, searchpair('\[', '', ']', 'Wn'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+ 4
+ call assert_equal(2, searchpair('\[', '', ']', 'bW',
+ \ 'getline(".") =~ "^\\s*\["'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ set nomagic
+ 4
+ call assert_equal(3, searchpair('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ set magic
+ 4|norm ^
+ call assert_equal(0, searchpair('{', '', '}', 'bW'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+
+ %d
+ call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
+
+ /\<if 1
+ call assert_equal(5, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ /\<if 2
+ call assert_equal(3, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 3, 3, 0], getpos('.'))
+
+ bwipe!
+endfunc
+
+func Test_searchpair_timeout()
+ CheckFeature reltime
+ let g:test_is_flaky = 1
+
+ func Waitabit()
+ sleep 20m
+ return 1 " skip match
+ endfunc
+
+ new
+ call setline(1, range(100))
+ call setline(1, "(start here")
+ call setline(100, "end here)")
+ let starttime = reltime()
+
+ " A timeout of 100 msec should happen after about five times of 20 msec wait
+ " in Waitabit(). When the timeout applies to each search the elapsed time
+ " will be much longer.
+ call assert_equal(0, searchpair('(', '\d', ')', '', "Waitabit()", 0, 100))
+ let elapsed = reltime(starttime)->reltimefloat()
+ call assert_inrange(0.09, 0.300, elapsed)
+
+ delfunc Waitabit
+ bwipe!
+endfunc
+
+func SearchpairSkip()
+ let id = synID(line('.'), col('.'), 0)
+ let attr = synIDattr(id, 'name')
+ return attr !~ 'comment'
+endfunc
+
+func Test_searchpair_timeout_with_skip()
+ let g:test_is_flaky = 1
+
+ edit ../evalfunc.c
+ if has('win32')
+ " Windows timeouts are rather coarse grained, about 16ms.
+ let ms = 20
+ let min_time = 0.016
+ let max_time = min_time * 10.0
+ else
+ let ms = 1
+ let min_time = 0.001
+ let max_time = min_time * 15.0
+ if RunningWithValgrind()
+ let max_time += 0.04 " this can be slow with valgrind
+ endif
+ if has('bsd')
+ " test often fails with FreeBSD
+ let max_time = max_time * 2.0
+ endif
+ endif
+ let start = reltime()
+ let found = searchpair('(', '', ')', 'crnm', 'SearchpairSkip()', 0, ms)
+ let elapsed = reltimefloat(reltime(start))
+ call assert_inrange(min_time, max_time, elapsed)
+
+ bwipe!
+endfunc
+
+func Test_searchpairpos()
+ new
+ call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]'])
+
+ 4
+ call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ 4
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4|norm ^
+ call assert_equal([5, 2], searchpairpos('\[', '', ']', 'Wn'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+ 4
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bW',
+ \ 'getline(".") =~ "^\\s*\["'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ 4
+ call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ set nomagic
+ 4
+ call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW'))
+ call assert_equal([0, 3, 2, 0], getpos('.'))
+ set magic
+ 4|norm ^
+ call assert_equal([0, 0], searchpairpos('{', '', '}', 'bW'))
+ call assert_equal([0, 4, 2, 0], getpos('.'))
+
+ %d
+ call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1'])
+ /\<if 1
+ call assert_equal([5, 1], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 5, 1, 0], getpos('.'))
+ /\<if 2
+ call assert_equal([3, 3], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W'))
+ call assert_equal([0, 3, 3, 0], getpos('.'))
+
+ q!
+endfunc
+
+func Test_searchpair_errors()
+ call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String')
+ call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String')
+ call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
+endfunc
+
+func Test_searchpairpos_errors()
+ call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String')
+ call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String')
+ call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e')
+ call assert_fails("call searchpairpos('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn')
+endfunc
+
+func Test_searchpair_skip()
+ func Zero()
+ return 0
+ endfunc
+ func Partial(x)
+ return a:x
+ endfunc
+ new
+ call setline(1, ['{', 'foo', 'foo', 'foo', '}'])
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', ''))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '0'))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', {-> 0}))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Zero')))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Partial', [0])))
+ bw!
+endfunc
+
+func Test_searchpair_leak()
+ new
+ call setline(1, 'if one else another endif')
+
+ " The error in the skip expression caused memory to leak.
+ call assert_fails("call searchpair('\\<if\\>', '\\<else\\>', '\\<endif\\>', '', '\"foo\" 2')", 'E15:')
+
+ bwipe!
+endfunc
+
+func Test_searchc()
+ " These commands used to cause memory overflow in searchc().
+ new
+ norm ixx
+ exe "norm 0t\u93cf"
+ bw!
+endfunc
+
+func Cmdline3_prep()
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, [' 1', ' 2 the~e', ' 3 the theother'])
+ set incsearch
+endfunc
+
+func Incsearch_cleanup()
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_search_cmdline3()
+ CheckOption incsearch
+
+ call Cmdline3_prep()
+ 1
+ " first match
+ call feedkeys("/the\<c-l>\<cr>", 'tx')
+ call assert_equal(' 2 the~e', getline('.'))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3s()
+ CheckOption incsearch
+
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%subs/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%substitute/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%smagic/the.e/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxx', getline('.'))
+ undo
+ call assert_fails(":%snomagic/the.e/xxx\<cr>", 'E486:')
+ "
+ call feedkeys(":%snomagic/the\\.e/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxx', getline('.'))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3g()
+ CheckOption incsearch
+
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline(2))
+ undo
+ call feedkeys(":global/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline(2))
+ undo
+ call feedkeys(":g!/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+ undo
+ call feedkeys(":global!/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3v()
+ CheckOption incsearch
+
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+ undo
+ call feedkeys(":vglobal/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline4()
+ CheckOption incsearch
+
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, [' 1 the first', ' 2 the second', ' 3 the third'])
+ set incsearch
+ $
+ call feedkeys("?the\<c-g>\<cr>", 'tx')
+ call assert_equal(' 3 the third', getline('.'))
+ $
+ call feedkeys("?the\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal(' 1 the first', getline('.'))
+ $
+ call feedkeys("?the\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal(' 2 the second', getline('.'))
+ $
+ call feedkeys("?the\<c-t>\<cr>", 'tx')
+ call assert_equal(' 1 the first', getline('.'))
+ $
+ call feedkeys("?the\<c-t>\<c-t>\<cr>", 'tx')
+ call assert_equal(' 3 the third', getline('.'))
+ $
+ call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx')
+ call assert_equal(' 2 the second', getline('.'))
+ " clean up
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_search_cmdline5()
+ CheckOption incsearch
+
+ " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work
+ " regardless char_avail.
+ new
+ call setline(1, [' 1 the first', ' 2 the second', ' 3 the third', ''])
+ set incsearch
+ 1
+ call feedkeys("/the\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal(' 3 the third', getline('.'))
+ $
+ call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx')
+ call assert_equal(' 1 the first', getline('.'))
+ " clean up
+ set noincsearch
+ bw!
+endfunc
+
+func Test_search_cmdline6()
+ " Test that consecutive matches
+ " are caught by <c-g>/<c-t>
+ CheckOption incsearch
+
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, [' bbvimb', ''])
+ set incsearch
+ " first match
+ norm! gg0
+ call feedkeys("/b\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ " second match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,3,0], getpos('.'))
+ " third match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+ " first match again
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ set nowrapscan
+ " last match
+ norm! gg0
+ call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+ " clean up
+ set wrapscan&vim
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_search_cmdline7()
+ " Test that pressing <c-g> in an empty command line
+ " does not move the cursor
+ CheckOption incsearch
+
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ let @/ = 'b'
+ call setline(1, [' bbvimb', ''])
+ set incsearch
+ " first match
+ norm! gg0
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<cr>", 'tx')
+ call assert_equal([0,1,3,0], getpos('.'))
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<c-t>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+
+ " using an offset uses the last search pattern
+ call cursor(1, 1)
+ call setline(1, ['1 bbvimb', ' 2 bbvimb'])
+ let @/ = 'b'
+ call feedkeys("//e\<c-g>\<cr>", 'tx')
+ call assert_equal('1 bbvimb', getline('.'))
+ call assert_equal(4, col('.'))
+
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_search_cmdline8()
+ " Highlighting is cleared in all windows
+ " since hls applies to all windows
+ CheckOption incsearch
+ CheckFeature terminal
+ CheckNotGui
+ if has("win32")
+ throw "Skipped: Bug with sending <ESC> to terminal window not fixed yet"
+ endif
+
+ let h = winheight(0)
+ if h < 3
+ return
+ endif
+ " Prepare buffer text
+ let lines = ['abb vim vim vi', 'vimvivim']
+ call writefile(lines, 'Xsearch.txt', 'D')
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})
+
+ call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
+
+ call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
+ call term_sendkeys(buf, ":14vsp\<cr>")
+ call term_sendkeys(buf, "/vim\<cr>")
+ call term_sendkeys(buf, "/b\<esc>")
+ call term_sendkeys(buf, "gg0")
+ call TermWait(buf, 250)
+ let screen_line = term_scrape(buf, 1)
+ let [a0,a1,a2,a3] = [screen_line[3].attr, screen_line[4].attr,
+ \ screen_line[18].attr, screen_line[19].attr]
+ call assert_notequal(a0, a1)
+ call assert_notequal(a0, a3)
+ call assert_notequal(a1, a2)
+ call assert_equal(a0, a2)
+ call assert_equal(a1, a3)
+
+ " clean up
+ bwipe!
+endfunc
+
+" Tests for regexp with various magic settings
+func Run_search_regexp_magic_opt()
+ put ='1 a aa abb abbccc'
+ exe 'normal! /a*b\{2}c\+/e' . "\<CR>"
+ call assert_equal([0, 2, 17, 0], getpos('.'))
+
+ put ='2 d dd dee deefff'
+ exe 'normal! /\Md\*e\{2}f\+/e' . "\<CR>"
+ call assert_equal([0, 3, 17, 0], getpos('.'))
+
+ set nomagic
+ put ='3 g gg ghh ghhiii'
+ exe 'normal! /g\*h\{2}i\+/e' . "\<CR>"
+ call assert_equal([0, 4, 17, 0], getpos('.'))
+
+ put ='4 j jj jkk jkklll'
+ exe 'normal! /\mj*k\{2}l\+/e' . "\<CR>"
+ call assert_equal([0, 5, 17, 0], getpos('.'))
+
+ put ='5 m mm mnn mnnooo'
+ exe 'normal! /\vm*n{2}o+/e' . "\<CR>"
+ call assert_equal([0, 6, 17, 0], getpos('.'))
+
+ put ='6 x ^aa$ x'
+ exe 'normal! /\V^aa$' . "\<CR>"
+ call assert_equal([0, 7, 5, 0], getpos('.'))
+
+ set magic
+ put ='7 (a)(b) abbaa'
+ exe 'normal! /\v(a)(b)\2\1\1/e' . "\<CR>"
+ call assert_equal([0, 8, 14, 0], getpos('.'))
+
+ put ='8 axx [ab]xx'
+ exe 'normal! /\V[ab]\(\[xy]\)\1' . "\<CR>"
+ call assert_equal([0, 9, 7, 0], getpos('.'))
+
+ %d
+endfunc
+
+func Test_search_regexp()
+ enew!
+
+ set regexpengine=1
+ call Run_search_regexp_magic_opt()
+ set regexpengine=2
+ call Run_search_regexp_magic_opt()
+ set regexpengine&
+
+ set undolevels=100
+ put ='9 foobar'
+ put =''
+ exe "normal! a\<C-G>u\<Esc>"
+ normal G
+ exe 'normal! dv?bar?' . "\<CR>"
+ call assert_equal('9 foo', getline('.'))
+ call assert_equal([0, 2, 5, 0], getpos('.'))
+ call assert_equal(2, line('$'))
+ normal u
+ call assert_equal('9 foobar', getline('.'))
+ call assert_equal([0, 2, 6, 0], getpos('.'))
+ call assert_equal(3, line('$'))
+
+ set undolevels&
+ enew!
+endfunc
+
+func Test_search_cmdline_incsearch_highlight()
+ CheckOption incsearch
+
+ set incsearch hlsearch
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['aaa 1 the first', ' 2 the second', ' 3 the third'])
+
+ 1
+ call feedkeys("/second\<cr>", 'tx')
+ call assert_equal('second', @/)
+ call assert_equal(' 2 the second', getline('.'))
+
+ " Canceling search won't change @/
+ 1
+ let @/ = 'last pattern'
+ call feedkeys("/third\<C-c>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/third\<Esc>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/3\<bs>\<bs>", 'tx')
+ call assert_equal('last pattern', @/)
+ call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx')
+ call assert_equal('last pattern', @/)
+
+ " clean up
+ set noincsearch nohlsearch
+ bw!
+endfunc
+
+func Test_search_cmdline_incsearch_highlight_attr()
+ CheckOption incsearch
+ CheckFeature terminal
+ CheckNotGui
+
+ let h = winheight(0)
+ if h < 3
+ return
+ endif
+
+ " Prepare buffer text
+ let lines = ['abb vim vim vi', 'vimvivim']
+ call writefile(lines, 'Xsearch.txt', 'D')
+ let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3})
+
+ call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
+ " wait for vim to complete initialization
+ call TermWait(buf)
+
+ " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight
+ call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
+ call term_sendkeys(buf, '/b')
+ call TermWait(buf, 100)
+ let screen_line1 = term_scrape(buf, 1)
+ call assert_true(len(screen_line1) > 2)
+ " a0: attr_normal
+ let a0 = screen_line1[0].attr
+ " a1: attr_incsearch
+ let a1 = screen_line1[1].attr
+ " a2: attr_hlsearch
+ let a2 = screen_line1[2].attr
+ call assert_notequal(a0, a1)
+ call assert_notequal(a0, a2)
+ call assert_notequal(a1, a2)
+ call term_sendkeys(buf, "\<cr>gg0")
+
+ " Test incremental highlight search
+ call term_sendkeys(buf, "/vim")
+ call TermWait(buf, 100)
+ " Buffer:
+ " abb vim vim vi
+ " vimvivim
+ " Search: /vim
+ let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test <C-g>
+ call term_sendkeys(buf, "\<C-g>\<C-g>")
+ call TermWait(buf, 100)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test <C-t>
+ call term_sendkeys(buf, "\<C-t>")
+ call TermWait(buf, 100)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight)
+ call term_sendkeys(buf, "\<cr>")
+ call TermWait(buf, 100)
+ let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
+ let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight)
+ call term_sendkeys(buf, ":1\<cr>")
+ call term_sendkeys(buf, ":set nohlsearch\<cr>")
+ call term_sendkeys(buf, "/vim")
+ call TermWait(buf, 100)
+ let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0]
+ let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0]
+ call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
+ call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr'))
+
+ bwipe!
+endfunc
+
+func Test_incsearch_cmdline_modifier()
+ CheckOption incsearch
+
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['foo'])
+ set incsearch
+ " Test that error E14 does not occur in parsing command modifier.
+ call feedkeys("V:tab", 'tx')
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_incsearch_scrolling()
+ CheckRunVimInTerminal
+ call assert_equal(0, &scrolloff)
+ call writefile([
+ \ 'let dots = repeat(".", 120)',
+ \ 'set incsearch cmdheight=2 scrolloff=0',
+ \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])',
+ \ 'normal gg',
+ \ 'redraw',
+ \ ], 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70})
+ " Need to send one key at a time to force a redraw
+ call term_sendkeys(buf, '/')
+ sleep 100m
+ call term_sendkeys(buf, 't')
+ sleep 100m
+ call term_sendkeys(buf, 'a')
+ sleep 100m
+ call term_sendkeys(buf, 'r')
+ sleep 100m
+ call term_sendkeys(buf, 'g')
+ call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_incsearch_search_dump()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'for n in range(1, 8)',
+ \ ' call setline(n, "foo " . n)',
+ \ 'endfor',
+ \ '3',
+ \ ], 'Xis_search_script', 'D')
+ let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, '/fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+ sleep 100m
+
+ call term_sendkeys(buf, '/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_hlsearch_dump()
+ CheckOption hlsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set hlsearch cursorline',
+ \ 'call setline(1, ["xxx", "xxx", "xxx"])',
+ \ '/.*',
+ \ '2',
+ \ ], 'Xhlsearch_script', 'D')
+ let buf = RunVimInTerminal('-S Xhlsearch_script', {'rows': 6, 'cols': 50})
+ call VerifyScreenDump(buf, 'Test_hlsearch_1', {})
+
+ call term_sendkeys(buf, "/\\_.*\<CR>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_hlsearch_and_visual()
+ CheckOption hlsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set hlsearch',
+ \ 'call setline(1, repeat(["xxx yyy zzz"], 3))',
+ \ 'hi Search cterm=bold',
+ \ '/yyy',
+ \ 'call cursor(1, 6)',
+ \ ], 'Xhlvisual_script', 'D')
+ let buf = RunVimInTerminal('-S Xhlvisual_script', {'rows': 6, 'cols': 40})
+ call term_sendkeys(buf, "vjj")
+ call VerifyScreenDump(buf, 'Test_hlsearch_visual_1', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_hlsearch_block_visual_match()
+ CheckScreendump
+
+ let lines =<< trim END
+ set hlsearch
+ call setline(1, ['aa', 'bbbb', 'cccccc'])
+ END
+ call writefile(lines, 'Xhlsearch_block', 'D')
+ let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60})
+
+ call term_sendkeys(buf, "G\<C-V>$kk\<Esc>")
+ sleep 100m
+ call term_sendkeys(buf, "/\\%V\<CR>")
+ sleep 100m
+ call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_incsearch_substitute()
+ CheckOption incsearch
+
+ call test_override("char_avail", 1)
+ new
+ set incsearch
+ for n in range(1, 10)
+ call setline(n, 'foo ' . n)
+ endfor
+ 4
+ call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
+ call assert_equal('foo 3', getline(3))
+ call assert_equal('xxx 4', getline(4))
+ call assert_equal('xxx 5', getline(5))
+ call assert_equal('xxx 6', getline(6))
+ call assert_equal('foo 7', getline(7))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_incsearch_substitute_long_line()
+ new
+ call test_override("char_avail", 1)
+ set incsearch
+
+ call repeat('x', 100000)->setline(1)
+ call feedkeys(':s/\%c', 'xt')
+ redraw
+ call feedkeys("\<Esc>", 'xt')
+
+ call Incsearch_cleanup()
+ bwipe!
+endfunc
+
+func Test_hlsearch_cursearch()
+ CheckScreendump
+
+ let lines =<< trim END
+ set hlsearch scrolloff=0
+ call setline(1, ['one', 'foo', 'bar', 'baz', 'foo the foo and foo', 'bar'])
+ hi Search ctermbg=yellow
+ hi CurSearch ctermbg=blue
+ END
+ call writefile(lines, 'Xhlsearch_cursearch', 'D')
+ let buf = RunVimInTerminal('-S Xhlsearch_cursearch', {'rows': 9, 'cols': 60})
+
+ call term_sendkeys(buf, "gg/foo\<CR>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_1', {})
+
+ call term_sendkeys(buf, "n")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2', {})
+
+ call term_sendkeys(buf, "n")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2a', {})
+
+ call term_sendkeys(buf, "n")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_2b', {})
+
+ call term_sendkeys(buf, ":call setline(5, 'foo')\<CR>")
+ call term_sendkeys(buf, "0?\<CR>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_single_line_3', {})
+
+ call term_sendkeys(buf, "gg/foo\\nbar\<CR>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_1', {})
+
+ call term_sendkeys(buf, ":call setline(1, ['---', 'abcdefg', 'hijkl', '---', 'abcdefg', 'hijkl'])\<CR>")
+ call term_sendkeys(buf, "gg/efg\\nhij\<CR>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_2', {})
+ call term_sendkeys(buf, "h\<C-L>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_3', {})
+ call term_sendkeys(buf, "j\<C-L>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_4', {})
+ call term_sendkeys(buf, "h\<C-L>")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_5', {})
+
+ " check clearing CurSearch when using it for another match
+ call term_sendkeys(buf, "G?^abcd\<CR>Y")
+ call term_sendkeys(buf, "kkP")
+ call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_changed_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Similar to Test_incsearch_substitute() but with a screendump halfway.
+func Test_incsearch_substitute_dump()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'for n in range(1, 10)',
+ \ ' call setline(n, "foo " . n)',
+ \ 'endfor',
+ \ 'call setline(11, "bar 11")',
+ \ '3',
+ \ ], 'Xis_subst_script', 'D')
+ let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ " Select three lines at the cursor with typed pattern.
+ call term_sendkeys(buf, ':.,.+2s/')
+ sleep 100m
+ call term_sendkeys(buf, 'f')
+ sleep 100m
+ call term_sendkeys(buf, 'o')
+ sleep 100m
+ call term_sendkeys(buf, 'o')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Select three lines at the cursor using previous pattern.
+ call term_sendkeys(buf, "/foo\<CR>")
+ sleep 100m
+ call term_sendkeys(buf, ':.,.+2s//')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_02', {})
+
+ " Deleting last slash should remove the match.
+ call term_sendkeys(buf, "\<BS>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_03', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Reverse range is accepted
+ call term_sendkeys(buf, ':5,2s/foo')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_04', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " White space after the command is skipped
+ call term_sendkeys(buf, ':2,3sub /fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_05', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Command modifiers are skipped
+ call term_sendkeys(buf, ':above below browse botr confirm keepmar keepalt keeppat keepjum filter xxx hide lockm leftabove noau noswap rightbel sandbox silent silent! $tab top unsil vert verbose 4,5s/fo.')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_06', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Cursorline highlighting at match
+ call term_sendkeys(buf, ":set cursorline\<CR>")
+ call term_sendkeys(buf, 'G9G')
+ call term_sendkeys(buf, ':9,11s/bar')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_07', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Cursorline highlighting at cursor when no match
+ call term_sendkeys(buf, ':9,10s/bar')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_08', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Only \v handled as empty pattern, does not move cursor
+ call term_sendkeys(buf, '3G4G')
+ call term_sendkeys(buf, ":nohlsearch\<CR>")
+ call term_sendkeys(buf, ':6,7s/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_09', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ":set nocursorline\<CR>")
+
+ " All matches are highlighted for 'hlsearch' after the incsearch canceled
+ call term_sendkeys(buf, "1G*")
+ call term_sendkeys(buf, ":2,5s/foo")
+ sleep 100m
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_10', {})
+
+ call term_sendkeys(buf, ":split\<CR>")
+ call term_sendkeys(buf, ":let @/ = 'xyz'\<CR>")
+ call term_sendkeys(buf, ":%s/.")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_11', {})
+ call term_sendkeys(buf, "\<BS>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_12', {})
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_13', {})
+ call term_sendkeys(buf, ":%bwipe!\<CR>")
+ call term_sendkeys(buf, ":only!\<CR>")
+
+ " get :'<,'>s command in history
+ call term_sendkeys(buf, ":set cmdheight=2\<CR>")
+ call term_sendkeys(buf, "aasdfasdf\<Esc>")
+ call term_sendkeys(buf, "V:s/a/b/g\<CR>")
+ " Using '<,'> does not give E20
+ call term_sendkeys(buf, ":new\<CR>")
+ call term_sendkeys(buf, "aasdfasdf\<Esc>")
+ call term_sendkeys(buf, ":\<Up>\<Up>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_14', {})
+ call term_sendkeys(buf, "<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_incsearch_highlighting()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch',
+ \ 'call setline(1, "hello/there")',
+ \ ], 'Xis_subst_hl_script', 'D')
+ let buf = RunVimInTerminal('-S Xis_subst_hl_script', {'rows': 4, 'cols': 20})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 300m
+
+ " Using a different search delimiter should still highlight matches
+ " that contain a '/'.
+ call term_sendkeys(buf, ":%s;ello/the")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {})
+ call term_sendkeys(buf, "<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_incsearch_with_change()
+ CheckFeature timers
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["one", "two ------ X", "three"])',
+ \ 'call timer_start(200, { _ -> setline(2, "x")})',
+ \ ], 'Xis_change_script', 'D')
+ let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 300m
+
+ " Highlight X, it will be deleted by the timer callback.
+ call term_sendkeys(buf, ':%s/X')
+ call VerifyScreenDump(buf, 'Test_incsearch_change_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Similar to Test_incsearch_substitute_dump() for :sort
+func Test_incsearch_sort_dump()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
+ \ ], 'Xis_sort_script', 'D')
+ let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ call term_sendkeys(buf, ':sort ni u /on')
+ call VerifyScreenDump(buf, 'Test_incsearch_sort_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':sort! /on')
+ call VerifyScreenDump(buf, 'Test_incsearch_sort_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
+func Test_incsearch_vimgrep_dump()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
+ \ ], 'Xis_vimgrep_script', 'D')
+ let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, ':vimgrep on')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':vimg /on/ *.txt')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':vimgrepadd "\<on')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_03', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':lv "tha')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_04', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':lvimgrepa "the" **/*.txt')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_05', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_keep_last_search_pattern()
+ CheckOption incsearch
+
+ new
+ call setline(1, ['foo', 'foo', 'foo'])
+ set incsearch
+ call test_override("char_avail", 1)
+ let @/ = 'bar'
+ call feedkeys(":/foo/s//\<Esc>", 'ntx')
+ call assert_equal('bar', @/)
+
+ " no error message if pattern not found
+ call feedkeys(":/xyz/s//\<Esc>", 'ntx')
+ call assert_equal('bar', @/)
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+func Test_word_under_cursor_after_match()
+ CheckOption incsearch
+
+ new
+ call setline(1, 'foo bar')
+ set incsearch
+ call test_override("char_avail", 1)
+ try
+ call feedkeys("/foo\<C-R>\<C-W>\<CR>", 'ntx')
+ catch /E486:/
+ endtry
+ call assert_equal('foobar', @/)
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+func Test_subst_word_under_cursor()
+ CheckOption incsearch
+
+ new
+ call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
+ set incsearch
+ call test_override("char_avail", 1)
+ call feedkeys("/LongName\<CR>", 'ntx')
+ call feedkeys(":%s/xxx/\<C-R>\<C-W>/g\<CR>", 'ntx')
+ call assert_equal('for (SomeLongName = 1; SomeLongName < len; ++SomeLongName)', getline(2))
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+func Test_search_skip_all_matches()
+ enew
+ call setline(1, ['no match here',
+ \ 'match this line',
+ \ 'nope',
+ \ 'match in this line',
+ \ 'last line',
+ \ ])
+ call cursor(1, 1)
+ let lnum = search('this', '', 0, 0, 'getline(".") =~ "this line"')
+ " Only check that no match is found. Previously it searched forever.
+ call assert_equal(0, lnum)
+
+ bwipe!
+endfunc
+
+func Test_search_undefined_behaviour()
+ CheckFeature terminal
+
+ let h = winheight(0)
+ if h < 3
+ return
+ endif
+ " did cause an undefined left shift
+ let g:buf = term_start([GetVimProg(), '--clean', '-e', '-s', '-c', 'call search(getline("."))', 'samples/test000'], {'term_rows': 3})
+ call assert_equal([''], getline(1, '$'))
+ call term_sendkeys(g:buf, ":qa!\<cr>")
+ bwipe!
+endfunc
+
+func Test_search_undefined_behaviour2()
+ call search("\%UC0000000")
+endfunc
+
+" Test for search('multi-byte char', 'bce')
+func Test_search_multibyte()
+ let save_enc = &encoding
+ set encoding=utf8
+ enew!
+ call append('$', 'A')
+ call cursor(2, 1)
+ call assert_equal(2, search('A', 'bce', line('.')))
+ enew!
+ let &encoding = save_enc
+endfunc
+
+" This was causing E874. Also causes an invalid read?
+func Test_look_behind()
+ new
+ call setline(1, '0\|\&\n\@<=')
+ call search(getline("."))
+ bwipe!
+endfunc
+
+func Test_search_visual_area_linewise()
+ new
+ call setline(1, ['aa', 'bb', 'cc'])
+ exe "normal 2GV\<Esc>"
+ for engine in [1, 2]
+ exe 'set regexpengine=' .. engine
+ exe "normal gg/\\%'<\<CR>>"
+ call assert_equal([0, 2, 1, 0, 1], getcurpos(), 'engine ' .. engine)
+ exe "normal gg/\\%'>\<CR>"
+ call assert_equal([0, 2, 2, 0, 2], getcurpos(), 'engine ' .. engine)
+ endfor
+
+ bwipe!
+ set regexpengine&
+endfunc
+
+func Test_search_sentence()
+ new
+ " this used to cause a crash
+ /\%'(
+ /
+ bwipe
+endfunc
+
+" Test that there is no crash when there is a last search pattern but no last
+" substitute pattern.
+func Test_no_last_substitute_pat()
+ " Use viminfo to set the last search pattern to a string and make the last
+ " substitute pattern the most recent used and make it empty (NULL).
+ call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo', 'D')
+ rviminfo! Xviminfo
+ call assert_fails('normal n', 'E35:')
+endfunc
+
+func Test_search_Ctrl_L_combining()
+ " Make sure, that Ctrl-L works correctly with combining characters.
+ " It uses an artificial example of an 'a' with 4 combining chars:
+ " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
+ " ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT &#x300; /\%u300\Z "\u0300"
+ " ' Ì' U+0301 Dec:769 COMBINING ACUTE ACCENT &#x301; /\%u301\Z "\u0301"
+ " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
+ " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
+ " Those should also appear on the commandline
+ CheckOption incsearch
+
+ call Cmdline3_prep()
+ 1
+ let bufcontent = ['', 'MiaÌ€Ị̀̇m']
+ call append('$', bufcontent)
+ call feedkeys("/Mi\<c-l>\<c-l>\<cr>", 'tx')
+ call assert_equal(5, line('.'))
+ call assert_equal(bufcontent[1], @/)
+ call Incsearch_cleanup()
+endfunc
+
+func Test_large_hex_chars1()
+ " This used to cause a crash, the character becomes an NFA state.
+ try
+ /\%Ufffffc23
+ catch
+ call assert_match('E678:', v:exception)
+ endtry
+ try
+ set re=1
+ /\%Ufffffc23
+ catch
+ call assert_match('E678:', v:exception)
+ endtry
+ set re&
+endfunc
+
+func Test_large_hex_chars2()
+ " This used to cause a crash, the character becomes an NFA state.
+ try
+ /[\Ufffffc1f]
+ catch
+ call assert_match('E486:', v:exception)
+ endtry
+ try
+ set re=1
+ /[\Ufffffc1f]
+ catch
+ call assert_match('E486:', v:exception)
+ endtry
+ set re&
+endfunc
+
+func Test_one_error_msg()
+ " This was also giving an internal error
+ call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:')
+endfunc
+
+func Test_incsearch_add_char_under_cursor()
+ CheckOption incsearch
+
+ set incsearch
+ new
+ call setline(1, ['find match', 'anything'])
+ 1
+ call test_override('char_avail', 1)
+ call feedkeys("fc/m\<C-L>\<C-L>\<C-L>\<C-L>\<C-L>\<CR>", 'tx')
+ call assert_equal('match', @/)
+ call test_override('char_avail', 0)
+
+ set incsearch&
+ bwipe!
+endfunc
+
+" Test for the search() function with match at the cursor position
+func Test_search_match_at_curpos()
+ new
+ call append(0, ['foobar', '', 'one two', ''])
+
+ normal gg
+
+ eval 'foobar'->search('c')
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ normal j
+ call search('^$', 'c')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ call search('^$', 'bc')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ exe "normal /two\<CR>"
+ call search('.', 'c')
+ call assert_equal([3, 5], [line('.'), col('.')])
+
+ close!
+endfunc
+
+" Test for error cases with the search() function
+func Test_search_errors()
+ call assert_fails("call search('pat', [])", 'E730:')
+ call assert_fails("call search('pat', 'b', {})", 'E728:')
+ call assert_fails("call search('pat', 'b', 1, [])", 'E745:')
+ call assert_fails("call search('pat', 'ns')", 'E475:')
+ call assert_fails("call search('pat', 'mr')", 'E475:')
+
+ new
+ call setline(1, ['foo', 'bar'])
+ call assert_fails('call feedkeys("/foo/;/bar/;\<CR>", "tx")', 'E386:')
+ bwipe!
+endfunc
+
+func Test_search_timeout()
+ let g:test_is_flaky = 1
+ new
+ " use a complicated pattern that should be slow with the BT engine
+ let pattern = '\%#=1a*.*X\@<=b*'
+
+ " use a timeout of 50 msec
+ let search_timeout = 0.05
+
+ " fill the buffer so that it takes 15 times the timeout to search
+ let slow_target_timeout = search_timeout * 15.0
+
+ " Fill the buffer with more and more text until searching takes more time
+ " than slow_target_timeout.
+ for n in range(40, 400, 30)
+ call setline(1, ['aaa', repeat('abc ', n), 'ccc'])
+ let start = reltime()
+ call search(pattern, '', 0)
+ let elapsed = reltimefloat(reltime(start))
+ if elapsed > slow_target_timeout
+ break
+ endif
+ endfor
+ call assert_true(elapsed > slow_target_timeout)
+
+ " Check that the timeout kicks in, the time should be less than half of what
+ " we measured without the timeout. This is permissive, because the timer is
+ " known to overrun, especially when using valgrind.
+ let max_time = elapsed / 2.0
+ let start = reltime()
+ call search(pattern, '', 0, float2nr(search_timeout * 1000))
+ let elapsed = reltimefloat(reltime(start))
+ call assert_inrange(search_timeout * 0.9, max_time, elapsed)
+
+ bwipe!
+endfunc
+
+func Test_search_display_pattern()
+ new
+ call setline(1, ['foo', 'bar', 'foobar'])
+
+ call cursor(1, 1)
+ let @/ = 'foo'
+ let pat = @/->escape('()*?'. '\s\+')
+ let g:a = execute(':unsilent :norm! n')
+ call assert_match(pat, g:a)
+
+ " right-left
+ if exists("+rightleft")
+ set rl
+ call cursor(1, 1)
+ let @/ = 'foo'
+ let pat = 'oof/\s\+'
+ let g:a = execute(':unsilent :norm! n')
+ call assert_match(pat, g:a)
+ set norl
+ endif
+endfunc
+
+func Test_searchdecl()
+ let lines =<< trim END
+ int global;
+
+ func()
+ {
+ int global;
+ if (cond) {
+ int local;
+ }
+ int local;
+ // comment
+ }
+ END
+ new
+ call setline(1, lines)
+ 10
+ call assert_equal(0, searchdecl('local', 0, 0))
+ call assert_equal(7, getcurpos()[1])
+
+ 10
+ call assert_equal(0, 'local'->searchdecl(0, 1))
+ call assert_equal(9, getcurpos()[1])
+
+ 10
+ call assert_equal(0, searchdecl('global'))
+ call assert_equal(5, getcurpos()[1])
+
+ 10
+ call assert_equal(0, searchdecl('global', 1))
+ call assert_equal(1, getcurpos()[1])
+
+ bwipe!
+endfunc
+
+func Test_search_special()
+ " this was causing illegal memory access and an endless loop
+ set t_PE=
+ exe "norm /\x80PS"
+endfunc
+
+" Test for command failures when the last search pattern is not set.
+" Need to run this in a new vim instance where last search pattern is not set.
+func Test_search_with_no_last_pat()
+ let lines =<< trim [SCRIPT]
+ call assert_fails("normal i\<C-R>/\e", 'E35:')
+ call assert_fails("exe '/'", 'E35:')
+ call assert_fails("exe '?'", 'E35:')
+ call assert_fails("/", 'E35:')
+ call assert_fails("?", 'E35:')
+ call assert_fails("normal n", 'E35:')
+ call assert_fails("normal N", 'E35:')
+ call assert_fails("normal gn", 'E35:')
+ call assert_fails("normal gN", 'E35:')
+ call assert_fails("normal cgn", 'E35:')
+ call assert_fails("normal cgN", 'E35:')
+ let p = []
+ let p = @/
+ call assert_equal('', p)
+ call assert_fails("normal :\<C-R>/", 'E35:')
+ call assert_fails("//p", 'E35:')
+ call assert_fails(";//p", 'E35:')
+ call assert_fails("??p", 'E35:')
+ call assert_fails(";??p", 'E35:')
+ call assert_fails('g//p', ['E35:', 'E476:'])
+ call assert_fails('v//p', ['E35:', 'E476:'])
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test for using tilde (~) atom in search. This should use the last used
+" substitute pattern
+func Test_search_tilde_pat()
+ let lines =<< trim [SCRIPT]
+ set regexpengine=1
+ call assert_fails('exe "normal /~\<CR>"', 'E33:')
+ call assert_fails('exe "normal ?~\<CR>"', 'E33:')
+ set regexpengine=2
+ call assert_fails('exe "normal /~\<CR>"', ['E33:', 'E383:'])
+ call assert_fails('exe "normal ?~\<CR>"', ['E33:', 'E383:'])
+ set regexpengine&
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test for searching a pattern that is not present with 'nowrapscan'
+func Test_search_pat_not_found()
+ new
+ set nowrapscan
+ let @/ = '1abcxyz2'
+ call assert_fails('normal n', 'E385:')
+ call assert_fails('normal N', 'E384:')
+ set wrapscan&
+ close
+endfunc
+
+" Test for v:searchforward variable
+func Test_searchforward_var()
+ new
+ call setline(1, ['foo', '', 'foo'])
+ call cursor(2, 1)
+ let @/ = 'foo'
+ let v:searchforward = 0
+ normal N
+ call assert_equal(3, line('.'))
+ call cursor(2, 1)
+ let v:searchforward = 1
+ normal N
+ call assert_equal(1, line('.'))
+ close!
+endfunc
+
+" Test for invalid regular expressions
+func Test_invalid_regexp()
+ set regexpengine=1
+ call assert_fails("call search(repeat('\\(.\\)', 10))", 'E51:')
+ call assert_fails("call search('\\%(')", 'E53:')
+ call assert_fails("call search('\\(')", 'E54:')
+ call assert_fails("call search('\\)')", 'E55:')
+ call assert_fails("call search('x\\@#')", 'E59:')
+ call assert_fails('call search(''\v%(%(%(%(%(%(%(%(%(%(%(a){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}'')', 'E60:')
+ call assert_fails("call search('a\\+*')", 'E61:')
+ call assert_fails("call search('\\_m')", 'E63:')
+ call assert_fails("call search('\\+')", 'E64:')
+ call assert_fails("call search('\\1')", 'E65:')
+ call assert_fails("call search('\\z\\(\\)')", 'E66:')
+ call assert_fails("call search('\\z2')", 'E67:')
+ call assert_fails("call search('\\zx')", 'E68:')
+ call assert_fails("call search('\\%[ab')", 'E69:')
+ call assert_fails("call search('\\%[]')", 'E70:')
+ call assert_fails("call search('\\%a')", 'E71:')
+ call assert_fails("call search('ab\\%[\\(cd\\)]')", 'E369:')
+ call assert_fails("call search('ab\\%[\\%(cd\\)]')", 'E369:')
+ set regexpengine=2
+ call assert_fails("call search('\\_')", 'E865:')
+ call assert_fails("call search('\\+')", 'E866:')
+ call assert_fails("call search('\\zx')", 'E867:')
+ call assert_fails("call search('\\%a')", 'E867:')
+ call assert_fails("call search('x\\@#')", 'E869:')
+ call assert_fails("call search(repeat('\\(.\\)', 10))", 'E872:')
+ call assert_fails("call search('\\_m')", 'E877:')
+ call assert_fails("call search('\\%(')", 'E53:')
+ call assert_fails("call search('\\(')", 'E54:')
+ call assert_fails("call search('\\)')", 'E55:')
+ call assert_fails("call search('\\z\\(\\)')", 'E66:')
+ call assert_fails("call search('\\z2')", 'E67:')
+ call assert_fails("call search('\\zx')", 'E867:')
+ call assert_fails("call search('\\%[ab')", 'E69:')
+ call assert_fails("call search('\\%[]')", 'E70:')
+ call assert_fails("call search('\\%9999999999999999999999999999v')", 'E951:')
+ set regexpengine&
+ call assert_fails("call search('\\%#=3ab')", 'E864:')
+endfunc
+
+" Test for searching a very complex pattern in a string. Should switch the
+" regexp engine from NFA to the old engine.
+func Test_regexp_switch_engine()
+ let l = readfile('samples/re.freeze.txt')
+ let v = substitute(l[4], '..\@<!', '', '')
+ call assert_equal(l[4], v)
+endfunc
+
+" Test for the \%V atom to search within visually selected text
+func Test_search_in_visual_area()
+ new
+ call setline(1, ['foo bar1', 'foo bar2', 'foo bar3', 'foo bar4'])
+ exe "normal 2GVjo/\\%Vbar\<CR>\<Esc>"
+ call assert_equal([2, 5], [line('.'), col('.')])
+ exe "normal 2GVj$?\\%Vbar\<CR>\<Esc>"
+ call assert_equal([3, 5], [line('.'), col('.')])
+ close!
+endfunc
+
+" Test for searching with 'smartcase' and 'ignorecase'
+func Test_search_smartcase()
+ new
+ call setline(1, ['', 'Hello'])
+ set noignorecase nosmartcase
+ call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:')
+
+ set ignorecase nosmartcase
+ exe "normal /\\a\\_.\\(.*\\)O\<CR>"
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ call cursor(1, 1)
+ set ignorecase smartcase
+ call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:')
+
+ exe "normal /\\a\\_.\\(.*\\)o\<CR>"
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ " Test for using special atoms with 'smartcase'
+ call setline(1, ['', ' Hello\ '])
+ call cursor(1, 1)
+ call feedkeys('/\_.\%(\uello\)\' .. "\<CR>", 'xt')
+ call assert_equal([2, 4], [line('.'), col('.')])
+
+ set ignorecase& smartcase&
+ close!
+endfun
+
+" Test 'smartcase' with utf-8.
+func Test_search_smartcase_utf8()
+ new
+ let save_enc = &encoding
+ set encoding=utf8 ignorecase smartcase
+
+ call setline(1, 'Café cafÉ')
+ 1s/café/x/g
+ call assert_equal('x x', getline(1))
+
+ call setline(1, 'Café cafÉ')
+ 1s/cafÉ/x/g
+ call assert_equal('Café x', getline(1))
+
+ set ignorecase& smartcase&
+ let &encoding = save_enc
+ bwipe!
+endfunc
+
+" Test searching past the end of a file
+func Test_search_past_eof()
+ new
+ call setline(1, ['Line'])
+ exe "normal /\\n\\zs\<CR>"
+ call assert_equal([1, 4], [line('.'), col('.')])
+ bwipe!
+endfunc
+
+" Test setting the start of the match and still finding a next match in the
+" same line.
+func Test_search_set_start_same_line()
+ new
+ set cpo-=c
+
+ call setline(1, ['1', '2', '3 .', '4', '5'])
+ exe "normal /\\_s\\zs\\S\<CR>"
+ call assert_equal([2, 1], [line('.'), col('.')])
+ exe 'normal n'
+ call assert_equal([3, 1], [line('.'), col('.')])
+ exe 'normal n'
+ call assert_equal([3, 3], [line('.'), col('.')])
+ exe 'normal n'
+ call assert_equal([4, 1], [line('.'), col('.')])
+ exe 'normal n'
+ call assert_equal([5, 1], [line('.'), col('.')])
+
+ set cpo+=c
+ bwipe!
+endfunc
+
+" Test for various search offsets
+func Test_search_offset()
+ " With /e, for a match in the first column of a line, the cursor should be
+ " placed at the end of the previous line.
+ new
+ call setline(1, ['one two', 'three four'])
+ call search('two\_.', 'e')
+ call assert_equal([1, 7], [line('.'), col('.')])
+
+ " with cursor at the beginning of the file, use /s+1
+ call cursor(1, 1)
+ exe "normal /two/s+1\<CR>"
+ call assert_equal([1, 6], [line('.'), col('.')])
+
+ " with cursor at the end of the file, use /e-1
+ call cursor(2, 10)
+ exe "normal ?three?e-1\<CR>"
+ call assert_equal([2, 4], [line('.'), col('.')])
+
+ " line offset - after the last line
+ call cursor(1, 1)
+ exe "normal /three/+1\<CR>"
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ " line offset - before the first line
+ call cursor(2, 1)
+ exe "normal ?one?-1\<CR>"
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ " character offset - before the first character in the file
+ call cursor(2, 1)
+ exe "normal ?one?s-1\<CR>"
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call cursor(2, 1)
+ exe "normal ?one?e-3\<CR>"
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ " character offset - after the last character in the file
+ call cursor(1, 1)
+ exe "normal /four/s+4\<CR>"
+ call assert_equal([2, 10], [line('.'), col('.')])
+ call cursor(1, 1)
+ exe "normal /four/e+1\<CR>"
+ call assert_equal([2, 10], [line('.'), col('.')])
+
+ close!
+endfunc
+
+" Test for searching for matching parenthesis using %
+func Test_search_match_paren()
+ new
+ call setline(1, "abc(def')'ghi'('jk'\\t'lm)no")
+ " searching for a matching parenthesis should skip single quoted characters
+ call cursor(1, 4)
+ normal %
+ call assert_equal([1, 25], [line('.'), col('.')])
+ normal %
+ call assert_equal([1, 4], [line('.'), col('.')])
+ call cursor(1, 5)
+ normal ])
+ call assert_equal([1, 25], [line('.'), col('.')])
+ call cursor(1, 24)
+ normal [(
+ call assert_equal([1, 4], [line('.'), col('.')])
+
+ " matching parenthesis in 'virtualedit' mode with cursor after the eol
+ call setline(1, 'abc(defgh)')
+ set virtualedit=all
+ normal 20|%
+ call assert_equal(4, col('.'))
+ set virtualedit&
+ close!
+endfunc
+
+" Test for searching a pattern and stopping before a specified line
+func Test_search_stopline()
+ new
+ call setline(1, ['', '', '', 'vim'])
+ call assert_equal(0, search('vim', 'n', 3))
+ call assert_equal(4, search('vim', 'n', 4))
+ call setline(1, ['vim', '', '', ''])
+ call cursor(4, 1)
+ call assert_equal(0, search('vim', 'bn', 2))
+ call assert_equal(1, search('vim', 'bn', 1))
+ close!
+endfunc
+
+func Test_incsearch_highlighting_newline()
+ CheckRunVimInTerminal
+ CheckOption incsearch
+ CheckScreendump
+ new
+ call test_override("char_avail", 1)
+
+ let commands =<< trim [CODE]
+ set incsearch nohls
+ call setline(1, ['test', 'xxx'])
+ [CODE]
+ call writefile(commands, 'Xincsearch_nl', 'D')
+ let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10})
+ call term_sendkeys(buf, '/test')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline1', {})
+ " Need to send one key at a time to force a redraw
+ call term_sendkeys(buf, '\n')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline2', {})
+ call term_sendkeys(buf, 'x')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline3', {})
+ call term_sendkeys(buf, 'x')
+ call VerifyScreenDump(buf, 'Test_incsearch_newline4', {})
+ call term_sendkeys(buf, "\<CR>")
+ call VerifyScreenDump(buf, 'Test_incsearch_newline5', {})
+ call StopVimInTerminal(buf)
+
+ " clean up
+ call test_override("char_avail", 0)
+ bw
+endfunc
+
+func Test_incsearch_substitute_dump2()
+ CheckOption incsearch
+ CheckScreendump
+
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'for n in range(1, 4)',
+ \ ' call setline(n, "foo " . n)',
+ \ 'endfor',
+ \ 'call setline(5, "abc|def")',
+ \ '3',
+ \ ], 'Xis_subst_script2', 'D')
+ let buf = RunVimInTerminal('-S Xis_subst_script2', {'rows': 9, 'cols': 70})
+
+ call term_sendkeys(buf, ':%s/\vabc|')
+ sleep 100m
+ call VerifyScreenDump(buf, 'Test_incsearch_sub_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " The following should not be highlighted
+ call term_sendkeys(buf, ':1,5s/\v|')
+ sleep 100m
+ call VerifyScreenDump(buf, 'Test_incsearch_sub_02', {})
+
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_pattern_is_uppercase_smartcase()
+ new
+ let input=['abc', 'ABC', 'Abc', 'abC']
+ call setline(1, input)
+ call cursor(1,1)
+ " default, matches firstline
+ %s/abc//g
+ call assert_equal(['', 'ABC', 'Abc', 'abC'],
+ \ getline(1, '$'))
+
+ set smartcase ignorecase
+ sil %d
+ call setline(1, input)
+ call cursor(1,1)
+ " with smartcase and incsearch set, matches everything
+ %s/abc//g
+ call assert_equal(['', '', '', ''], getline(1, '$'))
+
+ sil %d
+ call setline(1, input)
+ call cursor(1,1)
+ " with smartcase and incsearch set and found an uppercase letter,
+ " match only that.
+ %s/abC//g
+ call assert_equal(['abc', 'ABC', 'Abc', ''],
+ \ getline(1, '$'))
+
+ sil %d
+ call setline(1, input)
+ call cursor(1,1)
+ exe "norm! vG$\<esc>"
+ " \%V should not be detected as uppercase letter
+ %s/\%Vabc//g
+ call assert_equal(['', '', '', ''], getline(1, '$'))
+
+ call setline(1, input)
+ call cursor(1,1)
+ exe "norm! vG$\<esc>"
+ " \v%V should not be detected as uppercase letter
+ %s/\v%Vabc//g
+ call assert_equal(['', '', '', ''], getline(1, '$'))
+
+ call setline(1, input)
+ call cursor(1,1)
+ exe "norm! vG$\<esc>"
+ " \v%VabC should be detected as uppercase letter
+ %s/\v%VabC//g
+ call assert_equal(['abc', 'ABC', 'Abc', ''],
+ \ getline(1, '$'))
+
+ call setline(1, input)
+ call cursor(1,1)
+ " \Vabc should match everything
+ %s/\Vabc//g
+ call assert_equal(['', '', '', ''], getline(1, '$'))
+
+ call setline(1, input + ['_abc'])
+ " _ matches normally
+ %s/\v_.*//g
+ call assert_equal(['abc', 'ABC', 'Abc', 'abC', ''], getline(1, '$'))
+
+ set smartcase& ignorecase&
+ bw!
+endfunc
+
+func Test_no_last_search_pattern()
+ CheckOption incsearch
+
+ let @/ = ""
+ set incsearch
+ " these were causing a crash
+ call feedkeys("//\<C-G>", 'xt')
+ call feedkeys("//\<C-T>", 'xt')
+ call feedkeys("??\<C-G>", 'xt')
+ call feedkeys("??\<C-T>", 'xt')
+endfunc
+
+func Test_search_with_invalid_range()
+ new
+ let lines =<< trim END
+ /\%.v
+ 5/
+ c
+ END
+ call writefile(lines, 'Xrangesearch', 'D')
+ source Xrangesearch
+
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_search_stat.vim b/src/testdir/test_search_stat.vim
new file mode 100644
index 0000000..e205df5
--- /dev/null
+++ b/src/testdir/test_search_stat.vim
@@ -0,0 +1,436 @@
+" Tests for search_stats, when "S" is not in 'shortmess'
+
+source check.vim
+source screendump.vim
+
+func Test_search_stat()
+ new
+ set shortmess-=S
+ " Append 50 lines with text to search for, "foobar" appears 20 times
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
+
+ call cursor(1, 1)
+
+ " searchcount() returns an empty dictionary when previous pattern was not set
+ call assert_equal({}, searchcount(#{pattern: ''}))
+ " but setting @/ should also work (even 'n' nor 'N' was executed)
+ " recompute the count when the last position is different.
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 40, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'foo'}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar'}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]}))
+ " on last char of match
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]}))
+ " on char after match
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
+ \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0], maxcount: 1}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
+ \ searchcount(#{pattern: 'fooooobar', maxcount: 1}))
+
+ " match at second line
+ let messages_before = execute('messages')
+ let @/ = 'fo*\(bar\?\)\?'
+ let g:a = execute(':unsilent :norm! n')
+ let stat = '\[2/50\]'
+ let pat = escape(@/, '()*?'). '\s\+'
+ call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ " didn't get added to message history
+ call assert_equal(messages_before, execute('messages'))
+
+ " Match at last line
+ call cursor(line('$')-2, 1)
+ let g:a = execute(':unsilent :norm! n')
+ let stat = '\[50/50\]'
+ call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+
+ " No search stat
+ set shortmess+=S
+ call cursor(1, 1)
+ let stat = '\[2/50\]'
+ let g:a = execute(':unsilent :norm! n')
+ call assert_notmatch(pat .. stat, g:a)
+ " n does not update search stat
+ call assert_equal(
+ \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: v:true}))
+ set shortmess-=S
+
+ " Many matches
+ call cursor(line('$')-2, 1)
+ let @/ = '.'
+ let pat = escape(@/, '()*?'). '\s\+'
+ let g:a = execute(':unsilent :norm! n')
+ let stat = '\[>99/>99\]'
+ call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200}))
+ call cursor(line('$'), 1)
+ let g:a = execute(':unsilent :norm! n')
+ let stat = 'W \[1/>99\]'
+ call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200}))
+ call assert_equal(
+ \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200}))
+
+ " Many matches
+ call cursor(1, 1)
+ let g:a = execute(':unsilent :norm! n')
+ let stat = '\[2/>99\]'
+ call assert_match(pat .. stat, g:a)
+ call cursor(1, 1)
+ let g:a = execute(':unsilent :norm! N')
+ let stat = 'W \[>99/>99\]'
+ call assert_match(pat .. stat, g:a)
+
+ " right-left
+ if exists("+rightleft")
+ set rl
+ call cursor(1,1)
+ let @/ = 'foobar'
+ let pat = 'raboof/\s\+'
+ let g:a = execute(':unsilent :norm! n')
+ let stat = '\[20/2\]'
+ call assert_match(pat .. stat, g:a)
+ set norl
+ endif
+
+ " right-left bottom
+ if exists("+rightleft")
+ set rl
+ call cursor('$',1)
+ let pat = 'raboof?\s\+'
+ let g:a = execute(':unsilent :norm! N')
+ let stat = '\[20/20\]'
+ call assert_match(pat .. stat, g:a)
+ set norl
+ endif
+
+ " right-left back at top
+ if exists("+rightleft")
+ set rl
+ call cursor('$',1)
+ let pat = 'raboof/\s\+'
+ let g:a = execute(':unsilent :norm! n')
+ let stat = 'W \[20/1\]'
+ call assert_match(pat .. stat, g:a)
+ call assert_match('search hit BOTTOM, continuing at TOP', g:a)
+ set norl
+ endif
+
+ " normal, back at bottom
+ call cursor(1,1)
+ let @/ = 'foobar'
+ let pat = '?foobar\s\+'
+ let g:a = execute(':unsilent :norm! N')
+ let stat = 'W \[20/20\]'
+ call assert_match(pat .. stat, g:a)
+ call assert_match('search hit TOP, continuing at BOTTOM', g:a)
+ call assert_match('W \[20/20\]', Screenline(&lines))
+
+ " normal, no match
+ call cursor(1,1)
+ let @/ = 'zzzzzz'
+ let g:a = ''
+ try
+ let g:a = execute(':unsilent :norm! n')
+ catch /^Vim\%((\a\+)\)\=:E486/
+ let stat = ''
+ " error message is not redir'ed to g:a, it is empty
+ call assert_true(empty(g:a))
+ catch
+ call assert_false(1)
+ endtry
+
+ " with count
+ call cursor(1, 1)
+ let @/ = 'fo*\(bar\?\)\?'
+ let g:a = execute(':unsilent :norm! 2n')
+ let stat = '\[3/50\]'
+ let pat = escape(@/, '()*?'). '\s\+'
+ call assert_match(pat .. stat, g:a)
+ let g:a = execute(':unsilent :norm! 2n')
+ let stat = '\[5/50\]'
+ call assert_match(pat .. stat, g:a)
+
+ " with offset
+ call cursor(1, 1)
+ call feedkeys("/fo*\\(bar\\?\\)\\?/+1\<cr>", 'tx')
+ let g:a = execute(':unsilent :norm! n')
+ let stat = '\[5/50\]'
+ let pat = escape(@/ .. '/+1', '()*?'). '\s\+'
+ call assert_match(pat .. stat, g:a)
+
+ " normal, n comes from a mapping
+ " Need to move over more than 64 lines to trigger char_avail(.
+ nnoremap n nzv
+ call cursor(1,1)
+ call append(50, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
+ call setline(2, 'find this')
+ call setline(70, 'find this')
+ let @/ = 'find this'
+ let pat = '/find this\s\+'
+ let g:a = execute(':unsilent :norm n')
+ " g:a will contain several lines
+ let g:b = split(g:a, "\n")[-1]
+ let stat = '\[1/2\]'
+ call assert_match(pat .. stat, g:b)
+ unmap n
+
+ " normal, but silent
+ call cursor(1,1)
+ let @/ = 'find this'
+ let pat = '/find this\s\+'
+ let g:a = execute(':norm! n')
+ let stat = '\[1/2\]'
+ call assert_notmatch(pat .. stat, g:a)
+
+ " normal, n comes from a silent mapping
+ " First test a normal mapping, then a silent mapping
+ call cursor(1,1)
+ nnoremap n n
+ let @/ = 'find this'
+ let pat = '/find this\s\+'
+ let g:a = execute(':unsilent :norm n')
+ let g:b = split(g:a, "\n")[-1]
+ let stat = '\[1/2\]'
+ call assert_match(pat .. stat, g:b)
+ nnoremap <silent> n n
+ call cursor(1,1)
+ let g:a = execute(':unsilent :norm n')
+ let g:b = split(g:a, "\n")[-1]
+ let stat = '\[1/2\]'
+ call assert_notmatch(pat .. stat, g:b)
+ call assert_match(stat, g:b)
+ " Test that the message is not truncated
+ " it would insert '...' into the output.
+ call assert_match('^\s\+' .. stat, g:b)
+ unmap n
+
+ " Time out
+ %delete _
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 100000))
+ call cursor(1, 1)
+ call assert_equal(1, searchcount(#{pattern: 'foo', maxcount: 0, timeout: 1}).incomplete)
+
+ " Clean up
+ set shortmess+=S
+ " close the window
+ bwipe!
+endfunc
+
+func Test_searchcount_fails()
+ call assert_fails('echo searchcount("boo!")', 'E1206:')
+ call assert_fails('echo searchcount({"timeout" : []})', 'E745:')
+ call assert_fails('echo searchcount({"maxcount" : []})', 'E745:')
+ call assert_fails('echo searchcount({"pattern" : []})', 'E730:')
+ call assert_fails('echo searchcount({"pos" : 1})', 'E475:')
+ call assert_fails('echo searchcount({"pos" : [1]})', 'E475:')
+ call assert_fails('echo searchcount({"pos" : [[], 2, 3]})', 'E745:')
+ call assert_fails('echo searchcount({"pos" : [1, [], 3]})', 'E745:')
+ call assert_fails('echo searchcount({"pos" : [1, 2, []]})', 'E745:')
+endfunc
+
+func Test_search_stat_narrow_screen()
+ " This used to crash Vim
+ let save_columns = &columns
+ try
+ let after =<< trim [CODE]
+ set laststatus=2
+ set columns=16
+ set shortmess-=S showcmd
+ call setline(1, 'abc')
+ call feedkeys("/abc\<CR>:quit!\<CR>")
+ autocmd VimLeavePre * call writefile(["done"], "Xdone")
+ [CODE]
+
+ if !RunVim([], after, '--clean')
+ return
+ endif
+ call assert_equal("done", readfile("Xdone")[0])
+ call delete('Xdone')
+ finally
+ let &columns = save_columns
+ endtry
+endfunc
+
+func Test_searchcount_in_statusline()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ call append(0, 'this is something')
+ function TestSearchCount() abort
+ let search_count = searchcount()
+ if !empty(search_count)
+ return '[' . search_count.current . '/' . search_count.total . ']'
+ else
+ return ''
+ endif
+ endfunction
+ set hlsearch
+ set laststatus=2 statusline+=%{TestSearchCount()}
+ END
+ call writefile(lines, 'Xsearchstatusline', 'D')
+ let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
+ call TermWait(buf)
+ call term_sendkeys(buf, "/something")
+ call VerifyScreenDump(buf, 'Test_searchstat_4', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_search_stat_foldopen()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ setl foldenable foldmethod=indent foldopen-=search
+ call append(0, ['if', "\tfoo", "\tfoo", 'endif'])
+ let @/ = 'foo'
+ call cursor(1,1)
+ norm n
+ END
+ call writefile(lines, 'Xsearchstat1', 'D')
+
+ let buf = RunVimInTerminal('-S Xsearchstat1', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call term_sendkeys(buf, "n")
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call term_sendkeys(buf, "n")
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func! Test_search_stat_screendump()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ " Append 50 lines with text to search for, "foobar" appears 20 times
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20))
+ call setline(2, 'find this')
+ call setline(70, 'find this')
+ nnoremap n n
+ let @/ = 'find this'
+ call cursor(1,1)
+ norm n
+ END
+ call writefile(lines, 'Xsearchstat', 'D')
+ let buf = RunVimInTerminal('-S Xsearchstat', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_searchstat_1', {})
+
+ call term_sendkeys(buf, ":nnoremap <silent> n n\<cr>")
+ call term_sendkeys(buf, "gg0n")
+ call VerifyScreenDump(buf, 'Test_searchstat_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_search_stat_then_gd()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['int cat;', 'int dog;', 'cat = dog;'])
+ set shortmess-=S
+ set hlsearch
+ END
+ call writefile(lines, 'Xsearchstatgd', 'D')
+
+ let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10})
+ call term_sendkeys(buf, "/dog\<CR>")
+ call VerifyScreenDump(buf, 'Test_searchstatgd_1', {})
+
+ call term_sendkeys(buf, "G0gD")
+ call VerifyScreenDump(buf, 'Test_searchstatgd_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_search_stat_and_incsearch()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['abc--c', '--------abc', '--abc'])
+ set hlsearch
+ set incsearch
+ set bg=dark
+ set showtabline=2
+
+ function MyTabLine()
+ try
+ let a=searchcount(#{recompute: 1, maxcount: -1})
+ return a.current .. '/' .. a.total
+ catch
+ return ''
+ endtry
+ endfunction
+
+ set tabline=%!MyTabLine()
+ END
+ call writefile(lines, 'Xsearchstat_inc', 'D')
+
+ let buf = RunVimInTerminal('-S Xsearchstat_inc', #{rows: 10})
+ call term_sendkeys(buf, "/abc")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_inc_1', {})
+
+ call term_sendkeys(buf, "\<c-g>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_inc_2', {})
+
+ call term_sendkeys(buf, "\<c-g>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_inc_3', {})
+
+ call term_sendkeys(buf, "\<esc>:qa\<cr>")
+ call TermWait(buf)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_searchpos.vim b/src/testdir/test_searchpos.vim
new file mode 100644
index 0000000..dd13c30
--- /dev/null
+++ b/src/testdir/test_searchpos.vim
@@ -0,0 +1,30 @@
+" Tests for searchpos()
+
+func Test_searchpos()
+ new one
+ 0put ='1a3'
+ 1put ='123xyz'
+ call cursor(1, 1)
+ call assert_equal([1, 1, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW'))
+ call cursor(1, 2)
+ call assert_equal([2, 1, 1], '\%(\([a-z]\)\|\_.\)\{-}xyz'->searchpos('pcW'))
+ set cpo-=c
+ call cursor(1, 2)
+ call assert_equal([1, 2, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW'))
+ call cursor(1, 3)
+ call assert_equal([1, 3, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW'))
+
+ " Now with \zs, first match is in column 0, "a" is matched.
+ call cursor(1, 3)
+ call assert_equal([2, 4, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcW'))
+ " With z flag start at cursor column, don't see the "a".
+ call cursor(1, 3)
+ call assert_equal([2, 4, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcWz'))
+
+ set cpo+=c
+ " close the window
+ q!
+
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_selectmode.vim b/src/testdir/test_selectmode.vim
new file mode 100644
index 0000000..6e2c369
--- /dev/null
+++ b/src/testdir/test_selectmode.vim
@@ -0,0 +1,313 @@
+" Test for Select-mode
+
+" This only works for Unix in a terminal
+source check.vim
+CheckNotGui
+CheckUnix
+
+source mouse.vim
+
+" Test for select mode
+func Test_selectmode_basic()
+ new
+ call setline(1, range(1,100))
+ 50
+ norm! gHy
+ call assert_equal('y51', getline('.'))
+ call setline(1, range(1,100))
+ 50
+ exe ":norm! V9jo\<c-g>y"
+ call assert_equal('y60', getline('.'))
+ call setline(1, range(1,100))
+ 50
+ call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx')
+ call assert_equal('c51', getline('.'))
+ " clean up
+ bw!
+endfunc
+
+" Test for starting selectmode
+func Test_selectmode_start()
+ new
+ set selectmode=key keymodel=startsel
+ call setline(1, ['abc', 'def', 'ghi'])
+ call cursor(1, 4)
+ call feedkeys("A\<s-home>start\<esc>", 'txin')
+ call assert_equal(['startdef', 'ghi'], getline(1, '$'))
+ " start select mode again with gv
+ set selectmode=cmd
+ call feedkeys('gvabc', 'xt')
+ call assert_equal('abctdef', getline(1))
+ " arrow keys without shift should not start selection
+ call feedkeys("A\<Home>\<Right>\<Left>ro", 'xt')
+ call assert_equal('roabctdef', getline(1))
+ set selectmode= keymodel=
+ bw!
+endfunc
+
+" Test for characterwise select mode
+func Test_characterwise_select_mode()
+ new
+
+ " Select mode maps
+ snoremap <lt>End> <End>
+ snoremap <lt>Down> <Down>
+ snoremap <lt>Del> <Del>
+
+ " characterwise select mode: delete middle line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<End>\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " characterwise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Ggh\<End>\<Del>"
+ call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
+
+ " characterwise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal Gkgh\<Down>\<End>\<Del>"
+ call assert_equal(['', 'a', ''], getline(1, '$'))
+
+ " CTRL-H in select mode behaves like 'x'
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>"
+ call assert_equal('ef', getline(1))
+
+ " CTRL-O in select mode switches to visual mode for one command
+ call setline(1, 'abcdef')
+ exe "normal! gggh\<C-O>3lm"
+ call assert_equal('mef', getline(1))
+
+ sunmap <lt>End>
+ sunmap <lt>Down>
+ sunmap <lt>Del>
+ bwipe!
+endfunc
+
+" Test for linewise select mode
+func Test_linewise_select_mode()
+ new
+
+ " linewise select mode: delete middle line
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Del>"
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " linewise select mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkkgH\<Down>\<Del>"
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " linewise select mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GgH\<Del>"
+ call assert_equal(['', 'a', 'b'], getline(1, '$'))
+
+ " linewise select mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ exe "normal GkgH\<Down>\<Del>"
+ call assert_equal(['', 'a'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for blockwise select mode (g CTRL-H)
+func Test_blockwise_select_mode()
+ new
+ call setline(1, ['foo', 'bar'])
+ call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt')
+ call assert_equal(['mmo', 'mmr'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for using visual mode maps in select mode
+func Test_select_mode_map()
+ new
+ vmap <buffer> <F2> 3l
+ call setline(1, 'Test line')
+ call feedkeys("gh\<F2>map", 'xt')
+ call assert_equal('map line', getline(1))
+
+ vmap <buffer> <F2> ygV
+ call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt')
+ call assert_equal('abc line', getline(1))
+
+ vmap <buffer> <F2> :<C-U>let v=100<CR>
+ call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt')
+ call assert_equal('foo line', getline(1))
+
+ " reselect the select mode using gv from a visual mode map
+ vmap <buffer> <F2> gv
+ set selectmode=cmd
+ call feedkeys("0gh\<F2>map", 'xt')
+ call assert_equal('map line', getline(1))
+ set selectmode&
+
+ close!
+endfunc
+
+" Test double/triple/quadruple click to start 'select' mode
+func Test_term_mouse_multiple_clicks_to_select_mode()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+
+ " 'mousetime' must be sufficiently large, or else the test is flaky when
+ " using a ssh connection with X forwarding; i.e. ssh -X.
+ set mouse=a term=xterm mousetime=1000
+ set selectmode=mouse
+ new
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ " Single-click and drag should 'select' the characters
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 3)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftDrag(1, 13)
+ call MouseLeftRelease(1, 13)
+ norm! o
+ call assert_equal(['foo foo', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on word should visually 'select' the word.
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call assert_equal('s', mode(), msg)
+ norm! bar
+ call assert_equal(['bar [foo bar] foo', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on opening square bracket should visually
+ " 'select' the whole [foo bar].
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 5)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 5)
+ call MouseLeftClick(1, 5)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 5)
+ call assert_equal('s', mode(), msg)
+ norm! bar
+ call assert_equal(['foo bar foo', 'foo'], getline(1, '$'), msg)
+
+ " To guarantee that the next click is not counted as a triple click
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+
+ " Triple-click should visually 'select' the whole line.
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 3)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 3)
+ call assert_equal('S', mode(), msg)
+ norm! baz
+ call assert_equal(['bazfoo'], getline(1, '$'), msg)
+
+ " Quadruple-click should start visual block 'select'.
+ call setline(1, ['aaaaaa', 'bbbbbb'])
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(96, getcharmod(), msg) " quadruple-click
+ call MouseLeftDrag(2, 4)
+ call MouseLeftRelease(2, 4)
+ call assert_equal("\<c-s>", mode(), msg)
+ norm! x
+ call assert_equal(['axaa', 'bxbb'], getline(1, '$'), msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ set selectmode&
+ call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+" Test for selecting a register with CTRL-R
+func Test_selectmode_register()
+ new
+
+ " Default behavior: use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Use the black hole register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>_a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('bar', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Invalid register: use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>?a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " Use unnamed register
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>\"a"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('baz', getreg('a'))
+
+ " use specicifed register, unnamed register is also written
+ call setline(1, 'foo')
+ call setreg('"', 'bar')
+ call setreg('a', 'baz')
+ exe ":norm! v\<c-g>\<c-r>aa"
+ call assert_equal(getline('.'), 'aoo')
+ call assert_equal('f', getreg('"'))
+ call assert_equal('f', getreg('a'))
+
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_set.vim b/src/testdir/test_set.vim
new file mode 100644
index 0000000..f38ac33
--- /dev/null
+++ b/src/testdir/test_set.vim
@@ -0,0 +1,78 @@
+" Tests for the :set command
+
+source check.vim
+
+function Test_set_backslash()
+ let isk_save = &isk
+
+ set isk=a,b,c
+ set isk+=d
+ call assert_equal('a,b,c,d', &isk)
+ set isk+=\\,e
+ call assert_equal('a,b,c,d,\,e', &isk)
+ set isk-=e
+ call assert_equal('a,b,c,d,\', &isk)
+ set isk-=\\
+ call assert_equal('a,b,c,d', &isk)
+
+ let &isk = isk_save
+endfunction
+
+function Test_set_add()
+ let wig_save = &wig
+
+ set wildignore=*.png,
+ set wildignore+=*.jpg
+ call assert_equal('*.png,*.jpg', &wig)
+
+ let &wig = wig_save
+endfunction
+
+
+" :set, :setlocal, :setglobal without arguments show values of options.
+func Test_set_no_arg()
+ set textwidth=79
+ let a = execute('set')
+ call assert_match("^\n--- Options ---\n.*textwidth=79\\>", a)
+ set textwidth&
+
+ setlocal textwidth=78
+ let a = execute('setlocal')
+ call assert_match("^\n--- Local option values ---\n.*textwidth=78\\>", a)
+ setlocal textwidth&
+
+ setglobal textwidth=77
+ let a = execute('setglobal')
+ call assert_match("^\n--- Global option values ---\n.*textwidth=77\\>", a)
+ setglobal textwidth&
+endfunc
+
+func Test_set_termcap()
+ CheckNotGui
+
+ let lines = split(execute('set termcap'), "\n")
+ call assert_match('--- Terminal codes ---', lines[0])
+ " four columns
+ call assert_match('t_..=.*t_..=.*t_..=.*t_..=', lines[1])
+
+ for keys_idx in range(len(lines))
+ if lines[keys_idx] =~ '--- Terminal keys ---'
+ break
+ endif
+ endfor
+ call assert_true(keys_idx < len(lines))
+ " three columns
+ call assert_match('<[^>]*> .*<[^>]*> .*<[^>]*> ', lines[keys_idx + 1])
+
+ let more_lines = split(execute('set! termcap'), "\n")
+ for i in range(len(more_lines))
+ if more_lines[i] =~ '--- Terminal keys ---'
+ break
+ endif
+ endfor
+ call assert_true(i < len(more_lines))
+ call assert_true(i > keys_idx)
+ call assert_true(len(more_lines) - i > len(lines) - keys_idx)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_sha256.vim b/src/testdir/test_sha256.vim
new file mode 100644
index 0000000..a1bec97
--- /dev/null
+++ b/src/testdir/test_sha256.vim
@@ -0,0 +1,24 @@
+" Tests for the sha256() function.
+
+source check.vim
+CheckFeature cryptv
+CheckFunction sha256
+
+function Test_sha256()
+ " test for empty string:
+ call assert_equal('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', sha256(""))
+
+ "'test for 1 char:
+ call assert_equal('ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', sha256("a"))
+ "
+ "test for 3 chars:
+ call assert_equal('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', "abc"->sha256())
+
+ " test for contains meta char:
+ call assert_equal('807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776', sha256("foo\nbar"))
+
+ " test for contains non-ascii char:
+ call assert_equal('5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953', sha256("\xde\xad\xbe\xef"))
+endfunction
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_shell.vim b/src/testdir/test_shell.vim
new file mode 100644
index 0000000..7d91dff
--- /dev/null
+++ b/src/testdir/test_shell.vim
@@ -0,0 +1,298 @@
+" Test for the shell related options ('shell', 'shellcmdflag', 'shellpipe',
+" 'shellquote', 'shellredir', 'shellxescape', and 'shellxquote')
+
+source check.vim
+source shared.vim
+
+func Test_shell_options()
+ if has('win32')
+ " FIXME: This test is flaky on MS-Windows.
+ let g:test_is_flaky = 1
+ endif
+
+ " The expected value of 'shellcmdflag', 'shellpipe', 'shellquote',
+ " 'shellredir', 'shellxescape', 'shellxquote' for the supported shells.
+ let shells = []
+ if has('unix')
+ let shells += [['sh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['ksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['mksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['zsh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['zsh-beta', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['bash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['fish', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['ash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['dash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''],
+ \ ['csh', '-c', '|& tee', '', '>&', '', ''],
+ \ ['tcsh', '-c', '|& tee', '', '>&', '', ''],
+ \ ['pwsh', '-c', '>%s 2>&1', '', '>%s 2>&1', '', '']]
+ endif
+ if has('win32')
+ let shells += [['cmd', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', ''],
+ \ ['cmd.exe', '/c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '('],
+ \ ['powershell.exe', '-Command', '2>&1 | Out-File -Encoding default',
+ \ '', '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
+ \ ['powershell', '-Command', '2>&1 | Out-File -Encoding default', '',
+ \ '2>&1 | Out-File -Encoding default', '"&|<>()@^', '"'],
+ \ ['pwsh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['pwsh', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['sh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['ksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['mksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['pdksh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['zsh.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['zsh-beta.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['bash.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['dash.exe', '-c', '>%s 2>&1', '', '>%s 2>&1', '"&|<>()@^', '"'],
+ \ ['csh.exe', '-c', '>&', '', '>&', '"&|<>()@^', '"'],
+ \ ['tcsh.exe', '-c', '>&', '', '>&', '"&|<>()@^', '"']]
+ endif
+
+ " start a new Vim instance with 'shell' set to each of the supported shells
+ " and check the default shell option settings
+ let after =<< trim END
+ let l = [&shell, &shellcmdflag, &shellpipe, &shellquote]
+ let l += [&shellredir, &shellxescape, &shellxquote]
+ call writefile([json_encode(l)], 'Xtestout')
+ qall!
+ END
+ for e in shells
+ if RunVim([], after, '--cmd "set shell=' .. e[0] .. '"')
+ call assert_equal(e, json_decode(readfile('Xtestout')[0]))
+ endif
+ endfor
+
+ " Test shellescape() for each of the shells.
+ for e in shells
+ exe 'set shell=' .. e[0]
+ if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$'
+ let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%# \\'\\'' \\\\! \\% \\#'"
+ let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\# \\'\\'' \\\\\\! \\\\% \\\\#'"
+ elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$'
+ \ || e[0] =~# '.*pwsh$' || e[0] =~# '.*pwsh.exe$'
+ let str1 = "'cmd \"arg1\" ''arg2'' !%# \\'' \\! \\% \\#'"
+ let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\# \\'' \\\\! \\\\% \\\\#'"
+ elseif e[0] =~# '.*fish$' || e[0] =~# '.*fish.exe$'
+ let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\\\'\\'' \\\\! \\\\% \\\\#'"
+ let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\\\'\\'' \\\\\\! \\\\\\% \\\\\\#'"
+ else
+ let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\'\\'' \\! \\% \\#'"
+ let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\'\\'' \\\\! \\\\% \\\\#'"
+ endif
+ call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%# \\' \\! \\% \\#"), e[0])
+ call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%# \\' \\! \\% \\#", 1), e[0])
+
+ " Try running an external command with the shell.
+ if executable(e[0])
+ " set the shell options for the current 'shell'
+ let [&shellcmdflag, &shellpipe, &shellquote, &shellredir,
+ \ &shellxescape, &shellxquote] = e[1:6]
+ new
+ try
+ r !echo hello
+ call assert_equal('hello', substitute(getline(2), '\W', '', 'g'), e[0])
+ catch
+ call assert_report('Failed to run shell command, shell: ' .. e[0]
+ \ .. ', caught ' .. v:exception)
+ finally
+ bwipe!
+ endtry
+
+ " filter buffer contents through an external command
+ new
+ call setline(1, ['tom', 'sam', 'andy'])
+ try
+ %!sort
+ call assert_equal(['andy', 'sam', 'tom'], getline(1, '$'), e[0])
+ catch
+ call assert_report($'Failed to filter buffer contents, shell: {e[0]}, caught {v:exception}')
+ finally
+ bwipe!
+ endtry
+ endif
+ endfor
+ set shell& shellcmdflag& shellpipe& shellquote&
+ set shellredir& shellxescape& shellxquote&
+ call delete('Xtestout')
+endfunc
+
+" Test for the 'shell' option
+func Test_shell()
+ CheckUnix
+ let save_shell = &shell
+ set shell=
+ let caught_e91 = 0
+ try
+ shell
+ catch /E91:/
+ let caught_e91 = 1
+ endtry
+ call assert_equal(1, caught_e91)
+ let &shell = save_shell
+endfunc
+
+" Test for the 'shellquote' option
+func Test_shellquote()
+ CheckUnix
+ set shellquote=#
+ set verbose=20
+ redir => v
+ silent! !echo Hello
+ redir END
+ set verbose&
+ set shellquote&
+ call assert_match(': "#echo Hello#"', v)
+endfunc
+
+" Test for the 'shellescape' option
+func Test_shellescape()
+ let save_shell = &shell
+ set shell=bash
+ call assert_equal("'text'", shellescape('text'))
+ call assert_equal("'te\"xt'", 'te"xt'->shellescape())
+ call assert_equal("'te'\\''xt'", shellescape("te'xt"))
+
+ call assert_equal("'te%xt'", shellescape("te%xt"))
+ call assert_equal("'te\\%xt'", shellescape("te%xt", 1))
+ call assert_equal("'te#xt'", shellescape("te#xt"))
+ call assert_equal("'te\\#xt'", shellescape("te#xt", 1))
+ call assert_equal("'te!xt'", shellescape("te!xt"))
+ call assert_equal("'te\\!xt'", shellescape("te!xt", 1))
+
+ call assert_equal("'te\nxt'", shellescape("te\nxt"))
+ call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1))
+ set shell=tcsh
+ call assert_equal("'te\\!xt'", shellescape("te!xt"))
+ call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1))
+ call assert_equal("'te\\\nxt'", shellescape("te\nxt"))
+ call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1))
+
+ let &shell = save_shell
+endfunc
+
+" Test for 'shellslash'
+func Test_shellslash()
+ CheckOption shellslash
+ let save_shellslash = &shellslash
+ " The shell and cmdflag, and expected slash in tempname with shellslash set or
+ " unset. The assert checks the file separator before the leafname.
+ " ".*\\\\[^\\\\]*$"
+ let shells = [['cmd', '/c', '\\', '/'],
+ \ ['powershell', '-Command', '\\', '/'],
+ \ ['pwsh', '-Command', '\\', '/'],
+ \ ['pwsh', '-c', '\\', '/'],
+ \ ['sh', '-c', '/', '/']]
+ for e in shells
+ exe 'set shell=' .. e[0] .. ' | set shellcmdflag=' .. e[1]
+ set noshellslash
+ let file = tempname()
+ call assert_match('^.\+' .. e[2] .. '[^' .. e[2] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' nossl')
+ set shellslash
+ let file = tempname()
+ call assert_match('^.\+' .. e[3] .. '[^' .. e[3] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' ssl')
+ endfor
+ let &shellslash = save_shellslash
+endfunc
+
+" Test for 'shellxquote'
+func Test_shellxquote()
+ CheckUnix
+
+ let save_shell = &shell
+ let save_sxq = &shellxquote
+ let save_sxe = &shellxescape
+
+ call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D')
+ call setfperm('Xtestshell', "r-x------")
+ set shell=./Xtestshell
+
+ set shellxquote=\\"
+ call feedkeys(":!pwd\<CR>\<CR>", 'xt')
+ call assert_equal(['Cmd: [-c "pwd"]'], readfile('Xlog'))
+
+ set shellxquote=(
+ call feedkeys(":!pwd\<CR>\<CR>", 'xt')
+ call assert_equal(['Cmd: [-c (pwd)]'], readfile('Xlog'))
+
+ set shellxquote=\\"(
+ call feedkeys(":!pwd\<CR>\<CR>", 'xt')
+ call assert_equal(['Cmd: [-c "(pwd)"]'], readfile('Xlog'))
+
+ set shellxescape=\"&<<()@^
+ set shellxquote=(
+ call feedkeys(":!pwd\"&<<{}@^\<CR>\<CR>", 'xt')
+ call assert_equal(['Cmd: [-c (pwd^"^&^<^<{}^@^^)]'], readfile('Xlog'))
+
+ let &shell = save_shell
+ let &shellxquote = save_sxq
+ let &shellxescape = save_sxe
+ call delete('Xlog')
+endfunc
+
+" Test for using the shell set in the $SHELL environment variable
+func Test_set_shell()
+ let after =<< trim [CODE]
+ call writefile([&shell], "Xtestout")
+ quit!
+ [CODE]
+
+ if has('win32')
+ let $SHELL = 'C:\with space\cmd.exe'
+ let expected = '"C:\with space\cmd.exe"'
+ else
+ let $SHELL = '/bin/with space/sh'
+ let expected = '/bin/with\ space/sh'
+ endif
+
+ if RunVimPiped([], after, '', '')
+ let lines = readfile('Xtestout')
+ call assert_equal(expected, lines[0])
+ endif
+ call delete('Xtestout')
+endfunc
+
+func Test_shell_repeat()
+ CheckUnix
+
+ let save_shell = &shell
+
+ call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D')
+ call setfperm('Xtestshell', "r-x------")
+ set shell=./Xtestshell
+ defer delete('Xlog')
+
+ call feedkeys(":!echo coconut\<CR>", 'xt') " Run command
+ call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))
+
+ call feedkeys(":!!\<CR>", 'xt') " Re-run previous
+ call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))
+
+ call writefile(['empty'], 'Xlog')
+ call feedkeys(":!\<CR>", 'xt') " :!
+ call assert_equal(['Cmd: [-c ]'], readfile('Xlog'))
+
+ call feedkeys(":!!\<CR>", 'xt') " :! doesn't clear previous command
+ call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))
+
+ call feedkeys(":!echo banana\<CR>", 'xt') " Make sure setting previous command keeps working after a :! no-op
+ call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))
+ call feedkeys(":!!\<CR>", 'xt')
+ call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))
+
+ let &shell = save_shell
+endfunc
+
+func Test_shell_no_prevcmd()
+ " this doesn't do anything, just check it doesn't crash
+ let after =<< trim END
+ exe "normal !!\<CR>"
+ call writefile([v:errmsg, 'done'], 'Xtestdone')
+ qall!
+ END
+ if RunVim([], after, '--clean')
+ call assert_equal(['E34: No previous command', 'done'], readfile('Xtestdone'))
+ endif
+ call delete('Xtestdone')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_shift.vim b/src/testdir/test_shift.vim
new file mode 100644
index 0000000..370082f
--- /dev/null
+++ b/src/testdir/test_shift.vim
@@ -0,0 +1,115 @@
+" Test shifting lines with :> and :<
+
+source check.vim
+
+func Test_ex_shift_right()
+ set shiftwidth=2
+
+ " shift right current line.
+ call setline(1, range(1, 5))
+ 2
+ >
+ 3
+ >>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ '5'], getline(1, '$'))
+
+ " shift right with range.
+ call setline(1, range(1, 4))
+ 2,3>>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ '5'], getline(1, '$'))
+
+ " shift right with range and count.
+ call setline(1, range(1, 4))
+ 2>3
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ '5'], getline(1, '$'))
+
+ bw!
+ set shiftwidth&
+endfunc
+
+func Test_ex_shift_left()
+ set shiftwidth=2
+
+ call setline(1, range(1, 5))
+ %>>>
+
+ " left shift current line.
+ 2<
+ 3<<
+ 4<<<<<
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ '4',
+ \ ' 5'], getline(1, '$'))
+
+ " shift right with range.
+ call setline(1, range(1, 5))
+ %>>>
+ 2,3<<
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ ' 5'], getline(1, '$'))
+
+ " shift right with range and count.
+ call setline(1, range(1, 5))
+ %>>>
+ 2<<3
+ call assert_equal([' 1',
+ \ ' 2',
+ \ ' 3',
+ \ ' 4',
+ \ ' 5'], getline(1, '$'))
+
+ bw!
+ set shiftwidth&
+endfunc
+
+func Test_ex_shift_rightleft()
+ CheckFeature rightleft
+
+ set shiftwidth=2 rightleft
+
+ call setline(1, range(1, 4))
+ 2,3<<
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4'], getline(1, '$'))
+
+ 3,4>
+ call assert_equal(['1',
+ \ ' 2',
+ \ ' 3',
+ \ '4'], getline(1, '$'))
+
+ bw!
+ set rightleft& shiftwidth&
+endfunc
+
+func Test_ex_shift_errors()
+ call assert_fails('><', 'E488:')
+ call assert_fails('<>', 'E488:')
+
+ call assert_fails('>!', 'E477:')
+ call assert_fails('<!', 'E477:')
+
+ call assert_fails('2,1>', 'E493:')
+ call assert_fails('2,1<', 'E493:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_short_sleep.py b/src/testdir/test_short_sleep.py
new file mode 100644
index 0000000..a290443
--- /dev/null
+++ b/src/testdir/test_short_sleep.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+#
+# Program that sleeps for 100 msec
+#
+# This requires Python 2.6 or later.
+
+import time
+
+if __name__ == "__main__":
+
+ time.sleep(0.1) # sleep 100 msec
diff --git a/src/testdir/test_shortpathname.vim b/src/testdir/test_shortpathname.vim
new file mode 100644
index 0000000..5964630
--- /dev/null
+++ b/src/testdir/test_shortpathname.vim
@@ -0,0 +1,99 @@
+" Test for shortpathname ':8' extension.
+" Only for use on Win32 systems!
+
+set encoding=utf-8
+scriptencoding utf-8
+
+source check.vim
+CheckMSWindows
+
+func TestIt(file, bits, expected)
+ let res = fnamemodify(a:file, a:bits)
+ if a:expected != ''
+ call assert_equal(substitute(a:expected, '/', '\\', 'g'),
+ \ substitute(res, '/', '\\', 'g'),
+ \ "'" . a:file . "'->(" . a:bits . ")->'" . res . "'")
+ endif
+endfunc
+
+func Test_ColonEight()
+ let save_dir = getcwd()
+
+ " This could change for CygWin to //cygdrive/c .
+ let dir1 = 'c:/x.x.y'
+ let trycount = 5
+ while 1
+ if !filereadable(dir1) && !isdirectory(dir1)
+ break
+ endif
+ if trycount == 1
+ call assert_report("Fatal: '" . dir1 . "' exists, cannot run this test")
+ return
+ endif
+ " When tests run in parallel the directory may exist, wait a bit until it
+ " is gone.
+ sleep 5
+ let trycount -= 1
+ endwhile
+
+ let file1 = dir1 . '/zz.y.txt'
+ let nofile1 = dir1 . '/z.y.txt'
+ let dir2 = dir1 . '/VimIsTheGreatestSinceSlicedBread'
+ let file2 = dir2 . '/z.txt'
+ let nofile2 = dir2 . '/zz.txt'
+
+ call mkdir(dir1, 'D')
+ let resdir1 = substitute(fnamemodify(dir1, ':p:8'), '/$', '', '')
+ call assert_match('\V\^c:/XX\x\x\x\x~1.Y\$', resdir1)
+
+ let resfile1 = resdir1 . '/ZZY~1.TXT'
+ let resnofile1 = resdir1 . '/z.y.txt'
+ let resdir2 = resdir1 . '/VIMIST~1'
+ let resfile2 = resdir2 . '/z.txt'
+ let resnofile2 = resdir2 . '/zz.txt'
+
+ call mkdir(dir2, 'D')
+ call writefile([], file1, 'D')
+ call writefile([], file2, 'D')
+
+ call TestIt(file1, ':p:8', resfile1)
+ call TestIt(nofile1, ':p:8', resnofile1)
+ call TestIt(file2, ':p:8', resfile2)
+ call TestIt(nofile2, ':p:8', resnofile2)
+ call TestIt(nofile2, ':p:8:h', fnamemodify(resnofile2, ':h'))
+ call chdir(dir1)
+ call TestIt(file1, ':.:8', strpart(resfile1, strlen(resdir1)+1))
+ call TestIt(nofile1, ':.:8', strpart(resnofile1, strlen(resdir1)+1))
+ call TestIt(file2, ':.:8', strpart(resfile2, strlen(resdir1)+1))
+ call TestIt(nofile2, ':.:8', strpart(resnofile2, strlen(resdir1)+1))
+ let $HOME=dir1
+ call TestIt(file1, ':~:8', '~' . strpart(resfile1, strlen(resdir1)))
+ call TestIt(nofile1, ':~:8', '~' . strpart(resnofile1, strlen(resdir1)))
+ call TestIt(file2, ':~:8', '~' . strpart(resfile2, strlen(resdir1)))
+ call TestIt(nofile2, ':~:8', '~' . strpart(resnofile2, strlen(resdir1)))
+
+ cd c:/
+
+ call chdir(save_dir)
+endfunc
+
+func Test_ColonEight_MultiByte()
+ let dir = 'Xtest'
+
+ let file = dir . '/日本語ã®ãƒ•ã‚¡ã‚¤ãƒ«.txt'
+
+ call mkdir(dir, 'D')
+ call writefile([], file, 'D')
+
+ let sfile = fnamemodify(file, ':8')
+
+ call assert_notequal(file, sfile)
+ call assert_match('\~', sfile)
+endfunc
+
+func Test_ColonEight_notexists()
+ let non_exists='C:\windows\newfile.txt'
+ call assert_equal(non_exists, fnamemodify(non_exists, ':p:8'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_signals.vim b/src/testdir/test_signals.vim
new file mode 100644
index 0000000..4b6c734
--- /dev/null
+++ b/src/testdir/test_signals.vim
@@ -0,0 +1,205 @@
+" Test signal handling.
+
+source check.vim
+source term_util.vim
+
+CheckUnix
+
+source shared.vim
+
+" Check whether a signal is available on this system.
+func HasSignal(signal)
+ let signals = system('kill -l')
+ return signals =~# '\<' .. a:signal .. '\>'
+endfunc
+
+" Test signal WINCH (window resize signal)
+func Test_signal_WINCH()
+ CheckNotGui
+ if !HasSignal('WINCH')
+ throw 'Skipped: WINCH signal not supported'
+ endif
+
+ " We do not actually want to change the size of the terminal.
+ let old_WS = ''
+ if exists('&t_WS')
+ let old_WS = &t_WS
+ let &t_WS = ''
+ endif
+
+ let old_lines = &lines
+ let old_columns = &columns
+ let new_lines = &lines - 2
+ let new_columns = &columns - 2
+
+ exe 'set lines=' .. new_lines
+ exe 'set columns=' .. new_columns
+ call assert_equal(new_lines, &lines)
+ call assert_equal(new_columns, &columns)
+
+ " Send signal and wait for signal to be processed.
+ " 'lines' and 'columns' should have been restored
+ " after handing signal WINCH.
+ exe 'silent !kill -s WINCH ' .. getpid()
+ call WaitForAssert({-> assert_equal(old_lines, &lines)})
+ call assert_equal(old_columns, &columns)
+
+ if old_WS != ''
+ let &t_WS = old_WS
+ endif
+endfunc
+
+" Test signal PWR, which should update the swap file.
+func Test_signal_PWR()
+ if !HasSignal('PWR')
+ throw 'Skipped: PWR signal not supported'
+ endif
+
+ " Set a very large 'updatetime' and 'updatecount', so that we can be sure
+ " that swap file is updated as a result of sending PWR signal, and not
+ " because of exceeding 'updatetime' or 'updatecount' when changing buffer.
+ set updatetime=100000 updatecount=100000
+ new Xtest_signal_PWR
+ let swap_name = swapname('%')
+ call setline(1, '123')
+ preserve
+ let swap_content = readfile(swap_name, 'b')
+
+ " Update the buffer and check that the swap file is not yet updated,
+ " since we set 'updatetime' and 'updatecount' to large values.
+ call setline(1, 'abc')
+ call assert_equal(swap_content, readfile(swap_name, 'b'))
+
+ " Sending PWR signal should update the swap file.
+ exe 'silent !kill -s PWR ' .. getpid()
+ call WaitForAssert({-> assert_notequal(swap_content, readfile(swap_name, 'b'))})
+
+ bwipe!
+ set updatetime& updatecount&
+endfunc
+
+" Test signal INT. Handler sets got_int. It should be like typing CTRL-C.
+func Test_signal_INT()
+ CheckRunVimInTerminal
+ if !HasSignal('INT')
+ throw 'Skipped: INT signal not supported'
+ endif
+
+ let buf = RunVimInTerminal('', {'rows': 6})
+ let pid_vim = term_getjob(buf)->job_info().process
+
+ " Check that an endless loop in Vim is interrupted by signal INT.
+ call term_sendkeys(buf, ":call setline(1, 'running')\n")
+ call term_sendkeys(buf, ":while 1 | endwhile\n")
+ call WaitForAssert({-> assert_equal(':while 1 | endwhile', term_getline(buf, 6))})
+ exe 'silent !kill -s INT ' .. pid_vim
+ sleep 50m
+ call term_sendkeys(buf, ":call setline(1, 'INTERRUPTED')\n")
+ call WaitForAssert({-> assert_equal('INTERRUPTED', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test signal TSTP. Handler sets got_tstp.
+func Test_signal_TSTP()
+ CheckRunVimInTerminal
+ if !HasSignal('TSTP')
+ throw 'Skipped: TSTP signal not supported'
+ endif
+
+ " If test fails once, it can leave temporary files and trying to rerun
+ " the test would then fail again if they are not deleted first.
+ call delete('.Xsig_TERM.swp')
+ call delete('XsetupAucmd')
+ call delete('XautoOut1')
+ call delete('XautoOut2')
+ let lines =<< trim END
+ au VimSuspend * call writefile(["VimSuspend triggered"], "XautoOut1", "as")
+ au VimResume * call writefile(["VimResume triggered"], "XautoOut2", "as")
+ END
+ call writefile(lines, 'XsetupAucmd', 'D')
+
+ let buf = RunVimInTerminal('-S XsetupAucmd Xsig_TERM', {'rows': 6})
+ let pid_vim = term_getjob(buf)->job_info().process
+
+ call term_sendkeys(buf, ":call setline(1, 'foo')\n")
+ call WaitForAssert({-> assert_equal('foo', term_getline(buf, 1))})
+
+ call assert_false(filereadable('Xsig_TERM'))
+
+ " After TSTP the file is not saved (same function as ^Z)
+ exe 'silent !kill -s TSTP ' .. pid_vim
+ call WaitForAssert({-> assert_true(filereadable('.Xsig_TERM.swp'))})
+ sleep 100m
+
+ " We resume after the suspend. Sleep a bit for the signal to take effect,
+ " also when running under valgrind.
+ exe 'silent !kill -s CONT ' .. pid_vim
+ call WaitForAssert({-> assert_true(filereadable('XautoOut2'))})
+ sleep 10m
+
+ call StopVimInTerminal(buf)
+
+ let result = readfile('XautoOut1')
+ call assert_equal(["VimSuspend triggered"], result)
+ let result = readfile('XautoOut2')
+ call assert_equal(["VimResume triggered"], result)
+
+ %bwipe!
+ call delete('.Xsig_TERM.swp')
+ call delete('XautoOut1')
+ call delete('XautoOut2')
+endfunc
+
+" Test a deadly signal.
+"
+" There are several deadly signals: SISEGV, SIBUS, SIGTERM...
+" Test uses signal SIGTERM as it does not create a core
+" dump file unlike SIGSEGV, SIGBUS, etc. See "man 7 signals.
+"
+" Vim should exit with a deadly signal and unsaved changes
+" should be recoverable from the swap file preserved as a
+" result of the deadly signal handler.
+func Test_deadly_signal_TERM()
+ if !HasSignal('TERM')
+ throw 'Skipped: TERM signal not supported'
+ endif
+ CheckRunVimInTerminal
+
+ " If test fails once, it can leave temporary files and trying to rerun
+ " the test would then fail again if they are not deleted first.
+ call delete('.Xsig_TERM.swp')
+ call delete('XsetupAucmd')
+ call delete('XautoOut')
+ let lines =<< trim END
+ au VimLeave * call writefile(["VimLeave triggered"], "XautoOut", "as")
+ au VimLeavePre * call writefile(["VimLeavePre triggered"], "XautoOut", "as")
+ END
+ call writefile(lines, 'XsetupAucmd', 'D')
+
+ let buf = RunVimInTerminal('-S XsetupAucmd Xsig_TERM', {'rows': 6})
+ let pid_vim = term_getjob(buf)->job_info().process
+
+ call term_sendkeys(buf, ":call setline(1, 'foo')\n")
+ call WaitForAssert({-> assert_equal('foo', term_getline(buf, 1))})
+
+ call assert_false(filereadable('Xsig_TERM'))
+ exe 'silent !kill -s TERM ' .. pid_vim
+ call WaitForAssert({-> assert_true(filereadable('.Xsig_TERM.swp'))})
+
+ " Don't call StopVimInTerminal() as it expects job to be still running.
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))})
+
+ new
+ silent recover .Xsig_TERM.swp
+ call assert_equal(['foo'], getline(1, '$'))
+
+ let result = readfile('XautoOut')
+ call assert_equal(["VimLeavePre triggered", "VimLeave triggered"], result)
+
+ %bwipe!
+ call delete('.Xsig_TERM.swp')
+ call delete('XautoOut')
+endfunc
+
+" vim: ts=8 sw=2 sts=2 tw=80 fdm=marker
diff --git a/src/testdir/test_signs.vim b/src/testdir/test_signs.vim
new file mode 100644
index 0000000..95e8d44
--- /dev/null
+++ b/src/testdir/test_signs.vim
@@ -0,0 +1,2062 @@
+" Test for signs
+
+source check.vim
+CheckFeature signs
+
+source screendump.vim
+
+func Test_sign()
+ new
+ call setline(1, ['a', 'b', 'c', 'd'])
+
+ " Define some signs.
+ " We can specify icons even if not all versions of vim support icons as
+ " icon is ignored when not supported. "(not supported)" is shown after
+ " the icon name when listing signs.
+ sign define Sign1 text=x
+
+ call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search numhl=Number icon=../../pixmaps/stock_vim_find_help.png')
+
+ " Test listing signs.
+ let a=execute('sign list')
+ call assert_match('^\nsign Sign1 text=x \nsign Sign2 ' .
+ \ 'icon=../../pixmaps/stock_vim_find_help.png .*text=xy ' .
+ \ 'linehl=Error texthl=Title culhl=Search numhl=Number$', a)
+
+ let a=execute('sign list Sign1')
+ call assert_equal("\nsign Sign1 text=x ", a)
+
+ " Split the window to the bottom to verify sign jump will stay in the
+ " current window if the buffer is displayed there.
+ let bn = bufnr('%')
+ let wn = winnr()
+ exe 'sign place 41 line=3 name=Sign1 buffer=' . bn
+ 1
+ bot split
+ exe 'sign jump 41 buffer=' . bufnr('%')
+ call assert_equal('c', getline('.'))
+ call assert_equal(3, winnr())
+ call assert_equal(bn, bufnr('%'))
+ call assert_notequal(wn, winnr())
+
+ " Create a new buffer and check that ":sign jump" switches to the old buffer.
+ 1
+ new foo
+ call assert_notequal(bn, bufnr('%'))
+ exe 'sign jump 41 buffer=' . bn
+ call assert_equal(bn, bufnr('%'))
+ call assert_equal('c', getline('.'))
+
+ " Redraw to make sure that screen redraw with sign gets exercised,
+ " with and without 'rightleft'.
+ if has('rightleft')
+ set rightleft
+ redraw
+ set norightleft
+ endif
+ redraw
+
+ " Check that we can't change sign.
+ call assert_fails("sign place 40 name=Sign1 buffer=" . bufnr('%'), 'E885:')
+
+ " Check placed signs
+ let a=execute('sign place')
+ call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n" .
+ \ " line=3 id=41 name=Sign1 priority=10\n", a)
+
+ " Unplace the sign and try jumping to it again should fail.
+ sign unplace 41
+ 1
+ call assert_fails("sign jump 41 buffer=" . bufnr('%'), 'E157:')
+ call assert_equal('a', getline('.'))
+
+ " Unplace sign on current line.
+ exe 'sign place 42 line=4 name=Sign2 buffer=' . bufnr('%')
+ 4
+ sign unplace
+ let a=execute('sign place')
+ call assert_equal("\n--- Signs ---\n", a)
+
+ " Try again to unplace sign on current line, it should fail this time.
+ call assert_fails('sign unplace', 'E159:')
+
+ " Unplace all signs.
+ exe 'sign place 41 line=3 name=Sign1 buffer=' . bufnr('%')
+ sign unplace *
+ let a=execute('sign place')
+ call assert_equal("\n--- Signs ---\n", a)
+
+ " Place a sign without specifying the filename or buffer
+ sign place 77 line=9 name=Sign2
+ let a=execute('sign place')
+ call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n" .
+ \ " line=9 id=77 name=Sign2 priority=10\n", a)
+ sign unplace *
+
+ " Check :jump with file=...
+ edit foo
+ call setline(1, ['A', 'B', 'C', 'D'])
+
+ call Sign_command_ignore_error('sign define Sign3 text=y texthl=DoesNotExist linehl=DoesNotExist icon=doesnotexist.xpm')
+
+ let fn = expand('%:p')
+ exe 'sign place 43 line=2 name=Sign3 file=' . fn
+ edit bar
+ call assert_notequal(fn, expand('%:p'))
+ exe 'sign jump 43 file=' . fn
+ call assert_equal('B', getline('.'))
+
+ " Check for jumping to a sign in a hidden buffer
+ enew! | only!
+ edit foo
+ call setline(1, ['A', 'B', 'C', 'D'])
+ let fn = expand('%:p')
+ exe 'sign place 21 line=3 name=Sign3 file=' . fn
+ hide edit bar
+ exe 'sign jump 21 file=' . fn
+ call assert_equal('C', getline('.'))
+
+ " can't define a sign with a non-printable character as text
+ call assert_fails("sign define Sign4 text=\e linehl=Comment", 'E239:')
+ call assert_fails("sign define Sign4 text=a\e linehl=Comment", 'E239:')
+ call assert_fails("sign define Sign4 text=\ea linehl=Comment", 'E239:')
+
+ " Only 1 or 2 character text is allowed
+ call assert_fails("sign define Sign4 text=abc linehl=Comment", 'E239:')
+ call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:')
+ call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:')
+
+ " an empty highlight argument for an existing sign clears it
+ sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl numhl=NumHl
+ let sl = sign_getdefined('SignY')[0]
+ call assert_equal('TextHl', sl.texthl)
+ call assert_equal('CulHl', sl.culhl)
+ call assert_equal('LineHl', sl.linehl)
+ call assert_equal('NumHl', sl.numhl)
+
+ sign define SignY texthl= culhl=CulHl linehl=LineHl numhl=NumHl
+ let sl = sign_getdefined('SignY')[0]
+ call assert_false(has_key(sl, 'texthl'))
+ call assert_equal('CulHl', sl.culhl)
+ call assert_equal('LineHl', sl.linehl)
+ call assert_equal('NumHl', sl.numhl)
+
+ sign define SignY linehl=
+ let sl = sign_getdefined('SignY')[0]
+ call assert_false(has_key(sl, 'linehl'))
+ call assert_equal('CulHl', sl.culhl)
+ call assert_equal('NumHl', sl.numhl)
+
+ sign define SignY culhl=
+ let sl = sign_getdefined('SignY')[0]
+ call assert_false(has_key(sl, 'culhl'))
+ call assert_equal('NumHl', sl.numhl)
+
+ sign define SignY numhl=
+ let sl = sign_getdefined('SignY')[0]
+ call assert_false(has_key(sl, 'numhl'))
+
+ sign undefine SignY
+
+ " define sign with whitespace
+ sign define Sign4 text=\ X linehl=Comment
+ sign undefine Sign4
+ sign define Sign4 linehl=Comment text=\ X
+ sign undefine Sign4
+
+ sign define Sign5 text=X\ linehl=Comment
+ sign undefine Sign5
+ sign define Sign5 linehl=Comment text=X\
+ sign undefine Sign5
+
+ " define sign with backslash
+ sign define Sign4 text=\\\\ linehl=Comment
+ sign undefine Sign4
+ sign define Sign4 text=\\ linehl=Comment
+ sign undefine Sign4
+
+ " define a sign with a leading 0 in the name
+ sign unplace *
+ sign define 004 text=#> linehl=Comment
+ let a = execute('sign list 4')
+ call assert_equal("\nsign 4 text=#> linehl=Comment", a)
+ exe 'sign place 20 line=3 name=004 buffer=' . bufnr('')
+ let a = execute('sign place')
+ call assert_equal("\n--- Signs ---\nSigns for foo:\n" .
+ \ " line=3 id=20 name=4 priority=10\n", a)
+ exe 'sign unplace 20 buffer=' . bufnr('')
+ sign undefine 004
+ call assert_fails('sign list 4', 'E155:')
+
+ " After undefining the sign, we should no longer be able to place it.
+ sign undefine Sign1
+ sign undefine Sign2
+ sign undefine Sign3
+ call assert_fails("sign place 41 line=3 name=Sign1 buffer=" .
+ \ bufnr('%'), 'E155:')
+endfunc
+
+func Test_sign_many_bytes()
+ new
+ set signcolumn=number
+ set number
+ call setline(1, 'some text')
+ " composing characters can use many bytes, check for overflow
+ sign define manyBytes text=▶᷄᷅᷆◀᷄᷅᷆᷇
+ sign place 17 line=1 name=manyBytes
+ redraw
+
+ bwipe!
+ sign undefine manyBytes
+endfunc
+
+" Undefining placed sign is not recommended.
+" Quoting :help sign
+"
+" :sign undefine {name}
+" Deletes a previously defined sign. If signs with this {name}
+" are still placed this will cause trouble.
+func Test_sign_undefine_still_placed()
+ new foobar
+ sign define Sign text=x
+ exe 'sign place 41 line=1 name=Sign buffer=' . bufnr('%')
+ sign undefine Sign
+
+ " Listing placed sign should show that sign is deleted.
+ let a=execute('sign place')
+ call assert_equal("\n--- Signs ---\nSigns for foobar:\n" .
+ \ " line=1 id=41 name=[Deleted] priority=10\n", a)
+
+ sign unplace 41
+ let a=execute('sign place')
+ call assert_equal("\n--- Signs ---\n", a)
+endfunc
+
+func Test_sign_completion()
+ sign define Sign1 text=x
+ sign define Sign2 text=y
+
+ call feedkeys(":sign \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign define jump list place undefine unplace', @:)
+
+ call feedkeys(":sign define Sign \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign define Sign culhl= icon= linehl= numhl= text= texthl=', @:)
+
+ for hl in ['culhl', 'linehl', 'numhl', 'texthl']
+ call feedkeys(":sign define Sign "..hl.."=Spell\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign define Sign '..hl..'=SpellBad SpellCap ' .
+ \ 'SpellLocal SpellRare', @:)
+ endfor
+
+ call writefile(repeat(["Sun is shining"], 30), "XsignOne", 'D')
+ call writefile(repeat(["Sky is blue"], 30), "XsignTwo", 'D')
+ call feedkeys(":sign define Sign icon=Xsig\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign define Sign icon=XsignOne XsignTwo', @:)
+
+ " Test for completion of arguments to ':sign undefine'
+ call feedkeys(":sign undefine \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign undefine Sign1 Sign2', @:)
+
+ call feedkeys(":sign place 1 \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 buffer= file= group= line= name= priority=',
+ \ @:)
+
+ call feedkeys(":sign place 1 name=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 name=Sign1 Sign2', @:)
+
+ edit XsignOne
+ sign place 1 name=Sign1 line=5
+ sign place 1 name=Sign1 group=g1 line=10
+ edit XsignTwo
+ sign place 1 name=Sign2 group=g2 line=15
+
+ " Test for completion of group= and file= arguments to ':sign place'
+ call feedkeys(":sign place 1 name=Sign1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 name=Sign1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign place 1 name=Sign1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 name=Sign1 group=g1 g2', @:)
+
+ " Test for completion of arguments to 'sign place' without sign identifier
+ call feedkeys(":sign place \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place buffer= file= group=', @:)
+ call feedkeys(":sign place file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign place group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place group=g1 g2', @:)
+ call feedkeys(":sign place group=g1 file=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place group=g1 file=XsignOne XsignTwo', @:)
+
+ " Test for completion of arguments to ':sign unplace'
+ call feedkeys(":sign unplace 1 \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 buffer= file= group=', @:)
+ call feedkeys(":sign unplace 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign unplace 1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 group=g1 g2', @:)
+ call feedkeys(":sign unplace 1 group=g2 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 group=g2 file=XsignOne XsignTwo', @:)
+
+ " Test for completion of arguments to ':sign list'
+ call feedkeys(":sign list \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign list Sign1 Sign2', @:)
+
+ " Test for completion of arguments to ':sign jump'
+ call feedkeys(":sign jump 1 \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign jump 1 buffer= file= group=', @:)
+ call feedkeys(":sign jump 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign jump 1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign jump 1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign jump 1 group=g1 g2', @:)
+
+ " Error cases
+ call feedkeys(":sign here\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign here', @:)
+ call feedkeys(":sign define Sign here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign define Sign here=\<C-A>", @:)
+ call feedkeys(":sign place 1 here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign place 1 here=\<C-A>", @:)
+ call feedkeys(":sign jump 1 here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign jump 1 here=\<C-A>", @:)
+ call feedkeys(":sign here there\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign here there\<C-A>", @:)
+ call feedkeys(":sign here there=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign here there=\<C-A>", @:)
+
+ sign unplace * group=*
+ sign undefine Sign1
+ sign undefine Sign2
+ enew
+endfunc
+
+func Test_sign_invalid_commands()
+ sign define Sign1 text=x
+
+ call assert_fails('sign', 'E471:')
+ call assert_fails('sign jump', 'E471:')
+ call assert_fails('sign xxx', 'E160:')
+ call assert_fails('sign define', 'E156:')
+ call assert_fails('sign define Sign1 xxx', 'E475:')
+ call assert_fails('sign undefine', 'E156:')
+ call assert_fails('sign list xxx', 'E155:')
+ call assert_fails('sign place 1 buffer=999', 'E158:')
+ call assert_fails('sign place 1 name=Sign1 buffer=999', 'E158:')
+ call assert_fails('sign place buffer=999', 'E158:')
+ call assert_fails('sign jump buffer=999', 'E158:')
+ call assert_fails('sign jump 1 file=', 'E158:')
+ call assert_fails('sign jump 1 group=', 'E474:')
+ call assert_fails('sign jump 1 name=', 'E474:')
+ call assert_fails('sign jump 1 name=Sign1', 'E474:')
+ call assert_fails('sign jump 1 line=100', '474:')
+ call assert_fails('sign define Sign2 text=', 'E239:')
+ " Non-numeric identifier for :sign place
+ call assert_fails("sign place abc line=3 name=Sign1 buffer=" . bufnr(''),
+ \ 'E474:')
+ " Non-numeric identifier for :sign unplace
+ call assert_fails("sign unplace abc name=Sign1 buffer=" . bufnr(''),
+ \ 'E474:')
+ " Number followed by an alphabet as sign identifier for :sign place
+ call assert_fails("sign place 1abc line=3 name=Sign1 buffer=" . bufnr(''),
+ \ 'E474:')
+ " Number followed by an alphabet as sign identifier for :sign unplace
+ call assert_fails("sign unplace 2abc name=Sign1 buffer=" . bufnr(''),
+ \ 'E474:')
+ " Sign identifier and '*' for :sign unplace
+ call assert_fails("sign unplace 2 *", 'E474:')
+ " Trailing characters after buffer number for :sign place
+ call assert_fails("sign place 1 line=3 name=Sign1 buffer=" .
+ \ bufnr('%') . 'xxx', 'E488:')
+ " Trailing characters after buffer number for :sign unplace
+ call assert_fails("sign unplace 1 buffer=" . bufnr('%') . 'xxx', 'E488:')
+ call assert_fails("sign unplace * buffer=" . bufnr('%') . 'xxx', 'E488:')
+ call assert_fails("sign unplace 1 xxx", 'E474:')
+ call assert_fails("sign unplace * xxx", 'E474:')
+ call assert_fails("sign unplace xxx", 'E474:')
+ " Placing a sign without line number
+ call assert_fails("sign place name=Sign1 buffer=" . bufnr('%'), 'E474:')
+ " Placing a sign without sign name
+ call assert_fails("sign place line=10 buffer=" . bufnr('%'), 'E474:')
+ " Unplacing a sign with line number
+ call assert_fails("sign unplace 2 line=10 buffer=" . bufnr('%'), 'E474:')
+ " Unplacing a sign with sign name
+ call assert_fails("sign unplace 2 name=Sign1 buffer=" . bufnr('%'), 'E474:')
+ " Placing a sign without sign name
+ call assert_fails("sign place 2 line=3 buffer=" . bufnr('%'), 'E474:')
+ " Placing a sign with only sign identifier
+ call assert_fails("sign place 2", 'E474:')
+ " Placing a sign with only a name
+ call assert_fails("sign place abc", 'E474:')
+ " Placing a sign with only line number
+ call assert_fails("sign place 5 line=3", 'E474:')
+ " Placing a sign with only sign group
+ call assert_fails("sign place 5 group=g1", 'E474:')
+ call assert_fails("sign place 5 group=*", 'E474:')
+ " Placing a sign with only sign priority
+ call assert_fails("sign place 5 priority=10", 'E474:')
+
+ sign undefine Sign1
+endfunc
+
+func Test_sign_delete_buffer()
+ new
+ sign define Sign text=x
+ let bufnr = bufnr('%')
+ new
+ exe 'bd ' . bufnr
+ exe 'sign place 61 line=3 name=Sign buffer=' . bufnr
+ call assert_fails('sign jump 61 buffer=' . bufnr, 'E934:')
+ sign unplace 61
+ sign undefine Sign
+endfunc
+
+" Ignore error: E255: Couldn't read in sign data!
+" This error can happen when running in the GUI.
+" Some gui like Motif do not support the png icon format.
+func Sign_command_ignore_error(cmd)
+ try
+ exe a:cmd
+ catch /E255:/
+ endtry
+endfunc
+
+" ignore error: E255: Couldn't read in sign data!
+" This error can happen when running in gui.
+func Sign_define_ignore_error(name, attr)
+ try
+ call sign_define(a:name, a:attr)
+ catch /E255:/
+ endtry
+endfunc
+
+" Test for Vim script functions for managing signs
+func Test_sign_funcs()
+ " Remove all the signs
+ call sign_unplace('*')
+ call sign_undefine()
+
+ " Tests for sign_define()
+ let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error',
+ \ 'culhl': 'Visual', 'numhl': 'Number'}
+ call assert_equal(0, "sign1"->sign_define(attr))
+ call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', 'linehl' : 'Search',
+ \ 'culhl' : 'Visual', 'numhl': 'Number', 'text' : '=>'}],
+ \ sign_getdefined())
+
+ " Define a new sign without attributes and then update it
+ call sign_define("sign2")
+ let attr = {'text' : '!!', 'linehl' : 'DiffAdd', 'texthl' : 'DiffChange',
+ \ 'culhl': 'DiffDelete', 'numhl': 'Number', 'icon' : 'sign2.ico'}
+ call Sign_define_ignore_error("sign2", attr)
+ call assert_equal([{'name' : 'sign2', 'texthl' : 'DiffChange',
+ \ 'linehl' : 'DiffAdd', 'culhl' : 'DiffDelete', 'text' : '!!',
+ \ 'numhl': 'Number', 'icon' : 'sign2.ico'}],
+ \ "sign2"->sign_getdefined())
+
+ " Test for a sign name with digits
+ call assert_equal(0, sign_define(0002, {'linehl' : 'StatusLine'}))
+ call assert_equal([{'name' : '2', 'linehl' : 'StatusLine'}],
+ \ sign_getdefined(0002))
+ eval 0002->sign_undefine()
+
+ " Tests for invalid arguments to sign_define()
+ call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:')
+ call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
+ call assert_fails('call sign_define({})', 'E731:')
+ call assert_fails('call sign_define("sign6", [])', 'E1206:')
+
+ " Tests for sign_getdefined()
+ call assert_equal([], sign_getdefined("none"))
+ call assert_fails('call sign_getdefined({})', 'E731:')
+
+ " Tests for sign_place()
+ call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D')
+ edit Xsign
+
+ call assert_equal(10, sign_place(10, '', 'sign1', 'Xsign',
+ \ {'lnum' : 20}))
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
+ \ 'priority' : 10}]}], sign_getplaced('Xsign'))
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
+ \ 'priority' : 10}]}],
+ \ '%'->sign_getplaced({'lnum' : 20}))
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
+ \ 'priority' : 10}]}],
+ \ sign_getplaced('', {'id' : 10}))
+
+ " Tests for invalid arguments to sign_place()
+ call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:')
+ call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:')
+ call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])', 'E1206:')
+ call assert_fails('call sign_place(-1, "", "sign1", "Xsign",
+ \ {"lnum" : 30})', 'E474:')
+ call assert_fails('call sign_place(10, "", "xsign1x", "Xsign",
+ \ {"lnum" : 30})', 'E155:')
+ call assert_fails('call sign_place(10, "", "", "Xsign",
+ \ {"lnum" : 30})', 'E155:')
+ call assert_fails('call sign_place(10, "", [], "Xsign",
+ \ {"lnum" : 30})', 'E730:')
+ call assert_fails('call sign_place(5, "", "sign1", "abcxyz.xxx",
+ \ {"lnum" : 10})', 'E158:')
+ call assert_fails('call sign_place(5, "", "sign1", "@", {"lnum" : 10})',
+ \ 'E158:')
+ call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})',
+ \ 'E730:')
+ call assert_fails('call sign_place(21, "", "sign1", "Xsign",
+ \ {"lnum" : -1})', 'E474:')
+ call assert_fails('call sign_place(22, "", "sign1", "Xsign",
+ \ {"lnum" : 0})', 'E474:')
+ call assert_fails('call sign_place(22, "", "sign1", "Xsign",
+ \ {"lnum" : []})', 'E745:')
+ call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10}))
+
+ " Tests for sign_getplaced()
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
+ \ 'priority' : 10}]}],
+ \ sign_getplaced(bufnr('Xsign')))
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1',
+ \ 'priority' : 10}]}],
+ \ sign_getplaced())
+ call assert_fails("call sign_getplaced('dummy.sign')", 'E158:')
+ call assert_fails('call sign_getplaced("&")', 'E158:')
+ call assert_fails('call sign_getplaced(-1)', 'E158:')
+ call assert_fails('call sign_getplaced("Xsign", [])', 'E1206:')
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}],
+ \ sign_getplaced('Xsign', {'lnum' : 1000000}))
+ call assert_fails("call sign_getplaced('Xsign', {'lnum' : []})",
+ \ 'E745:')
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}],
+ \ sign_getplaced('Xsign', {'id' : 44}))
+ call assert_fails("call sign_getplaced('Xsign', {'id' : []})",
+ \ 'E745:')
+
+ " Tests for sign_unplace()
+ eval 20->sign_place('', 'sign2', 'Xsign', {"lnum" : 30})
+ call assert_equal(0, sign_unplace('',
+ \ {'id' : 20, 'buffer' : 'Xsign'}))
+ call assert_equal(-1, ''->sign_unplace(
+ \ {'id' : 30, 'buffer' : 'Xsign'}))
+ call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30})
+ call assert_fails("call sign_unplace('',
+ \ {'id' : 20, 'buffer' : 'buffer.c'})", 'E158:')
+ call assert_fails("call sign_unplace('',
+ \ {'id' : 20, 'buffer' : '&'})", 'E158:')
+ call assert_fails("call sign_unplace('g1',
+ \ {'id' : 20, 'buffer' : 200})", 'E158:')
+ call assert_fails("call sign_unplace('g1', 'mySign')", 'E1206:')
+
+ call sign_unplace('*')
+
+ " Test for modifying a placed sign
+ call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20}))
+ call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign'))
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2',
+ \ 'priority' : 10}]}],
+ \ sign_getplaced())
+
+ " Tests for sign_undefine()
+ call assert_equal(0, sign_undefine("sign1"))
+ call assert_equal([], sign_getdefined("sign1"))
+ call assert_fails('call sign_undefine("none")', 'E155:')
+ call assert_fails('call sign_undefine({})', 'E731:')
+
+ " Test for using '.' as the line number for sign_place()
+ call Sign_define_ignore_error("sign1", attr)
+ call cursor(22, 1)
+ call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign',
+ \ {'lnum' : '.'}))
+ call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+ \ [{'id' : 15, 'group' : '', 'lnum' : 22, 'name' : 'sign1',
+ \ 'priority' : 10}]}],
+ \ sign_getplaced('%', {'lnum' : 22}))
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew | only
+endfunc
+
+" Tests for sign groups
+func Test_sign_group()
+ enew | only
+ " Remove all the signs
+ call sign_unplace('*')
+ call sign_undefine()
+
+ call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D')
+
+ let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
+ call assert_equal(0, sign_define("sign1", attr))
+
+ edit Xsign
+ let bnum = bufnr('%')
+
+ " Error case
+ call assert_fails("call sign_place(5, [], 'sign1', 'Xsign',
+ \ {'lnum' : 30})", 'E730:')
+
+ " place three signs with the same identifier. One in the global group and
+ " others in the named groups
+ call assert_equal(5, sign_place(5, '', 'sign1', 'Xsign',
+ \ {'lnum' : 10}))
+ call assert_equal(5, sign_place(5, 'g1', 'sign1', bnum, {'lnum' : 20}))
+ call assert_equal(5, sign_place(5, 'g2', 'sign1', bnum, {'lnum' : 30}))
+
+ " Test for sign_getplaced() with group
+ let s = sign_getplaced('Xsign')
+ call assert_equal(1, len(s[0].signs))
+ call assert_equal(s[0].signs[0].group, '')
+ let s = sign_getplaced(bnum, {'group' : ''})
+ call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
+ \ 'priority' : 10}], s[0].signs)
+ call assert_equal(1, len(s[0].signs))
+ let s = sign_getplaced(bnum, {'group' : 'g2'})
+ call assert_equal('g2', s[0].signs[0].group)
+ let s = sign_getplaced(bnum, {'group' : 'g3'})
+ call assert_equal([], s[0].signs)
+ let s = sign_getplaced(bnum, {'group' : '*'})
+ call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
+ \ 'priority' : 10},
+ \ {'id' : 5, 'group' : 'g1', 'name' : 'sign1', 'lnum' : 20,
+ \ 'priority' : 10},
+ \ {'id' : 5, 'group' : 'g2', 'name' : 'sign1', 'lnum' : 30,
+ \ 'priority' : 10}],
+ \ s[0].signs)
+
+ " Test for sign_getplaced() with id
+ let s = sign_getplaced(bnum, {'id' : 5})
+ call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ let s = sign_getplaced(bnum, {'id' : 5, 'group' : 'g2'})
+ call assert_equal(
+ \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ let s = sign_getplaced(bnum, {'id' : 5, 'group' : '*'})
+ call assert_equal([{'id' : 5, 'group' : '', 'name' : 'sign1', 'lnum' : 10,
+ \ 'priority' : 10},
+ \ {'id' : 5, 'group' : 'g1', 'name' : 'sign1', 'lnum' : 20,
+ \ 'priority' : 10},
+ \ {'id' : 5, 'group' : 'g2', 'name' : 'sign1', 'lnum' : 30,
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ let s = sign_getplaced(bnum, {'id' : 5, 'group' : 'g3'})
+ call assert_equal([], s[0].signs)
+
+ " Test for sign_getplaced() with lnum
+ let s = sign_getplaced(bnum, {'lnum' : 20})
+ call assert_equal([], s[0].signs)
+ let s = sign_getplaced(bnum, {'lnum' : 20, 'group' : 'g1'})
+ call assert_equal(
+ \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : 'g1',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ let s = sign_getplaced(bnum, {'lnum' : 30, 'group' : '*'})
+ call assert_equal(
+ \ [{'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ let s = sign_getplaced(bnum, {'lnum' : 40, 'group' : '*'})
+ call assert_equal([], s[0].signs)
+
+ " Error case
+ call assert_fails("call sign_getplaced(bnum, {'group' : []})",
+ \ 'E730:')
+
+ " Clear the sign in global group
+ call sign_unplace('', {'id' : 5, 'buffer' : bnum})
+ let s = sign_getplaced(bnum, {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : 'g1',
+ \ 'priority' : 10},
+ \ {'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+
+ " Clear the sign in one of the groups
+ call sign_unplace('g1', {'buffer' : 'Xsign'})
+ let s = sign_getplaced(bnum, {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 5, 'name' : 'sign1', 'lnum' : 30, 'group' : 'g2',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+
+ " Clear all the signs from the buffer
+ call sign_unplace('*', {'buffer' : bnum})
+ call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
+
+ " Clear sign across groups using an identifier
+ call sign_place(25, '', 'sign1', bnum, {'lnum' : 10})
+ call sign_place(25, 'g1', 'sign1', bnum, {'lnum' : 11})
+ call sign_place(25, 'g2', 'sign1', bnum, {'lnum' : 12})
+ call assert_equal(0, sign_unplace('*', {'id' : 25}))
+ call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
+
+ " Error case
+ call assert_fails("call sign_unplace({})", 'E1174:')
+
+ " Place a sign in the global group and try to delete it using a group
+ call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
+ call assert_equal(-1, sign_unplace('g1', {'id' : 5}))
+
+ " Place signs in multiple groups and delete all the signs in one of the
+ " group
+ call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
+ call assert_equal(6, sign_place(6, '', 'sign1', bnum, {'lnum' : 11}))
+ call assert_equal(5, sign_place(5, 'g1', 'sign1', bnum, {'lnum' : 10}))
+ call assert_equal(5, sign_place(5, 'g2', 'sign1', bnum, {'lnum' : 10}))
+ call assert_equal(6, sign_place(6, 'g1', 'sign1', bnum, {'lnum' : 11}))
+ call assert_equal(6, sign_place(6, 'g2', 'sign1', bnum, {'lnum' : 11}))
+ call assert_equal(0, sign_unplace('g1'))
+ let s = sign_getplaced(bnum, {'group' : 'g1'})
+ call assert_equal([], s[0].signs)
+ let s = sign_getplaced(bnum)
+ call assert_equal(2, len(s[0].signs))
+ let s = sign_getplaced(bnum, {'group' : 'g2'})
+ call assert_equal('g2', s[0].signs[0].group)
+ call assert_equal(0, sign_unplace('', {'id' : 5}))
+ call assert_equal(0, sign_unplace('', {'id' : 6}))
+ let s = sign_getplaced(bnum, {'group' : 'g2'})
+ call assert_equal('g2', s[0].signs[0].group)
+ call assert_equal(0, sign_unplace('', {'buffer' : bnum}))
+
+ call sign_unplace('*')
+
+ " Test for :sign command and groups
+ sign place 5 line=10 name=sign1 file=Xsign
+ sign place 5 group=g1 line=10 name=sign1 file=Xsign
+ sign place 5 group=g2 line=10 name=sign1 file=Xsign
+
+ " Tests for the ':sign place' command
+
+ " :sign place file={fname}
+ let a = execute('sign place file=Xsign')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 name=sign1 priority=10\n", a)
+
+ " :sign place group={group} file={fname}
+ let a = execute('sign place group=g2 file=Xsign')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 group=g2 name=sign1 priority=10\n", a)
+
+ " :sign place group=* file={fname}
+ let a = execute('sign place group=* file=Xsign')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 group=g2 name=sign1 priority=10\n" .
+ \ " line=10 id=5 group=g1 name=sign1 priority=10\n" .
+ \ " line=10 id=5 name=sign1 priority=10\n", a)
+
+ " Error case: non-existing group
+ let a = execute('sign place group=xyz file=Xsign')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a)
+
+ call sign_unplace('*')
+ let bnum = bufnr('Xsign')
+ exe 'sign place 5 line=10 name=sign1 buffer=' . bnum
+ exe 'sign place 5 group=g1 line=11 name=sign1 buffer=' . bnum
+ exe 'sign place 5 group=g2 line=12 name=sign1 buffer=' . bnum
+
+ " :sign place buffer={fname}
+ let a = execute('sign place buffer=' . bnum)
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 name=sign1 priority=10\n", a)
+
+ " :sign place group={group} buffer={fname}
+ let a = execute('sign place group=g2 buffer=' . bnum)
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a)
+
+ " :sign place group=* buffer={fname}
+ let a = execute('sign place group=* buffer=' . bnum)
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 name=sign1 priority=10\n" .
+ \ " line=11 id=5 group=g1 name=sign1 priority=10\n" .
+ \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a)
+
+ " Error case: non-existing group
+ let a = execute('sign place group=xyz buffer=' . bnum)
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n", a)
+
+ " :sign place
+ let a = execute('sign place')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 name=sign1 priority=10\n", a)
+
+ " Place signs in more than one buffer and list the signs
+ split foo
+ set buftype=nofile
+ sign place 25 line=76 name=sign1 priority=99 file=foo
+ let a = execute('sign place')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 name=sign1 priority=10\n" .
+ \ "Signs for foo:\n" .
+ \ " line=76 id=25 name=sign1 priority=99\n", a)
+ close
+ bwipe foo
+
+ " :sign place group={group}
+ let a = execute('sign place group=g1')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=11 id=5 group=g1 name=sign1 priority=10\n", a)
+
+ " :sign place group=*
+ let a = execute('sign place group=*')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 name=sign1 priority=10\n" .
+ \ " line=11 id=5 group=g1 name=sign1 priority=10\n" .
+ \ " line=12 id=5 group=g2 name=sign1 priority=10\n", a)
+
+ " Test for ':sign jump' command with groups
+ sign jump 5 group=g1 file=Xsign
+ call assert_equal(11, line('.'))
+ call assert_equal('Xsign', bufname(''))
+ sign jump 5 group=g2 file=Xsign
+ call assert_equal(12, line('.'))
+
+ " Test for :sign jump command without the filename or buffer
+ sign jump 5
+ call assert_equal(10, line('.'))
+ sign jump 5 group=g1
+ call assert_equal(11, line('.'))
+
+ " Error cases
+ call assert_fails("sign place 3 group= name=sign1 buffer=" . bnum, 'E474:')
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew | only
+endfunc
+
+" Place signs used for ":sign unplace" command test
+func Place_signs_for_test()
+ call sign_unplace('*')
+
+ sign place 3 line=10 name=sign1 file=Xsign1
+ sign place 3 group=g1 line=11 name=sign1 file=Xsign1
+ sign place 3 group=g2 line=12 name=sign1 file=Xsign1
+ sign place 4 line=15 name=sign1 file=Xsign1
+ sign place 4 group=g1 line=16 name=sign1 file=Xsign1
+ sign place 4 group=g2 line=17 name=sign1 file=Xsign1
+ sign place 5 line=20 name=sign1 file=Xsign2
+ sign place 5 group=g1 line=21 name=sign1 file=Xsign2
+ sign place 5 group=g2 line=22 name=sign1 file=Xsign2
+ sign place 6 line=25 name=sign1 file=Xsign2
+ sign place 6 group=g1 line=26 name=sign1 file=Xsign2
+ sign place 6 group=g2 line=27 name=sign1 file=Xsign2
+endfunc
+
+" Place multiple signs in a single line for test
+func Place_signs_at_line_for_test()
+ call sign_unplace('*')
+ sign place 3 line=13 name=sign1 file=Xsign1
+ sign place 3 group=g1 line=13 name=sign1 file=Xsign1
+ sign place 3 group=g2 line=13 name=sign1 file=Xsign1
+ sign place 4 line=13 name=sign1 file=Xsign1
+ sign place 4 group=g1 line=13 name=sign1 file=Xsign1
+ sign place 4 group=g2 line=13 name=sign1 file=Xsign1
+endfunc
+
+" Tests for the ':sign unplace' command
+func Test_sign_unplace()
+ enew | only
+ " Remove all the signs
+ call sign_unplace('*')
+ call sign_undefine()
+
+ " Create two files and define signs
+ call writefile(repeat(["Sun is shining"], 30), "Xsign1", 'D')
+ call writefile(repeat(["It is beautiful"], 30), "Xsign2", 'D')
+
+ let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
+ call sign_define("sign1", attr)
+
+ edit Xsign1
+ let bnum1 = bufnr('%')
+ split Xsign2
+ let bnum2 = bufnr('%')
+
+ let signs1 = [{'id' : 3, 'name' : 'sign1', 'lnum' : 10, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign1', 'lnum' : 11, 'group' : 'g1',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign1', 'lnum' : 12, 'group' : 'g2',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign1', 'lnum' : 15, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign1', 'lnum' : 16, 'group' : 'g1',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign1', 'lnum' : 17, 'group' : 'g2',
+ \ 'priority' : 10},]
+ let signs2 = [{'id' : 5, 'name' : 'sign1', 'lnum' : 20, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 5, 'name' : 'sign1', 'lnum' : 21, 'group' : 'g1',
+ \ 'priority' : 10},
+ \ {'id' : 5, 'name' : 'sign1', 'lnum' : 22, 'group' : 'g2',
+ \ 'priority' : 10},
+ \ {'id' : 6, 'name' : 'sign1', 'lnum' : 25, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 6, 'name' : 'sign1', 'lnum' : 26, 'group' : 'g1',
+ \ 'priority' : 10},
+ \ {'id' : 6, 'name' : 'sign1', 'lnum' : 27, 'group' : 'g2',
+ \ 'priority' : 10},]
+
+ " Test for :sign unplace {id} file={fname}
+ call Place_signs_for_test()
+ sign unplace 3 file=Xsign1
+ sign unplace 6 file=Xsign2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 3 || val.group != ''}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 6 || val.group != ''}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id} group={group} file={fname}
+ call Place_signs_for_test()
+ sign unplace 4 group=g1 file=Xsign1
+ sign unplace 5 group=g2 file=Xsign2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4 || val.group != 'g1'}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 5 || val.group != 'g2'}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id} group=* file={fname}
+ call Place_signs_for_test()
+ sign unplace 3 group=* file=Xsign1
+ sign unplace 6 group=* file=Xsign2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 3}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 6}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * file={fname}
+ call Place_signs_for_test()
+ sign unplace * file=Xsign1
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.group != ''}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(signs2, sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * group={group} file={fname}
+ call Place_signs_for_test()
+ sign unplace * group=g1 file=Xsign1
+ sign unplace * group=g2 file=Xsign2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.group != 'g1'}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.group != 'g2'}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * group=* file={fname}
+ call Place_signs_for_test()
+ sign unplace * group=* file=Xsign2
+ call assert_equal(signs1, sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal([], sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id} buffer={nr}
+ call Place_signs_for_test()
+ exe 'sign unplace 3 buffer=' . bnum1
+ exe 'sign unplace 6 buffer=' . bnum2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 3 || val.group != ''}),
+ \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 6 || val.group != ''}),
+ \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id} group={group} buffer={nr}
+ call Place_signs_for_test()
+ exe 'sign unplace 4 group=g1 buffer=' . bnum1
+ exe 'sign unplace 5 group=g2 buffer=' . bnum2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4 || val.group != 'g1'}),
+ \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 5 || val.group != 'g2'}),
+ \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id} group=* buffer={nr}
+ call Place_signs_for_test()
+ exe 'sign unplace 3 group=* buffer=' . bnum1
+ exe 'sign unplace 6 group=* buffer=' . bnum2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 3}),
+ \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 6}),
+ \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * buffer={nr}
+ call Place_signs_for_test()
+ exe 'sign unplace * buffer=' . bnum1
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.group != ''}),
+ \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
+ call assert_equal(signs2, sign_getplaced(bnum2, {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * group={group} buffer={nr}
+ call Place_signs_for_test()
+ exe 'sign unplace * group=g1 buffer=' . bnum1
+ exe 'sign unplace * group=g2 buffer=' . bnum2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.group != 'g1'}),
+ \ sign_getplaced(bnum1, {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.group != 'g2'}),
+ \ sign_getplaced(bnum2, {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * group=* buffer={nr}
+ call Place_signs_for_test()
+ exe 'sign unplace * group=* buffer=' . bnum2
+ call assert_equal(signs1, sign_getplaced(bnum1, {'group' : '*'})[0].signs)
+ call assert_equal([], sign_getplaced(bnum2, {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id}
+ call Place_signs_for_test()
+ sign unplace 4
+ sign unplace 6
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4 || val.group != ''}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 6 || val.group != ''}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id} group={group}
+ call Place_signs_for_test()
+ sign unplace 4 group=g1
+ sign unplace 6 group=g2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4 || val.group != 'g1'}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 6 || val.group != 'g2'}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace {id} group=*
+ call Place_signs_for_test()
+ sign unplace 3 group=*
+ sign unplace 5 group=*
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 3}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.id != 5}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace *
+ call Place_signs_for_test()
+ sign unplace *
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.group != ''}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.group != ''}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * group={group}
+ call Place_signs_for_test()
+ sign unplace * group=g1
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.group != 'g1'}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(
+ \ filter(copy(signs2),
+ \ {idx, val -> val.group != 'g1'}),
+ \ sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Test for :sign unplace * group=*
+ call Place_signs_for_test()
+ sign unplace * group=*
+ call assert_equal([], sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal([], sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Negative test cases
+ call Place_signs_for_test()
+ sign unplace 3 group=xy file=Xsign1
+ sign unplace * group=xy file=Xsign1
+ silent! sign unplace * group=* file=FileNotPresent
+ call assert_equal(signs1, sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ call assert_equal(signs2, sign_getplaced('Xsign2', {'group' : '*'})[0].signs)
+
+ " Tests for removing sign at the current cursor position
+
+ " Test for ':sign unplace'
+ let signs1 = [{'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g2',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g1',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign1', 'lnum' : 13, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g2',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : 'g1',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign1', 'lnum' : 13, 'group' : '',
+ \ 'priority' : 10},]
+ exe bufwinnr('Xsign1') . 'wincmd w'
+ call cursor(13, 1)
+
+ " Should remove only one sign in the global group
+ call Place_signs_at_line_for_test()
+ sign unplace
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4 || val.group != ''}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ " Should remove the second sign in the global group
+ sign unplace
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.group != ''}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+
+ " Test for ':sign unplace group={group}'
+ call Place_signs_at_line_for_test()
+ " Should remove only one sign in group g1
+ sign unplace group=g1
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4 || val.group != 'g1'}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ sign unplace group=g2
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4 || val.group == ''}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+
+ " Test for ':sign unplace group=*'
+ call Place_signs_at_line_for_test()
+ sign unplace group=*
+ sign unplace group=*
+ sign unplace group=*
+ call assert_equal(
+ \ filter(copy(signs1),
+ \ {idx, val -> val.id != 4}),
+ \ sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+ sign unplace group=*
+ sign unplace group=*
+ sign unplace group=*
+ call assert_equal([], sign_getplaced('Xsign1', {'group' : '*'})[0].signs)
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew | only
+endfunc
+
+" Tests for auto-generating the sign identifier.
+func Test_aaa_sign_id_autogen()
+ enew | only
+ call sign_unplace('*')
+ call sign_undefine()
+
+ let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
+ call assert_equal(0, sign_define("sign1", attr))
+
+ call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D')
+ edit Xsign
+
+ call assert_equal(1, sign_place(0, '', 'sign1', 'Xsign',
+ \ {'lnum' : 10}))
+ call assert_equal(2, sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 12}))
+ call assert_equal(3, sign_place(0, '', 'sign1', 'Xsign',
+ \ {'lnum' : 14}))
+ call sign_unplace('', {'buffer' : 'Xsign', 'id' : 2})
+ call assert_equal(4, sign_place(0, '', 'sign1', 'Xsign',
+ \ {'lnum' : 12}))
+
+ call assert_equal(1, sign_place(0, 'g1', 'sign1', 'Xsign',
+ \ {'lnum' : 11}))
+ " Check for the next generated sign id in this group
+ call assert_equal(2, sign_place(0, 'g1', 'sign1', 'Xsign',
+ \ {'lnum' : 12}))
+ call assert_equal(0, sign_unplace('g1', {'id' : 1}))
+ call assert_equal(10,
+ \ sign_getplaced('Xsign', {'id' : 1})[0].signs[0].lnum)
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew | only
+endfunc
+
+" Test for sign priority
+func Test_sign_priority()
+ enew | only
+ call sign_unplace('*')
+ call sign_undefine()
+
+ let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Search'}
+ call sign_define("sign1", attr)
+ call sign_define("sign2", attr)
+ call sign_define("sign3", attr)
+
+ " Place three signs with different priority in the same line
+ call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D')
+ edit Xsign
+
+ call sign_place(1, 'g1', 'sign1', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 50})
+ call sign_place(2, 'g2', 'sign2', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 100})
+ call sign_place(3, '', 'sign3', 'Xsign', {'lnum' : 11})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11, 'group' : 'g2',
+ \ 'priority' : 100},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : 'g1',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11, 'group' : '',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Three signs on different lines with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 50})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 12, 'priority' : 60})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 13, 'priority' : 70})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 12, 'priority' : 40})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 13, 'priority' : 30})
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 50})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 12, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 13, 'group' : '',
+ \ 'priority' : 30}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Two signs on the same line with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Change the priority of the last sign to highest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 40})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30}],
+ \ s[0].signs)
+ " Change the priority of the first sign to lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 25})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25}],
+ \ s[0].signs)
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 45})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 55})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 55},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 45}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Three signs on the same line with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 40})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to the highest
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 50})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to the lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15}],
+ \ s[0].signs)
+
+ " Change the priority of the last sign to the highest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 55})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 55},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the first sign to the lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Three signs on the same line with changing priorities along with other
+ " signs
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 2, 'priority' : 10})
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ call sign_place(3, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(4, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 25})
+ call sign_place(5, '', 'sign2', 'Xsign',
+ \ {'lnum' : 6, 'priority' : 80})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the first sign to lowest
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the last sign to highest
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to lowest
+ call sign_place(4, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to highest
+ call sign_place(3, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 35})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 35},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Multiple signs with the same priority on the same line
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the last sign again with the same priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the first sign again with the same priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the middle sign again with the same priority
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Place multiple signs with same id on a line with different priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 20})
+ call sign_place(1, '', 'sign2', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 10})
+ let s = sign_getplaced('Xsign', {'lnum' : 5})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ call sign_place(1, '', 'sign2', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 5})
+ let s = sign_getplaced('Xsign', {'lnum' : 5})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '',
+ \ 'priority' : 5}],
+ \ s[0].signs)
+
+ " Error case
+ call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign', [])", 'E1206:')
+ call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
+ \ {'priority' : []})", 'E745:')
+ call sign_unplace('*')
+
+ " Tests for the :sign place command with priority
+ sign place 5 line=10 name=sign1 priority=30 file=Xsign
+ sign place 5 group=g1 line=10 name=sign1 priority=20 file=Xsign
+ sign place 5 group=g2 line=10 name=sign1 priority=25 file=Xsign
+ let a = execute('sign place group=*')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 name=sign1 priority=30\n" .
+ \ " line=10 id=5 group=g2 name=sign1 priority=25\n" .
+ \ " line=10 id=5 group=g1 name=sign1 priority=20\n", a)
+
+ " Test for :sign place group={group}
+ let a = execute('sign place group=g1')
+ call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" .
+ \ " line=10 id=5 group=g1 name=sign1 priority=20\n", a)
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew | only
+endfunc
+
+" Tests for memory allocation failures in sign functions
+func Test_sign_memfailures()
+ call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D')
+ edit Xsign
+
+ call test_alloc_fail(GetAllocId('sign_getdefined'), 0, 0)
+ call assert_fails('call sign_getdefined("sign1")', 'E342:')
+ call test_alloc_fail(GetAllocId('sign_getplaced'), 0, 0)
+ call assert_fails('call sign_getplaced("Xsign")', 'E342:')
+ call test_alloc_fail(GetAllocId('sign_define_by_name'), 0, 0)
+ let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
+ call assert_fails('call sign_define("sign1", attr)', 'E342:')
+
+ let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'}
+ call sign_define("sign1", attr)
+ call test_alloc_fail(GetAllocId('sign_getlist'), 0, 0)
+ call assert_fails('call sign_getdefined("sign1")', 'E342:')
+
+ call sign_place(3, 'g1', 'sign1', 'Xsign', {'lnum' : 10})
+ call test_alloc_fail(GetAllocId('sign_getplaced_dict'), 0, 0)
+ call assert_fails('call sign_getplaced("Xsign")', 'E342:')
+ call test_alloc_fail(GetAllocId('sign_getplaced_list'), 0, 0)
+ call assert_fails('call sign_getplaced("Xsign")', 'E342:')
+
+ call test_alloc_fail(GetAllocId('insert_sign'), 0, 0)
+ call assert_fails('call sign_place(4, "g1", "sign1", "Xsign", {"lnum" : 11})',
+ \ 'E342:')
+
+ call test_alloc_fail(GetAllocId('sign_getinfo'), 0, 0)
+ call assert_fails('call getbufinfo()', 'E342:')
+ call sign_place(4, 'g1', 'sign1', 'Xsign', {'lnum' : 11})
+ call test_alloc_fail(GetAllocId('sign_getinfo'), 0, 0)
+ call assert_fails('let binfo=getbufinfo("Xsign")', 'E342:')
+ call assert_equal([{'lnum': 11, 'id': 4, 'name': 'sign1',
+ \ 'priority': 10, 'group': 'g1'}], binfo[0].signs)
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew | only
+endfunc
+
+" Test for auto-adjusting the line number of a placed sign.
+func Test_sign_lnum_adjust()
+ enew! | only!
+
+ sign define sign1 text=#> linehl=Comment
+ call setline(1, ['A', 'B', 'C', 'D', 'E'])
+ exe 'sign place 5 line=3 name=sign1 buffer=' . bufnr('')
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(3, l[0].signs[0].lnum)
+
+ " Add some lines before the sign and check the sign line number
+ call append(2, ['BA', 'BB', 'BC'])
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(6, l[0].signs[0].lnum)
+
+ " Delete some lines before the sign and check the sign line number
+ call deletebufline('%', 1, 2)
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(4, l[0].signs[0].lnum)
+
+ " Insert some lines after the sign and check the sign line number
+ call append(5, ['DA', 'DB'])
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(4, l[0].signs[0].lnum)
+
+ " Delete some lines after the sign and check the sign line number
+ call deletebufline('', 6, 7)
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(4, l[0].signs[0].lnum)
+
+ " Break the undo. Otherwise the undo operation below will undo all the
+ " changes made by this function.
+ let &undolevels=&undolevels
+
+ " Delete the line with the sign
+ call deletebufline('', 4)
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(4, l[0].signs[0].lnum)
+
+ " Undo the delete operation
+ undo
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(5, l[0].signs[0].lnum)
+
+ " Break the undo
+ let &undolevels=&undolevels
+
+ " Delete few lines at the end of the buffer including the line with the sign
+ " Sign line number should not change (as it is placed outside of the buffer)
+ call deletebufline('', 3, 6)
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(5, l[0].signs[0].lnum)
+
+ " Undo the delete operation. Sign should be restored to the previous line
+ undo
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal(5, l[0].signs[0].lnum)
+
+ sign unplace * group=*
+ sign undefine sign1
+ enew!
+endfunc
+
+" Test for changing the type of a placed sign
+func Test_sign_change_type()
+ enew! | only!
+
+ sign define sign1 text=#> linehl=Comment
+ sign define sign2 text=@@ linehl=Comment
+
+ call setline(1, ['A', 'B', 'C', 'D'])
+ exe 'sign place 4 line=3 name=sign1 buffer=' . bufnr('')
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal('sign1', l[0].signs[0].name)
+ exe 'sign place 4 name=sign2 buffer=' . bufnr('')
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal('sign2', l[0].signs[0].name)
+ call sign_place(4, '', 'sign1', '')
+ let l = sign_getplaced(bufnr(''))
+ call assert_equal('sign1', l[0].signs[0].name)
+
+ exe 'sign place 4 group=g1 line=4 name=sign1 buffer=' . bufnr('')
+ let l = sign_getplaced(bufnr(''), {'group' : 'g1'})
+ call assert_equal('sign1', l[0].signs[0].name)
+ exe 'sign place 4 group=g1 name=sign2 buffer=' . bufnr('')
+ let l = sign_getplaced(bufnr(''), {'group' : 'g1'})
+ call assert_equal('sign2', l[0].signs[0].name)
+ call sign_place(4, 'g1', 'sign1', '')
+ let l = sign_getplaced(bufnr(''), {'group' : 'g1'})
+ call assert_equal('sign1', l[0].signs[0].name)
+
+ sign unplace * group=*
+ sign undefine sign1
+ sign undefine sign2
+ enew!
+endfunc
+
+" Test for the sign_jump() function
+func Test_sign_jump_func()
+ enew! | only!
+
+ sign define sign1 text=#> linehl=Comment
+
+ edit foo
+ set buftype=nofile
+ call setline(1, ['A', 'B', 'C', 'D', 'E'])
+ call sign_place(5, '', 'sign1', '', {'lnum' : 2})
+ call sign_place(5, 'g1', 'sign1', '', {'lnum' : 3})
+ call sign_place(6, '', 'sign1', '', {'lnum' : 4})
+ call sign_place(6, 'g1', 'sign1', '', {'lnum' : 5})
+ split bar
+ set buftype=nofile
+ call setline(1, ['P', 'Q', 'R', 'S', 'T'])
+ call sign_place(5, '', 'sign1', '', {'lnum' : 2})
+ call sign_place(5, 'g1', 'sign1', '', {'lnum' : 3})
+ call sign_place(6, '', 'sign1', '', {'lnum' : 4})
+ call sign_place(6, 'g1', 'sign1', '', {'lnum' : 5})
+
+ let r = sign_jump(5, '', 'foo')
+ call assert_equal(2, r)
+ call assert_equal(2, line('.'))
+ let r = 6->sign_jump('g1', 'foo')
+ call assert_equal(5, r)
+ call assert_equal(5, line('.'))
+ let r = sign_jump(5, '', 'bar')
+ call assert_equal(2, r)
+ call assert_equal(2, line('.'))
+
+ " Error cases
+ call assert_fails("call sign_jump(99, '', 'bar')", 'E157:')
+ call assert_fails("call sign_jump(0, '', 'foo')", 'E474:')
+ call assert_fails("call sign_jump(5, 'g5', 'foo')", 'E157:')
+ call assert_fails('call sign_jump([], "", "foo")', 'E745:')
+ call assert_fails('call sign_jump(2, [], "foo")', 'E730:')
+ call assert_fails('call sign_jump(2, "", {})', 'E731:')
+ call assert_fails('call sign_jump(2, "", "baz")', 'E158:')
+
+ sign unplace * group=*
+ sign undefine sign1
+ enew! | only!
+endfunc
+
+" Test for correct cursor position after the sign column appears or disappears.
+func Test_sign_cursor_position()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, [repeat('x', 75), 'mmmm', 'yyyy'])
+ call cursor(2,1)
+ sign define s1 texthl=Search text==>
+ sign define s2 linehl=Pmenu
+ redraw
+ sign place 10 line=2 name=s1
+ END
+ call writefile(lines, 'XtestSigncolumn', 'D')
+ let buf = RunVimInTerminal('-S XtestSigncolumn', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_sign_cursor_1', {})
+
+ " Change the sign text
+ call term_sendkeys(buf, ":sign define s1 text=-)\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_2', {})
+
+ " Also place a line HL sign
+ call term_sendkeys(buf, ":sign place 11 line=2 name=s2\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_3', {})
+
+ " update cursor position calculation
+ call term_sendkeys(buf, "lh")
+ call term_sendkeys(buf, ":sign unplace 11\<CR>")
+ call term_sendkeys(buf, ":sign unplace 10\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_4', {})
+
+ " 'cursorline' highlighting overrules sign
+ call term_sendkeys(buf, ":sign place 12 line=2 name=s2\<CR>")
+ call term_sendkeys(buf, ":set cursorline\<CR>")
+ call term_sendkeys(buf, ":hi CursorLine ctermbg=Green\<CR>")
+ call term_sendkeys(buf, "2G")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_5', {})
+ call term_sendkeys(buf, ":set cursorlineopt=number,screenline\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_5', {})
+
+ " sign highlighting overrules 'cursorline'
+ call term_sendkeys(buf, ":sign unplace 12\<CR>")
+ call term_sendkeys(buf, ":sign place 13 line=2 priority=100 name=s2\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_6', {})
+ call term_sendkeys(buf, ":set cursorlineopt&\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_6', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Return the 'len' characters in screen starting from (row,col)
+func s:ScreenLine(row, col, len)
+ let s = ''
+ for i in range(a:len)
+ let s .= nr2char(screenchar(a:row, a:col + i))
+ endfor
+ return s
+endfunc
+
+" Test for 'signcolumn' set to 'number'.
+func Test_sign_numcol()
+ new
+ call append(0, "01234")
+ " With 'signcolumn' set to 'number', make sure sign is displayed in the
+ " number column and line number is not displayed.
+ set numberwidth=2
+ set number
+ set signcolumn=number
+ sign define sign1 text==>
+ sign define sign2 text=V
+ sign place 10 line=1 name=sign1
+ redraw!
+ call assert_equal("=> 01234", s:ScreenLine(1, 1, 8))
+
+ " With 'signcolumn' set to 'number', when there is no sign, make sure line
+ " number is displayed in the number column
+ sign unplace 10
+ redraw!
+ call assert_equal("1 01234", s:ScreenLine(1, 1, 7))
+
+ " Disable number column. Check whether sign is displayed in the sign column
+ set numberwidth=4
+ set nonumber
+ sign place 10 line=1 name=sign1
+ redraw!
+ call assert_equal("=>01234", s:ScreenLine(1, 1, 7))
+
+ " Enable number column. Check whether sign is displayed in the number column
+ set number
+ redraw!
+ call assert_equal(" => 01234", s:ScreenLine(1, 1, 9))
+
+ " Disable sign column. Make sure line number is displayed
+ set signcolumn=no
+ redraw!
+ call assert_equal(" 1 01234", s:ScreenLine(1, 1, 9))
+
+ " Enable auto sign column. Make sure both sign and line number are displayed
+ set signcolumn=auto
+ redraw!
+ call assert_equal("=> 1 01234", s:ScreenLine(1, 1, 11))
+
+ " Test displaying signs in the number column with width 1
+ call sign_unplace('*')
+ call append(1, "abcde")
+ call append(2, "01234")
+ " Enable number column with width 1
+ set number numberwidth=1 signcolumn=auto
+ redraw!
+ call assert_equal("3 01234", s:ScreenLine(3, 1, 7))
+ " Place a sign and make sure number column width remains the same
+ sign place 20 line=2 name=sign1
+ redraw!
+ call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9))
+ " Set 'signcolumn' to 'number', make sure the number column width increases
+ set signcolumn=number
+ redraw!
+ call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
+ " Set 'signcolumn' to 'auto', make sure the number column width is 1.
+ set signcolumn=auto
+ redraw!
+ call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9))
+ " Set 'signcolumn' to 'number', make sure the number column width is 2.
+ set signcolumn=number
+ redraw!
+ call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
+ " Disable 'number' column
+ set nonumber
+ redraw!
+ call assert_equal("=>abcde", s:ScreenLine(2, 1, 7))
+ call assert_equal(" 01234", s:ScreenLine(3, 1, 7))
+ " Enable 'number' column
+ set number
+ redraw!
+ call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
+ " Remove the sign and make sure the width of the number column is 1.
+ call sign_unplace('', {'id' : 20})
+ redraw!
+ call assert_equal("3 01234", s:ScreenLine(3, 1, 7))
+ " When the first sign is placed with 'signcolumn' set to number, verify that
+ " the number column width increases
+ sign place 30 line=1 name=sign1
+ redraw!
+ call assert_equal("=> 01234", s:ScreenLine(1, 1, 8))
+ call assert_equal(" 2 abcde", s:ScreenLine(2, 1, 8))
+ " Add sign with multi-byte text
+ set numberwidth=4
+ sign place 40 line=2 name=sign2
+ redraw!
+ call assert_equal(" => 01234", s:ScreenLine(1, 1, 9))
+ call assert_equal(" V abcde", s:ScreenLine(2, 1, 9))
+
+ sign unplace * group=*
+ sign undefine sign1
+ set signcolumn&
+ set number&
+ enew! | close
+endfunc
+
+" Test for managing multiple signs using the sign functions
+func Test_sign_funcs_multi()
+ call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D')
+ edit Xsign
+ let bnum = bufnr('')
+
+ " Define multiple signs at once
+ call assert_equal([0, 0, 0, 0], sign_define([
+ \ {'name' : 'sign1', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'},
+ \ {'name' : 'sign2', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'},
+ \ {'name' : 'sign3', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'},
+ \ {'name' : 'sign4', 'text' : '=>', 'linehl' : 'Search',
+ \ 'texthl' : 'Search'}]))
+
+ " Negative cases for sign_define()
+ call assert_equal([], sign_define([]))
+ call assert_equal([-1], sign_define([{}]))
+ call assert_fails('call sign_define([6])', 'E715:')
+ call assert_fails('call sign_define(["abc"])', 'E715:')
+ call assert_fails('call sign_define([[]])', 'E715:')
+
+ " Place multiple signs at once with specific sign identifier
+ let l = sign_placelist([{'id' : 1, 'group' : 'g1', 'name' : 'sign1',
+ \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 50},
+ \ {'id' : 2, 'group' : 'g2', 'name' : 'sign2',
+ \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 100},
+ \ {'id' : 3, 'group' : '', 'name' : 'sign3',
+ \ 'buffer' : 'Xsign', 'lnum' : 11}])
+ call assert_equal([1, 2, 3], l)
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11,
+ \ 'group' : 'g2', 'priority' : 100},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
+ \ 'group' : 'g1', 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11,
+ \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+ call sign_unplace('*')
+
+ " Place multiple signs at once with auto-generated sign identifier
+ call assert_equal([1, 1, 5], sign_placelist([
+ \ {'group' : 'g1', 'name' : 'sign1',
+ \ 'buffer' : 'Xsign', 'lnum' : 11},
+ \ {'group' : 'g2', 'name' : 'sign2',
+ \ 'buffer' : 'Xsign', 'lnum' : 11},
+ \ {'group' : '', 'name' : 'sign3',
+ \ 'buffer' : 'Xsign', 'lnum' : 11}]))
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11,
+ \ 'group' : '', 'priority' : 10},
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11,
+ \ 'group' : 'g2', 'priority' : 10},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
+ \ 'group' : 'g1', 'priority' : 10}], s[0].signs)
+
+ " Change an existing sign without specifying the group
+ call assert_equal([5], [{'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist())
+ let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''})
+ call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11,
+ \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+ " Place a sign using '.' as the line number
+ call cursor(23, 1)
+ call assert_equal([7], sign_placelist([
+ \ {'id' : 7, 'name' : 'sign1', 'buffer' : '%', 'lnum' : '.'}]))
+ let s = sign_getplaced('%', {'lnum' : '.'})
+ call assert_equal([{'id' : 7, 'name' : 'sign1', 'lnum' : 23,
+ \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+ " Place sign without a sign name
+ call assert_equal([-1], sign_placelist([{'id' : 10, 'buffer' : 'Xsign',
+ \ 'lnum' : 12, 'group' : ''}]))
+
+ " Place sign without a buffer
+ call assert_equal([-1], sign_placelist([{'id' : 10, 'name' : 'sign1',
+ \ 'lnum' : 12, 'group' : ''}]))
+
+ " Invalid arguments
+ call assert_equal([], sign_placelist([]))
+ call assert_fails('call sign_placelist({})', "E1211:")
+ call assert_fails('call sign_placelist([[]])', "E715:")
+ call assert_fails('call sign_placelist(["abc"])', "E715:")
+ call assert_fails('call sign_placelist([100])', "E715:")
+
+ " Unplace multiple signs
+ call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5},
+ \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}]))
+
+ " Invalid arguments
+ call assert_equal([], []->sign_unplacelist())
+ call assert_fails('call sign_unplacelist({})', "E1211:")
+ call assert_fails('call sign_unplacelist([[]])', "E715:")
+ call assert_fails('call sign_unplacelist(["abc"])', "E715:")
+ call assert_fails('call sign_unplacelist([100])', "E715:")
+ call assert_fails("call sign_unplacelist([{'id' : -1}])", 'E474:')
+
+ call assert_equal([0, 0, 0, 0],
+ \ sign_undefine(['sign1', 'sign2', 'sign3', 'sign4']))
+ call assert_equal([], sign_getdefined())
+
+ " Invalid arguments
+ call assert_equal([], sign_undefine([]))
+ call assert_fails('call sign_undefine([[]])', 'E730:')
+ call assert_fails('call sign_undefine([{}])', 'E731:')
+ call assert_fails('call sign_undefine(["1abc2"])', 'E155:')
+
+ call sign_unplace('*')
+ call sign_undefine()
+ enew!
+endfunc
+
+func Test_sign_null_list()
+ eval test_null_list()->sign_define()
+ eval test_null_list()->sign_placelist()
+ eval test_null_list()->sign_undefine()
+ eval test_null_list()->sign_unplacelist()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_sleep.vim b/src/testdir/test_sleep.vim
new file mode 100644
index 0000000..a428f38
--- /dev/null
+++ b/src/testdir/test_sleep.vim
@@ -0,0 +1,27 @@
+" Test for sleep and sleep! commands
+
+func! s:get_time_ms()
+ let timestr = reltimestr(reltime())
+ let dotidx = stridx(timestr, '.')
+ let sec = str2nr(timestr[:dotidx])
+ let msec = str2nr(timestr[dotidx + 1:])
+ return (sec * 1000) + (msec / 1000)
+endfunc
+
+func! s:assert_takes_longer(cmd, time_ms)
+ let start = s:get_time_ms()
+ execute a:cmd
+ let end = s:get_time_ms()
+ call assert_true(end - start >=# a:time_ms)
+endfun
+
+func! Test_sleep_bang()
+ call s:assert_takes_longer('sleep 50m', 50)
+ call s:assert_takes_longer('sleep! 50m', 50)
+ call s:assert_takes_longer('sl 50m', 50)
+ call s:assert_takes_longer('sl! 50m', 50)
+ call s:assert_takes_longer('1sleep', 1000)
+ call s:assert_takes_longer('normal 1gs', 1000)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_smartindent.vim b/src/testdir/test_smartindent.vim
new file mode 100644
index 0000000..9bb7b9c
--- /dev/null
+++ b/src/testdir/test_smartindent.vim
@@ -0,0 +1,159 @@
+" Tests for smartindent
+
+" Tests for not doing smart indenting when it isn't set.
+func Test_nosmartindent()
+ new
+ call append(0, [" some test text",
+ \ " test text",
+ \ "test text",
+ \ " test text"])
+ set nocindent nosmartindent autoindent
+ exe "normal! gg/some\<CR>"
+ exe "normal! 2cc#test\<Esc>"
+ call assert_equal(" #test", getline(1))
+ enew! | close
+endfunc
+
+func MyIndent()
+endfunc
+
+" When 'indentexpr' is set, setting 'si' has no effect.
+func Test_smartindent_has_no_effect()
+ new
+ exe "normal! i\<Tab>one\<Esc>"
+ setlocal noautoindent smartindent indentexpr=
+ exe "normal! Gotwo\<Esc>"
+ call assert_equal("\ttwo", getline("$"))
+
+ set indentexpr=MyIndent
+ exe "normal! Gothree\<Esc>"
+ call assert_equal("three", getline("$"))
+
+ delfunction! MyIndent
+ bwipe!
+endfunc
+
+" Test for inserting '{' and '} with smartindent
+func Test_smartindent_braces()
+ new
+ setlocal smartindent shiftwidth=4
+ call setline(1, [' if (a)', "\tif (b)", "\t return 1"])
+ normal 2ggO{
+ normal 3ggA {
+ normal 4ggo}
+ normal o}
+ normal 4ggO#define FOO 1
+ call assert_equal([
+ \ ' if (a)',
+ \ ' {',
+ \ "\tif (b) {",
+ \ '#define FOO 1',
+ \ "\t return 1",
+ \ "\t}",
+ \ ' }'
+ \ ], getline(1, '$'))
+ close!
+endfunc
+
+" Test for adding a new line before and after comments with smartindent
+func Test_si_add_line_around_comment()
+ new
+ setlocal smartindent shiftwidth=4
+ call setline(1, [' A', '# comment1', '# comment2'])
+ exe "normal GoC\<Esc>2GOB"
+ call assert_equal([' A', ' B', '# comment1', '# comment2', ' C'],
+ \ getline(1, '$'))
+ close!
+endfunc
+
+" After a C style comment, indent for a following line should line up with the
+" line containing the start of the comment.
+func Test_si_indent_after_c_comment()
+ new
+ setlocal smartindent shiftwidth=4 fo+=ro
+ exe "normal i\<C-t>/*\ncomment\n/\n#define FOOBAR\n75\<Esc>ggOabc"
+ normal 3jOcont
+ call assert_equal([' abc', ' /*', ' * comment', ' * cont',
+ \ ' */', '#define FOOBAR', ' 75'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for indenting a statement after a if condition split across lines
+func Test_si_if_cond_split_across_lines()
+ new
+ setlocal smartindent shiftwidth=4
+ exe "normal i\<C-t>if (cond1 &&\n\<C-t>cond2) {\ni = 10;\n}"
+ call assert_equal([' if (cond1 &&', "\t cond2) {", "\ti = 10;",
+ \ ' }'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for inserting lines before and after a one line comment
+func Test_si_one_line_comment()
+ new
+ setlocal smartindent shiftwidth=4
+ exe "normal i\<C-t>abc;\n\<C-t>/* comment */"
+ normal oi = 10;
+ normal kOj = 1;
+ call assert_equal([' abc;', "\tj = 1;", "\t/* comment */", "\ti = 10;"],
+ \ getline(1, '$'))
+ close!
+endfunc
+
+" Test for smartindent with a comment continued across multiple lines
+func Test_si_comment_line_continuation()
+ new
+ setlocal smartindent shiftwidth=4
+ call setline(1, ['# com1', '# com2 \', ' contd', '# com3', ' xyz'])
+ normal ggOabc
+ call assert_equal([' abc', '# com1', '# com2 \', ' contd', '# com3',
+ \ ' xyz'], getline(1, '$'))
+ close!
+endfunc
+
+" When 'paste' is set, 'smartindent' should not take effect.
+func Test_si_with_paste()
+ new
+ setlocal smartindent autoindent
+ set paste
+ " insert text that will trigger smartindent
+ exe "norm! i {\nif (x)\ni = 1;\n#define FOO 1\nj = 2;\n}"
+ exe "norm! Ok = 3;"
+ exe "norm! 4G>>"
+ call assert_equal([' {', 'if (x)', 'i = 1;', '#define FOO 1',
+ \ 'j = 2;', 'k = 3;', '}'], getline(1, '$'))
+ call assert_true(&smartindent)
+ set nopaste
+ %d _
+ exe "norm! i {\nif (x)\ni = 1;\n#define FOO 1\nj = 2;\n}"
+ exe "norm! Ok = 3;"
+ exe "norm! 4G>>"
+ call assert_equal([' {', "\t if (x)", "\t\t i = 1;",
+ \ '#define FOO 1', "\t\t j = 2;", "\t k = 3;", ' }'],
+ \ getline(1, '$'))
+ bw!
+endfunc
+
+func Test_si_after_completion()
+ new
+ setlocal ai smartindent indentexpr=
+ call setline(1, 'foo foot')
+ call feedkeys("o f\<C-X>\<C-N>#", 'tx')
+ call assert_equal(' foo#', getline(2))
+
+ call setline(2, '')
+ call feedkeys("1Go f\<C-X>\<C-N>}", 'tx')
+ call assert_equal(' foo}', getline(2))
+
+ bwipe!
+endfunc
+
+func Test_no_si_after_completion()
+ new
+ call setline(1, 'foo foot')
+ call feedkeys("o f\<C-X>\<C-N>#", 'tx')
+ call assert_equal(' foo#', getline(2))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_sort.vim b/src/testdir/test_sort.vim
new file mode 100644
index 0000000..a2731a8
--- /dev/null
+++ b/src/testdir/test_sort.vim
@@ -0,0 +1,1561 @@
+" Tests for the "sort()" function and for the ":sort" command.
+
+source check.vim
+
+func Compare1(a, b) abort
+ call sort(range(3), 'Compare2')
+ return a:a - a:b
+endfunc
+
+func Compare2(a, b) abort
+ return a:a - a:b
+endfunc
+
+func Test_sort_strings()
+ " numbers compared as strings
+ call assert_equal([1, 2, 3], sort([3, 2, 1]))
+ call assert_equal([13, 28, 3], sort([3, 28, 13]))
+
+ call assert_equal(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
+ \ sort(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'œ', 'Œ']))
+
+ call assert_equal(['A', 'a', 'o', 'O', 'p', 'P', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'i'))
+
+ " This does not appear to work correctly on Mac.
+ if !has('mac')
+ if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
+ " with Canadian English capitals come before lower case.
+ " 'Å’' is omitted because it can sort before or after 'Å“'
+ call assert_equal(['A', 'a', 'Ä', 'ä', 'O', 'o', 'Ô', 'ô', 'œ', 'P', 'p'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
+ " With the following locales, the accentuated letters are ordered
+ " similarly to the non-accentuated letters...
+ call assert_equal(['a', 'A', 'ä', 'Ä', 'o', 'O', 'ô', 'Ô', 'œ', 'Œ', 'p', 'P'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ elseif v:collate =~? '^sv.*utf-\?8$'
+ " ... whereas with a Swedish locale, the accentuated letters are ordered
+ " after Z.
+ call assert_equal(['a', 'A', 'o', 'O', 'p', 'P', 'ä', 'Ä', 'œ', 'œ', 'ô', 'Ô'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ endif
+ endif
+endfunc
+
+func Test_sort_null_string()
+ " null strings are sorted as empty strings.
+ call assert_equal(['', 'a', 'b'], sort(['b', test_null_string(), 'a']))
+endfunc
+
+func Test_sort_numeric()
+ call assert_equal([1, 2, 3], sort([3, 2, 1], 'n'))
+ call assert_equal([3, 13, 28], sort([13, 28, 3], 'n'))
+ " strings are not sorted
+ call assert_equal(['13', '28', '3'], sort(['13', '28', '3'], 'n'))
+endfunc
+
+func Test_sort_numbers()
+ call assert_equal([3, 13, 28], sort([13, 28, 3], 'N'))
+ call assert_equal(['3', '13', '28'], sort(['13', '28', '3'], 'N'))
+ call assert_equal([3997, 4996], sort([4996, 3997], 'Compare1'))
+endfunc
+
+func Test_sort_float()
+ call assert_equal([0.28, 3, 13.5], sort([13.5, 0.28, 3], 'f'))
+endfunc
+
+func Test_sort_nested()
+ " test ability to call sort() from a compare function
+ call assert_equal([1, 3, 5], sort([3, 1, 5], 'Compare1'))
+endfunc
+
+func Test_sort_default()
+ " docs say omitted, empty or zero argument sorts on string representation.
+ call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"]))
+ call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], ''))
+ call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 0))
+ call assert_equal(['2', 'A', 'a', 'AA', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 1))
+ call assert_fails('call sort([3.3, 1, "2"], 3)', "E474:")
+endfunc
+
+" Tests for the ":sort" command.
+func Test_sort_cmd()
+ let tests = [
+ \ {
+ \ 'name' : 'Alphabetical sort',
+ \ 'cmd' : '%sort',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ],
+ \ 'expected' : [
+ \ ' 123b',
+ \ 'a',
+ \ 'a122',
+ \ 'a123',
+ \ 'a321',
+ \ 'ab',
+ \ 'abc',
+ \ 'b123',
+ \ 'b321',
+ \ 'b321',
+ \ 'b321b',
+ \ 'b322b',
+ \ 'c123d',
+ \ 'c321d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Numeric sort',
+ \ 'cmd' : '%sort n',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'a',
+ \ 'x-22',
+ \ 'b321',
+ \ 'b123',
+ \ '',
+ \ 'c123d',
+ \ '-24',
+ \ ' 123b',
+ \ 'c321d',
+ \ '0',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '-24',
+ \ 'x-22',
+ \ '0',
+ \ 'a122',
+ \ 'a123',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b321',
+ \ 'b321b',
+ \ 'b322b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Hexadecimal sort',
+ \ 'cmd' : '%sort x',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ 'ab',
+ \ 'abc',
+ \ ' 123b',
+ \ 'a122',
+ \ 'a123',
+ \ 'a321',
+ \ 'b123',
+ \ 'b321',
+ \ 'b321',
+ \ 'b321b',
+ \ 'b322b',
+ \ 'c123d',
+ \ 'c321d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Alphabetical unique sort',
+ \ 'cmd' : '%sort u',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ],
+ \ 'expected' : [
+ \ ' 123b',
+ \ 'a',
+ \ 'a122',
+ \ 'a123',
+ \ 'a321',
+ \ 'ab',
+ \ 'abc',
+ \ 'b123',
+ \ 'b321',
+ \ 'b321b',
+ \ 'b322b',
+ \ 'c123d',
+ \ 'c321d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Alphabetical reverse sort',
+ \ 'cmd' : '%sort!',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ],
+ \ 'expected' : [
+ \ 'c321d',
+ \ 'c123d',
+ \ 'b322b',
+ \ 'b321b',
+ \ 'b321',
+ \ 'b321',
+ \ 'b123',
+ \ 'abc',
+ \ 'ab',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'a',
+ \ ' 123b',
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Numeric reverse sort',
+ \ 'cmd' : '%sort! n',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ],
+ \ 'expected' : [
+ \ 'b322b',
+ \ 'b321b',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b321',
+ \ 'a321',
+ \ ' 123b',
+ \ 'c123d',
+ \ 'b123',
+ \ 'a123',
+ \ 'a122',
+ \ 'a',
+ \ 'ab',
+ \ 'abc'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Unique reverse sort',
+ \ 'cmd' : 'sort! u',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ],
+ \ 'expected' : [
+ \ 'c321d',
+ \ 'c123d',
+ \ 'b322b',
+ \ 'b321b',
+ \ 'b321',
+ \ 'b123',
+ \ 'abc',
+ \ 'ab',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'a',
+ \ ' 123b',
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Octal sort',
+ \ 'cmd' : 'sort o',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a122',
+ \ 'a123',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b321',
+ \ 'b321b',
+ \ 'b322b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Reverse hexadecimal sort',
+ \ 'cmd' : 'sort! x',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'c321d',
+ \ 'c123d',
+ \ 'b322b',
+ \ 'b321b',
+ \ 'b321',
+ \ 'b321',
+ \ 'b123',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ ' 123b',
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ ''
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Alpha (skip first character) sort',
+ \ 'cmd' : 'sort/./',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a122',
+ \ 'a123',
+ \ 'b123',
+ \ ' 123b',
+ \ 'c123d',
+ \ 'a321',
+ \ 'b321',
+ \ 'b321',
+ \ 'b321b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'ab',
+ \ 'abc'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'Alpha (skip first 2 characters) sort',
+ \ 'cmd' : 'sort/../',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a321',
+ \ 'b321',
+ \ 'b321',
+ \ 'b321b',
+ \ 'c321d',
+ \ 'a122',
+ \ 'b322b',
+ \ 'a123',
+ \ 'b123',
+ \ ' 123b',
+ \ 'c123d',
+ \ 'abc'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alpha, unique, skip first 2 characters',
+ \ 'cmd' : 'sort/../u',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ 'a321',
+ \ 'b321',
+ \ 'b321b',
+ \ 'c321d',
+ \ 'a122',
+ \ 'b322b',
+ \ 'a123',
+ \ 'b123',
+ \ ' 123b',
+ \ 'c123d',
+ \ 'abc'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'numeric, skip first character',
+ \ 'cmd' : 'sort/./n',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a122',
+ \ 'a123',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b321',
+ \ 'b321b',
+ \ 'b322b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alpha, sort on first character',
+ \ 'cmd' : 'sort/./r',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ '',
+ \ '',
+ \ ' 123b',
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ 'c123d',
+ \ 'c321d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alpha, sort on first 2 characters',
+ \ 'cmd' : 'sort/../r',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ '',
+ \ '',
+ \ ' 123b',
+ \ 'a123',
+ \ 'a122',
+ \ 'a321',
+ \ 'abc',
+ \ 'ab',
+ \ 'b123',
+ \ 'b321',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ 'c123d',
+ \ 'c321d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'numeric, sort on first character',
+ \ 'cmd' : 'sort/./rn',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alpha, skip past first digit',
+ \ 'cmd' : 'sort/\d/',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a321',
+ \ 'b321',
+ \ 'b321',
+ \ 'b321b',
+ \ 'c321d',
+ \ 'a122',
+ \ 'b322b',
+ \ 'a123',
+ \ 'b123',
+ \ ' 123b',
+ \ 'c123d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alpha, sort on first digit',
+ \ 'cmd' : 'sort/\d/r',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a123',
+ \ 'a122',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'numeric, skip past first digit',
+ \ 'cmd' : 'sort/\d/n',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b321',
+ \ 'b321b',
+ \ 'a122',
+ \ 'b322b',
+ \ 'a123',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'numeric, sort on first digit',
+ \ 'cmd' : 'sort/\d/rn',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a123',
+ \ 'a122',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alpha, skip past first 2 digits',
+ \ 'cmd' : 'sort/\d\d/',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a321',
+ \ 'b321',
+ \ 'b321',
+ \ 'b321b',
+ \ 'c321d',
+ \ 'a122',
+ \ 'b322b',
+ \ 'a123',
+ \ 'b123',
+ \ ' 123b',
+ \ 'c123d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'numeric, skip past first 2 digits',
+ \ 'cmd' : 'sort/\d\d/n',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b321',
+ \ 'b321b',
+ \ 'a122',
+ \ 'b322b',
+ \ 'a123',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'hexadecimal, skip past first 2 digits',
+ \ 'cmd' : 'sort/\d\d/x',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a321',
+ \ 'b321',
+ \ 'b321',
+ \ 'a122',
+ \ 'a123',
+ \ 'b123',
+ \ 'b321b',
+ \ 'c321d',
+ \ 'b322b',
+ \ ' 123b',
+ \ 'c123d'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alpha, sort on first 2 digits',
+ \ 'cmd' : 'sort/\d\d/r',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a123',
+ \ 'a122',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'numeric, sort on first 2 digits',
+ \ 'cmd' : 'sort/\d\d/rn',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a123',
+ \ 'a122',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'hexadecimal, sort on first 2 digits',
+ \ 'cmd' : 'sort/\d\d/rx',
+ \ 'input' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ 'a321',
+ \ 'a123',
+ \ 'a122',
+ \ 'b321',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ 'abc',
+ \ 'ab',
+ \ 'a',
+ \ '',
+ \ '',
+ \ 'a123',
+ \ 'a122',
+ \ 'b123',
+ \ 'c123d',
+ \ ' 123b',
+ \ 'a321',
+ \ 'b321',
+ \ 'c321d',
+ \ 'b322b',
+ \ 'b321',
+ \ 'b321b'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'binary',
+ \ 'cmd' : 'sort b',
+ \ 'input' : [
+ \ '0b111000',
+ \ '0b101100',
+ \ '0b101001',
+ \ '0b101001',
+ \ '0b101000',
+ \ '0b000000',
+ \ '0b001000',
+ \ '0b010000',
+ \ '0b101000',
+ \ '0b100000',
+ \ '0b101010',
+ \ '0b100010',
+ \ '0b100100',
+ \ '0b100010',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ '',
+ \ '',
+ \ '0b000000',
+ \ '0b001000',
+ \ '0b010000',
+ \ '0b100000',
+ \ '0b100010',
+ \ '0b100010',
+ \ '0b100100',
+ \ '0b101000',
+ \ '0b101000',
+ \ '0b101001',
+ \ '0b101001',
+ \ '0b101010',
+ \ '0b101100',
+ \ '0b111000'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'binary with leading characters',
+ \ 'cmd' : 'sort b',
+ \ 'input' : [
+ \ '0b100010',
+ \ '0b010000',
+ \ ' 0b101001',
+ \ 'b0b101100',
+ \ '0b100010',
+ \ ' 0b100100',
+ \ 'a0b001000',
+ \ '0b101000',
+ \ '0b101000',
+ \ 'a0b101001',
+ \ 'ab0b100000',
+ \ '0b101010',
+ \ '0b000000',
+ \ 'b0b111000',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ '',
+ \ '',
+ \ '0b000000',
+ \ 'a0b001000',
+ \ '0b010000',
+ \ 'ab0b100000',
+ \ '0b100010',
+ \ '0b100010',
+ \ ' 0b100100',
+ \ '0b101000',
+ \ '0b101000',
+ \ ' 0b101001',
+ \ 'a0b101001',
+ \ '0b101010',
+ \ 'b0b101100',
+ \ 'b0b111000'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alphabetical, sorted input',
+ \ 'cmd' : 'sort',
+ \ 'input' : [
+ \ 'a',
+ \ 'b',
+ \ 'c',
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ 'b',
+ \ 'c',
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alphabetical, sorted input, unique at end',
+ \ 'cmd' : 'sort u',
+ \ 'input' : [
+ \ 'aa',
+ \ 'bb',
+ \ 'cc',
+ \ 'cc',
+ \ ],
+ \ 'expected' : [
+ \ 'aa',
+ \ 'bb',
+ \ 'cc',
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'sort one line buffer',
+ \ 'cmd' : 'sort',
+ \ 'input' : [
+ \ 'single line'
+ \ ],
+ \ 'expected' : [
+ \ 'single line'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'sort ignoring case',
+ \ 'cmd' : '%sort i',
+ \ 'input' : [
+ \ 'BB',
+ \ 'Cc',
+ \ 'aa'
+ \ ],
+ \ 'expected' : [
+ \ 'aa',
+ \ 'BB',
+ \ 'Cc'
+ \ ]
+ \ },
+ \ ]
+
+ " This does not appear to work correctly on Mac.
+ if !has('mac')
+ if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
+ " en_CA.utf-8 sorts capitals before lower case
+ " 'Å’' is omitted because it can sort before or after 'Å“'
+ let tests += [
+ \ {
+ \ 'name' : 'sort with locale ' .. v:collate,
+ \ 'cmd' : '%sort l',
+ \ 'input' : [
+ \ 'A',
+ \ 'E',
+ \ 'O',
+ \ 'À',
+ \ 'È',
+ \ 'É',
+ \ 'Ô',
+ \ 'Z',
+ \ 'a',
+ \ 'e',
+ \ 'o',
+ \ 'à',
+ \ 'è',
+ \ 'é',
+ \ 'ô',
+ \ 'Å“',
+ \ 'z'
+ \ ],
+ \ 'expected' : [
+ \ 'A',
+ \ 'a',
+ \ 'À',
+ \ 'à',
+ \ 'E',
+ \ 'e',
+ \ 'É',
+ \ 'é',
+ \ 'È',
+ \ 'è',
+ \ 'O',
+ \ 'o',
+ \ 'Ô',
+ \ 'ô',
+ \ 'Å“',
+ \ 'Z',
+ \ 'z'
+ \ ]
+ \ },
+ \ ]
+ elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
+ " With these locales, the accentuated letters are ordered
+ " similarly to the non-accentuated letters.
+ let tests += [
+ \ {
+ \ 'name' : 'sort with locale ' .. v:collate,
+ \ 'cmd' : '%sort l',
+ \ 'input' : [
+ \ 'A',
+ \ 'E',
+ \ 'O',
+ \ 'À',
+ \ 'È',
+ \ 'É',
+ \ 'Ô',
+ \ 'Å’',
+ \ 'Z',
+ \ 'a',
+ \ 'e',
+ \ 'o',
+ \ 'à',
+ \ 'è',
+ \ 'é',
+ \ 'ô',
+ \ 'Å“',
+ \ 'z'
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ 'A',
+ \ 'à',
+ \ 'À',
+ \ 'e',
+ \ 'E',
+ \ 'é',
+ \ 'É',
+ \ 'è',
+ \ 'È',
+ \ 'o',
+ \ 'O',
+ \ 'ô',
+ \ 'Ô',
+ \ 'Å“',
+ \ 'Å’',
+ \ 'z',
+ \ 'Z'
+ \ ]
+ \ },
+ \ ]
+ endif
+ endif
+ let tests += [
+ \ {
+ \ 'name' : 'float',
+ \ 'cmd' : 'sort f',
+ \ 'input' : [
+ \ '1.234',
+ \ '0.88',
+ \ ' + 123.456',
+ \ '1.15e-6',
+ \ '-1.1e3',
+ \ '-1.01e3',
+ \ '',
+ \ ''
+ \ ],
+ \ 'expected' : [
+ \ '',
+ \ '',
+ \ '-1.1e3',
+ \ '-1.01e3',
+ \ '1.15e-6',
+ \ '0.88',
+ \ '1.234',
+ \ ' + 123.456'
+ \ ]
+ \ },
+ \ ]
+
+ for t in tests
+ enew!
+ call append(0, t.input)
+ $delete _
+ setlocal nomodified
+ execute t.cmd
+
+ call assert_equal(t.expected, getline(1, '$'), t.name)
+
+ " Previously, the ":sort" command would set 'modified' even if the buffer
+ " contents did not change. Here, we check that this problem is fixed.
+ if t.input == t.expected
+ call assert_false(&modified, t.name . ': &mod is not correct')
+ else
+ call assert_true(&modified, t.name . ': &mod is not correct')
+ endif
+ endfor
+
+ " Needs at least two lines for this test
+ call setline(1, ['line1', 'line2'])
+ call assert_fails('sort no', 'E474:')
+ call assert_fails('sort c', 'E475:')
+ call assert_fails('sort #pat%', 'E654:')
+ call assert_fails('sort /\%(/', 'E53:')
+
+ enew!
+endfunc
+
+func Test_sort_large_num()
+ new
+ a
+-2147483648
+-2147483647
+
+-1
+0
+1
+-2147483646
+2147483646
+2147483647
+2147483647
+-2147483648
+abc
+
+.
+ " Numerical sort. Non-numeric lines are ordered before numerical lines.
+ " Ordering of non-numerical is stable.
+ sort n
+ call assert_equal(['',
+ \ 'abc',
+ \ '',
+ \ '-2147483648',
+ \ '-2147483648',
+ \ '-2147483647',
+ \ '-2147483646',
+ \ '-1',
+ \ '0',
+ \ '1',
+ \ '2147483646',
+ \ '2147483647',
+ \ '2147483647'], getline(1, '$'))
+ bwipe!
+
+ new
+ a
+-9223372036854775808
+-9223372036854775807
+
+-1
+0
+1
+-9223372036854775806
+9223372036854775806
+9223372036854775807
+9223372036854775807
+-9223372036854775808
+abc
+
+.
+ sort n
+ call assert_equal(['',
+ \ 'abc',
+ \ '',
+ \ '-9223372036854775808',
+ \ '-9223372036854775808',
+ \ '-9223372036854775807',
+ \ '-9223372036854775806',
+ \ '-1',
+ \ '0',
+ \ '1',
+ \ '9223372036854775806',
+ \ '9223372036854775807',
+ \ '9223372036854775807'], getline(1, '$'))
+ bwipe!
+endfunc
+
+
+func Test_sort_cmd_report()
+ enew!
+ call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
+ $delete _
+ setlocal nomodified
+ let res = execute('%sort u')
+
+ call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
+ call assert_match("6 fewer lines", res)
+ enew!
+ call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
+ $delete _
+ setlocal nomodified report=10
+ let res = execute('%sort u')
+
+ call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
+ call assert_equal("", res)
+ enew!
+ call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
+ $delete _
+ setl report&vim
+ setlocal nomodified
+ let res = execute('1g/^/%sort u')
+
+ call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
+ " the output comes from the :g command, not from the :sort
+ call assert_match("6 fewer lines", res)
+ enew!
+endfunc
+
+" Test for a :sort command followed by another command
+func Test_sort_followed_by_cmd()
+ new
+ let var = ''
+ call setline(1, ['cc', 'aa', 'bb'])
+ %sort | let var = "sortcmdtest"
+ call assert_equal(var, "sortcmdtest")
+ call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
+ " Test for :sort followed by a comment
+ call setline(1, ['3b', '1c', '2a'])
+ %sort /\d\+/ " sort alphabetically
+ call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for :sort using last search pattern
+func Test_sort_last_search_pat()
+ new
+ let @/ = '\d\+'
+ call setline(1, ['3b', '1c', '2a'])
+ sort //
+ call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for :sort with no last search pattern
+func Test_sort_with_no_last_search_pat()
+ let lines =<< trim [SCRIPT]
+ call setline(1, ['3b', '1c', '2a'])
+ call assert_fails('sort //', 'E35:')
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test for retaining marks across a :sort
+func Test_sort_with_marks()
+ new
+ call setline(1, ['cc', 'aa', 'bb'])
+ call setpos("'c", [0, 1, 0, 0])
+ call setpos("'a", [0, 2, 0, 0])
+ call setpos("'b", [0, 3, 0, 0])
+ %sort
+ call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
+ call assert_equal(2, line("'a"))
+ call assert_equal(3, line("'b"))
+ call assert_equal(1, line("'c"))
+ close!
+endfunc
+
+" Test for sort() using a dict function
+func Test_sort_using_dict_func()
+ func DictSort(a, b) dict
+ if self.order == 'reverse'
+ return a:b - a:a
+ else
+ return a:a - a:b
+ endif
+ endfunc
+ let d = #{order: ''}
+ call assert_equal([1, 2, 3], sort([2, 1, 3], 'DictSort', d))
+ let d = #{order: 'reverse'}
+ call assert_equal([3, 2, 1], sort([2, 1, 3], 'DictSort', d))
+ delfunc DictSort
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_sound.vim b/src/testdir/test_sound.vim
new file mode 100644
index 0000000..e97ac61
--- /dev/null
+++ b/src/testdir/test_sound.vim
@@ -0,0 +1,100 @@
+" Tests for the sound feature
+
+source check.vim
+source shared.vim
+
+CheckFeature sound
+
+func PlayCallback(id, result)
+ let g:playcallback_count += 1
+ let g:id = a:id
+ let g:result = a:result
+endfunc
+
+func Test_play_event()
+ if has('win32')
+ throw 'Skipped: Playing event with callback is not supported on Windows'
+ endif
+ let g:playcallback_count = 0
+ let g:id = 0
+ let event_name = 'bell'
+ if has('osx')
+ let event_name = 'Tink'
+ endif
+ let id = event_name->sound_playevent('PlayCallback')
+ if id == 0
+ throw 'Skipped: bell event not available'
+ endif
+
+ " Stop it quickly, avoid annoying the user.
+ sleep 20m
+ eval id->sound_stop()
+ call WaitForAssert({-> assert_equal(id, g:id)})
+ call assert_equal(1, g:result) " sound was aborted
+ call assert_equal(1, g:playcallback_count)
+endfunc
+
+func Test_play_silent()
+ let fname = fnamemodify('silent.wav', '%p')
+ let g:playcallback_count = 0
+
+ " play without callback
+ let id1 = sound_playfile(fname)
+ if id1 == 0
+ throw 'Skipped: playing a sound is not working'
+ endif
+
+ " play until the end
+ let id2 = fname->sound_playfile('PlayCallback')
+ call assert_true(id2 > 0)
+ call WaitForAssert({-> assert_equal(id2, g:id)})
+ call assert_equal(0, g:result)
+ call assert_equal(1, g:playcallback_count)
+
+ let id2 = sound_playfile(fname, 'PlayCallback')
+ call assert_true(id2 > 0)
+ sleep 20m
+ call sound_clear()
+ call WaitForAssert({-> assert_equal(id2, g:id)})
+ call assert_equal(1, g:result) " sound was aborted
+ call assert_equal(2, g:playcallback_count)
+
+ " Play 2 sounds almost at the same time to exercise
+ " code with multiple callbacks in the callback list.
+ call sound_playfile(fname, 'PlayCallback')
+ call sound_playfile(fname, 'PlayCallback')
+ call WaitForAssert({-> assert_equal(4, g:playcallback_count)})
+
+ " recursive use was causing a crash
+ func PlayAgain(id, fname)
+ let g:id = a:id
+ let g:id_again = sound_playfile(a:fname)
+ endfunc
+
+ let id3 = sound_playfile(fname, {id, res -> PlayAgain(id, fname)})
+ call assert_true(id3 > 0)
+ sleep 50m
+ call sound_clear()
+ call WaitForAssert({-> assert_true(g:id > 0)})
+endfunc
+
+func Test_play_event_error()
+ " FIXME: sound_playevent() doesn't return 0 in case of error on Windows.
+ if !has('win32')
+ call assert_equal(0, sound_playevent(''))
+ call assert_equal(0, sound_playevent(test_null_string()))
+ call assert_equal(0, sound_playevent('doesnotexist'))
+ call assert_equal(0, sound_playevent('doesnotexist', 'doesnotexist'))
+ call assert_equal(0, sound_playevent(test_null_string(), test_null_string()))
+ call assert_equal(0, sound_playevent(test_null_string(), test_null_function()))
+ endif
+
+ call assert_equal(0, sound_playfile(''))
+ call assert_equal(0, sound_playfile(test_null_string()))
+ call assert_equal(0, sound_playfile('doesnotexist'))
+ call assert_equal(0, sound_playfile('doesnotexist', 'doesnotexist'))
+ call assert_equal(0, sound_playfile(test_null_string(), test_null_string()))
+ call assert_equal(0, sound_playfile(test_null_string(), test_null_function()))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_source.vim b/src/testdir/test_source.vim
new file mode 100644
index 0000000..3692eee
--- /dev/null
+++ b/src/testdir/test_source.vim
@@ -0,0 +1,677 @@
+" Tests for the :source command.
+
+source check.vim
+source view_util.vim
+
+func Test_source_autocmd()
+ call writefile([
+ \ 'let did_source = 1',
+ \ ], 'Xsourced', 'D')
+ au SourcePre *source* let did_source_pre = 1
+ au SourcePost *source* let did_source_post = 1
+
+ source Xsourced
+
+ call assert_equal(g:did_source, 1)
+ call assert_equal(g:did_source_pre, 1)
+ call assert_equal(g:did_source_post, 1)
+
+ au! SourcePre
+ au! SourcePost
+ unlet g:did_source
+ unlet g:did_source_pre
+ unlet g:did_source_post
+endfunc
+
+func Test_source_cmd()
+ au SourceCmd *source* let did_source = expand('<afile>')
+ au SourcePre *source* let did_source_pre = 2
+ au SourcePost *source* let did_source_post = 2
+
+ source Xsourced
+
+ call assert_equal(g:did_source, 'Xsourced')
+ call assert_false(exists('g:did_source_pre'))
+ call assert_equal(g:did_source_post, 2)
+
+ au! SourceCmd
+ au! SourcePre
+ au! SourcePost
+endfunc
+
+func Test_source_sandbox()
+ new
+ call writefile(["Ohello\<Esc>"], 'Xsourcehello', 'D')
+ source! Xsourcehello | echo
+ call assert_equal('hello', getline(1))
+ call assert_fails('sandbox source! Xsourcehello', 'E48:')
+ bwipe!
+endfunc
+
+" When deleting a file and immediately creating a new one the inode may be
+" recycled. Vim should not recognize it as the same script.
+func Test_different_script()
+ call writefile(['let s:var = "asdf"'], 'XoneScript', 'D')
+ source XoneScript
+ call writefile(['let g:var = s:var'], 'XtwoScript', 'D')
+ call assert_fails('source XtwoScript', 'E121:')
+endfunc
+
+" When sourcing a vim script, shebang should be ignored.
+func Test_source_ignore_shebang()
+ call writefile(['#!./xyzabc', 'let g:val=369'], 'Xsisfile.vim', 'D')
+ source Xsisfile.vim
+ call assert_equal(g:val, 369)
+endfunc
+
+" Test for expanding <sfile> in an autocmd and for <slnum> and <sflnum>
+func Test_source_autocmd_sfile()
+ let code =<< trim [CODE]
+ let g:SfileName = ''
+ augroup sfiletest
+ au!
+ autocmd User UserAutoCmd let g:Sfile = '<sfile>:t'
+ augroup END
+ doautocmd User UserAutoCmd
+ let g:Slnum = expand('<slnum>')
+ let g:Sflnum = expand('<sflnum>')
+ augroup! sfiletest
+ [CODE]
+ call writefile(code, 'Xscript.vim', 'D')
+ source Xscript.vim
+ call assert_equal('Xscript.vim', g:Sfile)
+ call assert_equal('7', g:Slnum)
+ call assert_equal('8', g:Sflnum)
+endfunc
+
+func Test_source_error()
+ call assert_fails('scriptencoding utf-8', 'E167:')
+ call assert_fails('finish', 'E168:')
+ call assert_fails('scriptversion 2', 'E984:')
+ call assert_fails('source!', 'E471:')
+ new
+ call setline(1, ['', '', '', ''])
+ call assert_fails('1,3source Xscript.vim', 'E481:')
+ call assert_fails('1,3source! Xscript.vim', 'E481:')
+ bw!
+endfunc
+
+" Test for sourcing a script recursively
+func Test_nested_script()
+ CheckRunVimInTerminal
+ call writefile([':source! Xscript.vim', ''], 'Xscript.vim', 'D')
+ let buf = RunVimInTerminal('', {'rows': 6})
+ call term_wait(buf)
+ call term_sendkeys(buf, ":set noruler\n")
+ call term_sendkeys(buf, ":source! Xscript.vim\n")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_match('E22: Scripts nested too deep\s*', term_getline(buf, 6))})
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for sourcing a script from the current buffer
+func Test_source_buffer()
+ new
+ " Source a simple script
+ let lines =<< trim END
+ let a = "Test"
+ let b = 20
+
+ let c = [1.1]
+ END
+ call setline(1, lines)
+ source
+ call assert_equal(['Test', 20, [1.1]], [g:a, g:b, g:c])
+
+ " Source a range of lines in the current buffer
+ %d _
+ let lines =<< trim END
+ let a = 10
+ let a += 20
+ let a += 30
+ let a += 40
+ END
+ call setline(1, lines)
+ .source
+ call assert_equal(10, g:a)
+ 3source
+ call assert_equal(40, g:a)
+ 2,3source
+ call assert_equal(90, g:a)
+
+ " Make sure the script line number is correct when sourcing a range of
+ " lines.
+ %d _
+ let lines =<< trim END
+ Line 1
+ Line 2
+ func Xtestfunc()
+ return expand("<sflnum>")
+ endfunc
+ Line 3
+ Line 4
+ END
+ call setline(1, lines)
+ 3,5source
+ call assert_equal('4', Xtestfunc())
+ delfunc Xtestfunc
+
+ " Source a script with line continuation lines
+ %d _
+ let lines =<< trim END
+ let m = [
+ \ 1,
+ \ 2,
+ \ ]
+ call add(m, 3)
+ END
+ call setline(1, lines)
+ source
+ call assert_equal([1, 2, 3], g:m)
+ " Source a script with line continuation lines and a comment
+ %d _
+ let lines =<< trim END
+ let m = [
+ "\ first entry
+ \ 'a',
+ "\ second entry
+ \ 'b',
+ \ ]
+ " third entry
+ call add(m, 'c')
+ END
+ call setline(1, lines)
+ source
+ call assert_equal(['a', 'b', 'c'], g:m)
+ " Source an incomplete line continuation line
+ %d _
+ let lines =<< trim END
+ let k = [
+ \
+ END
+ call setline(1, lines)
+ call assert_fails('source', 'E697:')
+ " Source a function with a for loop
+ %d _
+ let lines =<< trim END
+ let m = []
+ " test function
+ func! Xtest()
+ for i in range(5, 7)
+ call add(g:m, i)
+ endfor
+ endfunc
+ call Xtest()
+ END
+ call setline(1, lines)
+ source
+ call assert_equal([5, 6, 7], g:m)
+ " Source an empty buffer
+ %d _
+ source
+
+ " test for script local functions and variables
+ let lines =<< trim END
+ let s:var1 = 10
+ func s:F1()
+ let s:var1 += 1
+ return s:var1
+ endfunc
+ func s:F2()
+ endfunc
+ let g:ScriptID = expand("<SID>")
+ END
+ call setline(1, lines)
+ source
+ call assert_true(g:ScriptID != '')
+ call assert_true(exists('*' .. g:ScriptID .. 'F1'))
+ call assert_true(exists('*' .. g:ScriptID .. 'F2'))
+ call assert_equal(11, call(g:ScriptID .. 'F1', []))
+
+ " the same script ID should be used even if the buffer is sourced more than
+ " once
+ %d _
+ let lines =<< trim END
+ let g:ScriptID = expand("<SID>")
+ let g:Count += 1
+ END
+ call setline(1, lines)
+ let g:Count = 0
+ source
+ call assert_true(g:ScriptID != '')
+ let scid = g:ScriptID
+ source
+ call assert_equal(scid, g:ScriptID)
+ call assert_equal(2, g:Count)
+ source
+ call assert_equal(scid, g:ScriptID)
+ call assert_equal(3, g:Count)
+
+ " test for the script line number
+ %d _
+ let lines =<< trim END
+ " comment
+ let g:Slnum1 = expand("<slnum>")
+ let i = 1 +
+ \ 2 +
+ "\ comment
+ \ 3
+ let g:Slnum2 = expand("<slnum>")
+ END
+ call setline(1, lines)
+ source
+ call assert_equal('2', g:Slnum1)
+ call assert_equal('7', g:Slnum2)
+
+ " test for retaining the same script number across source calls
+ let lines =<< trim END
+ let g:ScriptID1 = expand("<SID>")
+ let g:Slnum1 = expand("<slnum>")
+ let l =<< trim END
+ let g:Slnum2 = expand("<slnum>")
+ let g:ScriptID2 = expand("<SID>")
+ END
+ new
+ call setline(1, l)
+ source
+ bw!
+ let g:ScriptID3 = expand("<SID>")
+ let g:Slnum3 = expand("<slnum>")
+ END
+ call writefile(lines, 'Xscript', 'D')
+ source Xscript
+ call assert_true(g:ScriptID1 != g:ScriptID2)
+ call assert_equal(g:ScriptID1, g:ScriptID3)
+ call assert_equal('2', g:Slnum1)
+ call assert_equal('1', g:Slnum2)
+ call assert_equal('12', g:Slnum3)
+
+ " test for sourcing a heredoc
+ %d _
+ let lines =<< trim END
+ let a = 1
+ let heredoc =<< trim DATA
+ red
+ green
+ blue
+ DATA
+ let b = 2
+ END
+ call setline(1, lines)
+ source
+ call assert_equal(['red', ' green', 'blue'], g:heredoc)
+
+ " test for a while and for statement
+ %d _
+ let lines =<< trim END
+ let a = 0
+ let b = 1
+ while b <= 10
+ let a += 10
+ let b += 1
+ endwhile
+ for i in range(5)
+ let a += 10
+ endfor
+ END
+ call setline(1, lines)
+ source
+ call assert_equal(150, g:a)
+
+ " test for sourcing the same buffer multiple times after changing a function
+ %d _
+ let lines =<< trim END
+ func Xtestfunc()
+ return "one"
+ endfunc
+ END
+ call setline(1, lines)
+ source
+ call assert_equal("one", Xtestfunc())
+ call setline(2, ' return "two"')
+ source
+ call assert_equal("two", Xtestfunc())
+ call setline(2, ' return "three"')
+ source
+ call assert_equal("three", Xtestfunc())
+ delfunc Xtestfunc
+
+ " test for using try/catch
+ %d _
+ let lines =<< trim END
+ let Trace = '1'
+ try
+ let a1 = b1
+ catch
+ let Trace ..= '2'
+ finally
+ let Trace ..= '3'
+ endtry
+ END
+ call setline(1, lines)
+ source
+ call assert_equal("123", g:Trace)
+
+ " test with the finish command
+ %d _
+ let lines =<< trim END
+ let g:Color = 'blue'
+ finish
+ let g:Color = 'green'
+ END
+ call setline(1, lines)
+ source
+ call assert_equal('blue', g:Color)
+
+ " Test for the SourcePre and SourcePost autocmds
+ augroup Xtest
+ au!
+ au SourcePre * let g:XsourcePre=4
+ \ | let g:XsourcePreFile = expand("<afile>")
+ au SourcePost * let g:XsourcePost=6
+ \ | let g:XsourcePostFile = expand("<afile>")
+ augroup END
+ %d _
+ let lines =<< trim END
+ let a = 1
+ END
+ call setline(1, lines)
+ source
+ call assert_equal(4, g:XsourcePre)
+ call assert_equal(6, g:XsourcePost)
+ call assert_equal(':source buffer=' .. bufnr(), g:XsourcePreFile)
+ call assert_equal(':source buffer=' .. bufnr(), g:XsourcePostFile)
+ augroup Xtest
+ au!
+ augroup END
+ augroup! Xtest
+
+ %bw!
+endfunc
+
+" Test for sourcing a Vim9 script from the current buffer
+func Test_source_buffer_vim9()
+ new
+
+ " test for sourcing a Vim9 script
+ %d _
+ let lines =<< trim END
+ vim9script
+
+ # check dict
+ var x: number = 10
+ def g:Xtestfunc(): number
+ return x
+ enddef
+ END
+ call setline(1, lines)
+ source
+ call assert_equal(10, Xtestfunc())
+
+ " test for sourcing a vim9 script with line continuation
+ %d _
+ let lines =<< trim END
+ vim9script
+
+ g:Str1 = "hello "
+ .. "world"
+ .. ", how are you?"
+ g:Colors = [
+ 'red',
+ # comment
+ 'blue'
+ ]
+ g:Dict = {
+ a: 22,
+ # comment
+ b: 33
+ }
+
+ # calling a function with line continuation
+ def Sum(...values: list<number>): number
+ var sum: number = 0
+ for v in values
+ sum += v
+ endfor
+ return sum
+ enddef
+ g:Total1 = Sum(10,
+ 20,
+ 30)
+
+ var i: number = 0
+ while i < 10
+ # while loop
+ i +=
+ 1
+ endwhile
+ g:Count1 = i
+
+ # for loop
+ g:Count2 = 0
+ for j in range(10, 20)
+ g:Count2 +=
+ i
+ endfor
+
+ g:Total2 = 10 +
+ 20 -
+ 5
+
+ g:Result1 = g:Total2 > 1
+ ? 'red'
+ : 'blue'
+
+ g:Str2 = 'x'
+ ->repeat(10)
+ ->trim()
+ ->strpart(4)
+
+ g:Result2 = g:Dict
+ .a
+
+ augroup Test
+ au!
+ au BufNewFile Xsubfile g:readFile = 1
+ | g:readExtra = 2
+ augroup END
+ g:readFile = 0
+ g:readExtra = 0
+ new Xsubfile
+ bwipe!
+ augroup Test
+ au!
+ augroup END
+ END
+ call setline(1, lines)
+ source
+ call assert_equal("hello world, how are you?", g:Str1)
+ call assert_equal(['red', 'blue'], g:Colors)
+ call assert_equal(#{a: 22, b: 33}, g:Dict)
+ call assert_equal(60, g:Total1)
+ call assert_equal(10, g:Count1)
+ call assert_equal(110, g:Count2)
+ call assert_equal(25, g:Total2)
+ call assert_equal('red', g:Result1)
+ call assert_equal('xxxxxx', g:Str2)
+ call assert_equal(22, g:Result2)
+ call assert_equal(1, g:readFile)
+ call assert_equal(2, g:readExtra)
+
+ " test for sourcing the same buffer multiple times after changing a function
+ %d _
+ let lines =<< trim END
+ vim9script
+ def g:Xtestfunc(): string
+ return "one"
+ enddef
+ END
+ call setline(1, lines)
+ source
+ call assert_equal("one", Xtestfunc())
+ call setline(3, ' return "two"')
+ source
+ call assert_equal("two", Xtestfunc())
+ call setline(3, ' return "three"')
+ source
+ call assert_equal("three", Xtestfunc())
+ delfunc Xtestfunc
+
+ " Test for sourcing a range of lines. Make sure the script line number is
+ " correct.
+ %d _
+ let lines =<< trim END
+ Line 1
+ Line 2
+ vim9script
+ def g:Xtestfunc(): string
+ return expand("<sflnum>")
+ enddef
+ Line 3
+ Line 4
+ END
+ call setline(1, lines)
+ 3,6source
+ call assert_equal('5', Xtestfunc())
+ delfunc Xtestfunc
+
+ " test for sourcing a heredoc
+ %d _
+ let lines =<< trim END
+ vim9script
+ var a = 1
+ g:heredoc =<< trim DATA
+ red
+ green
+ blue
+ DATA
+ var b = 2
+ END
+ call setline(1, lines)
+ source
+ call assert_equal(['red', ' green', 'blue'], g:heredoc)
+
+ " test for using the :vim9cmd modifier
+ %d _
+ let lines =<< trim END
+ first line
+ g:Math = {
+ pi: 3.12,
+ e: 2.71828
+ }
+ g:Editors = [
+ 'vim',
+ # comment
+ 'nano'
+ ]
+ last line
+ END
+ call setline(1, lines)
+ vim9cmd :2,10source
+ call assert_equal(#{pi: 3.12, e: 2.71828}, g:Math)
+ call assert_equal(['vim', 'nano'], g:Editors)
+
+ " '<,'> range before the cmd modifier works
+ unlet g:Math
+ unlet g:Editors
+ exe "normal 6GV4j:vim9cmd source\<CR>"
+ call assert_equal(['vim', 'nano'], g:Editors)
+ unlet g:Editors
+
+ " test for using try/catch
+ %d _
+ let lines =<< trim END
+ vim9script
+ g:Trace = '1'
+ try
+ a1 = b1
+ catch
+ g:Trace ..= '2'
+ finally
+ g:Trace ..= '3'
+ endtry
+ END
+ call setline(1, lines)
+ source
+ call assert_equal('123', g:Trace)
+
+ " test with the finish command
+ %d _
+ let lines =<< trim END
+ vim9script
+ g:Color = 'red'
+ finish
+ g:Color = 'blue'
+ END
+ call setline(1, lines)
+ source
+ call assert_equal('red', g:Color)
+
+ " test for ++clear argument to clear all the functions/variables
+ %d _
+ let lines =<< trim END
+ g:ScriptVarFound = exists("color")
+ g:MyFuncFound = exists('*Myfunc')
+ if g:MyFuncFound
+ finish
+ endif
+ var color = 'blue'
+ def Myfunc()
+ enddef
+ END
+ call setline(1, lines)
+ vim9cmd source
+ call assert_false(g:MyFuncFound)
+ call assert_false(g:ScriptVarFound)
+ vim9cmd source
+ call assert_true(g:MyFuncFound)
+ call assert_true(g:ScriptVarFound)
+ vim9cmd source ++clear
+ call assert_false(g:MyFuncFound)
+ call assert_false(g:ScriptVarFound)
+ vim9cmd source ++clear
+ call assert_false(g:MyFuncFound)
+ call assert_false(g:ScriptVarFound)
+ call assert_fails('vim9cmd source ++clearx', 'E475:')
+ call assert_fails('vim9cmd source ++abcde', 'E484:')
+
+ %bw!
+endfunc
+
+func Test_source_buffer_long_line()
+ " This was reading past the end of the line.
+ new
+ norm300gr0
+ so
+ bwipe!
+
+ let lines =<< trim END
+ new
+ norm 10a0000000000ø00000000000
+ norm i0000000000000000000
+ silent! so
+ END
+ call writefile(lines, 'Xtest.vim', 'D')
+ source Xtest.vim
+ bwipe!
+endfunc
+
+func Test_source_buffer_with_NUL_char()
+ " This was trying to use a line below the buffer.
+ let lines =<< trim END
+ if !exists('g:loaded')
+ let g:loaded = 1
+ source
+ endif
+ END
+ " Can't have a NL in heredoc
+ let lines += ["silent! vim9 echo [0 \<NL> ? 'a' : 'b']"]
+ call writefile(lines, 'XsourceNul', 'D')
+ edit XsourceNul
+ source
+
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_source_utf8.vim b/src/testdir/test_source_utf8.vim
new file mode 100644
index 0000000..bd7cd8d
--- /dev/null
+++ b/src/testdir/test_source_utf8.vim
@@ -0,0 +1,63 @@
+" Test the :source! command
+
+source check.vim
+
+func Test_source_utf8()
+ " check that sourcing a script with 0x80 as second byte works
+ new
+ call setline(1, [':%s/àx/--à1234--/g', ':%s/Àx/--À1234--/g'])
+ write! Xscript
+ bwipe!
+ new
+ call setline(1, [' àx ', ' Àx '])
+ source! Xscript | echo
+ call assert_equal(' --à1234-- ', getline(1))
+ call assert_equal(' --À1234-- ', getline(2))
+ bwipe!
+ call delete('Xscript')
+endfunc
+
+func Test_source_latin()
+ " check that sourcing a latin1 script with a 0xc0 byte works
+ new
+ call setline(1, ["call feedkeys('r')", "call feedkeys('\xc0', 'xt')"])
+ write! Xscript
+ bwipe!
+ new
+ call setline(1, ['xxx'])
+ source Xscript
+ call assert_equal("\u00c0xx", getline(1))
+ bwipe!
+ call delete('Xscript')
+endfunc
+
+" Test for sourcing a file with CTRL-V's at the end of the line
+func Test_source_ctrl_v()
+ call writefile(['map __1 afirst',
+ \ 'map __2 asecond',
+ \ 'map __3 athird',
+ \ 'map __4 afourth',
+ \ 'map __5 afifth',
+ \ "map __1 asd\<C-V>",
+ \ "map __2 asd\<C-V>\<C-V>",
+ \ "map __3 asd\<C-V>\<C-V>",
+ \ "map __4 asd\<C-V>\<C-V>\<C-V>",
+ \ "map __5 asd\<C-V>\<C-V>\<C-V>",
+ \ ], 'Xtestfile', 'D')
+ source Xtestfile
+ enew!
+ exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>"
+ exe "%s/\<C-J>/0/g"
+ call assert_equal(['sd',
+ \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"],
+ \ getline(1, 2))
+
+ enew!
+ unmap __1
+ unmap __2
+ unmap __3
+ unmap __4
+ unmap __5
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_spell.vim b/src/testdir/test_spell.vim
new file mode 100644
index 0000000..bd387f1
--- /dev/null
+++ b/src/testdir/test_spell.vim
@@ -0,0 +1,1505 @@
+" Test spell checking
+" Note: this file uses latin1 encoding, but is used with utf-8 encoding.
+
+source check.vim
+CheckFeature spell
+
+source screendump.vim
+
+func TearDown()
+ set nospell
+ call delete('Xtest.aff')
+ call delete('Xtest.dic')
+ call delete('Xtest.latin1.add')
+ call delete('Xtest.latin1.add.spl')
+ call delete('Xtest.latin1.spl')
+ call delete('Xtest.latin1.sug')
+ " set 'encoding' to clear the word list
+ set encoding=utf-8
+endfunc
+
+func Test_wrap_search()
+ new
+ call setline(1, ['The', '', 'A plong line with two zpelling mistakes', '', 'End'])
+ set spell wrapscan
+ normal ]s
+ call assert_equal('plong', expand('<cword>'))
+ normal ]s
+ call assert_equal('zpelling', expand('<cword>'))
+ normal ]s
+ call assert_equal('plong', expand('<cword>'))
+ bwipe!
+ set nospell
+endfunc
+
+func Test_curswant()
+ new
+ call setline(1, ['Another plong line', 'abcdefghijklmnopq'])
+ set spell wrapscan
+ normal 0]s
+ call assert_equal('plong', expand('<cword>'))
+ normal j
+ call assert_equal(9, getcurpos()[2])
+ normal 0[s
+ call assert_equal('plong', expand('<cword>'))
+ normal j
+ call assert_equal(9, getcurpos()[2])
+
+ normal 0]S
+ call assert_equal('plong', expand('<cword>'))
+ normal j
+ call assert_equal(9, getcurpos()[2])
+ normal 0[S
+ call assert_equal('plong', expand('<cword>'))
+ normal j
+ call assert_equal(9, getcurpos()[2])
+
+ normal 1G0
+ call assert_equal('plong', spellbadword()[0])
+ normal j
+ call assert_equal(9, getcurpos()[2])
+
+ bwipe!
+ set nospell
+endfunc
+
+func Test_z_equal_on_invalid_utf8_word()
+ split
+ set spell
+ call setline(1, "\xff")
+ norm z=
+ set nospell
+ bwipe!
+endfunc
+
+func Test_z_equal_on_single_character()
+ " this was decrementing the index below zero
+ new
+ norm a0\Ê
+ norm zW
+ norm z=
+
+ bwipe!
+endfunc
+
+" Test spellbadword() with argument
+func Test_spellbadword()
+ set spell
+
+ call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
+ call assert_equal(['another', 'caps'], 'A sentence. another sentence'->spellbadword())
+
+ call assert_equal(['TheCamelWord', 'bad'], 'TheCamelWord asdf'->spellbadword())
+ set spelloptions=camel
+ call assert_equal(['asdf', 'bad'], 'TheCamelWord asdf'->spellbadword())
+ set spelloptions=
+
+ set spelllang=en
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('center'))
+ set spelllang=en_us
+ call assert_equal(['centre', 'local'], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('center'))
+ set spelllang=en_gb
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['center', 'local'], spellbadword('center'))
+
+ " Create a small word list to test that spellbadword('...')
+ " can return ['...', 'rare'].
+ e Xwords
+ insert
+foo
+foobar/?
+.
+ w!
+ mkspell! Xwords.spl Xwords
+ set spelllang=Xwords.spl
+ call assert_equal(['foobar', 'rare'], spellbadword('foo foobar'))
+
+ " Typo should be detected even without the 'spell' option.
+ set spelllang=en_gb nospell
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
+ call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence'))
+
+ set spelllang=
+ call assert_fails("call spellbadword('maxch')", 'E756:')
+ call assert_fails("spelldump", 'E756:')
+
+ call delete('Xwords.spl')
+ call delete('Xwords')
+ set spelllang&
+ set spell&
+endfunc
+
+func Test_spell_file_missing()
+ let s:spell_file_missing = 0
+ augroup TestSpellFileMissing
+ autocmd! SpellFileMissing * let s:spell_file_missing += 1
+ augroup END
+
+ set spell spelllang=ab_cd
+ let messages = GetMessages()
+ call assert_equal('Warning: Cannot find word list "ab.utf-8.spl" or "ab.ascii.spl"', messages[-1])
+ call assert_equal(1, s:spell_file_missing)
+
+ new XTestSpellFileMissing
+ augroup TestSpellFileMissing
+ autocmd! SpellFileMissing * bwipe
+ augroup END
+ call assert_fails('set spell spelllang=ab_cd', 'E937:')
+
+ " clean up
+ augroup TestSpellFileMissing
+ autocmd! SpellFileMissing
+ augroup END
+ augroup! TestSpellFileMissing
+ unlet s:spell_file_missing
+ set spell& spelllang&
+ %bwipe!
+endfunc
+
+func Test_spell_file_missing_bwipe()
+ " this was using a window that was wiped out in a SpellFileMissing autocmd
+ set spelllang=xy
+ au SpellFileMissing * n0
+ set spell
+ au SpellFileMissing * bw
+ snext somefile
+
+ au! SpellFileMissing
+ bwipe!
+ set nospell spelllang=en
+endfunc
+
+func Test_spelldump()
+ " In case the spell file is not found avoid getting the download dialog, we
+ " would get stuck at the prompt.
+ let g:en_not_found = 0
+ augroup TestSpellFileMissing
+ au! SpellFileMissing * let g:en_not_found = 1
+ augroup END
+ set spell spelllang=en
+ spellrare! emacs
+ if g:en_not_found
+ call assert_report("Could not find English spell file")
+ else
+ spelldump
+
+ " Check assumption about region: 1: us, 2: au, 3: ca, 4: gb, 5: nz.
+ call assert_equal('/regions=usaucagbnz', getline(1))
+ call assert_notequal(0, search('^theater/1$')) " US English only.
+ call assert_notequal(0, search('^theatre/2345$')) " AU, CA, GB or NZ English.
+
+ call assert_notequal(0, search('^emacs/?$')) " ? for a rare word.
+ call assert_notequal(0, search('^the the/!$')) " ! for a wrong word.
+ endif
+
+ " clean up
+ unlet g:en_not_found
+ augroup TestSpellFileMissing
+ autocmd! SpellFileMissing
+ augroup END
+ augroup! TestSpellFileMissing
+ bwipe
+ set spell&
+endfunc
+
+func Test_spelldump_bang()
+ new
+ call setline(1, 'This is a sample sentence.')
+ redraw
+
+ " In case the spell file is not found avoid getting the download dialog, we
+ " would get stuck at the prompt.
+ let g:en_not_found = 0
+ augroup TestSpellFileMissing
+ au! SpellFileMissing * let g:en_not_found = 1
+ augroup END
+
+ set spell
+
+ if g:en_not_found
+ call assert_report("Could not find English spell file")
+ else
+ redraw
+ spelldump!
+
+ " :spelldump! includes the number of times a word was found while updating
+ " the screen.
+ " Common word count starts at 10, regular word count starts at 0.
+ call assert_notequal(0, search("^is\t11$")) " common word found once.
+ call assert_notequal(0, search("^the\t10$")) " common word never found.
+ call assert_notequal(0, search("^sample\t1$")) " regular word found once.
+ call assert_equal(0, search("^screen\t")) " regular word never found.
+ endif
+
+ " clean up
+ unlet g:en_not_found
+ augroup TestSpellFileMissing
+ autocmd! SpellFileMissing
+ augroup END
+ augroup! TestSpellFileMissing
+ %bwipe!
+ set spell&
+endfunc
+
+func Test_spelllang_inv_region()
+ set spell spelllang=en_xx
+ let messages = GetMessages()
+ call assert_equal('Warning: region xx not supported', messages[-1])
+ set spell& spelllang&
+endfunc
+
+func Test_compl_with_CTRL_X_CTRL_K_using_spell()
+ " When spell checking is enabled and 'dictionary' is empty,
+ " CTRL-X CTRL-K in insert mode completes using the spelling dictionary.
+ new
+ set spell spelllang=en dictionary=
+
+ set ignorecase
+ call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+ call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+
+ set noignorecase
+ call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['englis'], getline(1, '$'))
+ call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+
+ set spelllang=en_us
+ call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['theater'], getline(1, '$'))
+ set spelllang=en_gb
+ call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
+ " FIXME: commented out, expected theatre bug got theater. See issue #7025.
+ " call assert_equal(['theatre'], getline(1, '$'))
+
+ bwipe!
+ set spell& spelllang& dictionary& ignorecase&
+endfunc
+
+func Test_spellreall()
+ new
+ set spell
+ call assert_fails('spellrepall', 'E752:')
+ call setline(1, ['A speling mistake. The same speling mistake.',
+ \ 'Another speling mistake.'])
+ call feedkeys(']s1z=', 'tx')
+ call assert_equal('A spelling mistake. The same speling mistake.', getline(1))
+ call assert_equal('Another speling mistake.', getline(2))
+ spellrepall
+ call assert_equal('A spelling mistake. The same spelling mistake.', getline(1))
+ call assert_equal('Another spelling mistake.', getline(2))
+ call assert_fails('spellrepall', 'E753:')
+ set spell&
+ bwipe!
+endfunc
+
+func Test_spell_dump_word_length()
+ " this was running over MAXWLEN
+ new
+ noremap 0 0a0zW0000000
+ sil! norm 0z=0
+ sil norm 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ sil! norm 0z=0
+
+ bwipe!
+ nunmap 0
+endfunc
+
+" Test spellsuggest({word} [, {max} [, {capital}]])
+func Test_spellsuggest()
+ " Verify suggestions are given even when spell checking is not enabled.
+ set nospell
+ call assert_equal(['march', 'March'], spellsuggest('marrch', 2))
+
+ set spell
+
+ " With 1 argument.
+ call assert_equal(['march', 'March'], spellsuggest('marrch')[0:1])
+
+ " With 2 arguments.
+ call assert_equal(['march', 'March'], spellsuggest('marrch', 2))
+
+ " With 3 arguments.
+ call assert_equal(['march'], spellsuggest('marrch', 1, 0))
+ call assert_equal(['March'], spellsuggest('marrch', 1, 1))
+
+ " Test with digits and hyphen.
+ call assert_equal('Carbon-14', spellsuggest('Carbon-15')[0])
+
+ " Comment taken from spellsuggest.c explains the following test cases:
+ "
+ " If there are more UPPER than lower case letters suggest an
+ " ALLCAP word. Otherwise, if the first letter is UPPER then
+ " suggest ONECAP. Exception: "ALl" most likely should be "All",
+ " require three upper case letters.
+ call assert_equal(['THIRD', 'third'], spellsuggest('thIRD', 2))
+ call assert_equal(['third', 'THIRD'], spellsuggest('tHIrd', 2))
+ call assert_equal(['Third'], spellsuggest('THird', 1))
+ call assert_equal(['All'], spellsuggest('ALl', 1))
+
+ " Special suggestion for repeated 'the the'.
+ call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the'))
+ call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the'))
+ call assert_inrange(0, 2, index(spellsuggest('The the', 3), 'The'))
+
+ call assert_fails("call spellsuggest('maxch', [])", 'E745:')
+ call assert_fails("call spellsuggest('maxch', 2, [])", 'E745:')
+
+ set spelllang=
+ call assert_fails("call spellsuggest('maxch')", 'E756:')
+ set spelllang&
+
+ set spell&
+endfunc
+
+" Test 'spellsuggest' option with methods fast, best and double.
+func Test_spellsuggest_option_methods()
+ set spell
+
+ for e in ['latin1', 'utf-8']
+ exe 'set encoding=' .. e
+
+ set spellsuggest=fast
+ call assert_equal(['Stick', 'Stitch'], spellsuggest('Stich', 2), e)
+
+ " With best or double option, "Stitch" should become the top suggestion
+ " because of better phonetic matching.
+ set spellsuggest=best
+ call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e)
+
+ set spellsuggest=double
+ call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e)
+ endfor
+
+ set spell& spellsuggest& encoding&
+endfunc
+
+" Test 'spellsuggest' option with value file:{filename}
+func Test_spellsuggest_option_file()
+ set spell spellsuggest=file:Xspellsuggest
+ call writefile(['emacs/vim',
+ \ 'theribal/terrible',
+ \ 'teribal/terrrible',
+ \ 'terribal'],
+ \ 'Xspellsuggest')
+
+ call assert_equal(['vim'], spellsuggest('emacs', 2))
+ call assert_equal(['terrible'], spellsuggest('theribal',2))
+
+ " If the suggestion is misspelled (*terrrible* with 3 r),
+ " it should not be proposed.
+ " The entry for "terribal" should be ignored because of missing slash.
+ call assert_equal([], spellsuggest('teribal', 2))
+ call assert_equal([], spellsuggest('terribal', 2))
+
+ set spell spellsuggest=best,file:Xspellsuggest
+ call assert_equal(['vim', 'Emacs'], spellsuggest('emacs', 2))
+ call assert_equal(['terrible', 'tribal'], spellsuggest('theribal', 2))
+ call assert_equal(['tribal'], spellsuggest('teribal', 1))
+ call assert_equal(['tribal'], spellsuggest('terribal', 1))
+
+ call delete('Xspellsuggest')
+ call assert_fails("call spellsuggest('vim')", "E484: Can't open file Xspellsuggest")
+
+ set spellsuggest& spell&
+endfunc
+
+" Test 'spellsuggest' option with value {number}
+" to limit the number of suggestions
+func Test_spellsuggest_option_number()
+ set spell spellsuggest=2,best
+ new
+
+ " We limited the number of suggestions to 2, so selecting
+ " the 1st and 2nd suggestion should correct the word, but
+ " selecting a 3rd suggestion should do nothing.
+ call setline(1, 'A baord')
+ norm $1z=
+ call assert_equal('A board', getline(1))
+
+ call setline(1, 'A baord')
+ norm $2z=
+ call assert_equal('A bard', getline(1))
+
+ call setline(1, 'A baord')
+ norm $3z=
+ call assert_equal('A baord', getline(1))
+
+ let a = execute('norm $z=')
+ call assert_equal(
+ \ "\n"
+ \ .. "Change \"baord\" to:\n"
+ \ .. " 1 \"board\"\n"
+ \ .. " 2 \"bard\"\n"
+ \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
+
+ set spell spellsuggest=0
+ call assert_equal("\nSorry, no suggestions", execute('norm $z='))
+
+ " Unlike z=, function spellsuggest(...) should not be affected by the
+ " max number of suggestions (2) set by the 'spellsuggest' option.
+ call assert_equal(['board', 'bard', 'broad'], spellsuggest('baord', 3))
+
+ set spellsuggest& spell&
+ bwipe!
+endfunc
+
+" Test 'spellsuggest' option with value expr:{expr}
+func Test_spellsuggest_option_expr()
+ " A silly 'spellsuggest' function which makes suggestions all uppercase
+ " and makes the score of each suggestion the length of the suggested word.
+ " So shorter suggestions are preferred.
+ func MySuggest()
+ let spellsuggest_save = &spellsuggest
+ set spellsuggest=3,best
+ let result = map(spellsuggest(v:val, 3), "[toupper(v:val), len(v:val)]")
+ let &spellsuggest = spellsuggest_save
+ return result
+ endfunc
+
+ set spell spellsuggest=expr:MySuggest()
+ call assert_equal(['BARD', 'BOARD', 'BROAD'], spellsuggest('baord', 3))
+
+ new
+ call setline(1, 'baord')
+ let a = execute('norm z=')
+ call assert_equal(
+ \ "\n"
+ \ .. "Change \"baord\" to:\n"
+ \ .. " 1 \"BARD\"\n"
+ \ .. " 2 \"BOARD\"\n"
+ \ .. " 3 \"BROAD\"\n"
+ \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
+
+ " With verbose, z= should show the score i.e. word length with
+ " our SpellSuggest() function.
+ set verbose=1
+ let a = execute('norm z=')
+ call assert_equal(
+ \ "\n"
+ \ .. "Change \"baord\" to:\n"
+ \ .. " 1 \"BARD\" (4 - 0)\n"
+ \ .. " 2 \"BOARD\" (5 - 0)\n"
+ \ .. " 3 \"BROAD\" (5 - 0)\n"
+ \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a)
+
+ set spell& spellsuggest& verbose&
+ bwipe!
+endfunc
+
+" Test for 'spellsuggest' expr errors
+func Test_spellsuggest_expr_errors()
+ " 'spellsuggest'
+ func MySuggest()
+ return range(3)
+ endfunc
+ set spell spellsuggest=expr:MySuggest()
+ call assert_equal([], spellsuggest('baord', 3))
+
+ " Test for 'spellsuggest' expression returning a non-list value
+ func! MySuggest2()
+ return 'good'
+ endfunc
+ set spellsuggest=expr:MySuggest2()
+ call assert_equal([], spellsuggest('baord'))
+
+ " Test for 'spellsuggest' expression returning a list with dict values
+ func! MySuggest3()
+ return [[{}, {}]]
+ endfunc
+ set spellsuggest=expr:MySuggest3()
+ call assert_fails("call spellsuggest('baord')", 'E731:')
+
+ set nospell spellsuggest&
+ delfunc MySuggest
+ delfunc MySuggest2
+ delfunc MySuggest3
+endfunc
+
+func Test_spellsuggest_timeout()
+ set spellsuggest=timeout:30
+ set spellsuggest=timeout:-123
+ set spellsuggest=timeout:999999
+ call assert_fails('set spellsuggest=timeout', 'E474:')
+ call assert_fails('set spellsuggest=timeout:x', 'E474:')
+ call assert_fails('set spellsuggest=timeout:-x', 'E474:')
+ call assert_fails('set spellsuggest=timeout:--9', 'E474:')
+endfunc
+
+func Test_spellsuggest_visual_end_of_line()
+ let enc_save = &encoding
+ set encoding=iso8859
+
+ " This was reading beyond the end of the line.
+ norm R00000000000
+ sil norm 0
+ sil! norm i00000)
+ sil! norm i00000)
+ call feedkeys("\<CR>")
+ norm z=
+
+ let &encoding = enc_save
+endfunc
+
+func Test_spellinfo()
+ new
+ let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
+
+ set enc=latin1 spell spelllang=en
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.latin1.spl\n$", execute('spellinfo'))
+
+ set enc=cp1250 spell spelllang=en
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.ascii.spl\n$", execute('spellinfo'))
+
+ set enc=utf-8 spell spelllang=en
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.utf-8.spl\n$", execute('spellinfo'))
+
+ set enc=latin1 spell spelllang=en_us,en_nz
+ call assert_match("^\n" .
+ \ "file: " .. runtime .. "/spell/en.latin1.spl\n" .
+ \ "file: " .. runtime.. "/spell/en.latin1.spl\n$", execute('spellinfo'))
+
+ set spell spelllang=
+ call assert_fails('spellinfo', 'E756:')
+
+ set nospell spelllang=en
+ call assert_fails('spellinfo', 'E756:')
+
+ call assert_fails('set spelllang=foo/bar', 'E474:')
+ call assert_fails('set spelllang=foo\ bar', 'E474:')
+ call assert_fails("set spelllang=foo\\\nbar", 'E474:')
+ call assert_fails("set spelllang=foo\\\rbar", 'E474:')
+ call assert_fails("set spelllang=foo+bar", 'E474:')
+
+ set enc& spell& spelllang&
+ bwipe
+endfunc
+
+func Test_zz_basic()
+ call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1)
+ call RunGoodBad("wrong OK puts. Test the end",
+ \ "bad: inputs comment ok Ok. test d\xE9\xF4l end the",
+ \["Comment", "deol", "d\xE9\xF4r", "input", "OK", "output", "outputs", "outtest", "put", "puts",
+ \ "test", "testen", "testn", "the end", "uk", "wrong"],
+ \[
+ \ ["bad", ["put", "uk", "OK"]],
+ \ ["inputs", ["input", "puts", "outputs"]],
+ \ ["comment", ["Comment", "outtest", "the end"]],
+ \ ["ok", ["OK", "uk", "put"]],
+ \ ["Ok", ["OK", "Uk", "Put"]],
+ \ ["test", ["Test", "testn", "testen"]],
+ \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]],
+ \ ["end", ["put", "uk", "test"]],
+ \ ["the", ["put", "uk", "test"]],
+ \ ]
+ \ )
+
+ call assert_equal("gebletegek", soundfold('goobledygoook'))
+ call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold())
+ call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale'))
+endfunc
+
+" Postponed prefixes
+func Test_zz_prefixes()
+ call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1)
+ call RunGoodBad("puts",
+ \ "bad: inputs comment ok Ok end the. test d\xE9\xF4l",
+ \ ["Comment", "deol", "d\xE9\xF4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"],
+ \ [
+ \ ["bad", ["put", "uk", "OK"]],
+ \ ["inputs", ["input", "puts", "outputs"]],
+ \ ["comment", ["Comment"]],
+ \ ["ok", ["OK", "uk", "put"]],
+ \ ["Ok", ["OK", "Uk", "Put"]],
+ \ ["end", ["put", "uk", "deol"]],
+ \ ["the", ["put", "uk", "test"]],
+ \ ["test", ["Test", "testn", "testen"]],
+ \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]],
+ \ ])
+endfunc
+
+"Compound words
+func Test_zz_compound()
+ call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3)
+ call RunGoodBad("foo m\xEF foobar foofoobar barfoo barbarfoo",
+ \ "bad: bar la foom\xEF barm\xEF m\xEFfoo m\xEFbar m\xEFm\xEF lala m\xEFla lam\xEF foola labar",
+ \ ["foo", "m\xEF"],
+ \ [
+ \ ["bad", ["foo", "m\xEF"]],
+ \ ["bar", ["barfoo", "foobar", "foo"]],
+ \ ["la", ["m\xEF", "foo"]],
+ \ ["foom\xEF", ["foo m\xEF", "foo", "foofoo"]],
+ \ ["barm\xEF", ["barfoo", "m\xEF", "barbar"]],
+ \ ["m\xEFfoo", ["m\xEF foo", "foo", "foofoo"]],
+ \ ["m\xEFbar", ["foobar", "barbar", "m\xEF"]],
+ \ ["m\xEFm\xEF", ["m\xEF m\xEF", "m\xEF"]],
+ \ ["lala", []],
+ \ ["m\xEFla", ["m\xEF", "m\xEF m\xEF"]],
+ \ ["lam\xEF", ["m\xEF", "m\xEF m\xEF"]],
+ \ ["foola", ["foo", "foobar", "foofoo"]],
+ \ ["labar", ["barbar", "foobar"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4)
+ call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword",
+ \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword",
+ \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"],
+ \ [
+ \ ["bad", ["end", "bork", "word"]],
+ \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]],
+ \ ["pro", ["bork", "word", "end"]],
+ \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]],
+ \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]],
+ \ ["endstart", ["end start", "start"]],
+ \ ["endend", ["end end", "end"]],
+ \ ["startstart", ["start start"]],
+ \ ["wordend", ["word end", "word", "wordword"]],
+ \ ["wordstart", ["word start", "bork start"]],
+ \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]],
+ \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]],
+ \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]],
+ \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]],
+ \ ["utilsbork", ["utilbork", "utils bork", "util bork"]],
+ \ ["startnouword", ["start nouword", "startword", "startborkword"]],
+ \ ])
+
+endfunc
+
+"Test affix flags with two characters
+func Test_zz_affix()
+ call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5)
+ call RunGoodBad("fooa1 fooa\xE9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend",
+ \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend",
+ \ ["bar", "barbork", "end", "fooa1", "fooa\xE9", "nouend", "prebar", "prebarbork", "start"],
+ \ [
+ \ ["bad", ["bar", "end", "fooa1"]],
+ \ ["foo", ["fooa1", "fooa\xE9", "bar"]],
+ \ ["fooa2", ["fooa1", "fooa\xE9", "bar"]],
+ \ ["prabar", ["prebar", "bar", "bar bar"]],
+ \ ["probarbirk", ["prebarbork"]],
+ \ ["middle", []],
+ \ ["startmiddle", ["startmiddleend", "startmiddlebar"]],
+ \ ["middleend", []],
+ \ ["endstart", ["end start", "start"]],
+ \ ["startprobar", ["startprebar", "start prebar", "startbar"]],
+ \ ["startnouend", ["start nouend", "startend"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6)
+ call RunGoodBad("meea1 meea\xE9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend",
+ \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar",
+ \ ["bar", "barbork", "end", "lead", "meea1", "meea\xE9", "prebar", "prebarbork"],
+ \ [
+ \ ["bad", ["bar", "end", "lead"]],
+ \ ["mee", ["meea1", "meea\xE9", "bar"]],
+ \ ["meea2", ["meea1", "meea\xE9", "lead"]],
+ \ ["prabar", ["prebar", "bar", "leadbar"]],
+ \ ["probarbirk", ["prebarbork"]],
+ \ ["middle", []],
+ \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]],
+ \ ["middleend", []],
+ \ ["endlead", ["end lead", "lead", "end end"]],
+ \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
+ call RunGoodBad("meea1 meezero meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
+ \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar",
+ \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "meezero", "prebar", "prebarmeat", "tail"],
+ \ [
+ \ ["bad", ["bar", "lead", "tail"]],
+ \ ["mee", ["meea1", "meea\xE9", "bar"]],
+ \ ["meea2", ["meea1", "meea\xE9", "lead"]],
+ \ ["prabar", ["prebar", "bar", "leadbar"]],
+ \ ["probarmaat", ["prebarmeat"]],
+ \ ["middle", []],
+ \ ["leadmiddle", ["leadmiddlebar"]],
+ \ ["middletail", []],
+ \ ["taillead", ["tail lead", "tail"]],
+ \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
+ \ ])
+endfunc
+
+func Test_zz_NOSLITSUGS()
+ call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8)
+ call RunGoodBad("foo bar faabar", "bad: foobar barfoo",
+ \ ["bar", "faabar", "foo"],
+ \ [
+ \ ["bad", ["bar", "foo"]],
+ \ ["foobar", ["faabar", "foo bar", "bar"]],
+ \ ["barfoo", ["bar foo", "bar", "foo"]],
+ \ ])
+endfunc
+
+" Numbers
+func Test_zz_Numbers()
+ call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9)
+ call RunGoodBad("0b1011 0777 1234 0x01ff", "",
+ \ ["bar", "foo"],
+ \ [
+ \ ])
+endfunc
+
+" Affix flags
+func Test_zz_affix_flags()
+ call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10)
+ call RunGoodBad("drink drinkable drinkables drinktable drinkabletable",
+ \ "bad: drinks drinkstable drinkablestable",
+ \ ["drink", "drinkable", "drinkables", "table"],
+ \ [['bad', []],
+ \ ['drinks', ['drink']],
+ \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']],
+ \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']],
+ \ ])
+endfunc
+
+function FirstSpellWord()
+ call feedkeys("/^start:\n", 'tx')
+ normal ]smm
+ let [str, a] = spellbadword()
+ return str
+endfunc
+
+function SecondSpellWord()
+ normal `m]s
+ let [str, a] = spellbadword()
+ return str
+endfunc
+
+"Test with SAL instead of SOFO items; test automatic reloading
+func Test_zz_sal_and_addition()
+ set enc=latin1
+ set spellfile=
+ call writefile(g:test_data_dic1, "Xtest.dic", 'D')
+ call writefile(g:test_data_aff_sal, "Xtest.aff", 'D')
+ mkspell! Xtest Xtest
+ set spl=Xtest.latin1.spl spell
+ call assert_equal('kbltykk', soundfold('goobledygoook'))
+ call assert_equal('kprnfn', soundfold('kóopërÿnôven'))
+ call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale'))
+
+ "also use an addition file
+ call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.latin1.add", 'D')
+ mkspell! Xtest.latin1.add.spl Xtest.latin1.add
+
+ bwipe!
+ call setline(1, ["start: elequint test elekwint test elekwent asdf"])
+
+ set spellfile=Xtest.latin1.add
+ call assert_equal("elekwent", FirstSpellWord())
+
+ set spl=Xtest_us.latin1.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwint", SecondSpellWord())
+
+ set spl=Xtest_gb.latin1.spl
+ call assert_equal("elekwint", FirstSpellWord())
+ call assert_equal("elekwent", SecondSpellWord())
+
+ set spl=Xtest_nz.latin1.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwent", SecondSpellWord())
+
+ set spl=Xtest_ca.latin1.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwint", SecondSpellWord())
+
+ bwipe!
+ set spellfile=
+ set spl&
+endfunc
+
+func Test_spellfile_value()
+ set spellfile=Xdir/Xtest.latin1.add
+ set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add
+endfunc
+
+func Test_region_error()
+ messages clear
+ call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add", 'D')
+ mkspell! Xtest.latin1.add.spl Xtest.latin1.add
+ call assert_match('Invalid region nr in Xtest.latin1.add line 2: 0', execute('messages'))
+ call delete('Xtest.latin1.add.spl')
+endfunc
+
+" Check using z= in new buffer (crash fixed by patch 7.4a.028).
+func Test_zeq_crash()
+ new
+ set maxmem=512 spell
+ call feedkeys('iasdz=:\"', 'tx')
+
+ bwipe!
+endfunc
+
+" Check that z= works even when 'nospell' is set. This test uses one of the
+" tests in Test_spellsuggest_option_number() just to verify that z= basically
+" works and that "E756: Spell checking is not enabled" is not generated.
+func Test_zeq_nospell()
+ new
+ set nospell spellsuggest=1,best
+ call setline(1, 'A baord')
+ try
+ norm $1z=
+ call assert_equal('A board', getline(1))
+ catch
+ call assert_report("Caught exception: " . v:exception)
+ endtry
+ set spell& spellsuggest&
+ bwipe!
+endfunc
+
+" Check that "E756: Spell checking is not possible" is reported when z= is
+" executed and 'spelllang' is empty.
+func Test_zeq_no_spelllang()
+ new
+ set spelllang= spellsuggest=1,best
+ call setline(1, 'A baord')
+ call assert_fails('normal $1z=', 'E756:')
+ set spelllang& spellsuggest&
+ bwipe!
+endfunc
+
+" Check handling a word longer than MAXWLEN.
+func Test_spell_long_word()
+ set enc=utf-8
+ new
+ call setline(1, "d\xCC\xB4\xCC\xBD\xCD\x88\xCD\x94a\xCC\xB5\xCD\x84\xCD\x84\xCC\xA8\xCD\x9Cr\xCC\xB5\xCC\x8E\xCD\x85\xCD\x85k\xCC\xB6\xCC\x89\xCC\x9D \xCC\xB6\xCC\x83\xCC\x8F\xCC\xA4\xCD\x8Ef\xCC\xB7\xCC\x81\xCC\x80\xCC\xA9\xCC\xB0\xCC\xAC\xCC\xA2\xCD\x95\xCD\x87\xCD\x8D\xCC\x9E\xCD\x99\xCC\xAD\xCC\xAB\xCC\x97\xCC\xBBo\xCC\xB6\xCC\x84\xCC\x95\xCC\x8C\xCC\x8B\xCD\x9B\xCD\x9C\xCC\xAFr\xCC\xB7\xCC\x94\xCD\x83\xCD\x97\xCC\x8C\xCC\x82\xCD\x82\xCD\x80\xCD\x91\xCC\x80\xCC\xBE\xCC\x82\xCC\x8F\xCC\xA3\xCD\x85\xCC\xAE\xCD\x8D\xCD\x99\xCC\xBC\xCC\xAB\xCC\xA7\xCD\x88c\xCC\xB7\xCD\x83\xCC\x84\xCD\x92\xCC\x86\xCC\x83\xCC\x88\xCC\x92\xCC\x94\xCC\xBE\xCC\x9D\xCC\xAF\xCC\x98\xCC\x9D\xCC\xBB\xCD\x8E\xCC\xBB\xCC\xB3\xCC\xA3\xCD\x8E\xCD\x99\xCC\xA5\xCC\xAD\xCC\x99\xCC\xB9\xCC\xAE\xCC\xA5\xCC\x9E\xCD\x88\xCC\xAE\xCC\x9E\xCC\xA9\xCC\x97\xCC\xBC\xCC\x99\xCC\xA5\xCD\x87\xCC\x97\xCD\x8E\xCD\x94\xCC\x99\xCC\x9D\xCC\x96\xCD\x94\xCC\xAB\xCC\xA7\xCC\xA5\xCC\x98\xCC\xBB\xCC\xAF\xCC\xABe\xCC\xB7\xCC\x8E\xCC\x82\xCD\x86\xCD\x9B\xCC\x94\xCD\x83\xCC\x85\xCD\x8A\xCD\x8C\xCC\x8B\xCD\x92\xCD\x91\xCC\x8F\xCC\x81\xCD\x95\xCC\xA2\xCC\xB9\xCC\xB2\xCD\x9C\xCC\xB1\xCC\xA6\xCC\xB3\xCC\xAF\xCC\xAE\xCC\x9C\xCD\x99s\xCC\xB8\xCC\x8C\xCC\x8E\xCC\x87\xCD\x81\xCD\x82\xCC\x86\xCD\x8C\xCD\x8C\xCC\x8B\xCC\x84\xCC\x8C\xCD\x84\xCD\x9B\xCD\x86\xCC\x93\xCD\x90\xCC\x85\xCC\x94\xCD\x98\xCD\x84\xCD\x92\xCD\x8B\xCC\x90\xCC\x83\xCC\x8F\xCD\x84\xCD\x81\xCD\x9B\xCC\x90\xCD\x81\xCC\x8F\xCC\xBD\xCC\x88\xCC\xBF\xCC\x88\xCC\x84\xCC\x8E\xCD\x99\xCD\x94\xCC\x99\xCD\x99\xCC\xB0\xCC\xA8\xCC\xA3\xCC\xA8\xCC\x96\xCC\x99\xCC\xAE\xCC\xBC\xCC\x99\xCD\x9A\xCC\xB2\xCC\xB1\xCC\x9F\xCC\xBB\xCC\xA6\xCD\x85\xCC\xAA\xCD\x89\xCC\x9D\xCC\x99\xCD\x96\xCC\xB1\xCC\xB1\xCC\x99\xCC\xA6\xCC\xA5\xCD\x95\xCC\xB2\xCC\xA0\xCD\x99 within")
+ set spell spelllang=en
+ redraw
+ redraw!
+ bwipe!
+ set nospell
+endfunc
+
+func Test_spellsuggest_too_deep()
+ " This was incrementing "depth" over MAXWLEN.
+ new
+ norm s000G00ý000000000000
+ sil norm ..vzG................vvzG0 v z=
+ bwipe!
+endfunc
+
+func Test_spell_good_word_invalid()
+ " This was adding a word with a 0x02 byte, which causes havoc.
+ enew
+ norm o0
+ sil! norm rzzWs00/
+ 2
+ sil! norm VzGprzzW
+ sil! norm z=
+
+ bwipe!
+endfunc
+
+func Test_spell_good_word_slash()
+ " This caused E1280.
+ new
+ norm afoo /
+ 1
+ norm zG
+
+ bwipe!
+endfunc
+
+func LoadAffAndDic(aff_contents, dic_contents)
+ set enc=latin1
+ set spellfile=
+ call writefile(a:aff_contents, "Xtest.aff")
+ call writefile(a:dic_contents, "Xtest.dic")
+ " Generate a .spl file from a .dic and .aff file.
+ mkspell! Xtest Xtest
+ " use that spell file
+ set spl=Xtest.latin1.spl spell
+endfunc
+
+func ListWords()
+ spelldump
+ %yank
+ quit
+ return split(@", "\n")
+endfunc
+
+func TestGoodBadBase()
+ exe '1;/^good:'
+ normal 0f:]s
+ let prevbad = ''
+ let result = []
+ while 1
+ let [bad, a] = spellbadword()
+ if bad == '' || bad == prevbad || bad == 'badend'
+ break
+ endif
+ let prevbad = bad
+ let lst = bad->spellsuggest(3)
+ normal mm
+
+ call add(result, [bad, lst])
+ normal `m]s
+ endwhile
+ return result
+endfunc
+
+func RunGoodBad(good, bad, expected_words, expected_bad_words)
+ bwipe!
+ call setline(1, ["good: ", a:good, a:bad, " badend "])
+ let words = ListWords()
+ call assert_equal(a:expected_words, words[1:-1])
+ let bad_words = TestGoodBadBase()
+ call assert_equal(a:expected_bad_words, bad_words)
+ bwipe!
+endfunc
+
+func Test_spell_screendump()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, [
+ \ "This is some text without any spell errors. Everything",
+ \ "should just be black, nothing wrong here.",
+ \ "",
+ \ "This line has a sepll error. and missing caps.",
+ \ "And and this is the the duplication.",
+ \ "with missing caps here.",
+ \ ])
+ set spell spelllang=en_nz
+ END
+ call writefile(lines, 'XtestSpell', 'D')
+ let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_spell_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_spell_screendump_spellcap()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, [
+ \ " This line has a sepll error. and missing caps and trailing spaces. ",
+ \ "another missing cap here.",
+ \ "",
+ \ "and here.",
+ \ " ",
+ \ "and here."
+ \ ])
+ set spell spelllang=en
+ END
+ call writefile(lines, 'XtestSpellCap', 'D')
+ let buf = RunVimInTerminal('-S XtestSpellCap', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_spell_2', {})
+
+ " After adding word missing Cap in next line is updated
+ call term_sendkeys(buf, "3GANot\<Esc>")
+ call VerifyScreenDump(buf, 'Test_spell_3', {})
+
+ " Deleting a full stop removes missing Cap in next line
+ call term_sendkeys(buf, "5Gddk$x")
+ call VerifyScreenDump(buf, 'Test_spell_4', {})
+
+ " Undo also updates the next line (go to command line to remove message)
+ call term_sendkeys(buf, "u:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_spell_5', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_spell_compatible()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, [
+ \ "test "->repeat(20),
+ \ "",
+ \ "end",
+ \ ])
+ set spell cpo+=$
+ END
+ call writefile(lines, 'XtestSpellComp', 'D')
+ let buf = RunVimInTerminal('-S XtestSpellComp', {'rows': 8})
+
+ call term_sendkeys(buf, "51|C")
+ call VerifyScreenDump(buf, 'Test_spell_compatible_1', {})
+
+ call term_sendkeys(buf, "x")
+ call VerifyScreenDump(buf, 'Test_spell_compatible_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+let g:test_data_aff1 = [
+ \"SET ISO8859-1",
+ \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF",
+ \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out .",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \ ]
+let g:test_data_dic1 = [
+ \"123456",
+ \"test/NO",
+ \"# comment",
+ \"wrong",
+ \"Comment",
+ \"OK",
+ \"uk",
+ \"put/ISO",
+ \"the end",
+ \"deol",
+ \"d\xE9\xF4r",
+ \ ]
+let g:test_data_aff2 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"PFXPOSTPONE",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out [a-z]",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \ ]
+let g:test_data_aff3 = [
+ \"SET ISO8859-1",
+ \"",
+ \"COMPOUNDMIN 3",
+ \"COMPOUNDRULE m*",
+ \"NEEDCOMPOUND x",
+ \ ]
+let g:test_data_dic3 = [
+ \"1234",
+ \"foo/m",
+ \"bar/mx",
+ \"m\xEF/m",
+ \"la/mx",
+ \ ]
+let g:test_data_aff4 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"COMPOUNDRULE m+",
+ \"COMPOUNDRULE sm*e",
+ \"COMPOUNDRULE sm+",
+ \"COMPOUNDMIN 3",
+ \"COMPOUNDWORDMAX 3",
+ \"COMPOUNDFORBIDFLAG t",
+ \"",
+ \"COMPOUNDSYLMAX 5",
+ \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \"",
+ \"NEEDAFFIX x",
+ \"",
+ \"PFXPOSTPONE",
+ \"",
+ \"MIDWORD '-",
+ \"",
+ \"SFX q N 1",
+ \"SFX q 0 -ok .",
+ \"",
+ \"SFX a Y 2",
+ \"SFX a 0 s .",
+ \"SFX a 0 ize/t .",
+ \"",
+ \"PFX p N 1",
+ \"PFX p 0 pre .",
+ \"",
+ \"PFX P N 1",
+ \"PFX P 0 nou .",
+ \ ]
+let g:test_data_dic4 = [
+ \"1234",
+ \"word/mP",
+ \"util/am",
+ \"pro/xq",
+ \"tomato/m",
+ \"bork/mp",
+ \"start/s",
+ \"end/e",
+ \ ]
+let g:test_data_aff5 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG long",
+ \"",
+ \"NEEDAFFIX !!",
+ \"",
+ \"COMPOUNDRULE ssmm*ee",
+ \"",
+ \"NEEDCOMPOUND xx",
+ \"COMPOUNDPERMITFLAG pp",
+ \"",
+ \"SFX 13 Y 1",
+ \"SFX 13 0 bork .",
+ \"",
+ \"SFX a1 Y 1",
+ \"SFX a1 0 a1 .",
+ \"",
+ \"SFX a\xE9 Y 1",
+ \"SFX a\xE9 0 a\xE9 .",
+ \"",
+ \"PFX zz Y 1",
+ \"PFX zz 0 pre/pp .",
+ \"",
+ \"PFX yy Y 1",
+ \"PFX yy 0 nou .",
+ \ ]
+let g:test_data_dic5 = [
+ \"1234",
+ \"foo/a1a\xE9!!",
+ \"bar/zz13ee",
+ \"start/ss",
+ \"end/eeyy",
+ \"middle/mmxx",
+ \ ]
+let g:test_data_aff6 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG caplong",
+ \"",
+ \"NEEDAFFIX A!",
+ \"",
+ \"COMPOUNDRULE sMm*Ee",
+ \"",
+ \"NEEDCOMPOUND Xx",
+ \"",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX N3 Y 1",
+ \"SFX N3 0 bork .",
+ \"",
+ \"SFX A1 Y 1",
+ \"SFX A1 0 a1 .",
+ \"",
+ \"SFX A\xE9 Y 1",
+ \"SFX A\xE9 0 a\xE9 .",
+ \"",
+ \"PFX Zz Y 1",
+ \"PFX Zz 0 pre/p .",
+ \ ]
+let g:test_data_dic6 = [
+ \"1234",
+ \"mee/A1A\xE9A!",
+ \"bar/ZzN3Ee",
+ \"lead/s",
+ \"end/Ee",
+ \"middle/MmXx",
+ \ ]
+let g:test_data_aff7 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG num",
+ \"",
+ \"NEEDAFFIX 9999",
+ \"",
+ \"COMPOUNDRULE 2,77*123",
+ \"",
+ \"NEEDCOMPOUND 1",
+ \"COMPOUNDPERMITFLAG 432",
+ \"",
+ \"SFX 61003 Y 1",
+ \"SFX 61003 0 meat .",
+ \"",
+ \"SFX 0 Y 1",
+ \"SFX 0 0 zero .",
+ \"",
+ \"SFX 391 Y 1",
+ \"SFX 391 0 a1 .",
+ \"",
+ \"SFX 111 Y 1",
+ \"SFX 111 0 a\xE9 .",
+ \"",
+ \"PFX 17 Y 1",
+ \"PFX 17 0 pre/432 .",
+ \ ]
+let g:test_data_dic7 = [
+ \"1234",
+ \"mee/0,391,111,9999",
+ \"bar/17,61003,123",
+ \"lead/2",
+ \"tail/123",
+ \"middle/77,1",
+ \ ]
+let g:test_data_aff8 = [
+ \"SET ISO8859-1",
+ \"",
+ \"NOSPLITSUGS",
+ \ ]
+let g:test_data_dic8 = [
+ \"1234",
+ \"foo",
+ \"bar",
+ \"faabar",
+ \ ]
+let g:test_data_aff9 = [
+ \ ]
+let g:test_data_dic9 = [
+ \"1234",
+ \"foo",
+ \"bar",
+ \ ]
+let g:test_data_aff10 = [
+ \"COMPOUNDRULE se",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX A Y 1",
+ \"SFX A 0 able/Mp .",
+ \"",
+ \"SFX M Y 1",
+ \"SFX M 0 s .",
+ \ ]
+let g:test_data_dic10 = [
+ \"1234",
+ \"drink/As",
+ \"table/e",
+ \ ]
+let g:test_data_aff_sal = [
+ \"SET ISO8859-1",
+ \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out .",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \"",
+ \"SAL AH(AEIOUY)-^ *H",
+ \"SAL AR(AEIOUY)-^ *R",
+ \"SAL A(HR)^ *",
+ \"SAL A^ *",
+ \"SAL AH(AEIOUY)- H",
+ \"SAL AR(AEIOUY)- R",
+ \"SAL A(HR) _",
+ \"SAL \xC0^ *",
+ \"SAL \xC5^ *",
+ \"SAL BB- _",
+ \"SAL B B",
+ \"SAL CQ- _",
+ \"SAL CIA X",
+ \"SAL CH X",
+ \"SAL C(EIY)- S",
+ \"SAL CK K",
+ \"SAL COUGH^ KF",
+ \"SAL CC< C",
+ \"SAL C K",
+ \"SAL DG(EIY) K",
+ \"SAL DD- _",
+ \"SAL D T",
+ \"SAL \xC9< E",
+ \"SAL EH(AEIOUY)-^ *H",
+ \"SAL ER(AEIOUY)-^ *R",
+ \"SAL E(HR)^ *",
+ \"SAL ENOUGH^$ *NF",
+ \"SAL E^ *",
+ \"SAL EH(AEIOUY)- H",
+ \"SAL ER(AEIOUY)- R",
+ \"SAL E(HR) _",
+ \"SAL FF- _",
+ \"SAL F F",
+ \"SAL GN^ N",
+ \"SAL GN$ N",
+ \"SAL GNS$ NS",
+ \"SAL GNED$ N",
+ \"SAL GH(AEIOUY)- K",
+ \"SAL GH _",
+ \"SAL GG9 K",
+ \"SAL G K",
+ \"SAL H H",
+ \"SAL IH(AEIOUY)-^ *H",
+ \"SAL IR(AEIOUY)-^ *R",
+ \"SAL I(HR)^ *",
+ \"SAL I^ *",
+ \"SAL ING6 N",
+ \"SAL IH(AEIOUY)- H",
+ \"SAL IR(AEIOUY)- R",
+ \"SAL I(HR) _",
+ \"SAL J K",
+ \"SAL KN^ N",
+ \"SAL KK- _",
+ \"SAL K K",
+ \"SAL LAUGH^ LF",
+ \"SAL LL- _",
+ \"SAL L L",
+ \"SAL MB$ M",
+ \"SAL MM M",
+ \"SAL M M",
+ \"SAL NN- _",
+ \"SAL N N",
+ \"SAL OH(AEIOUY)-^ *H",
+ \"SAL OR(AEIOUY)-^ *R",
+ \"SAL O(HR)^ *",
+ \"SAL O^ *",
+ \"SAL OH(AEIOUY)- H",
+ \"SAL OR(AEIOUY)- R",
+ \"SAL O(HR) _",
+ \"SAL PH F",
+ \"SAL PN^ N",
+ \"SAL PP- _",
+ \"SAL P P",
+ \"SAL Q K",
+ \"SAL RH^ R",
+ \"SAL ROUGH^ RF",
+ \"SAL RR- _",
+ \"SAL R R",
+ \"SAL SCH(EOU)- SK",
+ \"SAL SC(IEY)- S",
+ \"SAL SH X",
+ \"SAL SI(AO)- X",
+ \"SAL SS- _",
+ \"SAL S S",
+ \"SAL TI(AO)- X",
+ \"SAL TH @",
+ \"SAL TCH-- _",
+ \"SAL TOUGH^ TF",
+ \"SAL TT- _",
+ \"SAL T T",
+ \"SAL UH(AEIOUY)-^ *H",
+ \"SAL UR(AEIOUY)-^ *R",
+ \"SAL U(HR)^ *",
+ \"SAL U^ *",
+ \"SAL UH(AEIOUY)- H",
+ \"SAL UR(AEIOUY)- R",
+ \"SAL U(HR) _",
+ \"SAL V^ W",
+ \"SAL V F",
+ \"SAL WR^ R",
+ \"SAL WH^ W",
+ \"SAL W(AEIOU)- W",
+ \"SAL X^ S",
+ \"SAL X KS",
+ \"SAL Y(AEIOU)- Y",
+ \"SAL ZZ- _",
+ \"SAL Z S",
+ \ ]
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_spell_utf8.vim b/src/testdir/test_spell_utf8.vim
new file mode 100644
index 0000000..bb2c354
--- /dev/null
+++ b/src/testdir/test_spell_utf8.vim
@@ -0,0 +1,831 @@
+" Test for spell checking with 'encoding' set to utf-8
+
+source check.vim
+CheckFeature spell
+
+scriptencoding utf-8
+
+func TearDown()
+ set nospell
+ call delete('Xtest.aff')
+ call delete('Xtest.dic')
+ call delete('Xtest.utf-8.add')
+ call delete('Xtest.utf-8.add.spl')
+ call delete('Xtest.utf-8.spl')
+ call delete('Xtest.utf-8.sug')
+ " set 'encoding' to clear the word list
+ set encoding=utf-8
+endfunc
+
+let g:test_data_aff1 = [
+ \"SET ISO8859-1",
+ \"TRY esianrtolcdugmphbyfvkwjkqxz-ëéèêïîäàâöüû'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
+ \"",
+ \"FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ",
+ \"LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ",
+ \"UPP ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞßÿ",
+ \"",
+ \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF",
+ \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out .",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF"
+ \ ]
+let g:test_data_dic1 = [
+ \"123456",
+ \"test/NO",
+ \"# comment",
+ \"wrong",
+ \"Comment",
+ \"OK",
+ \"uk",
+ \"put/ISO",
+ \"the end",
+ \"deol",
+ \"d\xE9\xF4r",
+ \ ]
+let g:test_data_aff2 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"PFXPOSTPONE",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out [a-z]",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \ ]
+let g:test_data_aff3 = [
+ \"SET ISO8859-1",
+ \"",
+ \"COMPOUNDMIN 3",
+ \"COMPOUNDRULE m*",
+ \"NEEDCOMPOUND x",
+ \ ]
+let g:test_data_dic3 = [
+ \"1234",
+ \"foo/m",
+ \"bar/mx",
+ \"m\xEF/m",
+ \"la/mx",
+ \ ]
+let g:test_data_aff4 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"COMPOUNDRULE m+",
+ \"COMPOUNDRULE sm*e",
+ \"COMPOUNDRULE sm+",
+ \"COMPOUNDMIN 3",
+ \"COMPOUNDWORDMAX 3",
+ \"COMPOUNDFORBIDFLAG t",
+ \"",
+ \"COMPOUNDSYLMAX 5",
+ \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \"",
+ \"NEEDAFFIX x",
+ \"",
+ \"PFXPOSTPONE",
+ \"",
+ \"MIDWORD '-",
+ \"",
+ \"SFX q N 1",
+ \"SFX q 0 -ok .",
+ \"",
+ \"SFX a Y 2",
+ \"SFX a 0 s .",
+ \"SFX a 0 ize/t .",
+ \"",
+ \"PFX p N 1",
+ \"PFX p 0 pre .",
+ \"",
+ \"PFX P N 1",
+ \"PFX P 0 nou .",
+ \ ]
+let g:test_data_dic4 = [
+ \"1234",
+ \"word/mP",
+ \"util/am",
+ \"pro/xq",
+ \"tomato/m",
+ \"bork/mp",
+ \"start/s",
+ \"end/e",
+ \ ]
+let g:test_data_aff5 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG long",
+ \"",
+ \"NEEDAFFIX !!",
+ \"",
+ \"COMPOUNDRULE ssmm*ee",
+ \"",
+ \"NEEDCOMPOUND xx",
+ \"COMPOUNDPERMITFLAG pp",
+ \"",
+ \"SFX 13 Y 1",
+ \"SFX 13 0 bork .",
+ \"",
+ \"SFX a1 Y 1",
+ \"SFX a1 0 a1 .",
+ \"",
+ \"SFX a\xE9 Y 1",
+ \"SFX a\xE9 0 a\xE9 .",
+ \"",
+ \"PFX zz Y 1",
+ \"PFX zz 0 pre/pp .",
+ \"",
+ \"PFX yy Y 1",
+ \"PFX yy 0 nou .",
+ \ ]
+let g:test_data_dic5 = [
+ \"1234",
+ \"foo/a1a\xE9!!",
+ \"bar/zz13ee",
+ \"start/ss",
+ \"end/eeyy",
+ \"middle/mmxx",
+ \ ]
+let g:test_data_aff6 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG caplong",
+ \"",
+ \"NEEDAFFIX A!",
+ \"",
+ \"COMPOUNDRULE sMm*Ee",
+ \"",
+ \"NEEDCOMPOUND Xx",
+ \"",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX N3 Y 1",
+ \"SFX N3 0 bork .",
+ \"",
+ \"SFX A1 Y 1",
+ \"SFX A1 0 a1 .",
+ \"",
+ \"SFX A\xE9 Y 1",
+ \"SFX A\xE9 0 a\xE9 .",
+ \"",
+ \"PFX Zz Y 1",
+ \"PFX Zz 0 pre/p .",
+ \ ]
+let g:test_data_dic6 = [
+ \"1234",
+ \"mee/A1A\xE9A!",
+ \"bar/ZzN3Ee",
+ \"lead/s",
+ \"end/Ee",
+ \"middle/MmXx",
+ \ ]
+let g:test_data_aff7 = [
+ \"SET ISO8859-1",
+ \"",
+ \"FLAG num",
+ \"",
+ \"NEEDAFFIX 9999",
+ \"",
+ \"COMPOUNDRULE 2,77*123",
+ \"",
+ \"NEEDCOMPOUND 1",
+ \"COMPOUNDPERMITFLAG 432",
+ \"",
+ \"SFX 61003 Y 1",
+ \"SFX 61003 0 meat .",
+ \"",
+ \"SFX 0 Y 1",
+ \"SFX 0 0 zero .",
+ \"",
+ \"SFX 391 Y 1",
+ \"SFX 391 0 a1 .",
+ \"",
+ \"SFX 111 Y 1",
+ \"SFX 111 0 a\xE9 .",
+ \"",
+ \"PFX 17 Y 1",
+ \"PFX 17 0 pre/432 .",
+ \ ]
+let g:test_data_dic7 = [
+ \"1234",
+ \"mee/0,391,111,9999",
+ \"bar/17,61003,123",
+ \"lead/2",
+ \"tail/123",
+ \"middle/77,1",
+ \ ]
+let g:test_data_aff8 = [
+ \"SET ISO8859-1",
+ \"",
+ \"NOSPLITSUGS",
+ \ ]
+let g:test_data_dic8 = [
+ \"1234",
+ \"foo",
+ \"bar",
+ \"faabar",
+ \ ]
+let g:test_data_aff9 = [
+ \ ]
+let g:test_data_dic9 = [
+ \"1234",
+ \"foo",
+ \"bar",
+ \ ]
+let g:test_data_aff10 = [
+ \"COMPOUNDRULE se",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX A Y 1",
+ \"SFX A 0 able/Mp .",
+ \"",
+ \"SFX M Y 1",
+ \"SFX M 0 s .",
+ \ ]
+let g:test_data_dic10 = [
+ \"1234",
+ \"drink/As",
+ \"table/e",
+ \ ]
+let g:test_data_aff_sal = [
+ \"SET ISO8859-1",
+ \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
+ \"",
+ \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF",
+ \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF",
+ \"",
+ \"MIDWORD\t'-",
+ \"",
+ \"KEP =",
+ \"RAR ?",
+ \"BAD !",
+ \"",
+ \"PFX I N 1",
+ \"PFX I 0 in .",
+ \"",
+ \"PFX O Y 1",
+ \"PFX O 0 out .",
+ \"",
+ \"SFX S Y 2",
+ \"SFX S 0 s [^s]",
+ \"SFX S 0 es s",
+ \"",
+ \"SFX N N 3",
+ \"SFX N 0 en [^n]",
+ \"SFX N 0 nen n",
+ \"SFX N 0 n .",
+ \"",
+ \"REP 3",
+ \"REP g ch",
+ \"REP ch g",
+ \"REP svp s.v.p.",
+ \"",
+ \"MAP 9",
+ \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5",
+ \"MAP e\xE8\xE9\xEA\xEB",
+ \"MAP i\xEC\xED\xEE\xEF",
+ \"MAP o\xF2\xF3\xF4\xF5\xF6",
+ \"MAP u\xF9\xFA\xFB\xFC",
+ \"MAP n\xF1",
+ \"MAP c\xE7",
+ \"MAP y\xFF\xFD",
+ \"MAP s\xDF",
+ \"",
+ \"SAL AH(AEIOUY)-^ *H",
+ \"SAL AR(AEIOUY)-^ *R",
+ \"SAL A(HR)^ *",
+ \"SAL A^ *",
+ \"SAL AH(AEIOUY)- H",
+ \"SAL AR(AEIOUY)- R",
+ \"SAL A(HR) _",
+ \"SAL \xC0^ *",
+ \"SAL \xC5^ *",
+ \"SAL BB- _",
+ \"SAL B B",
+ \"SAL CQ- _",
+ \"SAL CIA X",
+ \"SAL CH X",
+ \"SAL C(EIY)- S",
+ \"SAL CK K",
+ \"SAL COUGH^ KF",
+ \"SAL CC< C",
+ \"SAL C K",
+ \"SAL DG(EIY) K",
+ \"SAL DD- _",
+ \"SAL D T",
+ \"SAL \xC9< E",
+ \"SAL EH(AEIOUY)-^ *H",
+ \"SAL ER(AEIOUY)-^ *R",
+ \"SAL E(HR)^ *",
+ \"SAL ENOUGH^$ *NF",
+ \"SAL E^ *",
+ \"SAL EH(AEIOUY)- H",
+ \"SAL ER(AEIOUY)- R",
+ \"SAL E(HR) _",
+ \"SAL FF- _",
+ \"SAL F F",
+ \"SAL GN^ N",
+ \"SAL GN$ N",
+ \"SAL GNS$ NS",
+ \"SAL GNED$ N",
+ \"SAL GH(AEIOUY)- K",
+ \"SAL GH _",
+ \"SAL GG9 K",
+ \"SAL G K",
+ \"SAL H H",
+ \"SAL IH(AEIOUY)-^ *H",
+ \"SAL IR(AEIOUY)-^ *R",
+ \"SAL I(HR)^ *",
+ \"SAL I^ *",
+ \"SAL ING6 N",
+ \"SAL IH(AEIOUY)- H",
+ \"SAL IR(AEIOUY)- R",
+ \"SAL I(HR) _",
+ \"SAL J K",
+ \"SAL KN^ N",
+ \"SAL KK- _",
+ \"SAL K K",
+ \"SAL LAUGH^ LF",
+ \"SAL LL- _",
+ \"SAL L L",
+ \"SAL MB$ M",
+ \"SAL MM M",
+ \"SAL M M",
+ \"SAL NN- _",
+ \"SAL N N",
+ \"SAL OH(AEIOUY)-^ *H",
+ \"SAL OR(AEIOUY)-^ *R",
+ \"SAL O(HR)^ *",
+ \"SAL O^ *",
+ \"SAL OH(AEIOUY)- H",
+ \"SAL OR(AEIOUY)- R",
+ \"SAL O(HR) _",
+ \"SAL PH F",
+ \"SAL PN^ N",
+ \"SAL PP- _",
+ \"SAL P P",
+ \"SAL Q K",
+ \"SAL RH^ R",
+ \"SAL ROUGH^ RF",
+ \"SAL RR- _",
+ \"SAL R R",
+ \"SAL SCH(EOU)- SK",
+ \"SAL SC(IEY)- S",
+ \"SAL SH X",
+ \"SAL SI(AO)- X",
+ \"SAL SS- _",
+ \"SAL S S",
+ \"SAL TI(AO)- X",
+ \"SAL TH @",
+ \"SAL TCH-- _",
+ \"SAL TOUGH^ TF",
+ \"SAL TT- _",
+ \"SAL T T",
+ \"SAL UH(AEIOUY)-^ *H",
+ \"SAL UR(AEIOUY)-^ *R",
+ \"SAL U(HR)^ *",
+ \"SAL U^ *",
+ \"SAL UH(AEIOUY)- H",
+ \"SAL UR(AEIOUY)- R",
+ \"SAL U(HR) _",
+ \"SAL V^ W",
+ \"SAL V F",
+ \"SAL WR^ R",
+ \"SAL WH^ W",
+ \"SAL W(AEIOU)- W",
+ \"SAL X^ S",
+ \"SAL X KS",
+ \"SAL Y(AEIOU)- Y",
+ \"SAL ZZ- _",
+ \"SAL Z S",
+ \ ]
+
+func LoadAffAndDic(aff_contents, dic_contents)
+ set spellfile=
+ call writefile(a:aff_contents, "Xtest.aff")
+ call writefile(a:dic_contents, "Xtest.dic")
+ " Generate a .spl file from a .dic and .aff file.
+ mkspell! Xtest Xtest
+ " use that spell file
+ set spl=Xtest.utf-8.spl spell
+endfunc
+
+func ListWords()
+ spelldump
+ %yank
+ quit
+ return split(@", "\n")
+endfunc
+
+func TestGoodBadBase()
+ exe '1;/^good:'
+ normal 0f:]s
+ let prevbad = ''
+ let result = []
+ while 1
+ let [bad, a] = spellbadword()
+ if bad == '' || bad == prevbad || bad == 'badend'
+ break
+ endif
+ let prevbad = bad
+ let lst = bad->spellsuggest(3)
+ normal mm
+
+ call add(result, [bad, lst])
+ normal `m]s
+ endwhile
+ return result
+endfunc
+
+func RunGoodBad(good, bad, expected_words, expected_bad_words)
+ %bwipe!
+ call setline(1, ['', "good: ", a:good, a:bad, " badend "])
+ let words = ListWords()
+ call assert_equal(a:expected_words, words[1:-1])
+ let bad_words = TestGoodBadBase()
+ call assert_equal(a:expected_bad_words, bad_words)
+ %bwipe!
+endfunc
+
+func Test_spell_basic()
+ call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1)
+ call RunGoodBad("wrong OK puts. Test the end",
+ \ "bad: inputs comment ok Ok. test d\u00E9\u00F4l end the",
+ \["Comment", "deol", "d\u00E9\u00F4r", "input", "OK", "output", "outputs", "outtest", "put", "puts",
+ \ "test", "testen", "testn", "the end", "uk", "wrong"],
+ \[
+ \ ["bad", ["put", "uk", "OK"]],
+ \ ["inputs", ["input", "puts", "outputs"]],
+ \ ["comment", ["Comment", "outtest", "the end"]],
+ \ ["ok", ["OK", "uk", "put"]],
+ \ ["Ok", ["OK", "Uk", "Put"]],
+ \ ["test", ["Test", "testn", "testen"]],
+ \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]],
+ \ ["end", ["put", "uk", "test"]],
+ \ ["the", ["put", "uk", "test"]],
+ \ ]
+ \ )
+
+ call assert_equal("gebletegek", soundfold('goobledygoook'))
+ call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold())
+ call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale'))
+endfunc
+
+" Postponed prefixes
+func Test_spell_prefixes()
+ call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1)
+ call RunGoodBad("puts",
+ \ "bad: inputs comment ok Ok end the. test d\u00E9\u00F4l",
+ \ ["Comment", "deol", "d\u00E9\u00F4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"],
+ \ [
+ \ ["bad", ["put", "uk", "OK"]],
+ \ ["inputs", ["input", "puts", "outputs"]],
+ \ ["comment", ["Comment"]],
+ \ ["ok", ["OK", "uk", "put"]],
+ \ ["Ok", ["OK", "Uk", "Put"]],
+ \ ["end", ["put", "uk", "deol"]],
+ \ ["the", ["put", "uk", "test"]],
+ \ ["test", ["Test", "testn", "testen"]],
+ \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]],
+ \ ])
+endfunc
+
+"Compound words
+func Test_spell_compound()
+ call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3)
+ call RunGoodBad("foo m\u00EF foobar foofoobar barfoo barbarfoo",
+ \ "bad: bar la foom\u00EF barm\u00EF m\u00EFfoo m\u00EFbar m\u00EFm\u00EF lala m\u00EFla lam\u00EF foola labar",
+ \ ["foo", "m\u00EF"],
+ \ [
+ \ ["bad", ["foo", "m\u00EF"]],
+ \ ["bar", ["barfoo", "foobar", "foo"]],
+ \ ["la", ["m\u00EF", "foo"]],
+ \ ["foom\u00EF", ["foo m\u00EF", "foo", "foofoo"]],
+ \ ["barm\u00EF", ["barfoo", "m\u00EF", "barbar"]],
+ \ ["m\u00EFfoo", ["m\u00EF foo", "foo", "foofoo"]],
+ \ ["m\u00EFbar", ["foobar", "barbar", "m\u00EF"]],
+ \ ["m\u00EFm\u00EF", ["m\u00EF m\u00EF", "m\u00EF"]],
+ \ ["lala", []],
+ \ ["m\u00EFla", ["m\u00EF", "m\u00EF m\u00EF"]],
+ \ ["lam\u00EF", ["m\u00EF", "m\u00EF m\u00EF"]],
+ \ ["foola", ["foo", "foobar", "foofoo"]],
+ \ ["labar", ["barbar", "foobar"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4)
+ call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword",
+ \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword",
+ \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"],
+ \ [
+ \ ["bad", ["end", "bork", "word"]],
+ \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]],
+ \ ["pro", ["bork", "word", "end"]],
+ \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]],
+ \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]],
+ \ ["endstart", ["end start", "start"]],
+ \ ["endend", ["end end", "end"]],
+ \ ["startstart", ["start start"]],
+ \ ["wordend", ["word end", "word", "wordword"]],
+ \ ["wordstart", ["word start", "bork start"]],
+ \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]],
+ \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]],
+ \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]],
+ \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]],
+ \ ["utilsbork", ["utilbork", "utils bork", "util bork"]],
+ \ ["startnouword", ["start nouword", "startword", "startborkword"]],
+ \ ])
+
+endfunc
+
+" Test affix flags with two characters
+func Test_spell_affix()
+ call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5)
+ call RunGoodBad("fooa1 fooa\u00E9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend",
+ \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend",
+ \ ["bar", "barbork", "end", "fooa1", "fooa\u00E9", "nouend", "prebar", "prebarbork", "start"],
+ \ [
+ \ ["bad", ["bar", "end", "fooa1"]],
+ \ ["foo", ["fooa1", "bar", "end"]],
+ \ ["fooa2", ["fooa1", "fooa\u00E9", "bar"]],
+ \ ["prabar", ["prebar", "bar", "bar bar"]],
+ \ ["probarbirk", ["prebarbork"]],
+ \ ["middle", []],
+ \ ["startmiddle", ["startmiddleend", "startmiddlebar"]],
+ \ ["middleend", []],
+ \ ["endstart", ["end start", "start"]],
+ \ ["startprobar", ["startprebar", "start prebar", "startbar"]],
+ \ ["startnouend", ["start nouend", "startend"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6)
+ call RunGoodBad("meea1 meea\u00E9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend",
+ \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar",
+ \ ["bar", "barbork", "end", "lead", "meea1", "meea\u00E9", "prebar", "prebarbork"],
+ \ [
+ \ ["bad", ["bar", "end", "lead"]],
+ \ ["mee", ["meea1", "bar", "end"]],
+ \ ["meea2", ["meea1", "meea\u00E9", "lead"]],
+ \ ["prabar", ["prebar", "bar", "leadbar"]],
+ \ ["probarbirk", ["prebarbork"]],
+ \ ["middle", []],
+ \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]],
+ \ ["middleend", []],
+ \ ["endlead", ["end lead", "lead", "end end"]],
+ \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
+ \ ])
+
+ call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
+ call RunGoodBad("meea1 meezero meea\u00E9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
+ \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar",
+ \ ["bar", "barmeat", "lead", "meea1", "meea\u00E9", "meezero", "prebar", "prebarmeat", "tail"],
+ \ [
+ \ ["bad", ["bar", "lead", "tail"]],
+ \ ["mee", ["meea1", "bar", "lead"]],
+ \ ["meea2", ["meea1", "meea\u00E9", "lead"]],
+ \ ["prabar", ["prebar", "bar", "leadbar"]],
+ \ ["probarmaat", ["prebarmeat"]],
+ \ ["middle", []],
+ \ ["leadmiddle", ["leadmiddlebar"]],
+ \ ["middletail", []],
+ \ ["taillead", ["tail lead", "tail"]],
+ \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]],
+ \ ])
+endfunc
+
+func Test_spell_NOSLITSUGS()
+ call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8)
+ call RunGoodBad("foo bar faabar", "bad: foobar barfoo",
+ \ ["bar", "faabar", "foo"],
+ \ [
+ \ ["bad", ["bar", "foo"]],
+ \ ["foobar", ["faabar", "foo bar", "bar"]],
+ \ ["barfoo", ["bar foo", "bar", "foo"]],
+ \ ])
+endfunc
+
+" Numbers
+func Test_spell_Numbers()
+ call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9)
+ call RunGoodBad("0b1011 0777 1234 0x01ff", "",
+ \ ["bar", "foo"],
+ \ [
+ \ ])
+endfunc
+
+" Affix flags
+func Test_spell_affix_flags()
+ call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10)
+ call RunGoodBad("drink drinkable drinkables drinktable drinkabletable",
+ \ "bad: drinks drinkstable drinkablestable",
+ \ ["drink", "drinkable", "drinkables", "table"],
+ \ [['bad', []],
+ \ ['drinks', ['drink']],
+ \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']],
+ \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']],
+ \ ])
+endfunc
+
+function FirstSpellWord()
+ call feedkeys("/^start:\n", 'tx')
+ normal ]smm
+ let [str, a] = spellbadword()
+ return str
+endfunc
+
+function SecondSpellWord()
+ normal `m]s
+ let [str, a] = spellbadword()
+ return str
+endfunc
+
+" Test with SAL instead of SOFO items; test automatic reloading
+func Test_spell_sal_and_addition()
+ set spellfile=
+ call writefile(g:test_data_dic1, "Xtest.dic", 'D')
+ call writefile(g:test_data_aff_sal, "Xtest.aff", 'D')
+ mkspell! Xtest Xtest
+ set spl=Xtest.utf-8.spl spell
+ call assert_equal('kbltykk', soundfold('goobledygoook'))
+ call assert_equal('kprnfn', soundfold('kóopërÿnôven'))
+ call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale'))
+
+ "also use an addition file
+ call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.utf-8.add", 'D')
+ mkspell! Xtest.utf-8.add.spl Xtest.utf-8.add
+
+ bwipe!
+ call setline(1, ["start: elequint test elekwint test elekwent asdf"])
+
+ set spellfile=Xtest.utf-8.add
+ call assert_equal("elekwent", FirstSpellWord())
+
+ set spl=Xtest_us.utf-8.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwint", SecondSpellWord())
+
+ set spl=Xtest_gb.utf-8.spl
+ call assert_equal("elekwint", FirstSpellWord())
+ call assert_equal("elekwent", SecondSpellWord())
+
+ set spl=Xtest_nz.utf-8.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwent", SecondSpellWord())
+
+ set spl=Xtest_ca.utf-8.spl
+ call assert_equal("elequint", FirstSpellWord())
+ call assert_equal("elekwint", SecondSpellWord())
+
+ bwipe!
+ set spellfile=
+ set spl&
+endfunc
+
+func Test_spellfile_value()
+ set spellfile=Xdir/Xtest.utf-8.add
+ set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add
+ set spellfile=
+endfunc
+
+func Test_no_crash_with_weird_text()
+ new
+ let lines =<< trim END
+ r<sfile>
+ €
+
+
+ €
+ END
+ call setline(1, lines)
+ try
+ exe "%norm \<C-v>ez=>\<C-v>wzG"
+ catch /E1280:/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+
+ bwipe!
+endfunc
+
+" Invalid bytes may cause trouble when creating the word list.
+func Test_check_for_valid_word()
+ call assert_fails("spellgood! 0\xac", 'E1280:')
+endfunc
+
+" This was going over the end of the word
+func Test_word_index()
+ new
+ norm R0
+ spellgood! fl0
+ sil norm z=
+
+ bwipe!
+ call delete('Xtmpfile')
+endfunc
+
+func Test_check_empty_line()
+ " This was using freed memory
+ enew
+ spellgood! fl
+ norm z=
+ norm yy
+ sil! norm P]svc
+ norm P]s
+
+ bwipe!
+endfunc
+
+func Test_spell_suggest_too_long()
+ " this was creating a word longer than MAXWLEN
+ new
+ call setline(1, 'a' .. repeat("\u0333", 150))
+ norm! z=
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_spellfile.vim b/src/testdir/test_spellfile.vim
new file mode 100644
index 0000000..d8d954b
--- /dev/null
+++ b/src/testdir/test_spellfile.vim
@@ -0,0 +1,1156 @@
+" Test for commands that operate on the spellfile.
+
+source shared.vim
+source check.vim
+
+CheckFeature spell
+CheckFeature syntax
+
+func Test_spell_normal()
+ new
+ call append(0, ['1 good', '2 goood', '3 goood'])
+ set spell spellfile=./Xspellfile.add spelllang=en
+ let oldlang=v:lang
+ lang C
+
+ " Test for zg
+ 1
+ norm! ]s
+ call assert_equal('2 goood', getline('.'))
+ norm! zg
+ 1
+ let a=execute('unsilent :norm! ]s')
+ call assert_equal('1 good', getline('.'))
+ call assert_equal('search hit BOTTOM, continuing at TOP', a[1:])
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('goood', cnt[0])
+
+ " zg should fail in operator-pending mode
+ call assert_beeps('norm! czg')
+
+ " zg fails in visual mode when not able to get the visual text
+ call assert_beeps('norm! ggVjzg')
+ norm! V
+
+ " zg fails for a non-identifier word
+ call append(line('$'), '###')
+ call assert_fails('norm! Gzg', 'E349:')
+ $d
+
+ " Test for zw
+ 2
+ norm! $zw
+ 1
+ norm! ]s
+ call assert_equal('2 goood', getline('.'))
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('#oood', cnt[0])
+ call assert_equal('goood/!', cnt[1])
+
+ " Test for :spellrare
+ spellrare rare
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal(['#oood', 'goood/!', 'rare/?'], cnt)
+
+ " Make sure :spellundo works for rare words.
+ spellundo rare
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal(['#oood', 'goood/!', '#are/?'], cnt)
+
+ " Test for zg in visual mode
+ let a=execute('unsilent :norm! V$zg')
+ call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
+ 1
+ norm! ]s
+ call assert_equal('3 goood', getline('.'))
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('2 goood', cnt[3])
+ " Remove "2 good" from spellfile
+ 2
+ let a=execute('unsilent norm! V$zw')
+ call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:])
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('2 goood/!', cnt[4])
+
+ " Test for zG
+ let a=execute('unsilent norm! V$zG')
+ call assert_match("Word '2 goood' added to .*", a)
+ let fname=matchstr(a, 'to\s\+\zs\f\+$')
+ let cnt=readfile(fname)
+ call assert_equal('2 goood', cnt[0])
+
+ " Test for zW
+ let a=execute('unsilent norm! V$zW')
+ call assert_match("Word '2 goood' added to .*", a)
+ let cnt=readfile(fname)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('2 goood/!', cnt[1])
+
+ " Test for zuW
+ let a=execute('unsilent norm! V$zuW')
+ call assert_match("Word '2 goood' removed from .*", a)
+ let cnt=readfile(fname)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+
+ " Test for zuG
+ let a=execute('unsilent norm! $zG')
+ call assert_match("Word 'goood' added to .*", a)
+ let cnt=readfile(fname)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+ call assert_equal('goood', cnt[2])
+ let a=execute('unsilent norm! $zuG')
+ let cnt=readfile(fname)
+ call assert_match("Word 'goood' removed from .*", a)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+ call assert_equal('#oood', cnt[2])
+ " word not found in wordlist
+ let a=execute('unsilent norm! V$zuG')
+ let cnt=readfile(fname)
+ call assert_match("", a)
+ call assert_equal('# goood', cnt[0])
+ call assert_equal('# goood/!', cnt[1])
+ call assert_equal('#oood', cnt[2])
+
+ " Test for zug
+ call delete('./Xspellfile.add')
+ 2
+ let a=execute('unsilent norm! $zg')
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('goood', cnt[0])
+ let a=execute('unsilent norm! $zug')
+ call assert_match("Word 'goood' removed from \./Xspellfile.add", a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('#oood', cnt[0])
+ " word not in wordlist
+ let a=execute('unsilent norm! V$zug')
+ call assert_match('', a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('#oood', cnt[0])
+
+ " Test for zuw
+ call delete('./Xspellfile.add')
+ 2
+ let a=execute('unsilent norm! Vzw')
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('2 goood/!', cnt[0])
+ let a=execute('unsilent norm! Vzuw')
+ call assert_match("Word '2 goood' removed from \./Xspellfile.add", a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('# goood/!', cnt[0])
+ " word not in wordlist
+ let a=execute('unsilent norm! $zug')
+ call assert_match('', a)
+ let cnt=readfile('./Xspellfile.add')
+ call assert_equal('# goood/!', cnt[0])
+
+ " add second entry to spellfile setting
+ set spellfile=./Xspellfile.add,./Xspellfile2.add
+ call delete('./Xspellfile.add')
+ 2
+ let a=execute('unsilent norm! $2zg')
+ let cnt=readfile('./Xspellfile2.add')
+ call assert_match("Word 'goood' added to ./Xspellfile2.add", a)
+ call assert_equal('goood', cnt[0])
+
+ " Test for :spellgood!
+ let temp = execute(':spe!0/0')
+ call assert_match('Invalid region', temp)
+ let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0')
+ call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile))
+
+ " Test for :spellrare!
+ :spellrare! raare
+ call assert_equal(['# goood', '# goood/!', '#oood', '0/0', 'raare/?'], readfile(spellfile))
+ call delete(spellfile)
+
+ " clean up
+ exe "lang" oldlang
+ call delete("./Xspellfile.add")
+ call delete("./Xspellfile2.add")
+ call delete("./Xspellfile.add.spl")
+ call delete("./Xspellfile2.add.spl")
+
+ " zux -> no-op
+ 2
+ norm! $zux
+ call assert_equal([], glob('Xspellfile.add',0,1))
+ call assert_equal([], glob('Xspellfile2.add',0,1))
+
+ set spellfile= spell& spelllang&
+ bw!
+endfunc
+
+" Spell file content test. Write 'content' to the spell file prefixed by the
+" spell file header and then enable spell checking. If 'emsg' is not empty,
+" then check for error.
+func Spellfile_Test(content, emsg)
+ let splfile = './Xtest/spell/Xtest.utf-8.spl'
+ " Add the spell file header and version (VIMspell2)
+ let v = 0z56494D7370656C6C32 + a:content
+ call writefile(v, splfile, 'b')
+
+ " 'encoding' is set before each test to clear the previously loaded suggest
+ " file from memory.
+ set encoding=utf-8
+
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ if a:emsg != ''
+ call assert_fails('set spell', a:emsg)
+ else
+ " FIXME: With some invalid spellfile contents, there are no error
+ " messages. So don't know how to check for the test result.
+ set spell
+ endif
+ set nospell spelllang& rtp&
+endfunc
+
+" Test for spell file format errors.
+" The spell file format is described in spellfile.c
+func Test_spellfile_format_error()
+ let save_rtp = &rtp
+ call mkdir('Xtest/spell', 'pR')
+ let splfile = './Xtest/spell/Xtest.utf-8.spl'
+
+ " empty spell file
+ call writefile([], splfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ call assert_fails('set spell', 'E757:')
+ set nospell spelllang&
+
+ " invalid file ID
+ call writefile(0z56494D, splfile, 'b')
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ call assert_fails('set spell', 'E757:')
+ set nospell spelllang&
+
+ " missing version number
+ call writefile(0z56494D7370656C6C, splfile, 'b')
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ call assert_fails('set spell', 'E771:')
+ set nospell spelllang&
+
+ " invalid version number
+ call writefile(0z56494D7370656C6C7A, splfile, 'b')
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ call assert_fails('set spell', 'E772:')
+ set nospell spelllang&
+
+ " no sections
+ call Spellfile_Test(0z, 'E758:')
+
+ " missing section length
+ call Spellfile_Test(0z00, 'E758:')
+
+ " unsupported required section
+ call Spellfile_Test(0z7A0100000004, 'E770:')
+
+ " unsupported not-required section
+ call Spellfile_Test(0z7A0000000004, 'E758:')
+
+ " SN_REGION: invalid number of region names
+ call Spellfile_Test(0z0000000000FF, 'E759:')
+
+ " SN_CHARFLAGS: missing <charflagslen> length
+ call Spellfile_Test(0z010000000004, 'E758:')
+
+ " SN_CHARFLAGS: invalid <charflagslen> length
+ call Spellfile_Test(0z0100000000010201, '')
+
+ " SN_CHARFLAGS: charflagslen == 0 and folcharslen != 0
+ call Spellfile_Test(0z01000000000400000101, 'E759:')
+
+ " SN_CHARFLAGS: missing <folcharslen> length
+ call Spellfile_Test(0z01000000000100, 'E758:')
+
+ " SN_PREFCOND: invalid prefcondcnt
+ call Spellfile_Test(0z03000000000100, 'E759:')
+
+ " SN_PREFCOND: invalid condlen
+ call Spellfile_Test(0z0300000000020001, 'E759:')
+
+ " SN_REP: invalid repcount
+ call Spellfile_Test(0z04000000000100, 'E758:')
+
+ " SN_REP: missing rep
+ call Spellfile_Test(0z0400000000020004, 'E758:')
+
+ " SN_REP: zero repfromlen
+ call Spellfile_Test(0z040000000003000100, 'E759:')
+
+ " SN_REP: invalid reptolen
+ call Spellfile_Test(0z0400000000050001014101, '')
+
+ " SN_REP: zero reptolen
+ call Spellfile_Test(0z0400000000050001014100, 'E759:')
+
+ " SN_SAL: missing salcount
+ call Spellfile_Test(0z05000000000102, 'E758:')
+
+ " SN_SAL: missing salfromlen
+ call Spellfile_Test(0z050000000003080001, 'E758:')
+
+ " SN_SAL: missing saltolen
+ call Spellfile_Test(0z0500000000050400010161, 'E758:')
+
+ " SN_WORDS: non-NUL terminated word
+ call Spellfile_Test(0z0D000000000376696D, 'E758:')
+
+ " SN_WORDS: very long word
+ let v = eval('0z0D000000012C' .. repeat('41', 300))
+ call Spellfile_Test(v, 'E759:')
+
+ " SN_SOFO: missing sofofromlen
+ call Spellfile_Test(0z06000000000100, 'E758:')
+
+ " SN_SOFO: missing sofotolen
+ call Spellfile_Test(0z06000000000400016100, 'E758:')
+
+ " SN_SOFO: missing sofoto
+ call Spellfile_Test(0z0600000000050001610000, 'E759:')
+
+ " SN_SOFO: empty sofofrom and sofoto
+ call Spellfile_Test(0z06000000000400000000FF000000000000000000000000, '')
+
+ " SN_SOFO: multi-byte characters in sofofrom and sofoto
+ call Spellfile_Test(0z0600000000080002CF810002CF82FF000000000000000000000000, '')
+
+ " SN_COMPOUND: compmax is less than 2
+ call Spellfile_Test(0z08000000000101, 'E759:')
+
+ " SN_COMPOUND: missing compsylmax and other options
+ call Spellfile_Test(0z0800000000020401, 'E759:')
+
+ " SN_COMPOUND: missing compoptions
+ call Spellfile_Test(0z080000000005040101, 'E758:')
+
+ " SN_COMPOUND: missing comppattern
+ call Spellfile_Test(0z08000000000704010100000001, 'E758:')
+
+ " SN_COMPOUND: incorrect comppatlen
+ call Spellfile_Test(0z080000000007040101000000020165, 'E758:')
+
+ " SN_INFO: missing info
+ call Spellfile_Test(0z0F0000000005040101, '')
+
+ " SN_MIDWORD: missing midword
+ call Spellfile_Test(0z0200000000040102, '')
+
+ " SN_MAP: missing midword
+ call Spellfile_Test(0z0700000000040102, '')
+
+ " SN_MAP: empty map string
+ call Spellfile_Test(0z070000000000FF000000000000000000000000, '')
+
+ " SN_MAP: duplicate multibyte character
+ call Spellfile_Test(0z070000000004DC81DC81, 'E783:')
+
+ " SN_SYLLABLE: missing SYLLABLE item
+ call Spellfile_Test(0z0900000000040102, '')
+
+ " SN_SYLLABLE: More than SY_MAXLEN size
+ let v = eval('0z090000000022612F' .. repeat('62', 32))
+ call Spellfile_Test(v, '')
+
+ " LWORDTREE: missing
+ call Spellfile_Test(0zFF, 'E758:')
+
+ " LWORDTREE: missing tree node
+ call Spellfile_Test(0zFF00000004, 'E758:')
+
+ " LWORDTREE: missing tree node value
+ call Spellfile_Test(0zFF0000000402, 'E758:')
+
+ " LWORDTREE: incorrect sibling node count
+ call Spellfile_Test(0zFF00000001040000000000000000, 'E759:')
+
+ " KWORDTREE: missing tree node
+ call Spellfile_Test(0zFF0000000000000004, 'E758:')
+
+ " PREFIXTREE: missing tree node
+ call Spellfile_Test(0zFF000000000000000000000004, 'E758:')
+
+ " PREFIXTREE: incorrect prefcondnr
+ call Spellfile_Test(0zFF000000000000000000000002010200000020, 'E759:')
+
+ " PREFIXTREE: invalid nodeidx
+ call Spellfile_Test(0zFF00000000000000000000000201010000, 'E759:')
+
+ let &rtp = save_rtp
+endfunc
+
+" Test for format errors in suggest file
+func Test_sugfile_format_error()
+ let save_rtp = &rtp
+ call mkdir('Xtest/spell', 'pR')
+ let splfile = './Xtest/spell/Xtest.utf-8.spl'
+ let sugfile = './Xtest/spell/Xtest.utf-8.sug'
+
+ " create an empty spell file with a suggest timestamp
+ call writefile(0z56494D7370656C6C320B00000000080000000000000044FF000000000000000000000000, splfile, 'b')
+
+ " 'encoding' is set before each test to clear the previously loaded suggest
+ " file from memory.
+
+ " empty suggest file
+ set encoding=utf-8
+ call writefile([], sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E778:')
+ set nospell spelllang&
+
+ " zero suggest version
+ set encoding=utf-8
+ call writefile(0z56494D73756700, sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E779:')
+ set nospell spelllang&
+
+ " unsupported suggest version
+ set encoding=utf-8
+ call writefile(0z56494D7375671F, sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E780:')
+ set nospell spelllang&
+
+ " missing suggest timestamp
+ set encoding=utf-8
+ call writefile(0z56494D73756701, sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E781:')
+ set nospell spelllang&
+
+ " incorrect suggest timestamp
+ set encoding=utf-8
+ call writefile(0z56494D7375670100000000000000FF, sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E781:')
+ set nospell spelllang&
+
+ " missing suggest wordtree
+ set encoding=utf-8
+ call writefile(0z56494D737567010000000000000044, sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E782:')
+ set nospell spelllang&
+
+ " invalid suggest word count in SUGTABLE
+ set encoding=utf-8
+ call writefile(0z56494D7375670100000000000000440000000022, sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E782:')
+ set nospell spelllang&
+
+ " missing sugline in SUGTABLE
+ set encoding=utf-8
+ call writefile(0z56494D7375670100000000000000440000000000000005, sugfile)
+ set runtimepath=./Xtest
+ set spelllang=Xtest
+ set spell
+ call assert_fails("let s = spellsuggest('abc')", 'E782:')
+ set nospell spelllang&
+
+ let &rtp = save_rtp
+endfunc
+
+" Test for using :mkspell to create a spell file from a list of words
+func Test_wordlist_dic()
+ " duplicate encoding
+ let lines =<< trim [END]
+ # This is an example word list
+
+ /encoding=latin1
+ /encoding=latin1
+ example
+ [END]
+ call writefile(lines, 'Xwordlist.dic', 'D')
+ let output = execute('mkspell Xwordlist.spl Xwordlist.dic')
+ call assert_match('Duplicate /encoding= line ignored in Xwordlist.dic line 4: /encoding=latin1', output)
+
+ " multiple encoding for a word
+ let lines =<< trim [END]
+ example
+ /encoding=latin1
+ example
+ [END]
+ call writefile(lines, 'Xwordlist.dic')
+ let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
+ call assert_match('/encoding= line after word ignored in Xwordlist.dic line 2: /encoding=latin1', output)
+
+ " unsupported encoding for a word
+ let lines =<< trim [END]
+ /encoding=Xtest
+ example
+ [END]
+ call writefile(lines, 'Xwordlist.dic')
+ let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
+ call assert_match('Conversion in Xwordlist.dic not supported: from Xtest to utf-8', output)
+
+ " duplicate region
+ let lines =<< trim [END]
+ /regions=usca
+ /regions=usca
+ example
+ [END]
+ call writefile(lines, 'Xwordlist.dic')
+ let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
+ call assert_match('Duplicate /regions= line ignored in Xwordlist.dic line 2: regions=usca', output)
+
+ " maximum regions
+ let lines =<< trim [END]
+ /regions=uscauscauscauscausca
+ example
+ [END]
+ call writefile(lines, 'Xwordlist.dic')
+ let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
+ call assert_match('Too many regions in Xwordlist.dic line 1: uscauscauscauscausca', output)
+
+ " unsupported '/' value
+ let lines =<< trim [END]
+ /test=abc
+ example
+ [END]
+ call writefile(lines, 'Xwordlist.dic')
+ let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
+ call assert_match('/ line ignored in Xwordlist.dic line 1: /test=abc', output)
+
+ " unsupported flag
+ let lines =<< trim [END]
+ example/+
+ [END]
+ call writefile(lines, 'Xwordlist.dic')
+ let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
+ call assert_match('Unrecognized flags in Xwordlist.dic line 1: +', output)
+
+ " non-ascii word
+ call writefile(["ʀʀ"], 'Xwordlist.dic')
+ let output = execute('mkspell! -ascii Xwordlist.spl Xwordlist.dic')
+ call assert_match('Ignored 1 words with non-ASCII characters', output)
+
+ " keep case of a word
+ let lines =<< trim [END]
+ example/=
+ [END]
+ call writefile(lines, 'Xwordlist.dic')
+ let output = execute('mkspell! Xwordlist.spl Xwordlist.dic')
+ call assert_match('Compressed keep-case:', output)
+
+ call delete('Xwordlist.spl')
+endfunc
+
+" Test for the :mkspell command
+func Test_mkspell()
+ call assert_fails('mkspell Xtest_us.spl', 'E751:')
+ call assert_fails('mkspell Xtest.spl abc', 'E484:')
+ call assert_fails('mkspell a b c d e f g h i j k', 'E754:')
+
+ " create a .aff file but not the .dic file
+ call writefile([], 'Xtest.aff')
+ call assert_fails('mkspell Xtest.spl Xtest', 'E484:')
+ call delete('Xtest.aff')
+
+ call writefile([], 'Xtest.spl')
+ call writefile([], 'Xtest.dic')
+ call assert_fails('mkspell Xtest.spl Xtest.dic', 'E13:')
+ call delete('Xtest.spl')
+ call delete('Xtest.dic')
+
+ call mkdir('Xtest.spl')
+ call assert_fails('mkspell! Xtest.spl Xtest.dic', 'E17:')
+ call delete('Xtest.spl', 'rf')
+
+ " can't write the .spl file as its directory does not exist
+ call writefile([], 'Xtest.aff')
+ call writefile([], 'Xtest.dic')
+ call assert_fails('mkspell DOES_NOT_EXIT/Xtest.spl Xtest.dic', 'E484:')
+ call delete('Xtest.aff')
+ call delete('Xtest.dic')
+
+ call assert_fails('mkspell en en_US abc_xyz', 'E755:')
+endfunc
+
+" Tests for :mkspell with a .dic and .aff file
+func Test_aff_file_format_error()
+ " FIXME: For some reason, the :mkspell command below doesn't fail on the
+ " MS-Windows CI build. Disable this test on MS-Windows for now.
+ CheckNotMSWindows
+
+ " No word count in .dic file
+ call writefile([], 'Xtest.dic', 'D')
+ call writefile([], 'Xtest.aff', 'D')
+ call assert_fails('mkspell! Xtest.spl Xtest', 'E760:')
+
+ " create a .dic file for the tests below
+ call writefile(['1', 'work'], 'Xtest.dic')
+
+ " Invalid encoding in .aff file
+ call writefile(['# comment', 'SET Xinvalidencoding'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Conversion in Xtest.aff not supported: from xinvalidencoding', output)
+
+ " Invalid flag in .aff file
+ call writefile(['FLAG xxx'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Invalid value for FLAG in Xtest.aff line 1: xxx', output)
+
+ " set FLAGS after using flag for an affix
+ call writefile(['SFX L Y 1', 'SFX L 0 re [^x]', 'FLAG long'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('FLAG after using flags in Xtest.aff line 3: long', output)
+
+ " INFO in affix file
+ let save_encoding = &encoding
+ call mkdir('Xrtp/spell', 'p')
+ call writefile(['1', 'work'], 'Xrtp/spell/Xtest.dic')
+ call writefile(['NAME klingon', 'VERSION 1.4', 'AUTHOR Spock'],
+ \ 'Xrtp/spell/Xtest.aff')
+ silent mkspell! Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest
+ let save_rtp = &rtp
+ set runtimepath=./Xrtp
+ set spelllang=Xtest
+ set spell
+ let output = split(execute('spellinfo'), "\n")
+ call assert_equal("NAME klingon", output[1])
+ call assert_equal("VERSION 1.4", output[2])
+ call assert_equal("AUTHOR Spock", output[3])
+ let &rtp = save_rtp
+ call delete('Xrtp', 'rf')
+ set spell& spelllang& spellfile&
+ %bw!
+ " 'encoding' must be set again to clear the spell file in memory
+ let &encoding = save_encoding
+
+ " COMPOUNDFORBIDFLAG flag after PFX in an affix file
+ call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDFLAG c', 'COMPOUNDFORBIDFLAG x'],
+ \ 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in Xtest.aff line 4', output)
+
+ " COMPOUNDPERMITFLAG flag after PFX in an affix file
+ call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDPERMITFLAG c'],
+ \ 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in Xtest.aff line 3', output)
+
+ " Wrong COMPOUNDRULES flag value in an affix file
+ call writefile(['COMPOUNDRULES a'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Wrong COMPOUNDRULES value in Xtest.aff line 1: a', output)
+
+ " Wrong COMPOUNDWORDMAX flag value in an affix file
+ call writefile(['COMPOUNDWORDMAX 0'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Wrong COMPOUNDWORDMAX value in Xtest.aff line 1: 0', output)
+
+ " Wrong COMPOUNDMIN flag value in an affix file
+ call writefile(['COMPOUNDMIN 0'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Wrong COMPOUNDMIN value in Xtest.aff line 1: 0', output)
+
+ " Wrong COMPOUNDSYLMAX flag value in an affix file
+ call writefile(['COMPOUNDSYLMAX 0'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Wrong COMPOUNDSYLMAX value in Xtest.aff line 1: 0', output)
+
+ " Wrong CHECKCOMPOUNDPATTERN flag value in an affix file
+ call writefile(['CHECKCOMPOUNDPATTERN 0'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Wrong CHECKCOMPOUNDPATTERN value in Xtest.aff line 1: 0', output)
+
+ " Both compounding and NOBREAK specified
+ call writefile(['COMPOUNDFLAG c', 'NOBREAK'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Warning: both compounding and NOBREAK specified', output)
+
+ " Duplicate affix entry in an affix file
+ call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L Y 1', 'PFX L 0 re x'],
+ \ 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Duplicate affix in Xtest.aff line 3: L', output)
+
+ " Duplicate affix entry in an affix file
+ call writefile(['PFX L Y 1', 'PFX L Y 1'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Unrecognized or duplicate item in Xtest.aff line 2: PFX', output)
+
+ " Different combining flags in an affix file
+ call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L N 1'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Different combining flag in continued affix block in Xtest.aff line 3', output)
+
+ " Try to reuse an affix used for BAD flag
+ call writefile(['BAD x', 'PFX x Y 1', 'PFX x 0 re x'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in Xtest.aff line 2: x', output)
+
+ " Trailing characters in an affix entry
+ call writefile(['PFX L Y 1 Test', 'PFX L 0 re x'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Trailing text in Xtest.aff line 1: Test', output)
+
+ " Trailing characters in an affix entry
+ call writefile(['PFX L Y 1', 'PFX L 0 re x Test'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Trailing text in Xtest.aff line 2: Test', output)
+
+ " Incorrect combine flag in an affix entry
+ call writefile(['PFX L X 1', 'PFX L 0 re x'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Expected Y or N in Xtest.aff line 1: X', output)
+
+ " Invalid count for REP item
+ call writefile(['REP a'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Expected REP(SAL) count in Xtest.aff line 1', output)
+
+ " Trailing characters in REP item
+ call writefile(['REP 1', 'REP f ph test'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Trailing text in Xtest.aff line 2: test', output)
+
+ " Invalid count for MAP item
+ call writefile(['MAP a'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Expected MAP count in Xtest.aff line 1', output)
+
+ " Duplicate character in a MAP item
+ call writefile(['MAP 2', 'MAP xx', 'MAP yy'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Duplicate character in MAP in Xtest.aff line 2', output)
+
+ " Use COMPOUNDSYLMAX without SYLLABLE
+ call writefile(['COMPOUNDSYLMAX 2'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('COMPOUNDSYLMAX used without SYLLABLE', output)
+
+ " Missing SOFOTO
+ call writefile(['SOFOFROM abcdef'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Missing SOFOTO line in Xtest.aff', output)
+
+ " Length of SOFOFROM and SOFOTO differ
+ call writefile(['SOFOFROM abcde', 'SOFOTO ABCD'], 'Xtest.aff')
+ call assert_fails('mkspell! Xtest.spl Xtest', 'E759:')
+
+ " Both SAL and SOFOFROM/SOFOTO items
+ call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Both SAL and SOFO lines in Xtest.aff', output)
+
+ " use an alphabet flag when FLAG is num
+ call writefile(['FLAG num', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Flag is not a number in Xtest.aff line 2: L', output)
+
+ " use number and alphabet flag when FLAG is num
+ call writefile(['FLAG num', 'SFX 4f Y 1', 'SFX 4f 0 re [^x]'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Affix name too long in Xtest.aff line 2: 4f', output)
+
+ " use a single character flag when FLAG is long
+ call writefile(['FLAG long', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Illegal flag in Xtest.aff line 2: L', output)
+
+ " missing character in UPP entry. The character table is used only in a
+ " non-utf8 encoding
+ call writefile(['FOL abc', 'LOW abc', 'UPP A'], 'Xtest.aff')
+ let save_encoding = &encoding
+ set encoding=cp949
+ call assert_fails('mkspell! Xtest.spl Xtest', 'E761:')
+ let &encoding = save_encoding
+
+ " character range doesn't match between FOL and LOW entries
+ call writefile(["FOL \u0102bc", 'LOW abc', 'UPP ABC'], 'Xtest.aff')
+ let save_encoding = &encoding
+ set encoding=cp949
+ call assert_fails('mkspell! Xtest.spl Xtest', 'E762:')
+ let &encoding = save_encoding
+
+ " character range doesn't match between FOL and UPP entries
+ call writefile(["FOL \u0102bc", "LOW \u0102bc", 'UPP ABC'], 'Xtest.aff')
+ let save_encoding = &encoding
+ set encoding=cp949
+ call assert_fails('mkspell! Xtest.spl Xtest', 'E762:')
+ let &encoding = save_encoding
+
+ " additional characters in LOW and UPP entries
+ call writefile(["FOL ab", "LOW abc", 'UPP ABC'], 'Xtest.aff')
+ let save_encoding = &encoding
+ set encoding=cp949
+ call assert_fails('mkspell! Xtest.spl Xtest', 'E761:')
+ let &encoding = save_encoding
+
+ " missing UPP entry
+ call writefile(["FOL abc", "LOW abc"], 'Xtest.aff')
+ let save_encoding = &encoding
+ set encoding=cp949
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('Missing FOL/LOW/UPP line in Xtest.aff', output)
+ let &encoding = save_encoding
+
+ " duplicate word in the .dic file
+ call writefile(['2', 'good', 'good', 'good'], 'Xtest.dic')
+ call writefile(['NAME vim'], 'Xtest.aff')
+ let output = execute('mkspell! Xtest.spl Xtest')
+ call assert_match('First duplicate word in Xtest.dic line 3: good', output)
+ call assert_match('2 duplicate word(s) in Xtest.dic', output)
+
+ " use multiple .aff files with different values for COMPOUNDWORDMAX and
+ " MIDWORD (number and string)
+ call writefile(['1', 'world'], 'Xtest_US.dic', 'D')
+ call writefile(['1', 'world'], 'Xtest_CA.dic', 'D')
+ call writefile(["COMPOUNDWORDMAX 3", "MIDWORD '-"], 'Xtest_US.aff', 'D')
+ call writefile(["COMPOUNDWORDMAX 4", "MIDWORD '="], 'Xtest_CA.aff', 'D')
+ let output = execute('mkspell! Xtest.spl Xtest_US Xtest_CA')
+ call assert_match('COMPOUNDWORDMAX value differs from what is used in another .aff file', output)
+ call assert_match('MIDWORD value differs from what is used in another .aff file', output)
+
+ call delete('Xtest.spl')
+ call delete('Xtest.sug')
+endfunc
+
+func Test_spell_add_word()
+ set spellfile=
+ call assert_fails('spellgood abc', 'E764:')
+
+ set spellfile=Xtest.utf-8.add
+ call assert_fails('2spellgood abc', 'E765:')
+
+ edit Xtest.utf-8.add
+ call setline(1, 'sample')
+ call assert_fails('spellgood abc', 'E139:')
+ set spellfile&
+ %bw!
+endfunc
+
+func Test_spellfile_verbose()
+ call writefile(['1', 'one'], 'XtestVerbose.dic', 'D')
+ call writefile([], 'XtestVerbose.aff', 'D')
+ mkspell! XtestVerbose-utf8.spl XtestVerbose
+ set spell
+
+ " First time: the spl file should be read.
+ let a = execute('3verbose set spelllang=XtestVerbose-utf8.spl')
+ call assert_match('Reading spell file "XtestVerbose-utf8.spl"', a)
+
+ " Second time time: the spl file should not be read (already read).
+ let a = execute('3verbose set spelllang=XtestVerbose-utf8.spl')
+ call assert_notmatch('Reading spell file "XtestVerbose-utf8.spl"', a)
+
+ set spell& spelllang&
+ call delete('XtestVerbose-utf8.spl')
+endfunc
+
+" Test NOBREAK (see :help spell-NOBREAK)
+func Test_NOBREAK()
+ call writefile(['3', 'one', 'two', 'three' ], 'XtestNOBREAK.dic', 'D')
+ call writefile(['NOBREAK' ], 'XtestNOBREAK.aff', 'D')
+
+ mkspell! XtestNOBREAK-utf8.spl XtestNOBREAK
+ set spell spelllang=XtestNOBREAK-utf8.spl
+
+ call assert_equal(['', ''], spellbadword('One two three onetwo onetwothree threetwoone'))
+
+ call assert_equal(['x', 'bad'], spellbadword('x'))
+ call assert_equal(['y', 'bad'], spellbadword('yone'))
+ call assert_equal(['z', 'bad'], spellbadword('onez'))
+ call assert_equal(['zero', 'bad'], spellbadword('Onetwozerothree'))
+
+ new
+ call setline(1, 'Onetwwothree')
+ norm! fw1z=
+ call assert_equal('Onetwothree', getline(1))
+ call setline(1, 'Onetwothre')
+ norm! fh1z=
+ call assert_equal('Onetwothree', getline(1))
+
+ bw!
+ set spell& spelllang&
+ call delete('XtestNOBREAK-utf8.spl')
+endfunc
+
+" Test CHECKCOMPOUNDPATTERN (see :help spell-CHECKCOMPOUNDPATTERN)
+func Test_spellfile_CHECKCOMPOUNDPATTERN()
+ call writefile(['4',
+ \ 'one/c',
+ \ 'two/c',
+ \ 'three/c',
+ \ 'four'], 'XtestCHECKCOMPOUNDPATTERN.dic', 'D')
+ " Forbid compound words where first word ends with 'wo' and second starts with 'on'.
+ call writefile(['CHECKCOMPOUNDPATTERN 1',
+ \ 'CHECKCOMPOUNDPATTERN wo on',
+ \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff', 'D')
+
+ mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN
+ set spell spelllang=XtestCHECKCOMPOUNDPATTERN-utf8.spl
+
+ " Check valid words with and without valid compounds.
+ for goodword in ['one', 'two', 'three', 'four',
+ \ 'oneone', 'onetwo', 'onethree',
+ \ 'twotwo', 'twothree',
+ \ 'threeone', 'threetwo', 'threethree',
+ \ 'onetwothree', 'onethreetwo', 'twothreeone', 'oneoneone']
+ call assert_equal(['', ''], spellbadword(goodword), goodword)
+ endfor
+
+ " Compounds 'twoone' or 'threetwoone' should be forbidden by CHECKCOMPOUNPATTERN.
+ " 'four' does not have the 'c' flag in *.aff file so no compound.
+ " 'five' is not in the *.dic file.
+ for badword in ['five', 'onetwox',
+ \ 'twoone', 'threetwoone',
+ \ 'fourone', 'onefour']
+ call assert_equal([badword, 'bad'], spellbadword(badword))
+ endfor
+
+ set spell& spelllang&
+ call delete('XtestCHECKCOMPOUNDPATTERN-utf8.spl')
+endfunc
+
+" Test NOCOMPOUNDSUGS (see :help spell-NOCOMPOUNDSUGS)
+func Test_spellfile_NOCOMPOUNDSUGS()
+ call writefile(['3',
+ \ 'one/c',
+ \ 'two/c',
+ \ 'three/c'], 'XtestNOCOMPOUNDSUGS.dic', 'D')
+
+ " pass 0 tests without NOCOMPOUNDSUGS, pass 1 tests with NOCOMPOUNDSUGS
+ for pass in [0, 1]
+ if pass == 0
+ call writefile(['COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff', 'D')
+ else
+ call writefile(['NOCOMPOUNDSUGS',
+ \ 'COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff', 'D')
+ endif
+
+ mkspell! XtestNOCOMPOUNDSUGS-utf8.spl XtestNOCOMPOUNDSUGS
+ set spell spelllang=XtestNOCOMPOUNDSUGS-utf8.spl
+
+ for goodword in ['one', 'two', 'three',
+ \ 'oneone', 'onetwo', 'onethree',
+ \ 'twoone', 'twotwo', 'twothree',
+ \ 'threeone', 'threetwo', 'threethree',
+ \ 'onetwothree', 'onethreetwo', 'twothreeone', 'oneoneone']
+ call assert_equal(['', ''], spellbadword(goodword), goodword)
+ endfor
+
+ for badword in ['four', 'onetwox', 'onexone']
+ call assert_equal([badword, 'bad'], spellbadword(badword))
+ endfor
+
+ if pass == 0
+ call assert_equal(['one', 'oneone'], spellsuggest('onne', 2))
+ call assert_equal(['onethree', 'one three'], spellsuggest('onethre', 2))
+ else
+ call assert_equal(['one', 'one one'], spellsuggest('onne', 2))
+ call assert_equal(['one three'], spellsuggest('onethre', 2))
+ endif
+ endfor
+
+ set spell& spelllang&
+ call delete('XtestNOCOMPOUNDSUGS-utf8.spl')
+endfunc
+
+" Test COMMON (better suggestions with common words, see :help spell-COMMON)
+func Test_spellfile_COMMON()
+ call writefile(['7',
+ \ 'and',
+ \ 'ant',
+ \ 'end',
+ \ 'any',
+ \ 'tee',
+ \ 'the',
+ \ 'ted'], 'XtestCOMMON.dic', 'D')
+ call writefile(['COMMON the and'], 'XtestCOMMON.aff', 'D')
+
+ mkspell! XtestCOMMON-utf8.spl XtestCOMMON
+ set spell spelllang=XtestCOMMON-utf8.spl
+
+ " COMMON words 'and' and 'the' should be the top suggestions.
+ call assert_equal(['and', 'ant'], spellsuggest('anr', 2))
+ call assert_equal(['and', 'end'], spellsuggest('ond', 2))
+ call assert_equal(['the', 'ted'], spellsuggest('tha', 2))
+ call assert_equal(['the', 'tee'], spellsuggest('dhe', 2))
+
+ set spell& spelllang&
+ call delete('XtestCOMMON-utf8.spl')
+endfunc
+
+" Test NOSUGGEST (see :help spell-COMMON)
+func Test_spellfile_NOSUGGEST()
+ call writefile(['2', 'foo/X', 'fog'], 'XtestNOSUGGEST.dic', 'D')
+ call writefile(['NOSUGGEST X'], 'XtestNOSUGGEST.aff', 'D')
+
+ mkspell! XtestNOSUGGEST-utf8.spl XtestNOSUGGEST
+ set spell spelllang=XtestNOSUGGEST-utf8.spl
+
+ for goodword in ['foo', 'Foo', 'FOO', 'fog', 'Fog', 'FOG']
+ call assert_equal(['', ''], spellbadword(goodword), goodword)
+ endfor
+ for badword in ['foO', 'fOO', 'fooo', 'foog', 'foofog', 'fogfoo']
+ call assert_equal([badword, 'bad'], spellbadword(badword))
+ endfor
+
+ call assert_equal(['fog'], spellsuggest('fooo', 1))
+ call assert_equal(['fog'], spellsuggest('fOo', 1))
+ call assert_equal(['fog'], spellsuggest('foG', 1))
+ call assert_equal(['fog'], spellsuggest('fogg', 1))
+
+ set spell& spelllang&
+ call delete('XtestNOSUGGEST-utf8.spl')
+endfunc
+
+
+" Test CIRCUMFIX (see: :help spell-CIRCUMFIX)
+func Test_spellfile_CIRCUMFIX()
+ " Example taken verbatim from https://github.com/hunspell/hunspell/tree/master/tests
+ call writefile(['1',
+ \ 'nagy/C po:adj'], 'XtestCIRCUMFIX.dic', 'D')
+ call writefile(['# circumfixes: ~ obligate prefix/suffix combinations',
+ \ '# superlative in Hungarian: leg- (prefix) AND -bb (suffix)',
+ \ '',
+ \ 'CIRCUMFIX X',
+ \ '',
+ \ 'PFX A Y 1',
+ \ 'PFX A 0 leg/X .',
+ \ '',
+ \ 'PFX B Y 1',
+ \ 'PFX B 0 legesleg/X .',
+ \ '',
+ \ 'SFX C Y 3',
+ \ 'SFX C 0 obb . is:COMPARATIVE',
+ \ 'SFX C 0 obb/AX . is:SUPERLATIVE',
+ \ 'SFX C 0 obb/BX . is:SUPERSUPERLATIVE'], 'XtestCIRCUMFIX.aff', 'D')
+
+ mkspell! XtestCIRCUMFIX-utf8.spl XtestCIRCUMFIX
+ set spell spelllang=XtestCIRCUMFIX-utf8.spl
+
+ " From https://catalog.ldc.upenn.edu/docs/LDC2008T01/acta04.pdf:
+ " Hungarian English
+ " --------- -------
+ " nagy great
+ " nagyobb greater
+ " legnagyobb greatest
+ " legeslegnagyob most greatest
+ call assert_equal(['', ''], spellbadword('nagy nagyobb legnagyobb legeslegnagyobb'))
+
+ for badword in ['legnagy', 'legeslegnagy', 'legobb', 'legeslegobb']
+ call assert_equal([badword, 'bad'], spellbadword(badword))
+ endfor
+
+ set spell& spelllang&
+ call delete('XtestCIRCUMFIX-utf8.spl')
+endfunc
+
+" Test SFX that strips/chops characters
+func Test_spellfile_SFX_strip()
+ " Simplified conjugation of Italian verbs ending in -are (first conjugation).
+ call writefile(['SFX A Y 4',
+ \ 'SFX A are iamo [^icg]are',
+ \ 'SFX A are hiamo [cg]are',
+ \ 'SFX A re mo iare',
+ \ 'SFX A re vamo are'],
+ \ 'XtestSFX.aff', 'D')
+ " Examples of Italian verbs:
+ " - cantare = to sing
+ " - cercare = to search
+ " - odiare = to hate
+ call writefile(['3', 'cantare/A', 'cercare/A', 'odiare/A'], 'XtestSFX.dic', 'D')
+
+ mkspell! XtestSFX-utf8.spl XtestSFX
+ set spell spelllang=XtestSFX-utf8.spl
+
+ " To sing, we're singing, we were singing.
+ call assert_equal(['', ''], spellbadword('cantare cantiamo cantavamo'))
+
+ " To search, we're searching, we were searching.
+ call assert_equal(['', ''], spellbadword('cercare cerchiamo cercavamo'))
+
+ " To hate, we hate, we were hating.
+ call assert_equal(['', ''], spellbadword('odiare odiamo odiavamo'))
+
+ for badword in ['canthiamo', 'cerciamo', 'cantarevamo', 'odiiamo']
+ call assert_equal([badword, 'bad'], spellbadword(badword))
+ endfor
+
+ call assert_equal(['cantiamo'], spellsuggest('canthiamo', 1))
+ call assert_equal(['cerchiamo'], spellsuggest('cerciamo', 1))
+ call assert_equal(['cantavamo'], spellsuggest('cantarevamo', 1))
+ call assert_equal(['odiamo'], spellsuggest('odiiamo', 1))
+
+ set spell& spelllang&
+ call delete('XtestSFX-utf8.spl')
+endfunc
+
+" When 'spellfile' is not set, adding a new good word will automatically set
+" the 'spellfile'
+func Test_init_spellfile()
+ let save_rtp = &rtp
+ let save_encoding = &encoding
+ call mkdir('Xrtp/spell', 'pR')
+ call writefile(['vim'], 'Xrtp/spell/Xtest.dic')
+ silent mkspell Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest.dic
+ set runtimepath=./Xrtp
+ set spelllang=Xtest
+ set spell
+ silent spellgood abc
+ call assert_equal('./Xrtp/spell/Xtest.utf-8.add', &spellfile)
+ call assert_equal(['abc'], readfile('Xrtp/spell/Xtest.utf-8.add'))
+ call assert_true(filereadable('Xrtp/spell/Xtest.utf-8.spl'))
+
+ set spell& spelllang& spellfile&
+ let &encoding = save_encoding
+ let &rtp = save_rtp
+ %bw!
+endfunc
+
+" Test for the 'mkspellmem' option
+func Test_mkspellmem_opt()
+ call assert_fails('set mkspellmem=1000', 'E474:')
+ call assert_fails('set mkspellmem=1000,', 'E474:')
+ call assert_fails('set mkspellmem=1000,50', 'E474:')
+ call assert_fails('set mkspellmem=1000,50,', 'E474:')
+ call assert_fails('set mkspellmem=1000,50,10,', 'E474:')
+ call assert_fails('set mkspellmem=1000,50,0', 'E474:')
+endfunc
+
+" 'spellfile' accepts '@' on top of 'isfname'.
+def Test_spellfile_allow_at_character()
+ mkdir('Xtest/the foo@bar,dir', 'p')
+ &spellfile = './Xtest/the foo@bar,dir/Xspellfile.add'
+ &spellfile = ''
+ delete('Xtest', 'rf')
+enddef
+
+" this was using a NULL pointer
+func Test_mkspell_empty_dic()
+ call writefile(['1'], 'XtestEmpty.dic', 'D')
+ call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'XtestEmpty.aff', 'D')
+ mkspell! XtestEmpty.spl XtestEmpty
+
+ call delete('XtestEmpty.spl')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_startup.vim b/src/testdir/test_startup.vim
new file mode 100644
index 0000000..9495533
--- /dev/null
+++ b/src/testdir/test_startup.vim
@@ -0,0 +1,1355 @@
+" Tests for startup.
+
+source shared.vim
+source screendump.vim
+source term_util.vim
+source check.vim
+
+" Check that loading startup.vim works.
+func Test_startup_script()
+ set compatible
+ source $VIMRUNTIME/defaults.vim
+
+ call assert_equal(0, &compatible)
+ " Restore some options, so that the following tests doesn't break
+ set nomore
+ set noshowmode
+endfunc
+
+" Verify the order in which plugins are loaded:
+" 1. plugins in non-after directories
+" 2. packages
+" 3. plugins in after directories
+func Test_after_comes_later()
+ CheckFeature packages
+ let before =<< trim [CODE]
+ set nocp viminfo+=nviminfo
+ set guioptions+=M
+ let $HOME = "/does/not/exist"
+ set loadplugins
+ set rtp=Xhere,Xafter,Xanother
+ set packpath=Xhere,Xafter
+ set nomore
+ let g:sequence = ""
+ [CODE]
+
+ let after =<< trim [CODE]
+ redir! > Xtestout
+ scriptnames
+ redir END
+ redir! > Xsequence
+ echo g:sequence
+ redir END
+ quit
+ [CODE]
+
+ call mkdir('Xhere/plugin', 'pR')
+ call writefile(['let g:sequence .= "here "'], 'Xhere/plugin/here.vim')
+ call mkdir('Xanother/plugin', 'pR')
+ call writefile(['let g:sequence .= "another "'], 'Xanother/plugin/another.vim')
+ call mkdir('Xhere/pack/foo/start/foobar/plugin', 'p')
+ call writefile(['let g:sequence .= "pack "'], 'Xhere/pack/foo/start/foobar/plugin/foo.vim')
+
+ call mkdir('Xafter/plugin', 'pR')
+ call writefile(['let g:sequence .= "after "'], 'Xafter/plugin/later.vim')
+
+ if RunVim(before, after, '')
+
+ let lines = readfile('Xtestout')
+ let expected = ['Xbefore.vim', 'here.vim', 'another.vim', 'foo.vim', 'later.vim', 'Xafter.vim']
+ let found = []
+ for line in lines
+ for one in expected
+ if line =~ one
+ call add(found, one)
+ endif
+ endfor
+ endfor
+ call assert_equal(expected, found)
+ endif
+
+ call assert_equal('here another pack after', substitute(join(readfile('Xsequence', 1), ''), '\s\+$', '', ''))
+
+ call delete('Xtestout')
+ call delete('Xsequence')
+endfunc
+
+func Test_pack_in_rtp_when_plugins_run()
+ CheckFeature packages
+ let before =<< trim [CODE]
+ set nocp viminfo+=nviminfo
+ set guioptions+=M
+ let $HOME = "/does/not/exist"
+ set loadplugins
+ set rtp=Xhere
+ set packpath=Xhere
+ set nomore
+ [CODE]
+
+ let after = [
+ \ 'quit',
+ \ ]
+ call mkdir('Xhere/plugin', 'pR')
+ call writefile(['redir! > Xtestout', 'silent set runtimepath?', 'silent! call foo#Trigger()', 'redir END'], 'Xhere/plugin/here.vim')
+ call mkdir('Xhere/pack/foo/start/foobar/autoload', 'p')
+ call writefile(['function! foo#Trigger()', 'echo "autoloaded foo"', 'endfunction'], 'Xhere/pack/foo/start/foobar/autoload/foo.vim')
+
+ if RunVim(before, after, '')
+
+ let lines = filter(readfile('Xtestout'), '!empty(v:val)')
+ call assert_match('Xhere[/\\]pack[/\\]foo[/\\]start[/\\]foobar', get(lines, 0))
+ call assert_match('autoloaded foo', get(lines, 1))
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+func Test_help_arg()
+ " This does not work with a GUI-only binary, such as on MS-Windows.
+ CheckAnyOf Unix NotGui
+
+ if RunVim([], [], '--help >Xtestout')
+ let lines = readfile('Xtestout')
+ call assert_true(len(lines) > 20)
+ call assert_match('Vi IMproved', lines[0])
+
+ " check if couple of lines are there
+ let found = []
+ for line in lines
+ if line =~ '-R.*Readonly mode'
+ call add(found, 'Readonly mode')
+ endif
+ " Watch out for a second --version line in the Gnome version.
+ if line =~ '--version.*Print version information and exit'
+ call add(found, "--version")
+ endif
+ endfor
+ call assert_equal(['Readonly mode', '--version'], found)
+ endif
+ call delete('Xtestout')
+endfunc
+
+func Test_compatible_args()
+ let after =<< trim [CODE]
+ call writefile([string(&compatible)], "Xtestout")
+ set viminfo+=nviminfo
+ quit
+ [CODE]
+
+ if RunVim([], after, '-C')
+ let lines = readfile('Xtestout')
+ call assert_equal('1', lines[0])
+ endif
+
+ if RunVim([], after, '-N')
+ let lines = readfile('Xtestout')
+ call assert_equal('0', lines[0])
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+" Test the -o[N] and -O[N] arguments to open N windows split
+" horizontally or vertically.
+func Test_o_arg()
+ let after =<< trim [CODE]
+ set cpo&vim
+ call writefile([winnr("$"),
+ \ winheight(1), winheight(2), &lines,
+ \ winwidth(1), winwidth(2), &columns,
+ \ bufname(winbufnr(1)), bufname(winbufnr(2))],
+ \ "Xtestout")
+ qall
+ [CODE]
+
+ if RunVim([], after, '-o2')
+ " Open 2 windows split horizontally. Expect:
+ " - 2 windows
+ " - both windows should have the same or almost the same height
+ " - sum of both windows height (+ 3 for both statusline and Ex command)
+ " should be equal to the number of lines
+ " - both windows should have the same width which should be equal to the
+ " number of columns
+ " - buffer of both windows should have no name
+ let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
+ call assert_equal('2', wn)
+ call assert_inrange(0, 1, wh1 - wh2)
+ call assert_equal(string(wh1 + wh2 + 3), ln)
+ call assert_equal(ww1, ww2)
+ call assert_equal(ww1, cn)
+ call assert_equal('', bn1)
+ call assert_equal('', bn2)
+ endif
+
+ if RunVim([], after, '-o foo bar')
+ " Same expectations as for -o2 but buffer names should be foo and bar
+ let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
+ call assert_equal('2', wn)
+ call assert_inrange(0, 1, wh1 - wh2)
+ call assert_equal(string(wh1 + wh2 + 3), ln)
+ call assert_equal(ww1, ww2)
+ call assert_equal(ww1, cn)
+ call assert_equal('foo', bn1)
+ call assert_equal('bar', bn2)
+ endif
+
+ if RunVim([], after, '-O2')
+ " Open 2 windows split vertically. Expect:
+ " - 2 windows
+ " - both windows should have the same or almost the same width
+ " - sum of both windows width (+ 1 for the separator) should be equal to
+ " the number of columns
+ " - both windows should have the same height
+ " - window height (+ 2 for the statusline and Ex command) should be equal
+ " to the number of lines
+ " - buffer of both windows should have no name
+ let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
+ call assert_equal('2', wn)
+ call assert_inrange(0, 1, ww1 - ww2)
+ call assert_equal(string(ww1 + ww2 + 1), cn)
+ call assert_equal(wh1, wh2)
+ call assert_equal(string(wh1 + 2), ln)
+ call assert_equal('', bn1)
+ call assert_equal('', bn2)
+ endif
+
+ if RunVim([], after, '-O foo bar')
+ " Same expectations as for -O2 but buffer names should be foo and bar
+ let [wn, wh1, wh2, ln, ww1, ww2, cn, bn1, bn2] = readfile('Xtestout')
+ call assert_equal('2', wn)
+ call assert_inrange(0, 1, ww1 - ww2)
+ call assert_equal(string(ww1 + ww2 + 1), cn)
+ call assert_equal(wh1, wh2)
+ call assert_equal(string(wh1 + 2), ln)
+ call assert_equal('foo', bn1)
+ call assert_equal('bar', bn2)
+ endif
+ call delete('Xtestout')
+endfunc
+
+" Test the -p[N] argument to open N tabpages.
+func Test_p_arg()
+ let after =<< trim [CODE]
+ call writefile(split(execute("tabs"), "\n"), "Xtestout")
+ qall
+ [CODE]
+
+ if RunVim([], after, '-p2')
+ let lines = readfile('Xtestout')
+ call assert_equal(4, len(lines))
+ call assert_equal('Tab page 1', lines[0])
+ call assert_equal('> [No Name]', lines[1])
+ call assert_equal('Tab page 2', lines[2])
+ call assert_equal(' [No Name]', lines[3])
+ endif
+
+ if RunVim([], after, '-p foo bar')
+ let lines = readfile('Xtestout')
+ call assert_equal(4, len(lines))
+ call assert_equal('Tab page 1', lines[0])
+ call assert_equal('> foo', lines[1])
+ call assert_equal('Tab page 2', lines[2])
+ call assert_equal(' bar', lines[3])
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+" Test the -V[N] argument to set the 'verbose' option to [N]
+func Test_V_arg()
+ " Can't catch the output of gvim.
+ CheckNotGui
+
+ let out = system(GetVimCommand() . ' --clean -es -X -V0 -c "set verbose?" -cq')
+ call assert_equal(" verbose=0\n", out)
+
+ let out = system(GetVimCommand() . ' --clean -es -X -V2 -c "set verbose?" -cq')
+ call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline \\d\\+: sourcing \"[^\"]*runtime[\\/]filetype\.vim\".*\n", out)
+ call assert_match(" verbose=2\n", out)
+
+ let out = system(GetVimCommand() . ' --clean -es -X -V15 -c "set verbose?" -cq')
+ call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline 1: \" The default vimrc file\..* verbose=15\n", out)
+endfunc
+
+" Test that an error is shown when the defaults.vim file could not be read
+func Test_defaults_error()
+ " Can't catch the output of gvim.
+ CheckNotGui
+ CheckNotMSWindows
+ " For unknown reasons freeing all memory does not work here, even though
+ " EXITFREE is defined.
+ CheckNotAsan
+
+ let out = system('VIMRUNTIME=/tmp ' .. GetVimCommand() .. ' --clean -cq')
+ call assert_match("E1187: Failed to source defaults.vim", out)
+
+ let out = system('VIMRUNTIME=/tmp ' .. GetVimCommand() .. ' -u DEFAULTS -cq')
+ call assert_match("E1187: Failed to source defaults.vim", out)
+endfunc
+
+" Test the '-q [errorfile]' argument.
+func Test_q_arg()
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ /* some file with an error */
+ main() {
+ functionCall(arg; arg, arg);
+ return 666
+ }
+ END
+ call writefile(lines, 'Xbadfile.c', 'D')
+
+ let after =<< trim [CODE]
+ call writefile([&errorfile, string(getpos("."))], "XtestoutQarg")
+ copen
+ w >> XtestoutQarg
+ qall
+ [CODE]
+
+ " Test with default argument '-q'.
+ call assert_equal('errors.err', &errorfile)
+ call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'errors.err', 'D')
+ if RunVim([], after, '-q')
+ let lines = readfile('XtestoutQarg')
+ call assert_equal(['errors.err',
+ \ '[0, 4, 12, 0]',
+ \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
+ \ lines)
+ endif
+ call delete('XtestoutQarg')
+
+ " Test with explicit argument '-q XerrorsQarg' (with space).
+ call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'XerrorsQarg', 'D')
+ if RunVim([], after, '-q XerrorsQarg')
+ let lines = readfile('XtestoutQarg')
+ call assert_equal(['XerrorsQarg',
+ \ '[0, 4, 12, 0]',
+ \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
+ \ lines)
+ endif
+ call delete('XtestoutQarg')
+
+ " Test with explicit argument '-qXerrorsQarg' (without space).
+ if RunVim([], after, '-qXerrorsQarg')
+ let lines = readfile('XtestoutQarg')
+ call assert_equal(['XerrorsQarg',
+ \ '[0, 4, 12, 0]',
+ \ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
+ \ lines)
+ endif
+
+ " Test with a non-existing error file (exits with value 3)
+ let out = system(GetVimCommand() .. ' -q xyz.err')
+ call assert_equal(3, v:shell_error)
+
+ call delete('XtestoutQarg')
+endfunc
+
+" Test the -V[N]{filename} argument to set the 'verbose' option to N
+" and set 'verbosefile' to filename.
+func Test_V_file_arg()
+ if RunVim([], [], ' --clean -V2Xverbosefile -c "set verbose? verbosefile?" -cq')
+ let out = join(readfile('Xverbosefile'), "\n")
+ call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\n", out)
+ call assert_match("\n verbose=2\n", out)
+ call assert_match("\n verbosefile=Xverbosefile", out)
+ endif
+
+ call delete('Xverbosefile')
+endfunc
+
+" Test the -m, -M and -R arguments:
+" -m resets 'write'
+" -M resets 'modifiable' and 'write'
+" -R sets 'readonly'
+func Test_m_M_R()
+ let after =<< trim [CODE]
+ call writefile([&write, &modifiable, &readonly, &updatecount], "Xtestout")
+ qall
+ [CODE]
+
+ if RunVim([], after, '')
+ let lines = readfile('Xtestout')
+ call assert_equal(['1', '1', '0', '200'], lines)
+ endif
+ if RunVim([], after, '-m')
+ let lines = readfile('Xtestout')
+ call assert_equal(['0', '1', '0', '200'], lines)
+ endif
+ if RunVim([], after, '-M')
+ let lines = readfile('Xtestout')
+ call assert_equal(['0', '0', '0', '200'], lines)
+ endif
+ if RunVim([], after, '-R')
+ let lines = readfile('Xtestout')
+ call assert_equal(['1', '1', '1', '10000'], lines)
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+" Test the -A and -H arguments (Arabic and Hebrew modes).
+func Test_A_H_arg()
+ let after =<< trim [CODE]
+ call writefile([&rightleft, &arabic, &fkmap, &hkmap], "Xtestout")
+ qall
+ [CODE]
+
+ " Use silent Ex mode to avoid the hit-Enter prompt for the warning that
+ " 'encoding' is not utf-8.
+ if has('arabic') && &encoding == 'utf-8' && RunVim([], after, '-e -s -A')
+ let lines = readfile('Xtestout')
+ call assert_equal(['1', '1', '0', '0'], lines)
+ endif
+
+ if has('rightleft') && RunVim([], after, '-H')
+ let lines = readfile('Xtestout')
+ call assert_equal(['1', '0', '0', '1'], lines)
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+" Test the --echo-wid argument (for GTK GUI only).
+func Test_echo_wid()
+ CheckCanRunGui
+ CheckFeature gui_gtk
+
+ if RunVim([], [], '-g --echo-wid -cq >Xtest_echo_wid')
+ let lines = readfile('Xtest_echo_wid')
+ call assert_equal(1, len(lines))
+ call assert_match('^WID: \d\+$', lines[0])
+ endif
+
+ call delete('Xtest_echo_wid')
+endfunction
+
+" Test the -reverse and +reverse arguments (for GUI only).
+func Test_reverse()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif
+
+ let after =<< trim [CODE]
+ call writefile([&background], "Xtest_reverse")
+ qall
+ [CODE]
+ if RunVim([], after, '-f -g -reverse')
+ let lines = readfile('Xtest_reverse')
+ call assert_equal(['dark'], lines)
+ endif
+ if RunVim([], after, '-f -g +reverse')
+ let lines = readfile('Xtest_reverse')
+ call assert_equal(['light'], lines)
+ endif
+
+ call delete('Xtest_reverse')
+endfunc
+
+" Test the -background and -foreground arguments (for GUI only).
+func Test_background_foreground()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif
+
+ " Is there a better way to check the effect of -background & -foreground
+ " other than merely looking at &background (dark or light)?
+ let after =<< trim [CODE]
+ call writefile([&background], "Xtest_fg_bg")
+ qall
+ [CODE]
+ if RunVim([], after, '-f -g -background darkred -foreground yellow')
+ let lines = readfile('Xtest_fg_bg')
+ call assert_equal(['dark'], lines)
+ endif
+ if RunVim([], after, '-f -g -background ivory -foreground darkgreen')
+ let lines = readfile('Xtest_fg_bg')
+ call assert_equal(['light'], lines)
+ endif
+
+ call delete('Xtest_fg_bg')
+endfunc
+
+" Test the -font argument (for GUI only).
+func Test_font()
+ CheckCanRunGui
+ CheckNotMSWindows
+
+ if has('gui_gtk')
+ let font = 'Courier 14'
+ elseif has('gui_motif')
+ let font = '-misc-fixed-bold-*'
+ else
+ throw 'Skipped: test does not set a valid font for this GUI'
+ endif
+
+ let after =<< trim [CODE]
+ call writefile([&guifont], "Xtest_font")
+ qall
+ [CODE]
+
+ if RunVim([], after, '--nofork -g -font "' .. font .. '"')
+ let lines = readfile('Xtest_font')
+ call assert_equal([font], lines)
+ endif
+
+ call delete('Xtest_font')
+endfunc
+
+" Test the -geometry argument (for GUI only).
+func Test_geometry()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif
+
+ if has('gui_motif')
+ " FIXME: With GUI Motif the value of getwinposx(),
+ " getwinposy() and getwinpos() do not match exactly the
+ " value given in -geometry. Why?
+ " So only check &columns and &lines for those GUIs.
+ let after =<< trim [CODE]
+ call writefile([&columns, &lines], "Xtest_geometry")
+ qall
+ [CODE]
+ if RunVim([], after, '-f -g -geometry 31x13+41+43')
+ let lines = readfile('Xtest_geometry')
+ call assert_equal(['31', '13'], lines)
+ endif
+ else
+ let after =<< trim [CODE]
+ call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry")
+ qall
+ [CODE]
+ " Some window managers have a bar at the top that pushes windows down,
+ " need to use at least 130, let's do 150
+ if RunVim([], after, '-f -g -geometry 31x13+41+150')
+ let lines = readfile('Xtest_geometry')
+ " Depending on the GUI library and the windowing system the final size
+ " might be a bit different, allow for some tolerance. Tuned based on
+ " actual failures.
+ call assert_inrange(31, 35, str2nr(lines[0]))
+ call assert_equal('13', lines[1])
+ call assert_equal('41', lines[2])
+ call assert_equal('150', lines[3])
+ call assert_equal('[41, 150]', lines[4])
+ endif
+ endif
+
+ call delete('Xtest_geometry')
+endfunc
+
+" Test the -iconic argument (for GUI only).
+func Test_iconic()
+ CheckCanRunGui
+ CheckAnyOf Feature:gui_gtk Feature:gui_motif
+
+ call RunVim([], [], '-f -g -iconic -cq')
+
+ " TODO: currently only start vim iconified, but does not
+ " check that vim is iconified. How could this be checked?
+endfunc
+
+
+func Test_invalid_args()
+ " must be able to get the output of Vim.
+ CheckUnix
+ CheckNotGui
+
+ for opt in ['-Y', '--does-not-exist']
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Unknown option argument: "' .. opt .. '"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+
+ for opt in ['-c', '-i', '-s', '-t', '-T', '-u', '-U', '-w', '-W', '--cmd', '--startuptime']
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "' .. opt .. '"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+
+ if has('clientserver')
+ for opt in ['--remote', '--remote-send', '--remote-silent', '--remote-expr',
+ \ '--remote-tab', '--remote-tab-wait',
+ \ '--remote-tab-wait-silent', '--remote-tab-silent',
+ \ '--remote-wait', '--remote-wait-silent',
+ \ '--servername',
+ \ ]
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "' .. opt .. '"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+ endif
+
+ if has('gui_gtk')
+ let out = split(system(GetVimCommand() .. ' --display'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "--display"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endif
+
+ if has('xterm_clipboard')
+ let out = split(system(GetVimCommand() .. ' -display'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "-display"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endif
+
+ let out = split(system(GetVimCommand() .. ' -ix'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Garbage after option argument: "-ix"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+
+ let out = split(system(GetVimCommand() .. ' - xxx'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Too many edit arguments: "xxx"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+
+ if has('quickfix')
+ " Detect invalid repeated arguments '-t foo -t foo', '-q foo -q foo'.
+ for opt in ['-t', '-q']
+ let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Too many edit arguments: "' .. opt .. '"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+ endif
+
+ for opt in [' -cq', ' --cmd q', ' +', ' -S foo']
+ let out = split(system(GetVimCommand() .. repeat(opt, 11)), "\n")
+ call assert_equal(1, v:shell_error)
+ " FIXME: The error message given by Vim is not ideal in case of repeated
+ " -S foo since it does not mention -S.
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Too many "+command", "-c command" or "--cmd command" arguments', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+
+ if has('gui_gtk')
+ let out = split(system(GetVimCommand() .. ' --socketid'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "--socketid"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+
+ for opt in ['--socketid x', '--socketid 0xg']
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Invalid argument for: "--socketid"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+
+ endif
+endfunc
+
+func Test_file_args()
+ let after =<< trim [CODE]
+ call writefile(argv(), "Xtestout")
+ qall
+ [CODE]
+
+ if RunVim([], after, '')
+ let lines = readfile('Xtestout')
+ call assert_equal(0, len(lines))
+ endif
+
+ if RunVim([], after, 'one')
+ let lines = readfile('Xtestout')
+ call assert_equal(1, len(lines))
+ call assert_equal('one', lines[0])
+ endif
+
+ if RunVim([], after, 'one two three')
+ let lines = readfile('Xtestout')
+ call assert_equal(3, len(lines))
+ call assert_equal('one', lines[0])
+ call assert_equal('two', lines[1])
+ call assert_equal('three', lines[2])
+ endif
+
+ if RunVim([], after, 'one -c echo two')
+ let lines = readfile('Xtestout')
+ call assert_equal(2, len(lines))
+ call assert_equal('one', lines[0])
+ call assert_equal('two', lines[1])
+ endif
+
+ if RunVim([], after, 'one -- -c echo two')
+ let lines = readfile('Xtestout')
+ call assert_equal(4, len(lines))
+ call assert_equal('one', lines[0])
+ call assert_equal('-c', lines[1])
+ call assert_equal('echo', lines[2])
+ call assert_equal('two', lines[3])
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+func Test_startuptime()
+ CheckFeature startuptime
+ let after = ['qall']
+ if RunVim([], after, '--startuptime Xtestout one')
+ let lines = readfile('Xtestout')
+ let expected = ['--- VIM STARTING ---', 'parsing arguments',
+ \ 'shell init', 'inits 3', 'start termcap', 'opening buffers']
+ let found = []
+ for line in lines
+ for exp in expected
+ if line =~ exp
+ call add(found, exp)
+ endif
+ endfor
+ endfor
+ call assert_equal(expected, found)
+ endif
+ call delete('Xtestout')
+endfunc
+
+func Test_log()
+ CheckFeature channel
+
+ call assert_false(filereadable('Xlogfile'))
+ let after = ['qall']
+ if RunVim([], after, '--log Xlogfile')
+ call assert_equal(1, readfile('Xlogfile')
+ \ ->filter({i, l -> l =~ '==== start log session'})
+ \ ->len())
+ " second time appends to the log
+ if RunVim([], after, '--log Xlogfile')
+ call assert_equal(2, readfile('Xlogfile')
+ \ ->filter({i, l -> l =~ '==== start log session'})
+ \ ->len())
+ endif
+ endif
+ call delete('Xlogfile')
+endfunc
+
+func Test_read_stdin()
+ let after =<< trim [CODE]
+ write Xtestout
+ quit!
+ [CODE]
+
+ if RunVimPiped([], after, '-', 'echo something | ')
+ let lines = readfile('Xtestout')
+ " MS-Windows adds a space after the word
+ call assert_equal(['something'], split(lines[0]))
+ endif
+ call delete('Xtestout')
+endfunc
+
+func Test_progpath()
+ " Tests normally run with "./vim" or "../vim", these must have been expanded
+ " to a full path.
+ if has('unix')
+ call assert_equal('/', v:progpath[0])
+ elseif has('win32')
+ call assert_equal(':', v:progpath[1])
+ call assert_match('[/\\]', v:progpath[2])
+ endif
+
+ " Only expect "vim" to appear in v:progname.
+ call assert_match('vim\c', v:progname)
+endfunc
+
+func Test_silent_ex_mode()
+ " must be able to get the output of Vim.
+ CheckUnix
+ CheckNotGui
+
+ " This caused an ml_get error.
+ let out = system(GetVimCommand() . '-u NONE -es -c''set verbose=1|h|exe "%norm\<c-y>\<c-d>"'' -c cq')
+ call assert_notmatch('E315:', out)
+endfunc
+
+func Test_default_term()
+ " must be able to get the output of Vim.
+ CheckUnix
+ CheckNotGui
+
+ let save_term = $TERM
+ let $TERM = 'unknownxxx'
+ let out = system(GetVimCommand() . ' -c''set term'' -c cq')
+ call assert_match("defaulting to 'ansi'", out)
+ let $TERM = save_term
+endfunc
+
+func Test_zzz_startinsert()
+ " Test :startinsert
+ call writefile(['123456'], 'Xtestout', 'D')
+ let after =<< trim [CODE]
+ :startinsert
+ call feedkeys("foobar\<c-o>:wq\<cr>","t")
+ [CODE]
+
+ if RunVim([], after, 'Xtestout')
+ let lines = readfile('Xtestout')
+ call assert_equal(['foobar123456'], lines)
+ endif
+ " Test :startinsert!
+ call writefile(['123456'], 'Xtestout')
+ let after =<< trim [CODE]
+ :startinsert!
+ call feedkeys("foobar\<c-o>:wq\<cr>","t")
+ [CODE]
+
+ if RunVim([], after, 'Xtestout')
+ let lines = readfile('Xtestout')
+ call assert_equal(['123456foobar'], lines)
+ endif
+endfunc
+
+func Test_issue_3969()
+ " Can't catch the output of gvim.
+ CheckNotGui
+
+ " Check that message is not truncated.
+ let out = system(GetVimCommand() . ' -es -X -V1 -c "echon ''hello''" -cq')
+ call assert_equal('hello', out)
+endfunc
+
+func Test_start_with_tabs()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('-p a b c', {})
+ call VerifyScreenDump(buf, 'Test_start_with_tabs', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_start_in_minimal_window()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('-c "set nomore"', {'cols': 12, 'rows': 2, 'keep_t_u7': 1})
+ call term_sendkeys(buf, "ahello\<Esc>")
+ call WaitForAssert({-> assert_match('^hello', term_getline(buf, 1))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_v_argv()
+ " Can't catch the output of gvim.
+ CheckNotGui
+
+ let out = system(GetVimCommand() . ' -es -V1 -X arg1 --cmd "echo v:argv" --cmd q')
+ let list = out->split("', '")
+ call assert_match('vim', list[0])
+ let idx = index(list, 'arg1')
+ call assert_true(idx > 2)
+ call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:])
+endfunc
+
+" Test for the "-r" recovery mode option
+func Test_r_arg()
+ " Can't catch the output of gvim.
+ CheckNotGui
+ CheckUnix
+ CheckEnglish
+ let cmd = GetVimCommand()
+ " There can be swap files anywhere, only check for the headers.
+ let expected =<< trim END
+ Swap files found:.*
+ In current directory:.*
+ In directory \~/tmp:.*
+ In directory /var/tmp:.*
+ In directory /tmp:.*
+ END
+ call assert_match(join(expected, ""), system(cmd .. " -r")->substitute("[\r\n]\\+", '', ''))
+endfunc
+
+" Test for the '-t' option to jump to a tag
+func Test_t_arg()
+ let before =<< trim [CODE]
+ set tags=Xtags
+ [CODE]
+ let after =<< trim [CODE]
+ let s = bufname('') .. ':L' .. line('.') .. 'C' .. col('.')
+ call writefile([s], "Xtestout")
+ qall
+ [CODE]
+
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfile1\t/^ \\zsfirst$/",
+ \ "second\tXfile1\t/^ \\zssecond$/",
+ \ "third\tXfile1\t/^ \\zsthird$/"],
+ \ 'Xtags', 'D')
+ call writefile([' first', ' second', ' third'], 'Xfile1', 'D')
+
+ for t_arg in ['-t second', '-tsecond']
+ if RunVim(before, after, t_arg)
+ call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg)
+ call delete('Xtestout')
+ endif
+ endfor
+endfunc
+
+" Test the '-T' argument which sets the 'term' option.
+func Test_T_arg()
+ CheckNotGui
+ let after =<< trim [CODE]
+ call writefile([&term], "Xtest_T_arg")
+ qall
+ [CODE]
+
+ for t in ['builtin_dumb', 'builtin_ansi']
+ if RunVim([], after, '-T ' .. t)
+ let lines = readfile('Xtest_T_arg')
+ call assert_equal([t], lines)
+ endif
+ endfor
+
+ call delete('Xtest_T_arg')
+endfunc
+
+" Test the '-x' argument to read/write encrypted files.
+func Test_x_arg()
+ CheckRunVimInTerminal
+ CheckFeature cryptv
+
+ " Create an encrypted file Xtest_x_arg.
+ let buf = RunVimInTerminal('-n -x Xtest_x_arg', #{rows: 10, wait_for_ruler: 0})
+ call WaitForAssert({-> assert_match('^Enter encryption key: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match('^Enter same key again: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match(' All$', term_getline(buf, 10))})
+ call term_sendkeys(buf, "itest\<Esc>:w\<Enter>")
+ call WaitForAssert({-> assert_match('"Xtest_x_arg" \[New\]\[blowfish2\] 1L, 5B written',
+ \ term_getline(buf, 10))})
+ call StopVimInTerminal(buf)
+
+ " Read the encrypted file and check that it contains the expected content "test"
+ let buf = RunVimInTerminal('-n -x Xtest_x_arg', #{rows: 10, wait_for_ruler: 0})
+ call WaitForAssert({-> assert_match('^Enter encryption key: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match('^Enter same key again: ', term_getline(buf, 10))})
+ call term_sendkeys(buf, "foo\n")
+ call WaitForAssert({-> assert_match('^test', term_getline(buf, 1))})
+ call StopVimInTerminal(buf)
+
+ call delete('Xtest_x_arg')
+endfunc
+
+" Test for entering the insert mode on startup
+func Test_start_insertmode()
+ let before =<< trim [CODE]
+ set insertmode
+ [CODE]
+ let after =<< trim [CODE]
+ call writefile(['insertmode=' .. &insertmode], 'Xtestout')
+ qall
+ [CODE]
+ if RunVim(before, after, '')
+ call assert_equal(['insertmode=1'], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for enabling the binary mode on startup
+func Test_b_arg()
+ let after =<< trim [CODE]
+ call writefile(['binary=' .. &binary], 'Xtestout')
+ qall
+ [CODE]
+ if RunVim([], after, '-b')
+ call assert_equal(['binary=1'], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for enabling the lisp mode on startup
+func Test_l_arg()
+ let after =<< trim [CODE]
+ let s = 'lisp=' .. &lisp .. ', showmatch=' .. &showmatch
+ call writefile([s], 'Xtestout')
+ qall
+ [CODE]
+ if RunVim([], after, '-l')
+ call assert_equal(['lisp=1, showmatch=1'], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for specifying a non-existing vimrc file using "-u"
+func Test_missing_vimrc()
+ CheckRunVimInTerminal
+ let after =<< trim [CODE]
+ call assert_match('^E282:', v:errmsg)
+ call writefile(v:errors, 'Xtestout')
+ [CODE]
+ call writefile(after, 'Xafter', 'D')
+
+ let cmd = GetVimCommandCleanTerm() . ' -u Xvimrc_missing -S Xafter'
+ let buf = term_start(cmd, {'term_rows' : 10})
+ call WaitForAssert({-> assert_equal("running", term_getstatus(buf))})
+ call TermWait(buf)
+ call term_sendkeys(buf, "\n:")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match(':', term_getline(buf, 10))})
+ call StopVimInTerminal(buf)
+ call assert_equal([], readfile('Xtestout'))
+
+ call delete('Xtestout')
+endfunc
+
+" Test for using the $VIMINIT environment variable
+func Test_VIMINIT()
+ let after =<< trim [CODE]
+ call assert_equal(1, exists('viminit_found'))
+ call assert_equal('yes', viminit_found)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ call writefile(after, 'Xafter', 'D')
+ let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
+ call setenv('VIMINIT', 'let viminit_found="yes"')
+ exe "silent !" . cmd
+ call assert_equal([], readfile('Xtestout'))
+
+ call delete('Xtestout')
+endfunc
+
+" Test for using the $EXINIT environment variable
+func Test_EXINIT()
+ let after =<< trim [CODE]
+ call assert_equal(1, exists('exinit_found'))
+ call assert_equal('yes', exinit_found)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ call writefile(after, 'Xafter', 'D')
+ let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
+ call setenv('EXINIT', 'let exinit_found="yes"')
+ exe "silent !" . cmd
+ call assert_equal([], readfile('Xtestout'))
+
+ call delete('Xtestout')
+endfunc
+
+" Test for using the 'exrc' option
+func Test_exrc()
+ let after =<< trim [CODE]
+ call assert_equal(1, &exrc)
+ call assert_equal(1, &secure)
+ call assert_equal(37, exrc_found)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ call mkdir('Xrcdir', 'R')
+ call writefile(['let exrc_found=37'], 'Xrcdir/.exrc')
+ call writefile(after, 'Xrcdir/Xafter')
+ let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "cd Xrcdir" --cmd "set enc=utf8 exrc secure"'
+ exe "silent !" . cmd
+ call assert_equal([], readfile('Xrcdir/Xtestout'))
+endfunc
+
+" Test for starting Vim with a non-terminal as input/output
+func Test_io_not_a_terminal()
+ " Can't catch the output of gvim.
+ CheckNotGui
+ CheckUnix
+ CheckEnglish
+ let l = systemlist(GetVimProg() .. ' --ttyfail')
+ call assert_equal(['Vim: Warning: Output is not to a terminal',
+ \ 'Vim: Warning: Input is not from a terminal'], l)
+endfunc
+
+" Test for --not-a-term avoiding escape codes.
+func Test_not_a_term()
+ CheckUnix
+ CheckNotGui
+
+ if &shellredir =~ '%s'
+ let redir = printf(&shellredir, 'Xvimout')
+ else
+ let redir = &shellredir .. ' Xvimout'
+ endif
+
+ " Without --not-a-term there are a few escape sequences.
+ " This will take 2 seconds because of the missing --not-a-term
+ let cmd = GetVimProg() .. ' --cmd quit ' .. redir
+ exe "silent !" . cmd
+ call assert_match("\<Esc>", readfile('Xvimout')->join())
+ call delete('Xvimout')
+
+ " With --not-a-term there are no escape sequences.
+ let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir
+ exe "silent !" . cmd
+ call assert_notmatch("\<Esc>", readfile('Xvimout')->join())
+ call delete('Xvimout')
+endfunc
+
+" Test quitting with CTRL-C when output is redirected.
+func Test_redirect_Ctrl_C()
+ CheckUnix
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ let buf = Run_shell_in_terminal({})
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, GetVimProg() .. " | grep word\<CR>")
+ call WaitForAssert({-> assert_match("Output is not to a terminal", getline(1, 4)->join())})
+ " wait for the hard coded delay, otherwise the CTRL-C interrupts startup
+ sleep 2
+ call term_sendkeys(buf, "\<C-C>")
+ sleep 100m
+ call term_sendkeys(buf, "exit\<CR>")
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+
+ exe buf . 'bwipe!'
+ unlet g:job
+endfunc
+
+
+" Test for the "-w scriptout" argument
+func Test_w_arg()
+ " Can't catch the output of gvim.
+ CheckNotGui
+
+ call writefile(["iVim Editor\<Esc>:q!\<CR>"], 'Xscriptin', 'bD')
+ if RunVim([], [], '-s Xscriptin -w Xscriptout')
+ call assert_equal(["iVim Editor\e:q!\r"], readfile('Xscriptout'))
+ call delete('Xscriptout')
+ endif
+ call delete('Xscriptin')
+
+ " Test for failing to open the script output file. This test works only when
+ " the language is English.
+ if v:lang == "C" || v:lang =~ '^[Ee]n'
+ call mkdir("Xargdir")
+ let m = system(GetVimCommand() .. " -w Xargdir")
+ call assert_equal("Cannot open for script output: \"Xargdir\"\n", m)
+ call delete("Xargdir", 'rf')
+ endif
+
+ " A number argument sets the 'window' option
+ call writefile(["iwindow \<C-R>=&window\<CR>\<Esc>:wq! Xresult\<CR>"], 'Xscriptin', 'b')
+ for w_arg in ['-w 17', '-w17']
+ if RunVim([], [], '-s Xscriptin ' .. w_arg)
+ call assert_equal(["window 17"], readfile('Xresult'), w_arg)
+ call delete('Xresult')
+ endif
+ endfor
+endfunc
+
+" Test for the "-s scriptin" argument
+func Test_s_arg()
+ " Can't catch the output of gvim.
+ CheckNotGui
+ CheckEnglish
+ " Test for failing to open the script input file.
+ let m = system(GetVimCommand() .. " -s abcxyz")
+ call assert_equal("Cannot open for reading: \"abcxyz\"\n", m)
+
+ call writefile([], 'Xinput', 'D')
+ let m = system(GetVimCommand() .. " -s Xinput -s Xinput")
+ call assert_equal("Attempt to open script file again: \"-s Xinput\"\n", m)
+endfunc
+
+" Test for the "-n" (no swap file) argument
+func Test_n_arg()
+ let after =<< trim [CODE]
+ call assert_equal(0, &updatecount)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ if RunVim([], after, '-n')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for the "-h" (help) argument
+func Test_h_arg()
+ " Can't catch the output of gvim.
+ CheckNotGui
+ let l = systemlist(GetVimProg() .. ' -h')
+ call assert_match('^VIM - Vi IMproved', l[0])
+ let l = systemlist(GetVimProg() .. ' -?')
+ call assert_match('^VIM - Vi IMproved', l[0])
+endfunc
+
+" Test for the "-F" (farsi) argument
+func Test_F_arg()
+ " Can't catch the output of gvim.
+ CheckNotGui
+ let l = systemlist(GetVimProg() .. ' -F')
+ call assert_match('^E27:', l[0])
+endfunc
+
+" Test for the "-E" (improved Ex mode) argument
+func Test_E_arg()
+ let after =<< trim [CODE]
+ call assert_equal('cv', mode(1))
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ if RunVim([], after, '-E')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for the "-D" (debugger) argument
+func Test_D_arg()
+ CheckRunVimInTerminal
+
+ let cmd = GetVimCommandCleanTerm() .. ' -D'
+ let buf = term_start(cmd, {'term_rows' : 10})
+ call WaitForAssert({-> assert_equal("running", term_getstatus(buf))})
+
+ call WaitForAssert({-> assert_equal('Entering Debug mode. Type "cont" to continue.',
+ \ term_getline(buf, 7))})
+ call WaitForAssert({-> assert_equal('>', term_getline(buf, 10))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for too many edit argument errors
+func Test_too_many_edit_args()
+ " Can't catch the output of gvim.
+ CheckNotGui
+ CheckEnglish
+ let l = systemlist(GetVimProg() .. ' - -')
+ call assert_match('^Too many edit arguments: "-"', l[1])
+endfunc
+
+" Test starting vim with various names: vim, ex, view, evim, etc.
+func Test_progname()
+ CheckUnix
+
+ call mkdir('Xprogname', 'pD')
+ call writefile(['silent !date',
+ \ 'call writefile([mode(1), '
+ \ .. '&insertmode, &diff, &readonly, &updatecount, '
+ \ .. 'join(split(execute("message"), "\n")[1:])], "Xprogname_out")',
+ \ 'qall'], 'Xprogname_after')
+
+ " +---------------------------------------------- progname
+ " | +--------------------------------- mode(1)
+ " | | +--------------------------- &insertmode
+ " | | | +---------------------- &diff
+ " | | | | +----------------- &readonly
+ " | | | | | +-------- &updatecount
+ " | | | | | | +--- :messages
+ " | | | | | | |
+ let expectations = {
+ \ 'vim': ['n', '0', '0', '0', '200', ''],
+ \ 'gvim': ['n', '0', '0', '0', '200', ''],
+ \ 'ex': ['ce', '0', '0', '0', '200', ''],
+ \ 'exim': ['cv', '0', '0', '0', '200', ''],
+ \ 'view': ['n', '0', '0', '1', '10000', ''],
+ \ 'gview': ['n', '0', '0', '1', '10000', ''],
+ \ 'evim': ['n', '1', '0', '0', '200', ''],
+ \ 'eview': ['n', '1', '0', '1', '10000', ''],
+ \ 'rvim': ['n', '0', '0', '0', '200', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
+ \ 'rgvim': ['n', '0', '0', '0', '200', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
+ \ 'rview': ['n', '0', '0', '1', '10000', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
+ \ 'rgview': ['n', '0', '0', '1', '10000', 'line 1: E145: Shell commands and some functionality not allowed in rvim'],
+ \ 'vimdiff': ['n', '0', '1', '0', '200', ''],
+ \ 'gvimdiff': ['n', '0', '1', '0', '200', '']}
+
+ let prognames = ['vim', 'gvim', 'ex', 'exim', 'view', 'gview',
+ \ 'evim', 'eview', 'rvim', 'rgvim', 'rview', 'rgview',
+ \ 'vimdiff', 'gvimdiff']
+
+ for progname in prognames
+ let run_with_gui = (progname =~# 'g') || (has('gui') && (progname ==# 'evim' || progname ==# 'eview'))
+
+ if empty($DISPLAY) && run_with_gui
+ " Can't run gvim, gview (etc.) if $DISPLAY is not setup.
+ continue
+ endif
+
+ exe 'silent !ln -s -f ' ..exepath(GetVimProg()) .. ' Xprogname/' .. progname
+
+ let stdout_stderr = ''
+ if progname =~# 'g'
+ let stdout_stderr = system('Xprogname/'..progname..' -f --clean --not-a-term -S Xprogname_after')
+ else
+ exe 'sil !Xprogname/'..progname..' -f --clean --not-a-term -S Xprogname_after'
+ endif
+
+ if progname =~# 'g' && !has('gui')
+ call assert_equal("E25: GUI cannot be used: Not enabled at compile time\n", stdout_stderr, progname)
+ else
+ " GUI motif can output some warnings like this:
+ " Warning:
+ " Name: subMenu
+ " Class: XmCascadeButton
+ " Illegal mnemonic character; Could not convert X KEYSYM to a keycode
+ " So don't check that stderr is empty with GUI Motif.
+ if run_with_gui && !has('gui_motif')
+ call assert_equal('', stdout_stderr, progname)
+ endif
+ call assert_equal(expectations[progname], readfile('Xprogname_out'), progname)
+ endif
+
+ call delete('Xprogname/' .. progname)
+ call delete('Xprogname_out')
+ endfor
+
+ call delete('Xprogname_after')
+endfunc
+
+" Test for doing a write from .vimrc
+func Test_write_in_vimrc()
+ call writefile(['silent! write'], 'Xvimrc', 'D')
+ let after =<< trim [CODE]
+ call assert_match('E32: ', v:errmsg)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ if RunVim([], after, '-u Xvimrc')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+func Test_echo_true_in_cmd()
+ CheckNotGui
+
+ let lines =<< trim END
+ echo v:true
+ call writefile(['done'], 'Xresult')
+ quit
+ END
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--cmd "source Xscript"')
+ call assert_equal(['done'], readfile('Xresult'))
+ endif
+
+ call delete('Xresult')
+endfunc
+
+func Test_rename_buffer_on_startup()
+ CheckUnix
+
+ let lines =<< trim END
+ call writefile(['done'], 'Xresult')
+ qa!
+ END
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], "--clean -e -s --cmd 'file x|new|file x' --cmd 'so Xscript'")
+ call assert_equal(['done'], readfile('Xresult'))
+ endif
+
+ call delete('Xresult')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_startup_utf8.vim b/src/testdir/test_startup_utf8.vim
new file mode 100644
index 0000000..e8b99e7
--- /dev/null
+++ b/src/testdir/test_startup_utf8.vim
@@ -0,0 +1,81 @@
+" Tests for startup using utf-8.
+
+source check.vim
+source shared.vim
+source screendump.vim
+
+func Test_read_stdin_utf8()
+ let linesin = ['テスト', '€ÀÈÌÒÙ']
+ call writefile(linesin, 'Xtestin', 'D')
+ let before = [
+ \ 'set enc=utf-8',
+ \ 'set fencs=cp932,utf-8',
+ \ ]
+ let after = [
+ \ 'write ++enc=utf-8 Xtestout',
+ \ 'quit!',
+ \ ]
+ if has('win32')
+ let pipecmd = 'type Xtestin | '
+ else
+ let pipecmd = 'cat Xtestin | '
+ endif
+ if RunVimPiped(before, after, '-', pipecmd)
+ let lines = readfile('Xtestout')
+ call assert_equal(linesin, lines)
+ else
+ call assert_equal('', 'RunVimPiped failed.')
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+func Test_read_fifo_utf8()
+ CheckUnix
+ " Using bash/zsh's process substitution.
+ if executable('bash')
+ set shell=bash
+ elseif executable('zsh')
+ set shell=zsh
+ else
+ throw 'Skipped: bash or zsh is required'
+ endif
+ let linesin = ['テスト', '€ÀÈÌÒÙ']
+ call writefile(linesin, 'Xtestin', 'D')
+ let before = [
+ \ 'set enc=utf-8',
+ \ 'set fencs=cp932,utf-8',
+ \ ]
+ let after = [
+ \ 'write ++enc=utf-8 Xtestout',
+ \ 'quit!',
+ \ ]
+ if RunVim(before, after, '<(cat Xtestin)')
+ let lines = readfile('Xtestout')
+ call assert_equal(linesin, lines)
+ else
+ call assert_equal('', 'RunVim failed.')
+ endif
+
+ call delete('Xtestout')
+endfunc
+
+func Test_detect_ambiwidth()
+ CheckRunVimInTerminal
+
+ " Use the title termcap entries to output the escape sequence.
+ call writefile([
+ \ 'set enc=utf-8',
+ \ 'set ambiwidth=double',
+ \ 'call test_option_not_set("ambiwidth")',
+ \ 'redraw',
+ \ ], 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', #{keep_t_u7: 1})
+ call TermWait(buf)
+ call term_sendkeys(buf, "S\<C-R>=&ambiwidth\<CR>\<Esc>")
+ call WaitForAssert({-> assert_match('single', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_stat.vim b/src/testdir/test_stat.vim
new file mode 100644
index 0000000..ed123e4
--- /dev/null
+++ b/src/testdir/test_stat.vim
@@ -0,0 +1,224 @@
+" Tests for stat functions and checktime
+
+source check.vim
+
+func CheckFileTime(doSleep)
+ let fnames = ['Xtest1.tmp', 'Xtest2.tmp', 'Xtest3.tmp']
+ let times = []
+ let result = 0
+
+ " Use three files instead of localtim(), with a network filesystem the file
+ " times may differ at bit
+ let fl = ['Hello World!']
+ for fname in fnames
+ call writefile(fl, fname)
+ call add(times, fname->getftime())
+ if a:doSleep
+ sleep 1
+ endif
+ endfor
+
+ let time_correct = (times[0] <= times[1] && times[1] <= times[2])
+ if a:doSleep || time_correct
+ call assert_true(time_correct, printf('Expected %s <= %s <= %s', times[0], times[1], times[2]))
+ call assert_equal(strlen(fl[0] . "\n"), fnames[0]->getfsize())
+ call assert_equal('file', fnames[0]->getftype())
+ call assert_equal('rw-', getfperm(fnames[0])[0:2])
+ let result = 1
+ endif
+
+ for fname in fnames
+ call delete(fname)
+ endfor
+ return result
+endfunc
+
+func Test_existent_file()
+ " On some systems the file timestamp is rounded to a multiple of 2 seconds.
+ " We need to sleep to handle that, but that makes the test slow. First try
+ " without the sleep, and if it fails try again with the sleep.
+ if CheckFileTime(0) == 0
+ call CheckFileTime(1)
+ endif
+endfunc
+
+func Test_existent_directory()
+ let dname = '.'
+
+ call assert_equal(0, getfsize(dname))
+ call assert_equal('dir', getftype(dname))
+ call assert_equal('rwx', getfperm(dname)[0:2])
+endfunc
+
+func SleepForTimestamp()
+ " FAT has a granularity of 2 seconds, otherwise it's usually 1 second
+ if has('win32')
+ sleep 2
+ else
+ sleep 1
+ endif
+endfunc
+
+func Test_checktime()
+ let fname = 'Xtest.tmp'
+
+ let fl = ['Hello World!']
+ call writefile(fl, fname, 'D')
+ set autoread
+ exec 'e' fname
+ call SleepForTimestamp()
+ let fl = readfile(fname)
+ let fl[0] .= ' - checktime'
+ call writefile(fl, fname)
+ checktime
+ call assert_equal(fl[0], getline(1))
+endfunc
+
+func Test_checktime_fast()
+ CheckFeature nanotime
+
+ let fname = 'Xtest.tmp'
+
+ let fl = ['Hello World!']
+ call writefile(fl, fname, 'D')
+ set autoread
+ exec 'e' fname
+ let fl = readfile(fname)
+ let fl[0] .= ' - checktime'
+ call writefile(fl, fname)
+ checktime
+ call assert_equal(fl[0], getline(1))
+endfunc
+
+func Test_autoread_fast()
+ CheckFeature nanotime
+
+ " this is timing sensitive
+ let g:test_is_flaky = 1
+
+ new Xautoread
+ setlocal autoread
+ call setline(1, 'foo')
+ w!
+ sleep 10m
+ call writefile(['bar'], 'Xautoread', 'D')
+ sleep 10m
+ checktime
+ call assert_equal('bar', trim(getline(1)))
+endfunc
+
+func Test_autoread_file_deleted()
+ new Xautoread
+ set autoread
+ call setline(1, 'original')
+ w!
+
+ call SleepForTimestamp()
+ if has('win32')
+ silent !echo changed > Xautoread
+ else
+ silent !echo 'changed' > Xautoread
+ endif
+ checktime
+ call assert_equal('changed', trim(getline(1)))
+
+ call SleepForTimestamp()
+ messages clear
+ if has('win32')
+ silent !del Xautoread
+ else
+ silent !rm Xautoread
+ endif
+ checktime
+ call assert_match('E211:', execute('messages'))
+ call assert_equal('changed', trim(getline(1)))
+
+ call SleepForTimestamp()
+ if has('win32')
+ silent !echo recreated > Xautoread
+ else
+ silent !echo 'recreated' > Xautoread
+ endif
+ checktime
+ call assert_equal('recreated', trim(getline(1)))
+
+ call delete('Xautoread')
+ bwipe!
+endfunc
+
+
+func Test_nonexistent_file()
+ let fname = 'Xtest.tmp'
+
+ call delete(fname)
+ call assert_equal(-1, getftime(fname))
+ call assert_equal(-1, getfsize(fname))
+ call assert_equal('', getftype(fname))
+ call assert_equal('', getfperm(fname))
+endfunc
+
+func Test_getftype()
+ call assert_equal('file', getftype(v:progpath))
+ call assert_equal('dir', getftype('.'))
+
+ if !has('unix')
+ return
+ endif
+
+ silent !ln -s Xlinkfile Xlink
+ call assert_equal('link', getftype('Xlink'))
+ call delete('Xlink')
+
+ if executable('mkfifo')
+ silent !mkfifo Xfifo
+ call assert_equal('fifo', getftype('Xfifo'))
+ call delete('Xfifo')
+ endif
+
+ for cdevfile in systemlist('find /dev -type c -maxdepth 2 2>/dev/null')
+ " On Mac /def/fd/2 is found but the type is "fifo"
+ if cdevfile !~ '/dev/fd/'
+ let type = getftype(cdevfile)
+ " ignore empty result, can happen if the file disappeared
+ if type != ''
+ call assert_equal('cdev', type, 'for ' .. cdevfile)
+ endif
+ endif
+ endfor
+
+ for bdevfile in systemlist('find /dev -type b -maxdepth 2 2>/dev/null')
+ let type = getftype(bdevfile)
+ " ignore empty result, can happen if the file disappeared
+ if type != ''
+ call assert_equal('bdev', type, 'for ' .. bdevfile)
+ endif
+ endfor
+
+ " The /run/ directory typically contains socket files.
+ " If it does not, test won't fail but will not test socket files.
+ for socketfile in systemlist('find /run -type s -maxdepth 2 2>/dev/null')
+ let type = getftype(socketfile)
+ " ignore empty result, can happen if the file disappeared
+ if type != ''
+ call assert_equal('socket', type, 'for ' .. socketfile)
+ endif
+ endfor
+
+ " TODO: file type 'other' is not tested. How can we test it?
+endfunc
+
+func Test_win32_symlink_dir()
+ " On Windows, non-admin users cannot create symlinks.
+ " So we use an existing symlink for this test.
+ CheckMSWindows
+ " Check if 'C:\Users\All Users' is a symlink to a directory.
+ let res = system('dir C:\Users /a')
+ if match(res, '\C<SYMLINKD> *All Users') >= 0
+ " Get the filetype of the symlink.
+ call assert_equal('dir', getftype('C:\Users\All Users'))
+ else
+ throw 'Skipped: cannot find an existing symlink'
+ endif
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_statusline.vim b/src/testdir/test_statusline.vim
new file mode 100644
index 0000000..1239e4e
--- /dev/null
+++ b/src/testdir/test_statusline.vim
@@ -0,0 +1,613 @@
+" Test 'statusline'
+"
+" Not tested yet:
+" %N
+
+source view_util.vim
+source check.vim
+source screendump.vim
+
+func SetUp()
+ set laststatus=2
+endfunc
+
+func TearDown()
+ set laststatus&
+endfunc
+
+func s:get_statusline()
+ return ScreenLines(&lines - 1, &columns)[0]
+endfunc
+
+func StatuslineWithCaughtError()
+ let s:func_in_statusline_called = 1
+ try
+ call eval('unknown expression')
+ catch
+ endtry
+ return ''
+endfunc
+
+func StatuslineWithError()
+ let s:func_in_statusline_called = 1
+ call eval('unknown expression')
+ return ''
+endfunc
+
+" Function used to display syntax group.
+func SyntaxItem()
+ call assert_equal(s:expected_curbuf, g:actual_curbuf)
+ call assert_equal(s:expected_curwin, g:actual_curwin)
+ return synIDattr(synID(line("."), col("."),1), "name")
+endfunc
+
+func Test_caught_error_in_statusline()
+ let s:func_in_statusline_called = 0
+ let statusline = '%{StatuslineWithCaughtError()}'
+ let &statusline = statusline
+ redrawstatus
+ call assert_true(s:func_in_statusline_called)
+ call assert_equal(statusline, &statusline)
+ set statusline=
+endfunc
+
+func Test_statusline_will_be_disabled_with_error()
+ let s:func_in_statusline_called = 0
+ let statusline = '%{StatuslineWithError()}'
+ try
+ let &statusline = statusline
+ redrawstatus
+ catch
+ endtry
+ call assert_true(s:func_in_statusline_called)
+ call assert_equal('', &statusline)
+ set statusline=
+endfunc
+
+func Test_statusline()
+ CheckFeature quickfix
+
+ " %a: Argument list ({current} of {max})
+ set statusline=%a
+ call assert_match('^\s*$', s:get_statusline())
+ arglocal a1 a2
+ rewind
+ call assert_match('^ (1 of 2)\s*$', s:get_statusline())
+ next
+ call assert_match('^ (2 of 2)\s*$', s:get_statusline())
+ e Xstatusline
+ call assert_match('^ ((2) of 2)\s*$', s:get_statusline())
+
+ only
+ set splitbelow
+ call setline(1, range(1, 10000))
+
+ " %b: Value of character under cursor.
+ " %B: As above, in hexadecimal.
+ call cursor(9000, 1)
+ set statusline=%b,%B
+ call assert_match('^57,39\s*$', s:get_statusline())
+
+ " %o: Byte number in file of byte under cursor, first byte is 1.
+ " %O: As above, in hexadecimal.
+ set statusline=%o,%O
+ set fileformat=dos
+ call assert_match('^52888,CE98\s*$', s:get_statusline())
+ set fileformat=mac
+ call assert_match('^43889,AB71\s*$', s:get_statusline())
+ set fileformat=unix
+ call assert_match('^43889,AB71\s*$', s:get_statusline())
+ set fileformat&
+
+ " %f: Path to the file in the buffer, as typed or relative to current dir.
+ set statusline=%f
+ call assert_match('^Xstatusline\s*$', s:get_statusline())
+
+ " %F: Full path to the file in the buffer.
+ set statusline=%F
+ call assert_match('/testdir/Xstatusline\s*$', s:get_statusline())
+
+ " Test for min and max width with %(. For some reason, if this test is moved
+ " after the below test for the help buffer flag, then the code to truncate
+ " the string is not executed.
+ set statusline=%015(%f%)
+ call assert_match('^ Xstatusline\s*$', s:get_statusline())
+ set statusline=%.6(%f%)
+ call assert_match('^<sline\s*$', s:get_statusline())
+ set statusline=%14f
+ call assert_match('^ Xstatusline\s*$', s:get_statusline())
+ set statusline=%.4L
+ call assert_match('^10>3\s*$', s:get_statusline())
+
+ " %h: Help buffer flag, text is "[help]".
+ " %H: Help buffer flag, text is ",HLP".
+ set statusline=%h,%H
+ call assert_match('^,\s*$', s:get_statusline())
+ help
+ call assert_match('^\[Help\],HLP\s*$', s:get_statusline())
+ helpclose
+
+ " %k: Value of "b:keymap_name" or 'keymap'
+ " when :lmap mappings are being used: <keymap>"
+ set statusline=%k
+ if has('keymap')
+ set keymap=esperanto
+ call assert_match('^<Eo>\s*$', s:get_statusline())
+ set keymap&
+ else
+ call assert_match('^\s*$', s:get_statusline())
+ endif
+
+ " %l: Line number.
+ " %L: Number of line in buffer.
+ " %c: Column number.
+ set statusline=%l/%L,%c
+ call assert_match('^9000/10000,1\s*$', s:get_statusline())
+
+ " %m: Modified flag, text is "[+]", "[-]" if 'modifiable' is off.
+ " %M: Modified flag, text is ",+" or ",-".
+ set statusline=%m%M
+ call assert_match('^\[+\],+\s*$', s:get_statusline())
+ set nomodifiable
+ call assert_match('^\[+-\],+-\s*$', s:get_statusline())
+ write
+ call assert_match('^\[-\],-\s*$', s:get_statusline())
+ set modifiable&
+ call assert_match('^\s*$', s:get_statusline())
+
+ " %n: Buffer number.
+ set statusline=%n
+ call assert_match('^'.bufnr('%').'\s*$', s:get_statusline())
+
+ " %p: Percentage through file in lines as in CTRL-G.
+ " %P: Percentage through file of displayed window.
+ set statusline=%p,%P
+ 0
+ call assert_match('^0,Top\s*$', s:get_statusline())
+ norm G
+ call assert_match('^100,Bot\s*$', s:get_statusline())
+ 9000
+ " Don't check the exact percentage as it depends on the window size
+ call assert_match('^90,\(Top\|Bot\|\d\+%\)\s*$', s:get_statusline())
+
+ " %q: "[Quickfix List]", "[Location List]" or empty.
+ set statusline=%q
+ call assert_match('^\s*$', s:get_statusline())
+ copen
+ call assert_match('^\[Quickfix List\]\s*$', s:get_statusline())
+ cclose
+ lexpr getline(1, 2)
+ lopen
+ call assert_match('^\[Location List\]\s*$', s:get_statusline())
+ lclose
+
+ " %r: Readonly flag, text is "[RO]".
+ " %R: Readonly flag, text is ",RO".
+ set statusline=%r,%R
+ call assert_match('^,\s*$', s:get_statusline())
+ help
+ call assert_match('^\[RO\],RO\s*$', s:get_statusline())
+ helpclose
+
+ " %t: File name (tail) of file in the buffer.
+ set statusline=%t
+ call assert_match('^Xstatusline\s*$', s:get_statusline())
+
+ " %v: Virtual column number.
+ " %V: Virtual column number as -{num}. Not displayed if equal to 'c'.
+ call cursor(9000, 2)
+ set statusline=%v,%V
+ call assert_match('^2,\s*$', s:get_statusline())
+ set virtualedit=all
+ norm 10|
+ call assert_match('^10,-10\s*$', s:get_statusline())
+ set list
+ call assert_match('^10,-10\s*$', s:get_statusline())
+ set virtualedit&
+ exe "norm A\<Tab>\<Tab>a\<Esc>"
+ " In list mode a <Tab> is shown as "^I", which is 2-wide.
+ call assert_match('^9,-9\s*$', s:get_statusline())
+ set list&
+ " Now the second <Tab> ends at the 16th screen column.
+ call assert_match('^17,-17\s*$', s:get_statusline())
+ undo
+
+ " %w: Preview window flag, text is "[Preview]".
+ " %W: Preview window flag, text is ",PRV".
+ set statusline=%w%W
+ call assert_match('^\s*$', s:get_statusline())
+ pedit
+ wincmd j
+ call assert_match('^\[Preview\],PRV\s*$', s:get_statusline())
+ pclose
+
+ " %y: Type of file in the buffer, e.g., "[vim]". See 'filetype'.
+ " %Y: Type of file in the buffer, e.g., ",VIM". See 'filetype'.
+ set statusline=%y\ %Y
+ call assert_match('^\s*$', s:get_statusline())
+ setfiletype vim
+ call assert_match('^\[vim\] VIM\s*$', s:get_statusline())
+
+ " %=: Separation point between left and right aligned items.
+ set statusline=foo%=bar
+ call assert_match('^foo\s\+bar\s*$', s:get_statusline())
+ set statusline=foo%=bar%=baz
+ call assert_match('^foo\s\+bar\s\+baz\s*$', s:get_statusline())
+ set statusline=foo%=bar%=baz%=qux
+ call assert_match('^foo\s\+bar\s\+baz\s\+qux\s*$', s:get_statusline())
+
+ " Test min/max width, leading zeroes, left/right justify.
+ set statusline=%04B
+ call cursor(9000, 1)
+ call assert_match('^0039\s*$', s:get_statusline())
+ set statusline=#%4B#
+ call assert_match('^# 39#\s*$', s:get_statusline())
+ set statusline=#%-4B#
+ call assert_match('^#39 #\s*$', s:get_statusline())
+ set statusline=%.6f
+ call assert_match('^<sline\s*$', s:get_statusline())
+
+ " %<: Where to truncate.
+ " First check with when %< should not truncate with many columns
+ exe 'set statusline=a%<b' . repeat('c', &columns - 3) . 'd'
+ call assert_match('^abc\+d$', s:get_statusline())
+ exe 'set statusline=a' . repeat('b', &columns - 2) . '%<c'
+ call assert_match('^ab\+c$', s:get_statusline())
+ " Then check when %< should truncate when there with too few columns.
+ exe 'set statusline=a%<b' . repeat('c', &columns - 2) . 'd'
+ call assert_match('^a<c\+d$', s:get_statusline())
+ exe 'set statusline=a' . repeat('b', &columns - 1) . '%<c'
+ call assert_match('^ab\+>$', s:get_statusline())
+
+ "%{: Evaluate expression between '%{' and '}' and substitute result.
+ syntax on
+ let s:expected_curbuf = string(bufnr(''))
+ let s:expected_curwin = string(win_getid())
+ set statusline=%{SyntaxItem()}
+ call assert_match('^vimNumber\s*$', s:get_statusline())
+ s/^/"/
+ call assert_match('^vimLineComment\s*$', s:get_statusline())
+ syntax off
+
+ "%{%expr%}: evaluates expressions present in result of expr
+ func! Inner_eval()
+ return '%n some other text'
+ endfunc
+ func! Outer_eval()
+ return 'some text %{%Inner_eval()%}'
+ endfunc
+ set statusline=%{%Outer_eval()%}
+ call assert_match('^some text ' . bufnr() . ' some other text\s*$', s:get_statusline())
+ delfunc Inner_eval
+ delfunc Outer_eval
+
+ "%{%expr%}: Doesn't get stuck in recursion
+ func! Recurse_eval()
+ return '%{%Recurse_eval()%}'
+ endfunc
+ set statusline=%{%Recurse_eval()%}
+ call assert_match('^%{%Recurse_eval()%}\s*$', s:get_statusline())
+ delfunc Recurse_eval
+
+ "%(: Start of item group.
+ set statusline=ab%(cd%q%)de
+ call assert_match('^abde\s*$', s:get_statusline())
+ copen
+ call assert_match('^abcd\[Quickfix List]de\s*$', s:get_statusline())
+ cclose
+
+ " %#: Set highlight group. The name must follow and then a # again.
+ set statusline=ab%#Todo#cd%#Error#ef
+ call assert_match('^abcdef\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 3)
+ let sa3=screenattr(&lines - 1, 5)
+ call assert_notequal(sa1, sa2)
+ call assert_notequal(sa1, sa3)
+ call assert_notequal(sa2, sa3)
+ call assert_equal(sa1, screenattr(&lines - 1, 2))
+ call assert_equal(sa2, screenattr(&lines - 1, 4))
+ call assert_equal(sa3, screenattr(&lines - 1, 6))
+ call assert_equal(sa3, screenattr(&lines - 1, 7))
+
+ " %*: Set highlight group to User{N}
+ set statusline=a%1*b%0*c
+ call assert_match('^abc\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 2)
+ let sa3=screenattr(&lines - 1, 3)
+ call assert_equal(sa1, sa3)
+ call assert_notequal(sa1, sa2)
+
+ " An empty group that contains highlight changes
+ let g:a = ''
+ set statusline=ab%(cd%1*%{g:a}%*%)de
+ call assert_match('^abde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 4)
+ call assert_equal(sa1, sa2)
+ let g:a = 'X'
+ call assert_match('^abcdXde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 5)
+ let sa3=screenattr(&lines - 1, 7)
+ call assert_equal(sa1, sa3)
+ call assert_notequal(sa1, sa2)
+
+ let g:a = ''
+ set statusline=ab%1*%(cd%*%{g:a}%1*%)de
+ call assert_match('^abde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 4)
+ call assert_notequal(sa1, sa2)
+ let g:a = 'X'
+ call assert_match('^abcdXde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 3)
+ let sa3=screenattr(&lines - 1, 5)
+ let sa4=screenattr(&lines - 1, 7)
+ call assert_notequal(sa1, sa2)
+ call assert_equal(sa1, sa3)
+ call assert_equal(sa2, sa4)
+
+ " An empty group that contains highlight changes and doesn't reset them
+ let g:a = ''
+ set statusline=ab%(cd%1*%{g:a}%)de
+ call assert_match('^abcdde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 5)
+ call assert_notequal(sa1, sa2)
+ let g:a = 'X'
+ call assert_match('^abcdXde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 5)
+ let sa3=screenattr(&lines - 1, 7)
+ call assert_notequal(sa1, sa2)
+ call assert_equal(sa2, sa3)
+
+ let g:a = ''
+ set statusline=ab%1*%(cd%*%{g:a}%)de
+ call assert_match('^abcdde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 3)
+ let sa3=screenattr(&lines - 1, 5)
+ call assert_notequal(sa1, sa2)
+ call assert_equal(sa1, sa3)
+ let g:a = 'X'
+ call assert_match('^abcdXde\s*$', s:get_statusline())
+ let sa1=screenattr(&lines - 1, 1)
+ let sa2=screenattr(&lines - 1, 3)
+ let sa3=screenattr(&lines - 1, 5)
+ let sa4=screenattr(&lines - 1, 7)
+ call assert_notequal(sa1, sa2)
+ call assert_equal(sa1, sa3)
+ call assert_equal(sa1, sa4)
+
+ let g:a = ''
+ set statusline=%#Error#{%(\ %{g:a}\ %)}
+ call assert_match('^{}\s*$', s:get_statusline())
+ let g:a = 'X'
+ call assert_match('^{ X }\s*$', s:get_statusline())
+
+ " %%: a percent sign.
+ set statusline=10%%
+ call assert_match('^10%\s*$', s:get_statusline())
+
+ " %!: evaluated expression is used as the option value
+ set statusline=%!2*3+1
+ call assert_match('7\s*$', s:get_statusline())
+
+ func GetNested()
+ call assert_equal(string(win_getid()), g:actual_curwin)
+ call assert_equal(string(bufnr('')), g:actual_curbuf)
+ return 'nested'
+ endfunc
+ func GetStatusLine()
+ call assert_equal(win_getid(), g:statusline_winid)
+ return 'the %{GetNested()} line'
+ endfunc
+ set statusline=%!GetStatusLine()
+ call assert_match('the nested line', s:get_statusline())
+ call assert_false(exists('g:actual_curwin'))
+ call assert_false(exists('g:actual_curbuf'))
+ call assert_false(exists('g:statusline_winid'))
+ delfunc GetNested
+ delfunc GetStatusLine
+
+ " Test statusline works with 80+ items
+ function! StatusLabel()
+ redrawstatus
+ return '[label]'
+ endfunc
+ let statusline = '%{StatusLabel()}'
+ for i in range(150)
+ let statusline .= '%#TabLine' . (i % 2 == 0 ? 'Fill' : 'Sel') . '#' . string(i)[0]
+ endfor
+ let &statusline = statusline
+ redrawstatus
+ set statusline&
+ delfunc StatusLabel
+
+
+ " Check statusline in current and non-current window
+ " with the 'fillchars' option.
+ set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:-
+ vsplit
+ set statusline=x%=y
+ call assert_match('^x^\+y^x=\+y$', s:get_statusline())
+ set fillchars&
+ close
+
+ %bw!
+ call delete('Xstatusline')
+ set statusline&
+ set splitbelow&
+endfunc
+
+func Test_statusline_trailing_percent_zero()
+ " this was causing illegal memory access
+ set laststatus=2 stl=%!%0
+ call assert_fails('redraw', 'E15: Invalid expression: "%0"')
+ set laststatus& stl&
+endfunc
+
+func Test_statusline_visual()
+ func CallWordcount()
+ call wordcount()
+ endfunc
+ new x1
+ setl statusline=count=%{CallWordcount()}
+ " buffer must not be empty
+ call setline(1, 'hello')
+
+ " window with more lines than x1
+ new x2
+ call setline(1, range(10))
+ $
+ " Visual mode in line below liast line in x1 should not give ml_get error
+ call feedkeys("\<C-V>", "xt")
+ redraw
+
+ delfunc CallWordcount
+ bwipe! x1
+ bwipe! x2
+endfunc
+
+func Test_statusline_removed_group()
+ CheckScreendump
+
+ let lines =<< trim END
+ scriptencoding utf-8
+ set laststatus=2
+ let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡'
+ END
+ call writefile(lines, 'XTest_statusline', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 10, 'cols': 50})
+ call VerifyScreenDump(buf, 'Test_statusline_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_statusline_using_mode()
+ CheckScreendump
+
+ let lines =<< trim END
+ setlocal statusline=-%{mode()}-
+ split
+ setlocal statusline=+%{mode()}+
+ END
+ call writefile(lines, 'XTest_statusline', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 7, 'cols': 50})
+ call VerifyScreenDump(buf, 'Test_statusline_mode_1', {})
+
+ call term_sendkeys(buf, ":")
+ call VerifyScreenDump(buf, 'Test_statusline_mode_2', {})
+
+ " clean up
+ call term_sendkeys(buf, "close\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_statusline_after_split_vsplit()
+ only
+
+ " Make the status line of each window show the window number.
+ set ls=2 stl=%{winnr()}
+
+ split | redraw
+ vsplit | redraw
+
+ " The status line of the third window should read '3' here.
+ call assert_equal('3', nr2char(screenchar(&lines - 1, 1)))
+
+ only
+ set ls& stl&
+endfunc
+
+" Test using a multibyte character for 'stl' and 'stlnc' items in 'fillchars'
+" with a custom 'statusline'
+func Test_statusline_mbyte_fillchar()
+ only
+ set fillchars=vert:\|,fold:-,stl:â”,stlnc:â•
+ set statusline=a%=b
+ call assert_match('^a\+â”\+b$', s:get_statusline())
+ vnew
+ call assert_match('^a\+â”\+bâ”a\+â•\+b$', s:get_statusline())
+ wincmd w
+ call assert_match('^a\+â•\+bâ•a\+â”\+b$', s:get_statusline())
+ set statusline& fillchars&
+ %bw!
+endfunc
+
+" Used to write beyond allocated memory. This assumes MAXPATHL is 4096 bytes.
+func Test_statusline_verylong_filename()
+ let fname = repeat('x', 4090)
+ exe "new " .. fname
+ set buftype=help
+ set previewwindow
+ redraw
+ bwipe!
+endfunc
+
+func Test_statusline_highlight_truncate()
+ CheckScreendump
+
+ let lines =<< trim END
+ set laststatus=2
+ hi! link User1 Directory
+ hi! link User2 ErrorMsg
+ set statusline=%.5(%1*ABC%2*DEF%1*GHI%)
+ END
+ call writefile(lines, 'XTest_statusline', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_statusline_hl', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_statusline_showcmd()
+ CheckScreendump
+
+ let lines =<< trim END
+ func MyStatusLine()
+ return '%S'
+ endfunc
+
+ set laststatus=2
+ set statusline=%!MyStatusLine()
+ set showcmdloc=statusline
+ call setline(1, ['a', 'b', 'c'])
+ set foldopen+=jump
+ 1,2fold
+ 3
+ END
+ call writefile(lines, 'XTest_statusline', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 6})
+
+ call term_sendkeys(buf, "g")
+ call VerifyScreenDump(buf, 'Test_statusline_showcmd_1', {})
+
+ " typing "gg" should open the fold
+ call term_sendkeys(buf, "g")
+ call VerifyScreenDump(buf, 'Test_statusline_showcmd_2', {})
+
+ call term_sendkeys(buf, "\<C-V>Gl")
+ call VerifyScreenDump(buf, 'Test_statusline_showcmd_3', {})
+
+ call term_sendkeys(buf, "\<Esc>1234")
+ call VerifyScreenDump(buf, 'Test_statusline_showcmd_4', {})
+
+ call term_sendkeys(buf, "\<Esc>:set statusline=\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call term_sendkeys(buf, "1234")
+ call VerifyScreenDump(buf, 'Test_statusline_showcmd_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim
new file mode 100644
index 0000000..7491b61
--- /dev/null
+++ b/src/testdir/test_substitute.vim
@@ -0,0 +1,1427 @@
+" Tests for the substitute (:s) command
+
+source shared.vim
+source check.vim
+source screendump.vim
+
+func Test_multiline_subst()
+ enew!
+ call append(0, ["1 aa",
+ \ "bb",
+ \ "cc",
+ \ "2 dd",
+ \ "ee",
+ \ "3 ef",
+ \ "gh",
+ \ "4 ij",
+ \ "5 a8",
+ \ "8b c9",
+ \ "9d",
+ \ "6 e7",
+ \ "77f",
+ \ "xxxxx"])
+
+ 1
+ " test if replacing a line break works with a back reference
+ /^1/,/^2/s/\n\(.\)/ \1/
+ " test if inserting a line break works with a back reference
+ /^3/,/^4/s/\(.\)$/\r\1/
+ " test if replacing a line break with another line break works
+ /^5/,/^6/s/\(\_d\{3}\)/x\1x/
+ call assert_equal('1 aa bb cc 2 dd ee', getline(1))
+ call assert_equal('3 e', getline(2))
+ call assert_equal('f', getline(3))
+ call assert_equal('g', getline(4))
+ call assert_equal('h', getline(5))
+ call assert_equal('4 i', getline(6))
+ call assert_equal('j', getline(7))
+ call assert_equal('5 ax8', getline(8))
+ call assert_equal('8xb cx9', getline(9))
+ call assert_equal('9xd', getline(10))
+ call assert_equal('6 ex7', getline(11))
+ call assert_equal('7x7f', getline(12))
+ call assert_equal('xxxxx', getline(13))
+ enew!
+endfunc
+
+func Test_substitute_variants()
+ " Validate that all the 2-/3-letter variants which embed the flags into the
+ " command name actually work.
+ enew!
+ let ln = 'Testing string'
+ let variants = [
+ \ { 'cmd': ':s/Test/test/c', 'exp': 'testing string', 'prompt': 'y' },
+ \ { 'cmd': ':s/foo/bar/ce', 'exp': ln },
+ \ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' },
+ \ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/c', 'exp': 'Testing string', 'prompt': 'n' },
+ \ { 'cmd': ':s/t/r/cn', 'exp': ln },
+ \ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'TestIng string', 'prompt': 'l' },
+ \ { 'cmd': ':s/foo/bar/ge', 'exp': ln },
+ \ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' },
+ \ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' },
+ \ { 'cmd': ':s/t/r/gI', 'exp': 'Tesring srring' },
+ \ { 'cmd': ':s/t/r/gn', 'exp': ln },
+ \ { 'cmd': ':s/t/r/gp', 'exp': 'Tesring srring' },
+ \ { 'cmd': ':s/t/r/gl', 'exp': 'Tesring srring' },
+ \ { 'cmd': ':s//r/gr', 'exp': 'Testr strr' },
+ \ { 'cmd': ':s/t/r/ic', 'exp': 'resting string', 'prompt': 'y' },
+ \ { 'cmd': ':s/foo/bar/ie', 'exp': ln },
+ \ { 'cmd': ':s/t/r/i', 'exp': 'resting string' },
+ \ { 'cmd': ':s/t/r/iI', 'exp': 'Tesring string' },
+ \ { 'cmd': ':s/t/r/in', 'exp': ln },
+ \ { 'cmd': ':s/t/r/ip', 'exp': 'resting string' },
+ \ { 'cmd': ':s//r/ir', 'exp': 'Testr string' },
+ \ { 'cmd': ':s/t/r/Ic', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/foo/bar/Ie', 'exp': ln },
+ \ { 'cmd': ':s/t/r/Ig', 'exp': 'Tesring srring' },
+ \ { 'cmd': ':s/t/r/Ii', 'exp': 'resting string' },
+ \ { 'cmd': ':s/t/r/I', 'exp': 'Tesring string' },
+ \ { 'cmd': ':s/t/r/Ip', 'exp': 'Tesring string' },
+ \ { 'cmd': ':s/t/r/Il', 'exp': 'Tesring string' },
+ \ { 'cmd': ':s//r/Ir', 'exp': 'Testr string' },
+ \ { 'cmd': ':s//r/rc', 'exp': 'Testr string', 'prompt': 'y' },
+ \ { 'cmd': ':s//r/rg', 'exp': 'Testr strr' },
+ \ { 'cmd': ':s//r/ri', 'exp': 'Testr string' },
+ \ { 'cmd': ':s//r/rI', 'exp': 'Testr string' },
+ \ { 'cmd': ':s//r/rn', 'exp': 'Testing string' },
+ \ { 'cmd': ':s//r/rp', 'exp': 'Testr string' },
+ \ { 'cmd': ':s//r/rl', 'exp': 'Testr string' },
+ \ { 'cmd': ':s//r/r', 'exp': 'Testr string' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'Testing string', 'prompt': 'q' },
+ \]
+
+ for var in variants
+ for run in [1, 2]
+ let cmd = var.cmd
+ if run == 2 && cmd =~ "/.*/.*/."
+ " Change :s/from/to/{flags} to :s{flags}
+ let cmd = substitute(cmd, '/.*/', '', '')
+ endif
+ call setline(1, [ln])
+ let msg = printf('using "%s"', cmd)
+ let @/='ing'
+ let v:errmsg = ''
+ call feedkeys(cmd . "\<CR>" . get(var, 'prompt', ''), 'ntx')
+ " No error should exist (matters for testing e flag)
+ call assert_equal('', v:errmsg, msg)
+ call assert_equal(var.exp, getline('.'), msg)
+ endfor
+ endfor
+endfunc
+
+" Test the l, p, # flags.
+func Test_substitute_flags_lp()
+ new
+ call setline(1, "abc\tdef\<C-h>ghi")
+
+ let a = execute('s/a/a/p')
+ call assert_equal("\nabc def^Hghi", a)
+
+ let a = execute('s/a/a/l')
+ call assert_equal("\nabc^Idef^Hghi$", a)
+
+ let a = execute('s/a/a/#')
+ call assert_equal("\n 1 abc def^Hghi", a)
+
+ let a = execute('s/a/a/p#')
+ call assert_equal("\n 1 abc def^Hghi", a)
+
+ let a = execute('s/a/a/l#')
+ call assert_equal("\n 1 abc^Idef^Hghi$", a)
+
+ let a = execute('s/a/a/')
+ call assert_equal("", a)
+
+ bwipe!
+endfunc
+
+func Test_substitute_repeat()
+ " This caused an invalid memory access.
+ split Xsubfile
+ s/^/x
+ call feedkeys("Qsc\<CR>y", 'tx')
+ bwipe!
+endfunc
+
+" Test %s/\n// which is implemented as a special case to use a
+" more efficient join rather than doing a regular substitution.
+func Test_substitute_join()
+ new
+
+ call setline(1, ["foo\tbar", "bar\<C-H>foo"])
+ let a = execute('%s/\n//')
+ call assert_equal("", a)
+ call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
+ call assert_equal('\n', histget("search", -1))
+
+ call setline(1, ["foo\tbar", "bar\<C-H>foo"])
+ let a = execute('%s/\n//g')
+ call assert_equal("", a)
+ call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
+ call assert_equal('\n', histget("search", -1))
+
+ call setline(1, ["foo\tbar", "bar\<C-H>foo"])
+ let a = execute('%s/\n//p')
+ call assert_equal("\nfoo barbar^Hfoo", a)
+ call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
+ call assert_equal('\n', histget("search", -1))
+
+ call setline(1, ["foo\tbar", "bar\<C-H>foo"])
+ let a = execute('%s/\n//l')
+ call assert_equal("\nfoo^Ibarbar^Hfoo$", a)
+ call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
+ call assert_equal('\n', histget("search", -1))
+
+ call setline(1, ["foo\tbar", "bar\<C-H>foo"])
+ let a = execute('%s/\n//#')
+ call assert_equal("\n 1 foo barbar^Hfoo", a)
+ call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
+ call assert_equal('\n', histget("search", -1))
+
+ call setline(1, ['foo', 'bar', 'baz', 'qux'])
+ call execute('1,2s/\n//')
+ call assert_equal(['foobarbaz', 'qux'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+func Test_substitute_count()
+ new
+ call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
+ 2
+
+ s/foo/bar/3
+ call assert_equal(['foo foo', 'bar foo', 'bar foo', 'bar foo', 'foo foo'],
+ \ getline(1, '$'))
+
+ call assert_fails('s/foo/bar/0', 'E939:')
+
+ call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
+ 2,4s/foo/bar/ 10
+ call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
+ \ getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test substitute 'n' flag (report number of matches, do not substitute).
+func Test_substitute_flag_n()
+ new
+ let lines = ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo']
+ call setline(1, lines)
+
+ call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/n'))
+ call assert_equal("\n6 matches on 3 lines", execute('2,4s/foo/bar/gn'))
+
+ " c flag (confirm) should be ignored when using n flag.
+ call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/nc'))
+
+ " No substitution should have been done.
+ call assert_equal(lines, getline(1, '$'))
+
+ %delete _
+ call setline(1, ['A', 'Bar', 'Baz'])
+ call assert_equal("\n1 match on 1 line", execute('s/\nB\@=//gn'))
+
+ bwipe!
+endfunc
+
+func Test_substitute_errors()
+ new
+ call setline(1, 'foobar')
+
+ call assert_fails('s/FOO/bar/', 'E486:')
+ call assert_fails('s/foo/bar/@', 'E488:')
+ call assert_fails('s/\(/bar/', 'E54:')
+ call assert_fails('s afooabara', 'E146:')
+ call assert_fails('s\\a', 'E10:')
+
+ setl nomodifiable
+ call assert_fails('s/foo/bar/', 'E21:')
+
+ call assert_fails("let s=substitute([], 'a', 'A', 'g')", 'E730:')
+ call assert_fails("let s=substitute('abcda', [], 'A', 'g')", 'E730:')
+ call assert_fails("let s=substitute('abcda', 'a', [], 'g')", 'E730:')
+ call assert_fails("let s=substitute('abcda', 'a', 'A', [])", 'E730:')
+ call assert_fails("let s=substitute('abc', '\\%(', 'A', 'g')", 'E53:')
+
+ bwipe!
+endfunc
+
+" Test for *sub-replace-special* and *sub-replace-expression* on substitute().
+func Test_sub_replace_1()
+ " Run the tests with 'magic' on
+ set magic
+ set cpo&
+ call assert_equal('AA', substitute('A', 'A', '&&', ''))
+ call assert_equal('&', substitute('B', 'B', '\&', ''))
+ call assert_equal('C123456789987654321', substitute('C123456789', 'C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', '\0\9\8\7\6\5\4\3\2\1', ''))
+ call assert_equal('d', substitute('D', 'D', 'd', ''))
+ call assert_equal('~', substitute('E', 'E', '~', ''))
+ call assert_equal('~', substitute('F', 'F', '\~', ''))
+ call assert_equal('Gg', substitute('G', 'G', '\ugg', ''))
+ call assert_equal('Hh', substitute('H', 'H', '\Uh\Eh', ''))
+ call assert_equal('iI', substitute('I', 'I', '\lII', ''))
+ call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', ''))
+ call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', ''))
+ call assert_equal("l\<C-V>\<C-M>l",
+ \ substitute('lLl', 'L', "\<C-V>\<C-M>", ''))
+ call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', ''))
+ call assert_equal("n\<C-V>\<C-M>n",
+ \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", ''))
+ call assert_equal("o\no", substitute('oOo', 'O', '\n', ''))
+ call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', ''))
+ call assert_equal("q\tq", substitute('qQq', 'Q', '\t', ''))
+ call assert_equal('r\r', substitute('rRr', 'R', '\\', ''))
+ call assert_equal('scs', substitute('sSs', 'S', '\c', ''))
+ call assert_equal("u\nu", substitute('uUu', 'U', "\n", ''))
+ call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", ''))
+ call assert_equal("w\\w", substitute('wWw', 'W', "\\", ''))
+ call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", ''))
+ call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', ''))
+ call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', ''))
+ " \v or \V after $
+ call assert_equal('abxx', substitute('abcd', 'xy$\v|cd$', 'xx', ''))
+ call assert_equal('abxx', substitute('abcd', 'xy$\V\|cd\$', 'xx', ''))
+endfunc
+
+func Test_sub_replace_2()
+ " Run the tests with 'magic' off
+ set nomagic
+ set cpo&
+ call assert_equal('AA', substitute('A', 'A', '&&', ''))
+ call assert_equal('&', substitute('B', 'B', '\&', ''))
+ call assert_equal('C123456789987654321', substitute('C123456789', 'C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', '\0\9\8\7\6\5\4\3\2\1', ''))
+ call assert_equal('d', substitute('D', 'D', 'd', ''))
+ call assert_equal('~', substitute('E', 'E', '~', ''))
+ call assert_equal('~', substitute('F', 'F', '\~', ''))
+ call assert_equal('Gg', substitute('G', 'G', '\ugg', ''))
+ call assert_equal('Hh', substitute('H', 'H', '\Uh\Eh', ''))
+ call assert_equal('iI', substitute('I', 'I', '\lII', ''))
+ call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', ''))
+ call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', ''))
+ call assert_equal("l\<C-V>\<C-M>l",
+ \ substitute('lLl', 'L', "\<C-V>\<C-M>", ''))
+ call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', ''))
+ call assert_equal("n\<C-V>\<C-M>n",
+ \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", ''))
+ call assert_equal("o\no", substitute('oOo', 'O', '\n', ''))
+ call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', ''))
+ call assert_equal("q\tq", substitute('qQq', 'Q', '\t', ''))
+ call assert_equal('r\r', substitute('rRr', 'R', '\\', ''))
+ call assert_equal('scs', substitute('sSs', 'S', '\c', ''))
+ call assert_equal("t\<C-M>t", substitute('tTt', 'T', "\r", ''))
+ call assert_equal("u\nu", substitute('uUu', 'U', "\n", ''))
+ call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", ''))
+ call assert_equal('w\w', substitute('wWw', 'W', "\\", ''))
+ call assert_equal('XxxX', substitute('X', 'X', '\L\uxXx\l\EX', ''))
+ call assert_equal('yYYy', substitute('Y', 'Y', '\U\lYyY\u\Ey', ''))
+endfunc
+
+func Test_sub_replace_3()
+ set magic&
+ set cpo&
+ call assert_equal('a\a', substitute('aAa', 'A', '\="\\"', ''))
+ call assert_equal('b\\b', substitute('bBb', 'B', '\="\\\\"', ''))
+ call assert_equal("c\rc", substitute('cCc', 'C', "\\=\"\r\"", ''))
+ call assert_equal("d\\\rd", substitute('dDd', 'D', "\\=\"\\\\\r\"", ''))
+ call assert_equal("e\\\\\re", substitute('eEe', 'E', "\\=\"\\\\\\\\\r\"", ''))
+ call assert_equal('f\rf', substitute('fFf', 'F', '\="\\r"', ''))
+ call assert_equal('j\nj', substitute('jJj', 'J', '\="\\n"', ''))
+ call assert_equal("k\<C-M>k", substitute('kKk', 'K', '\="\r"', ''))
+ call assert_equal("l\nl", substitute('lLl', 'L', '\="\n"', ''))
+endfunc
+
+" Test for submatch() on substitute().
+func Test_sub_replace_4()
+ set magic&
+ set cpo&
+ call assert_equal('a\a', substitute('aAa', 'A',
+ \ '\=substitute(submatch(0), ".", "\\", "")', ''))
+ call assert_equal('b\b', substitute('bBb', 'B',
+ \ '\=substitute(submatch(0), ".", "\\\\", "")', ''))
+ call assert_equal("c\<C-V>\<C-M>c", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\<C-M>", "")', ''))
+ call assert_equal("d\<C-V>\<C-M>d", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\<C-M>", "")', ''))
+ call assert_equal("e\\\<C-V>\<C-M>e", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-M>", "")', ''))
+ call assert_equal("f\<C-M>f", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', ''))
+ call assert_equal("j\nj", substitute('jJj', 'J', '\=substitute(submatch(0), ".", "\\n", "")', ''))
+ call assert_equal("k\rk", substitute('kKk', 'K', '\=substitute(submatch(0), ".", "\r", "")', ''))
+ call assert_equal("l\nl", substitute('lLl', 'L', '\=substitute(submatch(0), ".", "\n", "")', ''))
+endfunc
+
+func Test_sub_replace_5()
+ set magic&
+ set cpo&
+ call assert_equal('A123456789987654321', substitute('A123456789',
+ \ 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
+ \ '\=submatch(0) . submatch(9) . submatch(8) . ' .
+ \ 'submatch(7) . submatch(6) . submatch(5) . ' .
+ \ 'submatch(4) . submatch(3) . submatch(2) . submatch(1)',
+ \ ''))
+ call assert_equal("[['A123456789'], ['9'], ['8'], ['7'], ['6'], " .
+ \ "['5'], ['4'], ['3'], ['2'], ['1']]",
+ \ substitute('A123456789',
+ \ 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
+ \ '\=string([submatch(0, 1), submatch(9, 1), ' .
+ \ 'submatch(8, 1), 7->submatch(1), submatch(6, 1), ' .
+ \ 'submatch(5, 1), submatch(4, 1), submatch(3, 1), ' .
+ \ 'submatch(2, 1), submatch(1, 1)])',
+ \ ''))
+endfunc
+
+func Test_sub_replace_6()
+ set magic&
+ set cpo+=/
+ call assert_equal('a', substitute('A', 'A', 'a', ''))
+ call assert_equal('%', substitute('B', 'B', '%', ''))
+ set cpo-=/
+ call assert_equal('c', substitute('C', 'C', 'c', ''))
+ call assert_equal('%', substitute('D', 'D', '%', ''))
+endfunc
+
+func Test_sub_replace_7()
+ set magic&
+ set cpo&
+ call assert_equal('AA', substitute('AA', 'A.', '\=submatch(0)', ''))
+ call assert_equal("B\nB", substitute("B\nB", 'B.', '\=submatch(0)', ''))
+ call assert_equal("['B\n']B", substitute("B\nB", 'B.', '\=string(submatch(0, 1))', ''))
+ call assert_equal('-abab', substitute('-bb', '\zeb', 'a', 'g'))
+ call assert_equal('c-cbcbc', substitute('-bb', '\ze', 'c', 'g'))
+endfunc
+
+" Test for *:s%* on :substitute.
+func Test_sub_replace_8()
+ new
+ set magic&
+ set cpo&
+ $put =',,X'
+ s/\(^\|,\)\ze\(,\|X\)/\1N/g
+ call assert_equal('N,,NX', getline("$"))
+ $put =',,Y'
+ let cmd = ':s/\(^\|,\)\ze\(,\|Y\)/\1N/gc'
+ call feedkeys(cmd . "\<CR>a", "xt")
+ call assert_equal('N,,NY', getline("$"))
+ :$put =',,Z'
+ let cmd = ':s/\(^\|,\)\ze\(,\|Z\)/\1N/gc'
+ call feedkeys(cmd . "\<CR>yy", "xt")
+ call assert_equal('N,,NZ', getline("$"))
+ enew! | close
+endfunc
+
+func Test_sub_replace_9()
+ new
+ set magic&
+ set cpo&
+ $put ='xxx'
+ call feedkeys(":s/x/X/gc\<CR>yyq", "xt")
+ call assert_equal('XXx', getline("$"))
+ enew! | close
+endfunc
+
+func Test_sub_replace_10()
+ set magic&
+ set cpo&
+ call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g'))
+ call assert_equal('aaa', substitute('123', '\zs.', 'a', 'g'))
+ call assert_equal('1a2a3a', substitute('123', '.\zs', 'a', 'g'))
+ call assert_equal('a1a2a3a', substitute('123', '\ze', 'a', 'g'))
+ call assert_equal('a1a2a3', substitute('123', '\ze.', 'a', 'g'))
+ call assert_equal('aaa', substitute('123', '.\ze', 'a', 'g'))
+ call assert_equal('aa2a3a', substitute('123', '1\|\ze', 'a', 'g'))
+ call assert_equal('1aaa', substitute('123', '1\zs\|[23]', 'a', 'g'))
+endfunc
+
+func SubReplacer(text, submatches)
+ return a:text .. a:submatches[0] .. a:text
+endfunc
+func SubReplacerVar(text, ...)
+ return a:text .. a:1[0] .. a:text
+endfunc
+def SubReplacerVar9(text: string, ...args: list<list<string>>): string
+ return text .. args[0][0] .. text
+enddef
+func SubReplacer20(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, submatches)
+ return a:t3 .. a:submatches[0] .. a:t11
+endfunc
+
+func Test_substitute_partial()
+ call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacer', ['foo']), 'g'))
+ call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacerVar', ['foo']), 'g'))
+ call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacerVar9', ['foo']), 'g'))
+
+ " 19 arguments plus one is just OK
+ let Replacer = function('SubReplacer20', repeat(['foo'], 19))
+ call assert_equal('1foo2foo3', substitute('123', '2', Replacer, 'g'))
+
+ " 20 arguments plus one is too many
+ let Replacer = function('SubReplacer20', repeat(['foo'], 20))
+ call assert_fails("call substitute('123', '2', Replacer, 'g')", 'E118:')
+endfunc
+
+func Test_substitute_float()
+ call assert_equal('number 1.23', substitute('number ', '$', { -> 1.23 }, ''))
+ vim9 assert_equal('number 1.23', substitute('number ', '$', () => 1.23, ''))
+endfunc
+
+" Tests for *sub-replace-special* and *sub-replace-expression* on :substitute.
+
+" Execute a list of :substitute command tests
+func Run_SubCmd_Tests(tests)
+ enew!
+ for t in a:tests
+ let start = line('.') + 1
+ let end = start + len(t[2]) - 1
+ exe "normal o" . t[0]
+ call cursor(start, 1)
+ exe t[1]
+ call assert_equal(t[2], getline(start, end), t[1])
+ endfor
+ enew!
+endfunc
+
+func Test_sub_cmd_1()
+ set magic
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [['A', 's/A/&&/', ['AA']],
+ \ ['B', 's/B/\&/', ['&']],
+ \ ['C123456789', 's/C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
+ \ ['D', 's/D/d/', ['d']],
+ \ ['E', 's/E/~/', ['d']],
+ \ ['F', 's/F/\~/', ['~']],
+ \ ['G', 's/G/\ugg/', ['Gg']],
+ \ ['H', 's/H/\Uh\Eh/', ['Hh']],
+ \ ['I', 's/I/\lII/', ['iI']],
+ \ ['J', 's/J/\LJ\EJ/', ['jJ']],
+ \ ['K', 's/K/\Uk\ek/', ['Kk']],
+ \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
+ \ ['mMm', 's/M/\r/', ['m', 'm']],
+ \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
+ \ ['oOo', 's/O/\n/', ["o\no"]],
+ \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
+ \ ['qQq', 's/Q/\t/', ["q\tq"]],
+ \ ['rRr', 's/R/\\/', ['r\r']],
+ \ ['sSs', 's/S/\c/', ['scs']],
+ \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
+ \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
+ \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
+ \ ['\', 's/\\/\\\\/', ['\\']]
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+func Test_sub_cmd_2()
+ set nomagic
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [['A', 's/A/&&/', ['&&']],
+ \ ['B', 's/B/\&/', ['B']],
+ \ ['C123456789', 's/\mC\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
+ \ ['D', 's/D/d/', ['d']],
+ \ ['E', 's/E/~/', ['~']],
+ \ ['F', 's/F/\~/', ['~']],
+ \ ['G', 's/G/\ugg/', ['Gg']],
+ \ ['H', 's/H/\Uh\Eh/', ['Hh']],
+ \ ['I', 's/I/\lII/', ['iI']],
+ \ ['J', 's/J/\LJ\EJ/', ['jJ']],
+ \ ['K', 's/K/\Uk\ek/', ['Kk']],
+ \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
+ \ ['mMm', 's/M/\r/', ['m', 'm']],
+ \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
+ \ ['oOo', 's/O/\n/', ["o\no"]],
+ \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
+ \ ['qQq', 's/Q/\t/', ["q\tq"]],
+ \ ['rRr', 's/R/\\/', ['r\r']],
+ \ ['sSs', 's/S/\c/', ['scs']],
+ \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
+ \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
+ \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
+ \ ['\', 's/\\/\\\\/', ['\\']]
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+func Test_sub_cmd_3()
+ set nomagic
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [['aAa', "s/A/\\='\\'/", ['a\a']],
+ \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']],
+ \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']],
+ \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']],
+ \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']],
+ \ ['fFf', "s/F/\\='\r'/", ['f', 'f']],
+ \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']],
+ \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']],
+ \ ['iIi', "s/I/\\='\\\\\<C-V>\<C-J>'/", ["i\\\\\<C-V>", 'i']],
+ \ ['jJj', "s/J/\\='\n'/", ['j', 'j']],
+ \ ['kKk', 's/K/\="\r"/', ['k', 'k']],
+ \ ['lLl', 's/L/\="\n"/', ['l', 'l']]
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+" Test for submatch() on :substitute.
+func Test_sub_cmd_4()
+ set magic&
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ['aAa', "s/A/\\=substitute(submatch(0), '.', '\\', '')/",
+ \ ['a\a']],
+ \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/",
+ \ ['b\b']],
+ \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/",
+ \ ["c\<C-V>", 'c']],
+ \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/",
+ \ ["d\<C-V>", 'd']],
+ \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/",
+ \ ["e\\\<C-V>", 'e']],
+ \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/",
+ \ ['f', 'f']],
+ \ ['gGg', 's/G/\=substitute(submatch(0), ".", "\<C-V>\<C-J>", "")/',
+ \ ["g\<C-V>", 'g']],
+ \ ['hHh', 's/H/\=substitute(submatch(0), ".", "\\\<C-V>\<C-J>", "")/',
+ \ ["h\<C-V>", 'h']],
+ \ ['iIi', 's/I/\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-J>", "")/',
+ \ ["i\\\<C-V>", 'i']],
+ \ ['jJj', "s/J/\\=substitute(submatch(0), '.', '\\n', '')/",
+ \ ['j', 'j']],
+ \ ['kKk', "s/K/\\=substitute(submatch(0), '.', '\\r', '')/",
+ \ ['k', 'k']],
+ \ ['lLl', "s/L/\\=substitute(submatch(0), '.', '\\n', '')/",
+ \ ['l', 'l']],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+func Test_sub_cmd_5()
+ set magic&
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ['A123456789', 's/A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=submatch(0) . submatch(9) . submatch(8) . submatch(7) . submatch(6) . submatch(5) . submatch(4) . submatch(3) . submatch(2) . submatch(1)/', ['A123456789987654321']],
+ \ ['B123456789', 's/B\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=string([submatch(0, 1), submatch(9, 1), submatch(8, 1), submatch(7, 1), submatch(6, 1), submatch(5, 1), submatch(4, 1), submatch(3, 1), submatch(2, 1), submatch(1, 1)])/', ["[['B123456789'], ['9'], ['8'], ['7'], ['6'], ['5'], ['4'], ['3'], ['2'], ['1']]"]],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+" Test for *:s%* on :substitute.
+func Test_sub_cmd_6()
+ set magic&
+ set cpo+=/
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ['A', 's/A/a/', ['a']],
+ \ ['B', 's/B/%/', ['a']],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+
+ set cpo-=/
+ let tests = [ ['C', 's/C/c/', ['c']],
+ \ ['D', 's/D/%/', ['%']],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+
+ set cpo&
+endfunc
+
+" Test for :s replacing \n with line break.
+func Test_sub_cmd_7()
+ set magic&
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']],
+ \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']],
+ \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]],
+ \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]],
+ \ ["E\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>E", 's/E\_.\{-}E/\=strtrans(string(submatch(0, 1)))/', [strtrans("['E\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>E']")]],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+
+ exe "normal oQ\nQ\<Esc>k"
+ call assert_fails('s/Q[^\n]Q/\=submatch(0)."foobar"/', 'E486:')
+ enew!
+endfunc
+
+func TitleString()
+ let check = 'foo' =~ 'bar'
+ return ""
+endfunc
+
+func Test_sub_cmd_8()
+ set titlestring=%{TitleString()}
+
+ enew!
+ call append(0, ['', 'test_one', 'test_two'])
+ call cursor(1,1)
+ /^test_one/s/.*/\="foo\nbar"/
+ call assert_equal('foo', getline(2))
+ call assert_equal('bar', getline(3))
+ call feedkeys(':/^test_two/s/.*/\="foo\nbar"/c', "t")
+ call feedkeys("\<CR>y", "xt")
+ call assert_equal('foo', getline(4))
+ call assert_equal('bar', getline(5))
+
+ enew!
+ set titlestring&
+endfunc
+
+func Test_sub_cmd_9()
+ new
+ let input = ['1 aaa', '2 aaa', '3 aaa']
+ call setline(1, input)
+ func Foo()
+ return submatch(0)
+ endfunc
+ %s/aaa/\=Foo()/gn
+ call assert_equal(input, getline(1, '$'))
+ call assert_equal(1, &modifiable)
+
+ delfunc Foo
+ bw!
+endfunc
+
+func Test_sub_highlight_zero_match()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three'])
+ END
+ call writefile(lines, 'XscriptSubHighlight', 'D')
+ let buf = RunVimInTerminal('-S XscriptSubHighlight', #{rows: 8, cols: 60})
+ call term_sendkeys(buf, ":%s/^/ /c\<CR>")
+ call VerifyScreenDump(buf, 'Test_sub_highlight_zer_match_1', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_nocatch_sub_failure_handling()
+ " normal error results in all replacements
+ func Foo()
+ foobar
+ endfunc
+ new
+ call setline(1, ['1 aaa', '2 aaa', '3 aaa'])
+ " need silent! to avoid a delay when entering Insert mode
+ silent! %s/aaa/\=Foo()/g
+ call assert_equal(['1 0', '2 0', '3 0'], getline(1, 3))
+
+ " Throw without try-catch causes abort after the first line.
+ " We cannot test this, since it would stop executing the test script.
+
+ " try/catch does not result in any changes
+ func! Foo()
+ throw 'error'
+ endfunc
+ call setline(1, ['1 aaa', '2 aaa', '3 aaa'])
+ let error_caught = 0
+ try
+ %s/aaa/\=Foo()/g
+ catch
+ let error_caught = 1
+ endtry
+ call assert_equal(1, error_caught)
+ call assert_equal(['1 aaa', '2 aaa', '3 aaa'], getline(1, 3))
+
+ " Same, but using "n" flag so that "sandbox" gets set
+ call setline(1, ['1 aaa', '2 aaa', '3 aaa'])
+ let error_caught = 0
+ try
+ %s/aaa/\=Foo()/gn
+ catch
+ let error_caught = 1
+ endtry
+ call assert_equal(1, error_caught)
+ call assert_equal(['1 aaa', '2 aaa', '3 aaa'], getline(1, 3))
+
+ delfunc Foo
+ bwipe!
+endfunc
+
+" Test ":s/pat/sub/" with different ~s in sub.
+func Test_replace_with_tilde()
+ new
+ " Set the last replace string to empty
+ s/^$//
+ call append(0, ['- Bug in "vPPPP" on this text:'])
+ normal gg
+ s/u/~u~/
+ call assert_equal('- Bug in "vPPPP" on this text:', getline(1))
+ s/i/~u~/
+ call assert_equal('- Bug uuun "vPPPP" on this text:', getline(1))
+ s/o/~~~/
+ call assert_equal('- Bug uuun "vPPPP" uuuuuuuuun this text:', getline(1))
+ close!
+endfunc
+
+func Test_replace_keeppatterns()
+ new
+ a
+foobar
+
+substitute foo asdf
+
+one two
+.
+
+ normal gg
+ /^substitute
+ s/foo/bar/
+ call assert_equal('foo', @/)
+ call assert_equal('substitute bar asdf', getline('.'))
+
+ /^substitute
+ keeppatterns s/asdf/xyz/
+ call assert_equal('^substitute', @/)
+ call assert_equal('substitute bar xyz', getline('.'))
+
+ exe "normal /bar /e\<CR>"
+ call assert_equal(15, col('.'))
+ normal -
+ keeppatterns /xyz
+ call assert_equal('bar ', @/)
+ call assert_equal('substitute bar xyz', getline('.'))
+ exe "normal 0dn"
+ call assert_equal('xyz', getline('.'))
+
+ close!
+endfunc
+
+func Test_sub_beyond_end()
+ new
+ call setline(1, '#')
+ let @/ = '^#\n\zs'
+ s///e
+ call assert_equal('#', getline(1))
+ bwipe!
+endfunc
+
+" Test for repeating last substitution using :~ and :&r
+func Test_repeat_last_sub()
+ new
+ call setline(1, ['blue green yellow orange white'])
+ s/blue/red/
+ let @/ = 'yellow'
+ ~
+ let @/ = 'white'
+ :&r
+ let @/ = 'green'
+ s//gray
+ call assert_equal('red gray red orange red', getline(1))
+ close!
+endfunc
+
+" Test for Vi compatible substitution:
+" \/{string}/, \?{string}? and \&{string}&
+func Test_sub_vi_compatibility()
+ new
+ call setline(1, ['blue green yellow orange blue'])
+ let @/ = 'orange'
+ s\/white/
+ let @/ = 'blue'
+ s\?amber?
+ let @/ = 'white'
+ s\&green&
+ call assert_equal('amber green yellow white green', getline(1))
+ close!
+
+ call assert_fails('vim9cmd s\/white/', 'E1270:')
+ call assert_fails('vim9cmd s\?white?', 'E1270:')
+ call assert_fails('vim9cmd s\&white&', 'E1270:')
+endfunc
+
+" Test for substitute with the new text longer than the original text
+func Test_sub_expand_text()
+ new
+ call setline(1, 'abcabcabcabcabcabcabcabc')
+ s/b/\=repeat('B', 10)/g
+ call assert_equal(repeat('aBBBBBBBBBBc', 8), getline(1))
+ close!
+endfunc
+
+" Test for command failures when the last substitute pattern is not set.
+func Test_sub_with_no_last_pat()
+ let lines =<< trim [SCRIPT]
+ call assert_fails('~', 'E33:')
+ call assert_fails('s//abc/g', 'E35:')
+ call assert_fails('s\/bar', 'E35:')
+ call assert_fails('s\&bar&', 'E33:')
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+
+ let lines =<< trim [SCRIPT]
+ set cpo+=/
+ call assert_fails('s/abc/%/', 'E33:')
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript')
+ if RunVim([], [], '--clean -S Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+
+ call delete('Xresult')
+endfunc
+
+func Test_substitute()
+ call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g'))
+ " Substitute with special keys
+ call assert_equal("a\<End>c", substitute('abc', "a.c", "a\<End>c", ''))
+endfunc
+
+func Test_substitute_expr()
+ let g:val = 'XXX'
+ call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', ''))
+ call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, ''))
+ call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
+ \ '\=nr2char("0x" . submatch(1))', 'g'))
+ call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
+ \ {-> nr2char("0x" . submatch(1))}, 'g'))
+
+ call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)',
+ \ {-> submatch(2) . submatch(3) . submatch(1)}, ''))
+
+ func Recurse()
+ return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '')
+ endfunc
+ " recursive call works
+ call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, ''))
+
+ call assert_fails("let s=submatch([])", 'E745:')
+ call assert_fails("let s=submatch(2, [])", 'E745:')
+endfunc
+
+func Test_invalid_submatch()
+ " This was causing invalid memory access in Vim-7.4.2232 and older
+ call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:')
+ call assert_fails('eval submatch(-1)', 'E935:')
+ call assert_equal('', submatch(0))
+ call assert_equal('', submatch(1))
+ call assert_equal([], submatch(0, 1))
+ call assert_equal([], submatch(1, 1))
+endfunc
+
+func Test_submatch_list_concatenate()
+ let pat = 'A\(.\)'
+ let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])}
+ call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]")
+endfunc
+
+func Test_substitute_expr_arg()
+ call assert_equal('123456789-123456789=', substitute('123456789',
+ \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_equal('123456-123456=789', substitute('123456789',
+ \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_equal('123456789-123456789x=', substitute('123456789',
+ \ '\(.\)\(.\)\(.*\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:')
+endfunc
+
+" Test for using a function to supply the substitute string
+func Test_substitute_using_func()
+ func Xfunc()
+ return '1234'
+ endfunc
+ call assert_equal('a1234f', substitute('abcdef', 'b..e',
+ \ function("Xfunc"), ''))
+ delfunc Xfunc
+endfunc
+
+" Test for using submatch() with a multiline match
+func Test_substitute_multiline_submatch()
+ new
+ call setline(1, ['line1', 'line2', 'line3', 'line4'])
+ %s/^line1\(\_.\+\)line4$/\=submatch(1)/
+ call assert_equal(['', 'line2', 'line3', ''], getline(1, '$'))
+ close!
+endfunc
+
+func Test_substitute_skipped_range()
+ new
+ if 0
+ /1/5/2/2/\n
+ endif
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+ bwipe!
+endfunc
+
+" Test using the 'gdefault' option (when on, flag 'g' is default on).
+func Test_substitute_gdefault()
+ new
+
+ " First check without 'gdefault'
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/
+ call assert_equal('FOO bar foo', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/g
+ call assert_equal('FOO bar FOO', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/gg
+ call assert_equal('FOO bar foo', getline(1))
+
+ " Then check with 'gdefault'
+ set gdefault
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/
+ call assert_equal('FOO bar FOO', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/g
+ call assert_equal('FOO bar foo', getline(1))
+ call setline(1, 'foo bar foo')
+ s/foo/FOO/gg
+ call assert_equal('FOO bar FOO', getline(1))
+
+ " Setting 'compatible' should reset 'gdefault'
+ call assert_equal(1, &gdefault)
+ set compatible
+ call assert_equal(0, &gdefault)
+ set nocompatible
+ call assert_equal(0, &gdefault)
+
+ bw!
+endfunc
+
+" This was using "old_sub" after it was freed.
+func Test_using_old_sub()
+ set compatible maxfuncdepth=10
+ new
+ call setline(1, 'some text.')
+ func Repl()
+ ~
+ s/
+ endfunc
+ silent! s/\%')/\=Repl()
+
+ delfunc Repl
+ bwipe!
+ set nocompatible
+endfunc
+
+" This was switching windows in between computing the length and using it.
+func Test_sub_change_window()
+ silent! lfile
+ sil! norm o0000000000000000000000000000000000000000000000000000
+ func Repl()
+ lopen
+ endfunc
+ silent! s/\%')/\=Repl()
+ bwipe!
+ bwipe!
+ delfunc Repl
+endfunc
+
+" This was undoign a change in between computing the length and using it.
+func Do_Test_sub_undo_change()
+ new
+ norm o0000000000000000000000000000000000000000000000000000
+ silent! s/\%')/\=Repl()
+ bwipe!
+endfunc
+
+func Test_sub_undo_change()
+ func Repl()
+ silent! norm g-
+ endfunc
+ call Do_Test_sub_undo_change()
+
+ func! Repl()
+ silent earlier
+ endfunc
+ call Do_Test_sub_undo_change()
+
+ delfunc Repl
+endfunc
+
+" This was opening a command line window from the expression
+func Test_sub_open_cmdline_win()
+ " the error only happens in a very specific setup, run a new Vim instance to
+ " get a clean starting point.
+ let lines =<< trim [SCRIPT]
+ set vb t_vb=
+ norm o0000000000000000000000000000000000000000000000000000
+ func Replace()
+ norm q/
+ endfunc
+ s/\%')/\=Replace()
+ redir >Xresult
+ messages
+ redir END
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '-u NONE -S Xscript')
+ call assert_match('E565: Not allowed to change text or change window',
+ \ readfile('Xresult')->join('XX'))
+ endif
+
+ call delete('Xresult')
+endfunc
+
+" This was editing a script file from the expression
+func Test_sub_edit_scriptfile()
+ new
+ norm o0000000000000000000000000000000000000000000000000000
+ func EditScript()
+ silent! scr! Xsedfile
+ endfunc
+ s/\%')/\=EditScript()
+
+ delfunc EditScript
+ bwipe!
+endfunc
+
+" This was editing another file from the expression.
+func Test_sub_expr_goto_other_file()
+ call writefile([''], 'Xfileone', 'D')
+ enew!
+ call setline(1, ['a', 'b', 'c', 'd',
+ \ 'Xfileone zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'])
+
+ func g:SplitGotoFile()
+ exe "sil! norm 0\<C-W>gf"
+ return ''
+ endfunc
+
+ $
+ s/\%')/\=g:SplitGotoFile()
+
+ delfunc g:SplitGotoFile
+ bwipe!
+endfunc
+
+func Test_recursive_expr_substitute()
+ " this was reading invalid memory
+ let lines =<< trim END
+ func Repl(g, n)
+ s
+ r%:s000
+ endfunc
+ next 0
+ let caught = 0
+ s/\%')/\=Repl(0, 0)
+ qall!
+ END
+ call writefile(lines, 'XexprSubst', 'D')
+ call RunVim([], [], '--clean -S XexprSubst')
+endfunc
+
+" Test for the 2-letter and 3-letter :substitute commands
+func Test_substitute_short_cmd()
+ new
+ call setline(1, ['one', 'one one one'])
+ s/one/two
+ call cursor(2, 1)
+
+ " :sc
+ call feedkeys(":sc\<CR>y", 'xt')
+ call assert_equal('two one one', getline(2))
+
+ " :scg
+ call setline(2, 'one one one')
+ call feedkeys(":scg\<CR>nyq", 'xt')
+ call assert_equal('one two one', getline(2))
+
+ " :sci
+ call setline(2, 'ONE One onE')
+ call feedkeys(":sci\<CR>y", 'xt')
+ call assert_equal('two One onE', getline(2))
+
+ " :scI
+ set ignorecase
+ call setline(2, 'ONE One one')
+ call feedkeys(":scI\<CR>y", 'xt')
+ call assert_equal('ONE One two', getline(2))
+ set ignorecase&
+
+ " :scn
+ call setline(2, 'one one one')
+ let t = execute('scn')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('one one one', getline(2))
+
+ " :scp
+ call setline(2, "\tone one one")
+ redir => output
+ call feedkeys(":scp\<CR>y", 'xt')
+ redir END
+ call assert_equal(' two one one', output->split("\n")[-1])
+ call assert_equal("\ttwo one one", getline(2))
+
+ " :scl
+ call setline(2, "\tone one one")
+ redir => output
+ call feedkeys(":scl\<CR>y", 'xt')
+ redir END
+ call assert_equal("^Itwo one one$", output->split("\n")[-1])
+ call assert_equal("\ttwo one one", getline(2))
+
+ " :sgc
+ call setline(2, 'one one one one one')
+ call feedkeys(":sgc\<CR>nyyq", 'xt')
+ call assert_equal('one two two one one', getline(2))
+
+ " :sg
+ call setline(2, 'one one one')
+ sg
+ call assert_equal('two two two', getline(2))
+
+ " :sgi
+ call setline(2, 'ONE One onE')
+ sgi
+ call assert_equal('two two two', getline(2))
+
+ " :sgI
+ set ignorecase
+ call setline(2, 'ONE One one')
+ sgI
+ call assert_equal('ONE One two', getline(2))
+ set ignorecase&
+
+ " :sgn
+ call setline(2, 'one one one')
+ let t = execute('sgn')->split("\n")
+ call assert_equal(['3 matches on 1 line'], t)
+ call assert_equal('one one one', getline(2))
+
+ " :sgp
+ call setline(2, "\tone one one")
+ redir => output
+ sgp
+ redir END
+ call assert_equal(' two two two', output->split("\n")[-1])
+ call assert_equal("\ttwo two two", getline(2))
+
+ " :sgl
+ call setline(2, "\tone one one")
+ redir => output
+ sgl
+ redir END
+ call assert_equal("^Itwo two two$", output->split("\n")[-1])
+ call assert_equal("\ttwo two two", getline(2))
+
+ " :sgr
+ call setline(2, "one one one")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sgr
+ call assert_equal('xyz xyz xyz', getline(2))
+
+ " :sic
+ call cursor(1, 1)
+ s/one/two/e
+ call setline(2, "ONE One one")
+ call cursor(2, 1)
+ call feedkeys(":sic\<CR>y", 'xt')
+ call assert_equal('two One one', getline(2))
+
+ " :si
+ call setline(2, "ONE One one")
+ si
+ call assert_equal('two One one', getline(2))
+
+ " :siI
+ call setline(2, "ONE One one")
+ siI
+ call assert_equal('ONE One two', getline(2))
+
+ " :sin
+ call setline(2, 'ONE One onE')
+ let t = execute('sin')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('ONE One onE', getline(2))
+
+ " :sip
+ call setline(2, "\tONE One onE")
+ redir => output
+ sip
+ redir END
+ call assert_equal(' two One onE', output->split("\n")[-1])
+ call assert_equal("\ttwo One onE", getline(2))
+
+ " :sir
+ call setline(2, "ONE One onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sir
+ call assert_equal('xyz One onE', getline(2))
+
+ " :sIc
+ call cursor(1, 1)
+ s/one/two/e
+ call setline(2, "ONE One one")
+ call cursor(2, 1)
+ call feedkeys(":sIc\<CR>y", 'xt')
+ call assert_equal('ONE One two', getline(2))
+
+ " :sIg
+ call setline(2, "ONE one onE one")
+ sIg
+ call assert_equal('ONE two onE two', getline(2))
+
+ " :sIi
+ call setline(2, "ONE One one")
+ sIi
+ call assert_equal('two One one', getline(2))
+
+ " :sI
+ call setline(2, "ONE One one")
+ sI
+ call assert_equal('ONE One two', getline(2))
+
+ " :sIn
+ call setline(2, 'ONE One one')
+ let t = execute('sIn')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('ONE One one', getline(2))
+
+ " :sIp
+ call setline(2, "\tONE One one")
+ redir => output
+ sIp
+ redir END
+ call assert_equal(' ONE One two', output->split("\n")[-1])
+ call assert_equal("\tONE One two", getline(2))
+
+ " :sIl
+ call setline(2, "\tONE onE one")
+ redir => output
+ sIl
+ redir END
+ call assert_equal("^IONE onE two$", output->split("\n")[-1])
+ call assert_equal("\tONE onE two", getline(2))
+
+ " :sIr
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sIr
+ call assert_equal('ONE xyz onE', getline(2))
+
+ " :src
+ call setline(2, "ONE one one")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ call feedkeys(":src\<CR>y", 'xt')
+ call assert_equal('ONE xyz one', getline(2))
+
+ " :srg
+ call setline(2, "one one one")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ srg
+ call assert_equal('xyz xyz xyz', getline(2))
+
+ " :sri
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sri
+ call assert_equal('xyz one onE', getline(2))
+
+ " :srI
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ srI
+ call assert_equal('ONE xyz onE', getline(2))
+
+ " :srn
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ let t = execute('srn')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('ONE one onE', getline(2))
+
+ " :srp
+ call setline(2, "\tONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ redir => output
+ srp
+ redir END
+ call assert_equal(' ONE xyz onE', output->split("\n")[-1])
+ call assert_equal("\tONE xyz onE", getline(2))
+
+ " :srl
+ call setline(2, "\tONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ redir => output
+ srl
+ redir END
+ call assert_equal("^IONE xyz onE$", output->split("\n")[-1])
+ call assert_equal("\tONE xyz onE", getline(2))
+
+ " :sr
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sr
+ call assert_equal('ONE xyz onE', getline(2))
+
+ " :sce
+ s/abc/xyz/e
+ call assert_fails("sc", 'E486:')
+ sce
+ " :sge
+ call assert_fails("sg", 'E486:')
+ sge
+ " :sie
+ call assert_fails("si", 'E486:')
+ sie
+ " :sIe
+ call assert_fails("sI", 'E486:')
+ sIe
+
+ bw!
+endfunc
+
+" This should be done last to reveal a memory leak when vim_regsub_both() is
+" called to evaluate an expression but it is not used in a second call.
+func Test_z_substitute_expr_leak()
+ func SubExpr()
+ ~n
+ endfunc
+ silent! s/\%')/\=SubExpr()
+ delfunc SubExpr
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_suspend.vim b/src/testdir/test_suspend.vim
new file mode 100644
index 0000000..5175c0f
--- /dev/null
+++ b/src/testdir/test_suspend.vim
@@ -0,0 +1,121 @@
+" Test :suspend
+
+source check.vim
+source term_util.vim
+source shared.vim
+
+func CheckSuspended(buf, fileExists)
+ call WaitForAssert({-> assert_match('[$#] $', term_getline(a:buf, '.'))})
+
+ if a:fileExists
+ call assert_equal(['foo'], readfile('Xfoo'))
+ else
+ " Without 'autowrite', buffer should not be written.
+ call assert_equal(0, filereadable('Xfoo'))
+ endif
+
+ call term_sendkeys(a:buf, "fg\<CR>\<C-L>")
+ call WaitForAssert({-> assert_equal(' 1 foo', term_getline(a:buf, '.'))})
+endfunc
+
+func Test_suspend()
+ CheckFeature terminal
+ CheckExecutable /bin/sh
+
+ " Somehow the modifyOtherKeys response may get to the terminal when using
+ " Mac OS. Make t_RK and 'keyprotocol' empty to avoid that.
+ set t_RK= keyprotocol=
+
+ call WaitForResponses()
+
+ let buf = term_start('/bin/sh')
+ " Wait for shell prompt.
+ call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
+
+ call term_sendkeys(buf, v:progpath
+ \ . " --clean -X"
+ \ . " -c 'set nu keyprotocol='"
+ \ . " -c 'call setline(1, \"foo\")'"
+ \ . " Xfoo\<CR>")
+ " Cursor in terminal buffer should be on first line in spawned vim.
+ call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
+
+ for suspend_cmd in [":suspend\<CR>",
+ \ ":stop\<CR>",
+ \ ":suspend!\<CR>",
+ \ ":stop!\<CR>",
+ \ "\<C-Z>"]
+ " Suspend and wait for shell prompt.
+ call term_sendkeys(buf, suspend_cmd)
+ call CheckSuspended(buf, 0)
+ endfor
+
+ " Test that :suspend! with 'autowrite' writes content of buffers if modified.
+ call term_sendkeys(buf, ":set autowrite\<CR>")
+ call assert_equal(0, filereadable('Xfoo'))
+ call term_sendkeys(buf, ":suspend\<CR>")
+ " Wait for shell prompt.
+ call CheckSuspended(buf, 1)
+
+ " Quit gracefully to dump coverage information.
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call TermWait(buf)
+ " Wait until Vim actually exited and shell shows a prompt
+ call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
+ call StopShellInTerminal(buf)
+
+ exe buf . 'bwipe!'
+ call delete('Xfoo')
+endfunc
+
+func Test_suspend_autocmd()
+ CheckFeature terminal
+ CheckExecutable /bin/sh
+
+ " Somehow the modifyOtherKeys response may get to the terminal when using
+ " Mac OS. Make t_RK and 'keyprotocol' empty to avoid that.
+ set t_RK= keyprotocol=
+
+ call WaitForResponses()
+
+ let buf = term_start('/bin/sh', #{term_rows: 6})
+ " Wait for shell prompt.
+ call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
+
+ call term_sendkeys(buf, v:progpath
+ \ . " --clean -X"
+ \ . " -c 'set nu keyprotocol='"
+ \ . " -c 'let g:count = 0'"
+ \ . " -c 'au VimSuspend * let g:count += 1'"
+ \ . " -c 'au VimResume * let g:count += 1'"
+ \ . " -c 'call setline(1, \"foo\")'"
+ \ . " Xfoo\<CR>")
+ " Cursor in terminal buffer should be on first line in spawned vim.
+ call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
+
+ for suspend_cmd in [":suspend\<CR>",
+ \ ":stop\<CR>",
+ \ ":suspend!\<CR>",
+ \ ":stop!\<CR>",
+ \ "\<C-Z>"]
+ " Suspend and wait for shell prompt. Then "fg" will restore Vim.
+ call term_sendkeys(buf, suspend_cmd)
+ call CheckSuspended(buf, 0)
+ endfor
+
+ call term_sendkeys(buf, ":echo g:count\<CR>")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^10', term_getline(buf, 6))})
+
+ " Quit gracefully to dump coverage information.
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call TermWait(buf)
+ " Wait until Vim actually exited and shell shows a prompt
+ call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
+ call StopShellInTerminal(buf)
+
+ exe buf . 'bwipe!'
+ call delete('Xfoo')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_swap.vim b/src/testdir/test_swap.vim
new file mode 100644
index 0000000..7532655
--- /dev/null
+++ b/src/testdir/test_swap.vim
@@ -0,0 +1,576 @@
+" Tests for the swap feature
+
+source check.vim
+source shared.vim
+source term_util.vim
+
+func s:swapname()
+ return trim(execute('swapname'))
+endfunc
+
+" Tests for 'directory' option.
+func Test_swap_directory()
+ CheckUnix
+
+ let content = ['start of testfile',
+ \ 'line 2 Abcdefghij',
+ \ 'line 3 Abcdefghij',
+ \ 'end of testfile']
+ call writefile(content, 'Xtest1', 'D')
+
+ " '.', swap file in the same directory as file
+ set dir=.,~
+
+ " Verify that the swap file doesn't exist in the current directory
+ call assert_equal([], glob(".Xtest1*.swp", 1, 1, 1))
+ edit Xtest1
+ let swfname = s:swapname()
+ call assert_equal([swfname], glob(swfname, 1, 1, 1))
+
+ " './dir', swap file in a directory relative to the file
+ set dir=./Xtest2,.,~
+
+ call mkdir("Xtest2", 'R')
+ edit Xtest1
+ call assert_equal([], glob(swfname, 1, 1, 1))
+ let swfname = "Xtest2/Xtest1.swp"
+ call assert_equal(swfname, s:swapname())
+ call assert_equal([swfname], glob("Xtest2/*", 1, 1, 1))
+
+ " 'dir', swap file in directory relative to the current dir
+ set dir=Xtest.je,~
+
+ call mkdir("Xtest.je", 'R')
+ call writefile(content, 'Xtest2/Xtest3')
+ edit Xtest2/Xtest3
+ call assert_equal(["Xtest2/Xtest3"], glob("Xtest2/*", 1, 1, 1))
+ let swfname = "Xtest.je/Xtest3.swp"
+ call assert_equal(swfname, s:swapname())
+ call assert_equal([swfname], glob("Xtest.je/*", 1, 1, 1))
+
+ set dir&
+endfunc
+
+func Test_swap_group()
+ CheckUnix
+
+ let groups = split(system('groups'))
+ if len(groups) <= 1
+ throw 'Skipped: need at least two groups, got ' . string(groups)
+ endif
+
+ try
+ call delete('Xtest')
+ split Xtest
+ call setline(1, 'just some text')
+ wq
+ if system('ls -l Xtest') !~ ' ' . groups[0] . ' \d'
+ throw 'Skipped: test file does not have the first group'
+ else
+ silent !chmod 640 Xtest
+ call system('chgrp ' . groups[1] . ' Xtest')
+ if system('ls -l Xtest') !~ ' ' . groups[1] . ' \d'
+ throw 'Skipped: cannot set second group on test file'
+ else
+ split Xtest
+ let swapname = s:swapname()
+ call assert_match('Xtest', swapname)
+ " Group of swapfile must now match original file.
+ call assert_match(' ' . groups[1] . ' \d', system('ls -l ' . swapname))
+
+ bwipe!
+ endif
+ endif
+ finally
+ call delete('Xtest')
+ endtry
+endfunc
+
+func Test_missing_dir()
+ call mkdir('Xswapdir')
+ exe 'set directory=' . getcwd() . '/Xswapdir'
+
+ call assert_equal('', glob('foo'))
+ call assert_equal('', glob('bar'))
+ edit foo/x.txt
+ " This should not give a warning for an existing swap file.
+ split bar/x.txt
+ only
+
+ " Delete the buffer so that swap file is removed before we try to delete the
+ " directory. That fails on MS-Windows.
+ %bdelete!
+ set directory&
+ call delete('Xswapdir', 'rf')
+endfunc
+
+func Test_swapinfo()
+ new Xswapinfo
+ call setline(1, ['one', 'two', 'three'])
+ w
+ let fname = s:swapname()
+ call assert_match('Xswapinfo', fname)
+
+ " Check the tail appears in the list from swapfilelist(). The path depends
+ " on the system.
+ let tail = fnamemodify(fname, ":t")->fnameescape()
+ let nr = 0
+ for name in swapfilelist()
+ if name =~ tail .. '$'
+ let nr += 1
+ endif
+ endfor
+ call assert_equal(1, nr, 'not found in ' .. string(swapfilelist()))
+
+ let info = fname->swapinfo()
+ let ver = printf('VIM %d.%d', v:version / 100, v:version % 100)
+ call assert_equal(ver, info.version)
+
+ call assert_match('\w', info.user)
+ " host name is truncated to 39 bytes in the swap file
+ call assert_equal(hostname()[:38], info.host)
+ call assert_match('Xswapinfo', info.fname)
+ call assert_match(0, info.dirty)
+ call assert_equal(getpid(), info.pid)
+ call assert_match('^\d*$', info.mtime)
+ if has_key(info, 'inode')
+ call assert_match('\d', info.inode)
+ endif
+ bwipe!
+ call delete(fname)
+ call delete('Xswapinfo')
+
+ let info = swapinfo('doesnotexist')
+ call assert_equal('Cannot open file', info.error)
+
+ call writefile(['burp'], 'Xnotaswapfile', 'D')
+ let info = swapinfo('Xnotaswapfile')
+ call assert_equal('Cannot read file', info.error)
+ call delete('Xnotaswapfile')
+
+ call writefile([repeat('x', 10000)], 'Xnotaswapfile')
+ let info = swapinfo('Xnotaswapfile')
+ call assert_equal('Not a swap file', info.error)
+endfunc
+
+func Test_swapname()
+ edit Xtest1
+ let expected = s:swapname()
+ call assert_equal(expected, swapname('%'))
+
+ new Xtest2
+ let buf = bufnr('%')
+ let expected = s:swapname()
+ wincmd p
+ call assert_equal(expected, buf->swapname())
+
+ new Xtest3
+ setlocal noswapfile
+ call assert_equal('', swapname('%'))
+
+ bwipe!
+ call delete('Xtest1')
+ call delete('Xtest2')
+ call delete('Xtest3')
+endfunc
+
+func Test_swapfile_delete()
+ autocmd! SwapExists
+ function s:swap_exists()
+ let v:swapchoice = s:swap_choice
+ let s:swapname = v:swapname
+ let s:filename = expand('<afile>')
+ endfunc
+ augroup test_swapfile_delete
+ autocmd!
+ autocmd SwapExists * call s:swap_exists()
+ augroup END
+
+
+ " Create a valid swapfile by editing a file.
+ split XswapfileText
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close the file and recreate the swap file.
+ " Now editing the file will run into the process still existing
+ quit
+ call writefile(swapfile_bytes, swapfile_name, 'D')
+ let s:swap_choice = 'e'
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
+
+ " This test won't work as root because root can successfully run kill(1, 0)
+ if !IsRoot()
+ " Write the swapfile with a modified PID, now it will be automatically
+ " deleted. Process 0x3fffffff most likely does not exist.
+ let swapfile_bytes[24:27] = 0zffffff3f
+ call writefile(swapfile_bytes, swapfile_name)
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal('', s:swapname)
+ endif
+
+ " Now set the modified flag, the swap file will not be deleted
+ let swapfile_bytes[28 + 80 + 899] = 0x55
+ call writefile(swapfile_bytes, swapfile_name)
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
+
+ call delete('XswapfileText')
+ augroup test_swapfile_delete
+ autocmd!
+ augroup END
+ augroup! test_swapfile_delete
+endfunc
+
+func Test_swap_recover()
+ autocmd! SwapExists
+ augroup test_swap_recover
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'r'
+ augroup END
+
+ call mkdir('Xswap', 'R')
+ let $Xswap = 'foo' " Check for issue #4369.
+ set dir=Xswap//
+ " Create a valid swapfile by editing a file.
+ split Xswap/text
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close the file and recreate the swap file.
+ quit
+ call writefile(swapfile_bytes, swapfile_name, 'D')
+ " Edit the file again. This triggers recovery.
+ try
+ split Xswap/text
+ catch
+ " E308 should be caught, not E305.
+ call assert_exception('E308:') " Original file may have been changed
+ endtry
+ " The file should be recovered.
+ call assert_equal(['one', 'two', 'three'], getline(1, 3))
+ quit!
+
+ unlet $Xswap
+ set dir&
+ augroup test_swap_recover
+ autocmd!
+ augroup END
+ augroup! test_swap_recover
+endfunc
+
+func Test_swap_recover_ext()
+ autocmd! SwapExists
+ augroup test_swap_recover_ext
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'r'
+ augroup END
+
+ " Create a valid swapfile by editing a file with a special extension.
+ split Xtest.scr
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ write " write again to make sure the swapfile is created
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close and delete the file and recreate the swap file.
+ quit
+ call delete('Xtest.scr')
+ call writefile(swapfile_bytes, swapfile_name, 'D')
+ " Edit the file again. This triggers recovery.
+ try
+ split Xtest.scr
+ catch
+ " E308 should be caught, not E306.
+ call assert_exception('E308:') " Original file may have been changed
+ endtry
+ " The file should be recovered.
+ call assert_equal(['one', 'two', 'three'], getline(1, 3))
+ quit!
+
+ call delete('Xtest.scr')
+ augroup test_swap_recover_ext
+ autocmd!
+ augroup END
+ augroup! test_swap_recover_ext
+endfunc
+
+" Test for closing a split window automatically when a swap file is detected
+" and 'Q' is selected in the confirmation prompt.
+func Test_swap_split_win()
+ autocmd! SwapExists
+ augroup test_swap_splitwin
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'q'
+ augroup END
+
+ " Create a valid swapfile by editing a file with a special extension.
+ split Xtest.scr
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ write " write again to make sure the swapfile is created
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close and delete the file and recreate the swap file.
+ quit
+ call delete('Xtest.scr')
+ call writefile(swapfile_bytes, swapfile_name, 'D')
+ " Split edit the file again. This should fail to open the window
+ try
+ split Xtest.scr
+ catch
+ " E308 should be caught, not E306.
+ call assert_exception('E308:') " Original file may have been changed
+ endtry
+ call assert_equal(1, winnr('$'))
+
+ call delete('Xtest.scr')
+
+ augroup test_swap_splitwin
+ autocmd!
+ augroup END
+ augroup! test_swap_splitwin
+endfunc
+
+" Test for selecting 'q' in the attention prompt
+func Test_swap_prompt_splitwin()
+ CheckRunVimInTerminal
+
+ call writefile(['foo bar'], 'Xfile1', 'D')
+ edit Xfile1
+ preserve " should help to make sure the swap file exists
+
+ let buf = RunVimInTerminal('', {'rows': 20})
+ call term_sendkeys(buf, ":set nomore\n")
+ call term_sendkeys(buf, ":set noruler\n")
+
+ call term_sendkeys(buf, ":split Xfile1\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: $', term_getline(buf, 20))})
+ call term_sendkeys(buf, "q")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":\<CR>")
+ call WaitForAssert({-> assert_match('^:$', term_getline(buf, 20))})
+ call term_sendkeys(buf, ":echomsg winnr('$')\<CR>")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))})
+ call StopVimInTerminal(buf)
+
+ " This caused Vim to crash when typing "q" at the swap file prompt.
+ let buf = RunVimInTerminal('-c "au bufadd * let foo_w = wincol()"', {'rows': 18})
+ call term_sendkeys(buf, ":e Xfile1\<CR>")
+ call WaitForAssert({-> assert_match('More', term_getline(buf, 18))})
+ call term_sendkeys(buf, " ")
+ call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 18))})
+ call term_sendkeys(buf, "q")
+ call TermWait(buf)
+ " check that Vim is still running
+ call term_sendkeys(buf, ":echo 'hello'\<CR>")
+ call WaitForAssert({-> assert_match('^hello', term_getline(buf, 18))})
+ call term_sendkeys(buf, ":%bwipe!\<CR>")
+ call StopVimInTerminal(buf)
+
+ %bwipe!
+endfunc
+
+func Test_swap_symlink()
+ CheckUnix
+
+ call writefile(['text'], 'Xtestfile', 'D')
+ silent !ln -s -f Xtestfile Xtestlink
+
+ set dir=.
+
+ " Test that swap file uses the name of the file when editing through a
+ " symbolic link (so that editing the file twice is detected)
+ edit Xtestlink
+ call assert_match('Xtestfile\.swp$', s:swapname())
+ bwipe!
+
+ call mkdir('Xswapdir', 'R')
+ exe 'set dir=' . getcwd() . '/Xswapdir//'
+
+ " Check that this also works when 'directory' ends with '//'
+ edit Xtestlink
+ call assert_match('Xswapdir[/\\]%.*testdir%Xtestfile\.swp$', s:swapname())
+ bwipe!
+
+ set dir&
+ call delete('Xtestlink')
+endfunc
+
+func s:get_unused_pid(base)
+ if has('job')
+ " Execute 'echo' as a temporary job, and return its pid as an unused pid.
+ if has('win32')
+ let cmd = 'cmd /c echo'
+ else
+ let cmd = 'echo'
+ endif
+ let j = job_start(cmd)
+ while job_status(j) ==# 'run'
+ sleep 10m
+ endwhile
+ if job_status(j) ==# 'dead'
+ return job_info(j).process
+ endif
+ endif
+ " Must add four for MS-Windows to see it as a different one.
+ return a:base + 4
+endfunc
+
+func s:blob_to_pid(b)
+ return a:b[3] * 16777216 + a:b[2] * 65536 + a:b[1] * 256 + a:b[0]
+endfunc
+
+func s:pid_to_blob(i)
+ let b = 0z
+ let b[0] = and(a:i, 0xff)
+ let b[1] = and(a:i / 256, 0xff)
+ let b[2] = and(a:i / 65536, 0xff)
+ let b[3] = and(a:i / 16777216, 0xff)
+ return b
+endfunc
+
+func Test_swap_auto_delete()
+ " Create a valid swapfile by editing a file with a special extension.
+ split Xtest.scr
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ write " write again to make sure the swapfile is created
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Forget about the file, recreate the swap file, then edit it again. The
+ " swap file should be automatically deleted.
+ bwipe!
+ " Change the process ID to avoid the "still running" warning.
+ let swapfile_bytes[24:27] = s:pid_to_blob(s:get_unused_pid(
+ \ s:blob_to_pid(swapfile_bytes[24:27])))
+ call writefile(swapfile_bytes, swapfile_name, 'D')
+ edit Xtest.scr
+ " will end up using the same swap file after deleting the existing one
+ call assert_equal(swapfile_name, swapname('%'))
+ bwipe!
+
+ " create the swap file again, but change the host name so that it won't be
+ " deleted
+ autocmd! SwapExists
+ augroup test_swap_recover_ext
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'e'
+ augroup END
+
+ " change the host name
+ let swapfile_bytes[28 + 40] = swapfile_bytes[28 + 40] + 2
+ call writefile(swapfile_bytes, swapfile_name)
+ edit Xtest.scr
+ call assert_equal(1, filereadable(swapfile_name))
+ " will use another same swap file name
+ call assert_notequal(swapfile_name, swapname('%'))
+ bwipe!
+
+ call delete('Xtest.scr')
+ augroup test_swap_recover_ext
+ autocmd!
+ augroup END
+ augroup! test_swap_recover_ext
+endfunc
+
+" Test for renaming a buffer when the swap file is deleted out-of-band
+func Test_missing_swap_file()
+ CheckUnix
+ new Xfile2
+ call delete(swapname(''))
+ call assert_fails('file Xfile3', 'E301:')
+ call assert_equal('Xfile3', bufname())
+ call assert_true(bufexists('Xfile2'))
+ call assert_true(bufexists('Xfile3'))
+ %bw!
+endfunc
+
+" Test for :preserve command
+func Test_preserve()
+ new Xfile4
+ setlocal noswapfile
+ call assert_fails('preserve', 'E313:')
+ bw!
+endfunc
+
+" Test for the v:swapchoice variable
+func Test_swapchoice()
+ call writefile(['aaa', 'bbb'], 'Xfile5', 'D')
+ edit Xfile5
+ preserve
+ let swapfname = swapname('')
+ let b = readblob(swapfname)
+ bw!
+ call writefile(b, swapfname, 'D')
+
+ autocmd! SwapExists
+
+ " Test for v:swapchoice = 'o' (readonly)
+ augroup test_swapchoice
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'o'
+ augroup END
+ edit Xfile5
+ call assert_true(&readonly)
+ call assert_equal(['aaa', 'bbb'], getline(1, '$'))
+ %bw!
+ call assert_true(filereadable(swapfname))
+
+ " Test for v:swapchoice = 'a' (abort)
+ augroup test_swapchoice
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'a'
+ augroup END
+ try
+ edit Xfile5
+ catch /^Vim:Interrupt$/
+ endtry
+ call assert_equal('', @%)
+ call assert_true(bufexists('Xfile5'))
+ %bw!
+ call assert_true(filereadable(swapfname))
+
+ " Test for v:swapchoice = 'd' (delete)
+ augroup test_swapchoice
+ autocmd!
+ autocmd SwapExists * let v:swapchoice = 'd'
+ augroup END
+ edit Xfile5
+ call assert_equal('Xfile5', @%)
+ %bw!
+ call assert_false(filereadable(swapfname))
+
+ call delete(swapfname)
+ augroup test_swapchoice
+ autocmd!
+ augroup END
+ augroup! test_swapchoice
+endfunc
+
+func Test_no_swap_file()
+ call assert_equal("\nNo swap file", execute('swapname'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_syn_attr.vim b/src/testdir/test_syn_attr.vim
new file mode 100644
index 0000000..bf4dfbc
--- /dev/null
+++ b/src/testdir/test_syn_attr.vim
@@ -0,0 +1,834 @@
+" Test syntax highlighting functions.
+
+func Test_missing_attr()
+ hi Mine term=bold cterm=italic
+ call assert_equal('Mine', synIDattr(hlID("Mine"), "name"))
+ call assert_equal('', synIDattr("Mine"->hlID(), "bg", 'term'))
+ call assert_equal('', synIDattr("Mine"->hlID(), "fg", 'term'))
+ call assert_equal('', synIDattr("Mine"->hlID(), "sp", 'term'))
+ call assert_equal('1', synIDattr(hlID("Mine"), "bold", 'term'))
+ call assert_equal('1', synIDattr(hlID("Mine"), "italic", 'cterm'))
+ hi Mine term=reverse cterm=inverse
+ call assert_equal('1', synIDattr(hlID("Mine"), "reverse", 'term'))
+ call assert_equal('1', synIDattr(hlID("Mine"), "inverse", 'cterm'))
+
+ hi Mine term=underline cterm=standout gui=undercurl
+ call assert_equal('1', synIDattr(hlID("Mine"), "underline", 'term'))
+ call assert_equal('1', synIDattr(hlID("Mine"), "standout", 'cterm'))
+ call assert_equal('1', synIDattr("Mine"->hlID(), "undercurl", 'gui'))
+
+ hi Mine term=underdouble cterm=underdotted gui=underdashed
+ call assert_equal('1', synIDattr(hlID("Mine"), "underdouble", 'term'))
+ call assert_equal('1', synIDattr(hlID("Mine"), "underdotted", 'cterm'))
+ call assert_equal('1', synIDattr("Mine"->hlID(), "underdashed", 'gui'))
+
+ hi Mine term=nocombine gui=strikethrough
+ call assert_equal('1', synIDattr(hlID("Mine"), "strikethrough", 'gui'))
+ call assert_equal('1', synIDattr(hlID("Mine"), "nocombine", 'term'))
+ call assert_equal('', synIDattr(hlID("Mine"), "nocombine", 'gui'))
+ hi Mine term=NONE cterm=NONE gui=NONE
+ call assert_equal('', synIDattr(hlID("Mine"), "bold", 'term'))
+ call assert_equal('', synIDattr(hlID("Mine"), "italic", 'cterm'))
+ call assert_equal('', synIDattr(hlID("Mine"), "reverse", 'term'))
+ call assert_equal('', synIDattr(hlID("Mine"), "inverse", 'cterm'))
+ call assert_equal('', synIDattr(hlID("Mine"), "underline", 'term'))
+ call assert_equal('', synIDattr(hlID("Mine"), "standout", 'cterm'))
+ call assert_equal('', synIDattr(hlID("Mine"), "undercurl", 'gui'))
+ call assert_equal('', synIDattr(hlID("Mine"), "strikethrough", 'gui'))
+
+ if has('gui')
+ let fontname = getfontname()
+ if fontname == ''
+ let fontname = 'something'
+ endif
+ exe "hi Mine guifg=blue guibg=red font='" . fontname . "'"
+ call assert_equal('blue', synIDattr(hlID("Mine"), "fg", 'gui'))
+ call assert_equal('red', synIDattr(hlID("Mine"), "bg", 'gui'))
+ call assert_equal(fontname, synIDattr(hlID("Mine"), "font", 'gui'))
+ endif
+endfunc
+
+func Test_color_names()
+ let colors = [
+ \ 'AliceBlue',
+ \ 'AntiqueWhite',
+ \ 'AntiqueWhite1',
+ \ 'AntiqueWhite2',
+ \ 'AntiqueWhite3',
+ \ 'AntiqueWhite4',
+ \ 'BlanchedAlmond',
+ \ 'BlueViolet',
+ \ 'CadetBlue',
+ \ 'CadetBlue1',
+ \ 'CadetBlue2',
+ \ 'CadetBlue3',
+ \ 'CadetBlue4',
+ \ 'CornflowerBlue',
+ \ 'DarkBlue',
+ \ 'DarkCyan',
+ \ 'DarkGoldenrod',
+ \ 'DarkGoldenrod1',
+ \ 'DarkGoldenrod2',
+ \ 'DarkGoldenrod3',
+ \ 'DarkGoldenrod4',
+ \ 'DarkGray',
+ \ 'DarkGreen',
+ \ 'DarkGrey',
+ \ 'DarkKhaki',
+ \ 'DarkMagenta',
+ \ 'DarkOliveGreen',
+ \ 'DarkOliveGreen1',
+ \ 'DarkOliveGreen2',
+ \ 'DarkOliveGreen3',
+ \ 'DarkOliveGreen4',
+ \ 'DarkOrange',
+ \ 'DarkOrange1',
+ \ 'DarkOrange2',
+ \ 'DarkOrange3',
+ \ 'DarkOrange4',
+ \ 'DarkOrchid',
+ \ 'DarkOrchid1',
+ \ 'DarkOrchid2',
+ \ 'DarkOrchid3',
+ \ 'DarkOrchid4',
+ \ 'DarkRed',
+ \ 'DarkSalmon',
+ \ 'DarkSeaGreen',
+ \ 'DarkSeaGreen1',
+ \ 'DarkSeaGreen2',
+ \ 'DarkSeaGreen3',
+ \ 'DarkSeaGreen4',
+ \ 'DarkSlateBlue',
+ \ 'DarkSlateGray',
+ \ 'DarkSlateGray1',
+ \ 'DarkSlateGray2',
+ \ 'DarkSlateGray3',
+ \ 'DarkSlateGray4',
+ \ 'DarkSlateGrey',
+ \ 'DarkTurquoise',
+ \ 'DarkViolet',
+ \ 'DeepPink',
+ \ 'DeepPink1',
+ \ 'DeepPink2',
+ \ 'DeepPink3',
+ \ 'DeepPink4',
+ \ 'DeepSkyBlue',
+ \ 'DeepSkyBlue1',
+ \ 'DeepSkyBlue2',
+ \ 'DeepSkyBlue3',
+ \ 'DeepSkyBlue4',
+ \ 'DimGray',
+ \ 'DimGrey',
+ \ 'DodgerBlue',
+ \ 'DodgerBlue1',
+ \ 'DodgerBlue2',
+ \ 'DodgerBlue3',
+ \ 'DodgerBlue4',
+ \ 'FloralWhite',
+ \ 'ForestGreen',
+ \ 'GhostWhite',
+ \ 'GreenYellow',
+ \ 'HotPink',
+ \ 'HotPink1',
+ \ 'HotPink2',
+ \ 'HotPink3',
+ \ 'HotPink4',
+ \ 'IndianRed',
+ \ 'IndianRed1',
+ \ 'IndianRed2',
+ \ 'IndianRed3',
+ \ 'IndianRed4',
+ \ 'LavenderBlush',
+ \ 'LavenderBlush1',
+ \ 'LavenderBlush2',
+ \ 'LavenderBlush3',
+ \ 'LavenderBlush4',
+ \ 'LawnGreen',
+ \ 'LemonChiffon',
+ \ 'LemonChiffon1',
+ \ 'LemonChiffon2',
+ \ 'LemonChiffon3',
+ \ 'LemonChiffon4',
+ \ 'LightBlue',
+ \ 'LightBlue1',
+ \ 'LightBlue2',
+ \ 'LightBlue3',
+ \ 'LightBlue4',
+ \ 'LightCoral',
+ \ 'LightCyan',
+ \ 'LightCyan1',
+ \ 'LightCyan2',
+ \ 'LightCyan3',
+ \ 'LightCyan4',
+ \ 'LightGoldenrod',
+ \ 'LightGoldenrod1',
+ \ 'LightGoldenrod2',
+ \ 'LightGoldenrod3',
+ \ 'LightGoldenrod4',
+ \ 'LightGoldenrodYellow',
+ \ 'LightGray',
+ \ 'LightGreen',
+ \ 'LightGrey',
+ \ 'LightPink',
+ \ 'LightPink1',
+ \ 'LightPink2',
+ \ 'LightPink3',
+ \ 'LightPink4',
+ \ 'LightSalmon',
+ \ 'LightSalmon1',
+ \ 'LightSalmon2',
+ \ 'LightSalmon3',
+ \ 'LightSalmon4',
+ \ 'LightSeaGreen',
+ \ 'LightSkyBlue',
+ \ 'LightSkyBlue1',
+ \ 'LightSkyBlue2',
+ \ 'LightSkyBlue3',
+ \ 'LightSkyBlue4',
+ \ 'LightSlateBlue',
+ \ 'LightSlateGray',
+ \ 'LightSlateGrey',
+ \ 'LightSteelBlue',
+ \ 'LightSteelBlue1',
+ \ 'LightSteelBlue2',
+ \ 'LightSteelBlue3',
+ \ 'LightSteelBlue4',
+ \ 'LightYellow',
+ \ 'LightYellow1',
+ \ 'LightYellow2',
+ \ 'LightYellow3',
+ \ 'LightYellow4',
+ \ 'LimeGreen',
+ \ 'MediumAquamarine',
+ \ 'MediumBlue',
+ \ 'MediumOrchid',
+ \ 'MediumOrchid1',
+ \ 'MediumOrchid2',
+ \ 'MediumOrchid3',
+ \ 'MediumOrchid4',
+ \ 'MediumPurple',
+ \ 'MediumPurple1',
+ \ 'MediumPurple2',
+ \ 'MediumPurple3',
+ \ 'MediumPurple4',
+ \ 'MediumSeaGreen',
+ \ 'MediumSlateBlue',
+ \ 'MediumSpringGreen',
+ \ 'MediumTurquoise',
+ \ 'MediumVioletRed',
+ \ 'MidnightBlue',
+ \ 'MintCream',
+ \ 'MistyRose',
+ \ 'MistyRose1',
+ \ 'MistyRose2',
+ \ 'MistyRose3',
+ \ 'MistyRose4',
+ \ 'NavajoWhite',
+ \ 'NavajoWhite1',
+ \ 'NavajoWhite2',
+ \ 'NavajoWhite3',
+ \ 'NavajoWhite4',
+ \ 'NavyBlue',
+ \ 'OldLace',
+ \ 'OliveDrab',
+ \ 'OliveDrab1',
+ \ 'OliveDrab2',
+ \ 'OliveDrab3',
+ \ 'OliveDrab4',
+ \ 'OrangeRed',
+ \ 'OrangeRed1',
+ \ 'OrangeRed2',
+ \ 'OrangeRed3',
+ \ 'OrangeRed4',
+ \ 'PaleGoldenrod',
+ \ 'PaleGreen',
+ \ 'PaleGreen1',
+ \ 'PaleGreen2',
+ \ 'PaleGreen3',
+ \ 'PaleGreen4',
+ \ 'PaleTurquoise',
+ \ 'PaleTurquoise1',
+ \ 'PaleTurquoise2',
+ \ 'PaleTurquoise3',
+ \ 'PaleTurquoise4',
+ \ 'PaleVioletRed',
+ \ 'PaleVioletRed1',
+ \ 'PaleVioletRed2',
+ \ 'PaleVioletRed3',
+ \ 'PaleVioletRed4',
+ \ 'PapayaWhip',
+ \ 'PeachPuff',
+ \ 'PeachPuff1',
+ \ 'PeachPuff2',
+ \ 'PeachPuff3',
+ \ 'PeachPuff4',
+ \ 'PowderBlue',
+ \ 'RosyBrown',
+ \ 'RosyBrown1',
+ \ 'RosyBrown2',
+ \ 'RosyBrown3',
+ \ 'RosyBrown4',
+ \ 'RoyalBlue',
+ \ 'RoyalBlue1',
+ \ 'RoyalBlue2',
+ \ 'RoyalBlue3',
+ \ 'RoyalBlue4',
+ \ 'SaddleBrown',
+ \ 'SandyBrown',
+ \ 'SeaGreen',
+ \ 'SeaGreen1',
+ \ 'SeaGreen2',
+ \ 'SeaGreen3',
+ \ 'SeaGreen4',
+ \ 'SkyBlue',
+ \ 'SkyBlue1',
+ \ 'SkyBlue2',
+ \ 'SkyBlue3',
+ \ 'SkyBlue4',
+ \ 'SlateBlue',
+ \ 'SlateBlue1',
+ \ 'SlateBlue2',
+ \ 'SlateBlue3',
+ \ 'SlateBlue4',
+ \ 'SlateGray',
+ \ 'SlateGray1',
+ \ 'SlateGray2',
+ \ 'SlateGray3',
+ \ 'SlateGray4',
+ \ 'SlateGrey',
+ \ 'SpringGreen',
+ \ 'SpringGreen1',
+ \ 'SpringGreen2',
+ \ 'SpringGreen3',
+ \ 'SpringGreen4',
+ \ 'SteelBlue',
+ \ 'SteelBlue1',
+ \ 'SteelBlue2',
+ \ 'SteelBlue3',
+ \ 'SteelBlue4',
+ \ 'VioletRed',
+ \ 'VioletRed1',
+ \ 'VioletRed2',
+ \ 'VioletRed3',
+ \ 'VioletRed4',
+ \ 'WhiteSmoke',
+ \ 'YellowGreen',
+ \ 'alice blue',
+ \ 'antique white',
+ \ 'aquamarine',
+ \ 'aquamarine1',
+ \ 'aquamarine2',
+ \ 'aquamarine3',
+ \ 'aquamarine4',
+ \ 'azure',
+ \ 'azure1',
+ \ 'azure2',
+ \ 'azure3',
+ \ 'azure4',
+ \ 'beige',
+ \ 'bisque',
+ \ 'bisque1',
+ \ 'bisque2',
+ \ 'bisque3',
+ \ 'bisque4',
+ \ 'black',
+ \ 'blanched almond',
+ \ 'blue violet',
+ \ 'blue',
+ \ 'blue1',
+ \ 'blue2',
+ \ 'blue3',
+ \ 'blue4',
+ \ 'brown',
+ \ 'brown1',
+ \ 'brown2',
+ \ 'brown3',
+ \ 'brown4',
+ \ 'burlywood',
+ \ 'burlywood1',
+ \ 'burlywood2',
+ \ 'burlywood3',
+ \ 'burlywood4',
+ \ 'cadet blue',
+ \ 'chartreuse',
+ \ 'chartreuse1',
+ \ 'chartreuse2',
+ \ 'chartreuse3',
+ \ 'chartreuse4',
+ \ 'chocolate',
+ \ 'chocolate1',
+ \ 'chocolate2',
+ \ 'chocolate3',
+ \ 'chocolate4',
+ \ 'coral',
+ \ 'coral1',
+ \ 'coral2',
+ \ 'coral3',
+ \ 'coral4',
+ \ 'cornflower blue',
+ \ 'cornsilk',
+ \ 'cornsilk1',
+ \ 'cornsilk2',
+ \ 'cornsilk3',
+ \ 'cornsilk4',
+ \ 'cyan',
+ \ 'cyan1',
+ \ 'cyan2',
+ \ 'cyan3',
+ \ 'cyan4',
+ \ 'dark blue',
+ \ 'dark cyan',
+ \ 'dark goldenrod',
+ \ 'dark gray',
+ \ 'dark green',
+ \ 'dark grey',
+ \ 'dark khaki',
+ \ 'dark magenta',
+ \ 'dark olive green',
+ \ 'dark orange',
+ \ 'dark orchid',
+ \ 'dark red',
+ \ 'dark salmon',
+ \ 'dark sea green',
+ \ 'dark slate blue',
+ \ 'dark slate gray',
+ \ 'dark slate grey',
+ \ 'dark turquoise',
+ \ 'dark violet',
+ \ 'darkblue',
+ \ 'darkcyan',
+ \ 'darkgray',
+ \ 'darkgreen',
+ \ 'darkgrey',
+ \ 'darkmagenta',
+ \ 'darkred',
+ \ 'darkyellow',
+ \ 'deep pink',
+ \ 'deep sky blue',
+ \ 'dim gray',
+ \ 'dim grey',
+ \ 'dodger blue',
+ \ 'firebrick',
+ \ 'firebrick1',
+ \ 'firebrick2',
+ \ 'firebrick3',
+ \ 'firebrick4',
+ \ 'floral white',
+ \ 'forest green',
+ \ 'gainsboro',
+ \ 'ghost white',
+ \ 'gold',
+ \ 'gold1',
+ \ 'gold2',
+ \ 'gold3',
+ \ 'gold4',
+ \ 'goldenrod',
+ \ 'goldenrod1',
+ \ 'goldenrod2',
+ \ 'goldenrod3',
+ \ 'goldenrod4',
+ \ 'gray',
+ \ 'gray0',
+ \ 'gray1',
+ \ 'gray10',
+ \ 'gray100',
+ \ 'gray11',
+ \ 'gray12',
+ \ 'gray13',
+ \ 'gray14',
+ \ 'gray15',
+ \ 'gray16',
+ \ 'gray17',
+ \ 'gray18',
+ \ 'gray19',
+ \ 'gray2',
+ \ 'gray20',
+ \ 'gray21',
+ \ 'gray22',
+ \ 'gray23',
+ \ 'gray24',
+ \ 'gray25',
+ \ 'gray26',
+ \ 'gray27',
+ \ 'gray28',
+ \ 'gray29',
+ \ 'gray3',
+ \ 'gray30',
+ \ 'gray31',
+ \ 'gray32',
+ \ 'gray33',
+ \ 'gray34',
+ \ 'gray35',
+ \ 'gray36',
+ \ 'gray37',
+ \ 'gray38',
+ \ 'gray39',
+ \ 'gray4',
+ \ 'gray40',
+ \ 'gray41',
+ \ 'gray42',
+ \ 'gray43',
+ \ 'gray44',
+ \ 'gray45',
+ \ 'gray46',
+ \ 'gray47',
+ \ 'gray48',
+ \ 'gray49',
+ \ 'gray5',
+ \ 'gray50',
+ \ 'gray51',
+ \ 'gray52',
+ \ 'gray53',
+ \ 'gray54',
+ \ 'gray55',
+ \ 'gray56',
+ \ 'gray57',
+ \ 'gray58',
+ \ 'gray59',
+ \ 'gray6',
+ \ 'gray60',
+ \ 'gray61',
+ \ 'gray62',
+ \ 'gray63',
+ \ 'gray64',
+ \ 'gray65',
+ \ 'gray66',
+ \ 'gray67',
+ \ 'gray68',
+ \ 'gray69',
+ \ 'gray7',
+ \ 'gray70',
+ \ 'gray71',
+ \ 'gray72',
+ \ 'gray73',
+ \ 'gray74',
+ \ 'gray75',
+ \ 'gray76',
+ \ 'gray77',
+ \ 'gray78',
+ \ 'gray79',
+ \ 'gray8',
+ \ 'gray80',
+ \ 'gray81',
+ \ 'gray82',
+ \ 'gray83',
+ \ 'gray84',
+ \ 'gray85',
+ \ 'gray86',
+ \ 'gray87',
+ \ 'gray88',
+ \ 'gray89',
+ \ 'gray9',
+ \ 'gray90',
+ \ 'gray91',
+ \ 'gray92',
+ \ 'gray93',
+ \ 'gray94',
+ \ 'gray95',
+ \ 'gray96',
+ \ 'gray97',
+ \ 'gray98',
+ \ 'gray99',
+ \ 'green yellow',
+ \ 'green',
+ \ 'green1',
+ \ 'green2',
+ \ 'green3',
+ \ 'green4',
+ \ 'grey',
+ \ 'grey0',
+ \ 'grey1',
+ \ 'grey10',
+ \ 'grey100',
+ \ 'grey11',
+ \ 'grey12',
+ \ 'grey13',
+ \ 'grey14',
+ \ 'grey15',
+ \ 'grey16',
+ \ 'grey17',
+ \ 'grey18',
+ \ 'grey19',
+ \ 'grey2',
+ \ 'grey20',
+ \ 'grey21',
+ \ 'grey22',
+ \ 'grey23',
+ \ 'grey24',
+ \ 'grey25',
+ \ 'grey26',
+ \ 'grey27',
+ \ 'grey28',
+ \ 'grey29',
+ \ 'grey3',
+ \ 'grey30',
+ \ 'grey31',
+ \ 'grey32',
+ \ 'grey33',
+ \ 'grey34',
+ \ 'grey35',
+ \ 'grey36',
+ \ 'grey37',
+ \ 'grey38',
+ \ 'grey39',
+ \ 'grey4',
+ \ 'grey40',
+ \ 'grey41',
+ \ 'grey42',
+ \ 'grey43',
+ \ 'grey44',
+ \ 'grey45',
+ \ 'grey46',
+ \ 'grey47',
+ \ 'grey48',
+ \ 'grey49',
+ \ 'grey5',
+ \ 'grey50',
+ \ 'grey51',
+ \ 'grey52',
+ \ 'grey53',
+ \ 'grey54',
+ \ 'grey55',
+ \ 'grey56',
+ \ 'grey57',
+ \ 'grey58',
+ \ 'grey59',
+ \ 'grey6',
+ \ 'grey60',
+ \ 'grey61',
+ \ 'grey62',
+ \ 'grey63',
+ \ 'grey64',
+ \ 'grey65',
+ \ 'grey66',
+ \ 'grey67',
+ \ 'grey68',
+ \ 'grey69',
+ \ 'grey7',
+ \ 'grey70',
+ \ 'grey71',
+ \ 'grey72',
+ \ 'grey73',
+ \ 'grey74',
+ \ 'grey75',
+ \ 'grey76',
+ \ 'grey77',
+ \ 'grey78',
+ \ 'grey79',
+ \ 'grey8',
+ \ 'grey80',
+ \ 'grey81',
+ \ 'grey82',
+ \ 'grey83',
+ \ 'grey84',
+ \ 'grey85',
+ \ 'grey86',
+ \ 'grey87',
+ \ 'grey88',
+ \ 'grey89',
+ \ 'grey9',
+ \ 'grey90',
+ \ 'grey91',
+ \ 'grey92',
+ \ 'grey93',
+ \ 'grey94',
+ \ 'grey95',
+ \ 'grey96',
+ \ 'grey97',
+ \ 'grey98',
+ \ 'grey99',
+ \ 'honeydew',
+ \ 'honeydew1',
+ \ 'honeydew2',
+ \ 'honeydew3',
+ \ 'honeydew4',
+ \ 'hot pink',
+ \ 'indian red',
+ \ 'ivory',
+ \ 'ivory1',
+ \ 'ivory2',
+ \ 'ivory3',
+ \ 'ivory4',
+ \ 'khaki',
+ \ 'khaki1',
+ \ 'khaki2',
+ \ 'khaki3',
+ \ 'khaki4',
+ \ 'lavender blush',
+ \ 'lavender',
+ \ 'lawn green',
+ \ 'lemon chiffon',
+ \ 'light blue',
+ \ 'light coral',
+ \ 'light cyan',
+ \ 'light goldenrod yellow',
+ \ 'light goldenrod',
+ \ 'light gray',
+ \ 'light green',
+ \ 'light grey',
+ \ 'light pink',
+ \ 'light salmon',
+ \ 'light sea green',
+ \ 'light sky blue',
+ \ 'light slate blue',
+ \ 'light slate gray',
+ \ 'light slate grey',
+ \ 'light steel blue',
+ \ 'light yellow',
+ \ 'lightblue',
+ \ 'lightcyan',
+ \ 'lightgray',
+ \ 'lightgreen',
+ \ 'lightgrey',
+ \ 'lightmagenta',
+ \ 'lightred',
+ \ 'lightyellow',
+ \ 'lime green',
+ \ 'linen',
+ \ 'magenta',
+ \ 'magenta1',
+ \ 'magenta2',
+ \ 'magenta3',
+ \ 'magenta4',
+ \ 'maroon',
+ \ 'maroon1',
+ \ 'maroon2',
+ \ 'maroon3',
+ \ 'maroon4',
+ \ 'medium aquamarine',
+ \ 'medium blue',
+ \ 'medium orchid',
+ \ 'medium purple',
+ \ 'medium sea green',
+ \ 'medium slate blue',
+ \ 'medium spring green',
+ \ 'medium turquoise',
+ \ 'medium violet red',
+ \ 'midnight blue',
+ \ 'mint cream',
+ \ 'misty rose',
+ \ 'moccasin',
+ \ 'navajo white',
+ \ 'navy blue',
+ \ 'navy',
+ \ 'old lace',
+ \ 'olive drab',
+ \ 'orange red',
+ \ 'orange',
+ \ 'orange1',
+ \ 'orange2',
+ \ 'orange3',
+ \ 'orange4',
+ \ 'orchid',
+ \ 'orchid1',
+ \ 'orchid2',
+ \ 'orchid3',
+ \ 'orchid4',
+ \ 'pale goldenrod',
+ \ 'pale green',
+ \ 'pale turquoise',
+ \ 'pale violet red',
+ \ 'papaya whip',
+ \ 'peach puff',
+ \ 'peru',
+ \ 'pink',
+ \ 'pink1',
+ \ 'pink2',
+ \ 'pink3',
+ \ 'pink4',
+ \ 'plum',
+ \ 'plum1',
+ \ 'plum2',
+ \ 'plum3',
+ \ 'plum4',
+ \ 'powder blue',
+ \ 'purple',
+ \ 'purple1',
+ \ 'purple2',
+ \ 'purple3',
+ \ 'purple4',
+ \ 'red',
+ \ 'red1',
+ \ 'red2',
+ \ 'red3',
+ \ 'red4',
+ \ 'rosy brown',
+ \ 'royal blue',
+ \ 'saddle brown',
+ \ 'salmon',
+ \ 'salmon1',
+ \ 'salmon2',
+ \ 'salmon3',
+ \ 'salmon4',
+ \ 'sandy brown',
+ \ 'sea green',
+ \ 'seagreen',
+ \ 'seashell',
+ \ 'seashell1',
+ \ 'seashell2',
+ \ 'seashell3',
+ \ 'seashell4',
+ \ 'sienna',
+ \ 'sienna1',
+ \ 'sienna2',
+ \ 'sienna3',
+ \ 'sienna4',
+ \ 'sky blue',
+ \ 'slate blue',
+ \ 'slate gray',
+ \ 'slate grey',
+ \ 'slateblue',
+ \ 'snow',
+ \ 'snow1',
+ \ 'snow2',
+ \ 'snow3',
+ \ 'snow4',
+ \ 'spring green',
+ \ 'steel blue',
+ \ 'tan',
+ \ 'tan1',
+ \ 'tan2',
+ \ 'tan3',
+ \ 'tan4',
+ \ 'thistle',
+ \ 'thistle1',
+ \ 'thistle2',
+ \ 'thistle3',
+ \ 'thistle4',
+ \ 'tomato',
+ \ 'tomato1',
+ \ 'tomato2',
+ \ 'tomato3',
+ \ 'tomato4',
+ \ 'turquoise',
+ \ 'turquoise1',
+ \ 'turquoise2',
+ \ 'turquoise3',
+ \ 'turquoise4',
+ \ 'violet red',
+ \ 'violet',
+ \ 'wheat',
+ \ 'wheat1',
+ \ 'wheat2',
+ \ 'wheat3',
+ \ 'wheat4',
+ \ 'white smoke',
+ \ 'white',
+ \ 'yellow green',
+ \ 'yellow',
+ \ 'yellow1',
+ \ 'yellow2',
+ \ 'yellow3',
+ \ 'yellow4',
+ \ ]
+ for color in colors
+ " just test that the color name can be found.
+ exe "hi Mine guifg='" . color . "'"
+ endfor
+
+ " case is ignored
+ hi Mine guifg=blanchedalmond
+ hi Mine guifg=BLANCHEDALMOND
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_syntax.vim b/src/testdir/test_syntax.vim
new file mode 100644
index 0000000..8c56730
--- /dev/null
+++ b/src/testdir/test_syntax.vim
@@ -0,0 +1,981 @@
+" Test for syntax and syntax iskeyword option
+
+source check.vim
+CheckFeature syntax
+
+source view_util.vim
+source screendump.vim
+
+func GetSyntaxItem(pat)
+ let c = ''
+ let a = ['a', getreg('a'), getregtype('a')]
+ 0
+ redraw!
+ call search(a:pat, 'W')
+ let synid = synID(line('.'), col('.'), 1)
+ while synid == synID(line('.'), col('.'), 1)
+ norm! v"ay
+ " stop at whitespace
+ if @a =~# '\s'
+ break
+ endif
+ let c .= @a
+ norm! l
+ endw
+ call call('setreg', a)
+ 0
+ return c
+endfunc
+
+func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "")
+ " Assert that the characters starting at a given (line, col)
+ " sequentially match the expected highlight groups.
+ " If groups are provided as a string, each character is assumed to be a
+ " group and spaces represent no group, useful for visually describing tests.
+ let l:expectedGroups = type(a:expected) == v:t_string
+ \ ? a:expected->split('\zs')->map({_, v -> trim(v)})
+ \ : a:expected
+ let l:errors = 0
+ let l:msg = (a:msg->empty() ? "" : a:msg .. ": ")
+ \ .. "Wrong highlight group at " .. a:lnum .. ","
+
+ for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1)
+ let l:errors += synID(a:lnum, l:i, a:trans)
+ \ ->synIDattr("name")
+ \ ->assert_equal(l:expectedGroups[l:i - 1],
+ \ l:msg .. l:i)
+ endfor
+endfunc
+
+func Test_syn_iskeyword()
+ new
+ call setline(1, [
+ \ 'CREATE TABLE FOOBAR(',
+ \ ' DLTD_BY VARCHAR2(100)',
+ \ ');',
+ \ ''])
+
+ syntax on
+ set ft=sql
+ syn match SYN /C\k\+\>/
+ hi link SYN ErrorMsg
+ call assert_equal('DLTD_BY', GetSyntaxItem('DLTD'))
+ /\<D\k\+\>/:norm! ygn
+ call assert_equal('DLTD_BY', @0)
+ redir @c
+ syn iskeyword
+ redir END
+ call assert_equal("\nsyntax iskeyword not set", @c)
+
+ syn iskeyword @,48-57,_,192-255
+ redir @c
+ syn iskeyword
+ redir END
+ call assert_equal("\nsyntax iskeyword @,48-57,_,192-255", @c)
+
+ setlocal isk-=_
+ call assert_equal('DLTD_BY', GetSyntaxItem('DLTD'))
+ /\<D\k\+\>/:norm! ygn
+ let b2 = @0
+ call assert_equal('DLTD', @0)
+
+ syn iskeyword clear
+ redir @c
+ syn iskeyword
+ redir END
+ call assert_equal("\nsyntax iskeyword not set", @c)
+
+ quit!
+endfunc
+
+func Test_syntax_after_reload()
+ split Xsomefile
+ call setline(1, ['hello', 'there'])
+ w!
+ only!
+ setl filetype=hello
+ au FileType hello let g:gotit = 1
+ call assert_false(exists('g:gotit'))
+ edit other
+ buf Xsomefile
+ call assert_equal('hello', &filetype)
+ call assert_true(exists('g:gotit'))
+ call delete('Xsomefile')
+endfunc
+
+func Test_syntime()
+ CheckFeature profile
+
+ syntax on
+ syntime on
+ let a = execute('syntime report')
+ call assert_equal("\nNo Syntax items defined for this buffer", a)
+
+ let a = execute('syntime clear')
+ call assert_equal("\nNo Syntax items defined for this buffer", a)
+
+ view ../memfile_test.c
+ setfiletype cpp
+ redraw
+ let a = execute('syntime report')
+ call assert_match('^ TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a)
+ call assert_match(' \d*\.\d* \+[^0]\d* .* cppRawString ', a)
+ call assert_match(' \d*\.\d* \+[^0]\d* .* cppNumber ', a)
+
+ syntime off
+ syntime clear
+ let a = execute('syntime report')
+ call assert_match('^ TOTAL *COUNT *MATCH *SLOWEST *AVERAGE *NAME *PATTERN', a)
+ call assert_notmatch('.* cppRawString *', a)
+ call assert_notmatch('.* cppNumber*', a)
+ call assert_notmatch('[1-9]', a)
+
+ call assert_fails('syntime abc', 'E475:')
+
+ syntax clear
+ let a = execute('syntime report')
+ call assert_equal("\nNo Syntax items defined for this buffer", a)
+
+ bd
+endfunc
+
+func Test_syntime_completion()
+ CheckFeature profile
+
+ call feedkeys(":syntime \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syntime clear off on report', @:)
+endfunc
+
+func Test_syntax_list()
+ syntax on
+ let a = execute('syntax list')
+ call assert_equal("\nNo Syntax items defined for this buffer", a)
+
+ view ../memfile_test.c
+ setfiletype c
+
+ let a = execute('syntax list')
+ call assert_match('cInclude*', a)
+ call assert_match('cDefine', a)
+
+ let a = execute('syntax list cDefine')
+ call assert_notmatch('cInclude*', a)
+ call assert_match('cDefine', a)
+ call assert_match(' links to Macro$', a)
+
+ call assert_fails('syntax list ABCD', 'E28:')
+ call assert_fails('syntax list @ABCD', 'E392:')
+
+ syntax clear
+ let a = execute('syntax list')
+ call assert_equal("\nNo Syntax items defined for this buffer", a)
+
+ syntax keyword Type int containedin=g1 skipwhite skipempty skipnl nextgroup=Abc
+ let exp = "Type xxx containedin=g1 nextgroup=Abc skipnl skipwhite skipempty int"
+ call assert_equal(exp, split(execute("syntax list"), "\n")[1])
+
+ bd
+endfunc
+
+func Test_syntax_completion()
+ call feedkeys(":syn \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syn case clear cluster conceal enable foldlevel include iskeyword keyword list manual match off on region reset spell sync', @:)
+
+ call feedkeys(":syn case \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syn case ignore match', @:)
+
+ call feedkeys(":syn spell \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syn spell default notoplevel toplevel', @:)
+
+ call feedkeys(":syn sync \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:)
+
+ " Check that clearing "Aap" avoids it showing up before Boolean.
+ hi Aap ctermfg=blue
+ call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('^"syn list Aap Boolean Character ', @:)
+ hi clear Aap
+
+ call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('^"syn list Boolean Character ', @:)
+
+ call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('^"syn match Boolean Character ', @:)
+
+ syn cluster Aax contains=Aap
+ call feedkeys(":syn list @A\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('^"syn list @Aax', @:)
+endfunc
+
+func Test_echohl_completion()
+ call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"echohl NonText Normal none', @:)
+endfunc
+
+func Test_syntax_arg_skipped()
+ syn clear
+ syntax case ignore
+ if 0
+ syntax case match
+ endif
+ call assert_match('case ignore', execute('syntax case'))
+
+ syn keyword Foo foo
+ call assert_match('Foo', execute('syntax'))
+ syn clear
+ call assert_match('case match', execute('syntax case'))
+ call assert_notmatch('Foo', execute('syntax'))
+
+ if has('conceal')
+ syn clear
+ syntax conceal on
+ if 0
+ syntax conceal off
+ endif
+ call assert_match('conceal on', execute('syntax conceal'))
+ syn clear
+ call assert_match('conceal off', execute('syntax conceal'))
+
+ syntax conceal on
+ syntax conceal off
+ call assert_match('conceal off', execute('syntax conceal'))
+ endif
+
+ syntax region Bar start=/</ end=/>/
+ if 0
+ syntax region NotTest start=/</ end=/>/ contains=@Spell
+ endif
+ call assert_match('Bar', execute('syntax'))
+ call assert_notmatch('NotTest', execute('syntax'))
+ call assert_notmatch('Spell', execute('syntax'))
+
+ hi Foo ctermfg=blue
+ let a = execute('hi Foo')
+ if 0
+ syntax rest
+ endif
+ call assert_equal(a, execute('hi Foo'))
+ hi clear Bar
+ hi clear Foo
+
+ set ft=tags
+ syn off
+ if 0
+ syntax enable
+ endif
+ call assert_match('No Syntax items defined', execute('syntax'))
+ syntax enable
+ call assert_match('tagComment', execute('syntax'))
+ set ft=
+
+ syn clear
+ if 0
+ syntax include @Spell nothing
+ endif
+ call assert_notmatch('Spell', execute('syntax'))
+
+ syn clear
+ syn iskeyword 48-57,$,_
+ call assert_match('48-57,$,_', execute('syntax iskeyword'))
+ if 0
+ syn clear
+ syn iskeyword clear
+ endif
+ call assert_match('48-57,$,_', execute('syntax iskeyword'))
+ syn iskeyword clear
+ call assert_match('not set', execute('syntax iskeyword'))
+ syn iskeyword 48-57,$,_
+ syn clear
+ call assert_match('not set', execute('syntax iskeyword'))
+
+ syn clear
+ syn keyword Foo foo
+ if 0
+ syn keyword NotAdded bar
+ endif
+ call assert_match('Foo', execute('syntax'))
+ call assert_notmatch('NotAdded', execute('highlight'))
+
+ syn clear
+ syn keyword Foo foo
+ call assert_match('Foo', execute('syntax'))
+ call assert_match('Foo', execute('syntax list'))
+ call assert_notmatch('Foo', execute('if 0 | syntax | endif'))
+ call assert_notmatch('Foo', execute('if 0 | syntax list | endif'))
+
+ syn clear
+ syn match Fopi /asdf/
+ if 0
+ syn match Fopx /asdf/
+ endif
+ call assert_match('Fopi', execute('syntax'))
+ call assert_notmatch('Fopx', execute('syntax'))
+
+ syn clear
+ syn spell toplevel
+ call assert_match('spell toplevel', execute('syntax spell'))
+ if 0
+ syn spell notoplevel
+ endif
+ call assert_match('spell toplevel', execute('syntax spell'))
+ syn spell notoplevel
+ call assert_match('spell notoplevel', execute('syntax spell'))
+ syn spell default
+ call assert_match('spell default', execute('syntax spell'))
+
+ syn clear
+ if 0
+ syntax cluster Spell
+ endif
+ call assert_notmatch('Spell', execute('syntax'))
+
+ syn clear
+ syn keyword Foo foo
+ syn sync ccomment
+ syn sync maxlines=5
+ if 0
+ syn sync maxlines=11
+ endif
+ call assert_match('on C-style comments', execute('syntax sync'))
+ call assert_match('maximal 5 lines', execute('syntax sync'))
+ syn sync clear
+ if 0
+ syn sync ccomment
+ endif
+ call assert_notmatch('on C-style comments', execute('syntax sync'))
+ syn sync fromstart
+ call assert_match('syncing starts at the first line', execute('syntax sync'))
+
+ syn clear
+endfunc
+
+" Check for an error. Used when multiple errors are thrown and we are checking
+" for an earliest error.
+func AssertFails(cmd, errcode)
+ let save_exception = ''
+ try
+ exe a:cmd
+ catch
+ let save_exception = v:exception
+ endtry
+ call assert_match(a:errcode, save_exception)
+endfunc
+
+func Test_syntax_invalid_arg()
+ call assert_fails('syntax case asdf', 'E390:')
+ if has('conceal')
+ call assert_fails('syntax conceal asdf', 'E390:')
+ endif
+ call assert_fails('syntax spell asdf', 'E390:')
+ call assert_fails('syntax clear @ABCD', 'E391:')
+ call assert_fails('syntax include random_file', 'E484:')
+ call assert_fails('syntax include <afile>', 'E495:')
+ call assert_fails('syntax sync x', 'E404:')
+ call assert_fails('syntax keyword Abc a[', 'E789:')
+ call assert_fails('syntax keyword Abc a[bc]d', 'E890:')
+ call assert_fails('syntax cluster Abc add=A add=', 'E406:')
+
+ " Test for too many \z\( and unmatched \z\(
+ " Not able to use assert_fails() here because both E50:/E879: and E475:
+ " messages are emitted.
+ set regexpengine=1
+ call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E52:')
+
+ let cmd = "syntax region MyRegion start='"
+ let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'"
+ call AssertFails(cmd, 'E50:')
+
+ set regexpengine=2
+ call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E54:')
+
+ let cmd = "syntax region MyRegion start='"
+ let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'"
+ call AssertFails(cmd, 'E879:')
+ set regexpengine&
+
+ call AssertFails('syntax keyword cMyItem grouphere G1', 'E393:')
+ call AssertFails('syntax sync match Abc grouphere MyItem "abc"', 'E394:')
+ call AssertFails('syn keyword Type contains int', 'E395:')
+ call assert_fails('syntax include @Xxx', 'E397:')
+ call AssertFails('syntax region X start', 'E398:')
+ call assert_fails('syntax region X start="{"', 'E399:')
+ call AssertFails('syntax cluster contains=Abc', 'E400:')
+ call AssertFails("syntax match Character /'.'", 'E401:')
+ call AssertFails("syntax match Character /'.'/a", 'E402:')
+ call assert_fails('syntax sync linecont /\%(/', 'E53:')
+ call assert_fails('syntax sync linecont /pat', 'E404:')
+ call assert_fails('syntax sync linecont', 'E404:')
+ call assert_fails('syntax sync linecont /pat1/ linecont /pat2/', 'E403:')
+ call assert_fails('syntax sync minlines=a', 'E404:')
+ call AssertFails('syntax match ABC /x/ contains=', 'E406:')
+ call AssertFails("syntax match Character contains /'.'/", 'E405:')
+ call AssertFails('syntax match ccFoo "Foo" nextgroup=ALLBUT,F', 'E407:')
+ call AssertFails('syntax region Block start="{" contains=F,ALLBUT', 'E408:')
+ call AssertFails("syntax match Characters contains=a.*x /'.'/", 'E409:')
+ call assert_fails('syntax match Search /abc/ contains=ALLBUT,/\%(/', 'E53:')
+endfunc
+
+func Test_syn_sync()
+ syntax region HereGroup start=/this/ end=/that/
+ syntax sync match SyncHere grouphere HereGroup "pattern"
+ call assert_match('SyncHere', execute('syntax sync'))
+ syn sync clear
+ call assert_notmatch('SyncHere', execute('syntax sync'))
+ syn clear
+endfunc
+
+func Test_syn_clear()
+ syntax keyword Foo foo
+ syntax keyword Bar tar
+ call assert_match('Foo', execute('syntax'))
+ call assert_match('Bar', execute('syntax'))
+ call assert_equal('Foo', synIDattr(hlID("Foo"), "name"))
+ syn clear Foo
+ call assert_notmatch('Foo', execute('syntax'))
+ call assert_match('Bar', execute('syntax'))
+ call assert_equal('Foo', synIDattr(hlID("Foo"), "name"))
+ syn clear Foo Bar
+ call assert_notmatch('Foo', execute('syntax'))
+ call assert_notmatch('Bar', execute('syntax'))
+ hi clear Foo
+ call assert_equal('Foo', synIDattr(hlID("Foo"), "name"))
+ hi clear Bar
+ call assert_fails('syntax clear invalid_syngroup', 'E28:')
+endfunc
+
+func Test_invalid_name()
+ syn clear
+ syn keyword Nop yes
+ call assert_fails("syntax keyword Wr\x17ong bar", 'E669:')
+ syntax keyword @Wrong bar
+ call assert_match('W18:', execute('1messages'))
+ syn clear
+ hi clear Nop
+ hi clear @Wrong
+endfunc
+
+func Test_ownsyntax()
+ new XfooOwnSyntax
+ call setline(1, '#define FOO')
+ syntax on
+ set filetype=c
+
+ ownsyntax perl
+ " this should not crash
+ set
+
+ call assert_equal('perlComment', synIDattr(synID(line('.'), col('.'), 1), 'name'))
+ call assert_equal('c', b:current_syntax)
+ call assert_equal('perl', w:current_syntax)
+
+ " A new split window should have the original syntax.
+ split
+ call assert_equal('cDefine', synIDattr(synID(line('.'), col('.'), 1), 'name'))
+ call assert_equal('c', b:current_syntax)
+ call assert_equal(0, exists('w:current_syntax'))
+
+ wincmd x
+ call assert_equal('perlComment', synIDattr(synID(line("."), col("."), 1), "name"))
+
+ syntax off
+ set filetype&
+ %bw!
+endfunc
+
+func Test_ownsyntax_completion()
+ call feedkeys(":ownsyntax java\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"ownsyntax java javacc javascript javascriptreact', @:)
+endfunc
+
+func Test_highlight_invalid_arg()
+ if has('gui_running')
+ call assert_fails('hi XXX guifg=xxx', 'E254:')
+ endif
+ call assert_fails('hi DoesNotExist', 'E411:')
+ call assert_fails('hi link', 'E412:')
+ call assert_fails('hi link a', 'E412:')
+ call assert_fails('hi link a b c', 'E413:')
+ call assert_fails('hi XXX =', 'E415:')
+ call assert_fails('hi XXX cterm', 'E416:')
+ call assert_fails('hi XXX cterm=', 'E417:')
+ call assert_fails('hi XXX cterm=DoesNotExist', 'E418:')
+ call assert_fails('hi XXX ctermfg=DoesNotExist', 'E421:')
+ call assert_fails('hi XXX xxx=White', 'E423:')
+endfunc
+
+func Test_bg_detection()
+ CheckNotGui
+
+ " auto-detection of &bg, make sure it isn't set anywhere before this test
+ hi Normal ctermbg=0
+ call assert_equal('dark', &bg)
+ hi Normal ctermbg=4
+ call assert_equal('dark', &bg)
+ hi Normal ctermbg=12
+ call assert_equal('light', &bg)
+ hi Normal ctermbg=15
+ call assert_equal('light', &bg)
+
+ " manually-set &bg takes precedence over auto-detection
+ set bg=light
+ hi Normal ctermbg=4
+ call assert_equal('light', &bg)
+ set bg=dark
+ hi Normal ctermbg=12
+ call assert_equal('dark', &bg)
+
+ hi Normal ctermbg=NONE
+endfunc
+
+func Test_syntax_hangs()
+ CheckFunction reltimefloat
+ CheckFeature syntax
+
+ " So, it turns out the Windows 7 implements TimerQueue timers differently
+ " and they can expire *before* the requested time has elapsed. So allow for
+ " the timeout occurring after 80 ms (5 * 16 (the typical clock tick)).
+ if has("win32")
+ let min_timeout = 0.08
+ else
+ let min_timeout = 0.1
+ endif
+
+ " This pattern takes a long time to match, it should timeout.
+ new
+ call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
+ let start = reltime()
+ set nolazyredraw redrawtime=101
+ syn match Error /\%#=1a*.*X\@<=b*/
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_inrange(min_timeout, 1.0, elapsed)
+
+ " second time syntax HL is disabled
+ let start = reltime()
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_inrange(0, 0.1, elapsed)
+
+ " after CTRL-L the timeout flag is reset
+ let start = reltime()
+ exe "normal \<C-L>"
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_inrange(min_timeout, 1.0, elapsed)
+
+ set redrawtime&
+ bwipe!
+endfunc
+
+func Test_conceal()
+ CheckFeature conceal
+
+ new
+ call setline(1, ['', '123456'])
+ syn match test23 "23" conceal cchar=X
+ syn match test45 "45" conceal
+
+ set conceallevel=0
+ call assert_equal('123456 ', ScreenLines(2, 7)[0])
+ call assert_equal([[0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
+
+ set conceallevel=1
+ call assert_equal('1X 6 ', ScreenLines(2, 7)[0])
+ call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, ' ', 2], [1, ' ', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
+
+ set conceallevel=1
+ set listchars=conceal:Y
+ call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, 'Y', 2], [1, 'Y', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
+ call assert_equal('1XY6 ', ScreenLines(2, 7)[0])
+
+ set conceallevel=2
+ call assert_match('1X6 ', ScreenLines(2, 7)[0])
+ call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
+
+ set conceallevel=3
+ call assert_match('16 ', ScreenLines(2, 7)[0])
+ call assert_equal([[0, '', 0], [1, '', 1], [1, '', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)'))
+
+ call AssertFails("syntax match Entity '&amp;' conceal cchar=\<Tab>", 'E844:')
+
+ syn clear
+ set conceallevel&
+ bw!
+endfunc
+
+func Test_synstack_synIDtrans()
+ new
+ setfiletype c
+ syntax on
+ call setline(1, ' /* A comment with a TODO */')
+
+ call assert_equal([], synstack(1, 1))
+
+ norm f/
+ eval synstack(line("."), col("."))->map('synIDattr(v:val, "name")')->assert_equal(['cComment', 'cCommentStart'])
+ eval synstack(line("."), col("."))->map('synIDattr(synIDtrans(v:val), "name")')->assert_equal(['Comment', 'Comment'])
+
+ norm fA
+ call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
+ call assert_equal(['Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")'))
+
+ norm fT
+ call assert_equal(['cComment', 'cTodo'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
+ call assert_equal(['Comment', 'Todo'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")'))
+
+ call assert_fails("let n=synIDtrans([])", 'E745:')
+
+ syn clear
+ bw!
+endfunc
+
+" Check highlighting for a small piece of C code with a screen dump.
+func Test_syntax_c()
+ CheckRunVimInTerminal
+ call writefile([
+ \ '/* comment line at the top */',
+ \ 'int main(int argc, char **argv) { // another comment',
+ \ '#if 0',
+ \ ' int not_used;',
+ \ '#else',
+ \ ' int used;',
+ \ '#endif',
+ \ ' printf("Just an example piece of C code\n");',
+ \ ' return 0x0ff;',
+ \ '}',
+ \ "\t\t ",
+ \ ' static void',
+ \ 'myFunction(const double count, struct nothing, long there) {',
+ \ "\t// 123: nothing to endif here",
+ \ "\tfor (int i = 0; i < count; ++i) {",
+ \ "\t break;",
+ \ "\t}",
+ \ "\tNote: asdf",
+ \ '}',
+ \ ], 'Xtest.c', 'D')
+
+ " This makes the default for 'background' use "dark", check that the
+ " response to t_RB corrects it to "light".
+ let $COLORFGBG = '15;0'
+
+ let buf = RunVimInTerminal('Xtest.c', {})
+ call term_sendkeys(buf, ":syn keyword Search Note\r")
+ call term_sendkeys(buf, ":syn match Error /^\\s\\+$/\r")
+ call term_sendkeys(buf, ":set hlsearch\r")
+ call term_sendkeys(buf, "/endif\r")
+ call term_sendkeys(buf, "vjfC")
+ call VerifyScreenDump(buf, 'Test_syntax_c_01', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+
+ let $COLORFGBG = ''
+endfun
+
+" Test \z(...) along with \z1
+func Test_syn_zsub()
+ new
+ syntax on
+ call setline(1, 'xxx start foo xxx not end foo xxx end foo xxx')
+ let l:expected = ' ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ '
+
+ for l:re in [0, 1, 2]
+ " Example taken from :help :syn-ext-match
+ syntax region Z start="start \z(\I\i*\)" skip="not end \z1" end="end \z1"
+ eval AssertHighlightGroups(1, 1, l:expected, 1, 'regexp=' .. l:re)
+ syntax clear Z
+ endfor
+
+ set re&
+ bw!
+endfunc
+
+" Using \z() in a region with NFA failing should not crash.
+func Test_syn_wrong_z_one()
+ new
+ call setline(1, ['just some text', 'with foo and bar to match with'])
+ syn region FooBar start="foo\z(.*\)bar" end="\z1"
+ call test_override("nfa_fail", 1)
+ redraw!
+ redraw!
+ call test_override("ALL", 0)
+ bwipe!
+endfunc
+
+func Test_syntax_after_bufdo()
+ call writefile(['/* aaa comment */'], 'Xaaa.c', 'D')
+ call writefile(['/* bbb comment */'], 'Xbbb.c', 'D')
+ call writefile(['/* ccc comment */'], 'Xccc.c', 'D')
+ call writefile(['/* ddd comment */'], 'Xddd.c', 'D')
+
+ let bnr = bufnr('%')
+ new Xaaa.c
+ badd Xbbb.c
+ badd Xccc.c
+ badd Xddd.c
+ exe "bwipe " . bnr
+ let l = []
+ bufdo call add(l, bufnr('%'))
+ call assert_equal(4, len(l))
+
+ syntax on
+
+ " This used to only enable syntax HL in the last buffer.
+ bufdo tab split
+ tabrewind
+ for tab in range(1, 4)
+ norm fm
+ call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
+ tabnext
+ endfor
+
+ bwipe! Xaaa.c
+ bwipe! Xbbb.c
+ bwipe! Xccc.c
+ bwipe! Xddd.c
+ syntax off
+endfunc
+
+func Test_syntax_foldlevel()
+ new
+ call setline(1, [
+ \ 'void f(int a)',
+ \ '{',
+ \ ' if (a == 1) {',
+ \ ' a = 0;',
+ \ ' } else if (a == 2) {',
+ \ ' a = 1;',
+ \ ' } else {',
+ \ ' a = 2;',
+ \ ' }',
+ \ ' if (a > 0) {',
+ \ ' if (a == 1) {',
+ \ ' a = 0;',
+ \ ' } /* missing newline */ } /* end of outer if */ else {',
+ \ ' a = 1;',
+ \ ' }',
+ \ ' if (a == 1)',
+ \ ' {',
+ \ ' a = 0;',
+ \ ' }',
+ \ ' else if (a == 2)',
+ \ ' {',
+ \ ' a = 1;',
+ \ ' }',
+ \ ' else',
+ \ ' {',
+ \ ' a = 2;',
+ \ ' }',
+ \ '}',
+ \ ])
+ setfiletype c
+ syntax on
+ set foldmethod=syntax
+
+ call assert_fails('syn foldlevel start start', 'E390:')
+ call assert_fails('syn foldlevel not_an_option', 'E390:')
+
+ set foldlevel=1
+
+ syn foldlevel start
+ redir @c
+ syn foldlevel
+ redir END
+ call assert_equal("\nsyntax foldlevel start", @c)
+ syn sync fromstart
+ call assert_match('from the first line$', execute('syn sync'))
+ let a = map(range(3,9), 'foldclosed(v:val)')
+ call assert_equal([3,3,3,3,3,3,3], a) " attached cascade folds together
+ let a = map(range(10,15), 'foldclosed(v:val)')
+ call assert_equal([10,10,10,10,10,10], a) " over-attached 'else' hidden
+ let a = map(range(16,27), 'foldclosed(v:val)')
+ let unattached_results = [-1,17,17,17,-1,21,21,21,-1,25,25,25]
+ call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+ syn foldlevel minimum
+ redir @c
+ syn foldlevel
+ redir END
+ call assert_equal("\nsyntax foldlevel minimum", @c)
+ syn sync fromstart
+ let a = map(range(3,9), 'foldclosed(v:val)')
+ call assert_equal([3,3,5,5,7,7,7], a) " attached cascade folds separately
+ let a = map(range(10,15), 'foldclosed(v:val)')
+ call assert_equal([10,10,10,13,13,13], a) " over-attached 'else' visible
+ let a = map(range(16,27), 'foldclosed(v:val)')
+ call assert_equal(unattached_results, a) " unattached cascade folds separately
+
+ set foldlevel=2
+
+ syn foldlevel start
+ syn sync fromstart
+ let a = map(range(11,14), 'foldclosed(v:val)')
+ call assert_equal([11,11,11,-1], a) " over-attached 'else' hidden
+
+ syn foldlevel minimum
+ syn sync fromstart
+ let a = map(range(11,14), 'foldclosed(v:val)')
+ call assert_equal([11,11,-1,-1], a) " over-attached 'else' visible
+
+ quit!
+endfunc
+
+func Test_search_syntax_skip()
+ new
+ let lines =<< trim END
+
+ /* This is VIM */
+ Another Text for VIM
+ let a = "VIM"
+ END
+ call setline(1, lines)
+ syntax on
+ syntax match Comment "^/\*.*\*/"
+ syntax match String '".*"'
+
+ " Skip argument using string evaluation.
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"')
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'cw', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using Lambda.
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"})
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'cw', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using funcref.
+ func InComment()
+ return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"
+ endfunc
+ func NotInString()
+ return synIDattr(synID(line("."), col("."), 1), "name") !~? "string"
+ endfunc
+
+ 1
+ call search('VIM', 'w', '', 0, function('InComment'))
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'cw', '', 0, function('NotInString'))
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ delfunc InComment
+ delfunc NotInString
+ bwipe!
+endfunc
+
+func Test_syn_contained_transparent()
+ " Comments starting with "Regression:" show the result when the highlighting
+ " span of the containing item is assigned to the contained region.
+ syntax on
+
+ let l:case = "Transparent region contained in region"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax region Y start=/(/ end=/)/ contains=X
+
+ call setline(1, "==(--[~~]--)==")
+ let l:expected = " YYYYYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Transparent region extends region"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax region Y start=/(/ end=/)/ end=/e/ contains=X
+
+ call setline(1, "==(--[~~e~~]--)==")
+ let l:expected = " YYYYYYYYYYYYY "
+ " Regression: " YYYYYYY YYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Nested transparent regions extend region"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax region Y start=/(/ end=/)/ end=/e/ contains=X
+
+ call setline(1, "==(--[~~e~~[~~e~~]~~e~~]--)==")
+ let l:expected = " YYYYYYYYYYYYYYYYYYYYYYYYY "
+ " Regression: " YYYYYYY YYYYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Transparent region contained in match"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax match Y /(.\{-})/ contains=X
+
+ call setline(1, "==(--[~~]--)==")
+ let l:expected = " YYYYYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Transparent region extends match"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax match Y /(.\{-}[e)]/ contains=X
+
+ call setline(1, "==(--[~~e~~]--)==")
+ let l:expected = " YYYYYYYYYY "
+ " Regression: " YYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Nested transparent regions extend match"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax match Y /(.\{-}[e)]/ contains=X
+
+ call setline(1, "==(--[~~e~~[~~e~~]~~e~~]--)==")
+ let l:expected = " YYYYYYYYYYYYYYYYYYYYYY "
+ " Regression: " YYYYYYY YYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+endfunc
+
+func Test_syn_include_contains_TOP()
+ let l:case = "TOP in included syntax means its group list name"
+ new
+ syntax include @INCLUDED syntax/c.vim
+ syntax region FencedCodeBlockC start=/```c/ end=/```/ contains=@INCLUDED
+
+ call setline(1, ['```c', '#if 0', 'int', '#else', 'int', '#endif', '```' ])
+ let l:expected = ["cCppOutIf2"]
+ eval AssertHighlightGroups(3, 1, l:expected, 1)
+ " cCppOutElse has contains=TOP
+ let l:expected = ["cType"]
+ eval AssertHighlightGroups(5, 1, l:expected, 1, l:case)
+ syntax clear
+ bw!
+endfunc
+
+" This was using freed memory
+func Test_WinEnter_synstack_synID()
+ autocmd WinEnter * call synstack(line("."), col("."))
+ autocmd WinEnter * call synID(line('.'), col('.') - 1, 1)
+ call setline(1, 'aaaaa')
+ normal! $
+ new
+ close
+
+ au! WinEnter
+ bw!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_system.vim b/src/testdir/test_system.vim
new file mode 100644
index 0000000..879eaed
--- /dev/null
+++ b/src/testdir/test_system.vim
@@ -0,0 +1,146 @@
+" Tests for system() and systemlist()
+
+source shared.vim
+source check.vim
+
+func Test_System()
+ if !has('win32')
+ call assert_equal("123\n", system('echo 123'))
+ call assert_equal(['123'], systemlist('echo 123'))
+ call assert_equal('123', system('cat', '123'))
+ call assert_equal(['123'], systemlist('cat', '123'))
+ call assert_equal(["as\<NL>df"], systemlist('cat', ["as\<NL>df"]))
+ else
+ call assert_equal("123\n", system('echo 123'))
+ call assert_equal(["123\r"], systemlist('echo 123'))
+ call assert_equal("123\n", system('more.com', '123'))
+ call assert_equal(["123\r"], systemlist('more.com', '123'))
+ call assert_equal(["as\r", "df\r"], systemlist('more.com', ["as\<NL>df"]))
+ endif
+
+ new Xdummy
+ call setline(1, ['asdf', "pw\<NL>er", 'xxxx'])
+
+ if executable('wc')
+ let out = system('wc -l', bufnr('%'))
+ " On OS/X we get leading spaces
+ let out = substitute(out, '^ *', '', '')
+ call assert_equal("3\n", out)
+
+ let out = systemlist('wc -l', bufnr('%'))
+ " On Windows we may get a trailing CR.
+ if out != ["3\r"]
+ " On OS/X we get leading spaces
+ if type(out) == v:t_list
+ let out[0] = substitute(out[0], '^ *', '', '')
+ endif
+ call assert_equal(['3'], out)
+ endif
+ endif
+
+ if !has('win32')
+ let out = systemlist('cat', bufnr('%'))
+ call assert_equal(['asdf', "pw\<NL>er", 'xxxx'], out)
+ else
+ let out = systemlist('more.com', bufnr('%'))
+ call assert_equal(["asdf\r", "pw\r", "er\r", "xxxx\r"], out)
+ endif
+ bwipe!
+
+ call assert_fails('call system("wc -l", 99999)', 'E86:')
+endfunc
+
+func Test_system_exmode()
+ if has('unix') " echo $? only works on Unix
+ let cmd = ' -es -c "source Xscript" +q; echo "result=$?"'
+ " Need to put this in a script, "catch" isn't found after an unknown
+ " function.
+ call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript', 'D')
+ let a = system(GetVimCommand() . cmd)
+ call assert_match('result=0', a)
+ call assert_equal(0, v:shell_error)
+ endif
+
+ " Error before try does set error flag.
+ call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript')
+ if has('unix') " echo $? only works on Unix
+ let a = system(GetVimCommand() . cmd)
+ call assert_notequal('0', a[0])
+ endif
+
+ let cmd = ' -es -c "source Xscript" +q'
+ let a = system(GetVimCommand() . cmd)
+ call assert_notequal(0, v:shell_error)
+
+ if has('unix') " echo $? only works on Unix
+ let cmd = ' -es -c "call doesnotexist()" +q; echo $?'
+ let a = system(GetVimCommand() . cmd)
+ call assert_notequal(0, a[0])
+ endif
+
+ let cmd = ' -es -c "call doesnotexist()" +q'
+ let a = system(GetVimCommand(). cmd)
+ call assert_notequal(0, v:shell_error)
+
+ if has('unix') " echo $? only works on Unix
+ let cmd = ' -es -c "call doesnotexist()|let a=1" +q; echo $?'
+ let a = system(GetVimCommand() . cmd)
+ call assert_notequal(0, a[0])
+ endif
+
+ let cmd = ' -es -c "call doesnotexist()|let a=1" +q'
+ let a = system(GetVimCommand() . cmd)
+ call assert_notequal(0, v:shell_error)
+endfunc
+
+func Test_system_with_shell_quote()
+ CheckMSWindows
+
+ call mkdir('Xdir with spaces', 'p')
+ call system('copy "%COMSPEC%" "Xdir with spaces\cmd.exe"')
+
+ let shell_save = &shell
+ let shellxquote_save = &shellxquote
+ try
+ " Set 'shell' always needs noshellslash.
+ let shellslash_save = &shellslash
+ set noshellslash
+ let shell_tests = [
+ \ expand('$COMSPEC'),
+ \ '"' . fnamemodify('Xdir with spaces\cmd.exe', ':p') . '"',
+ \]
+ let &shellslash = shellslash_save
+
+ let sxq_tests = ['', '(', '"']
+
+ " Matrix tests: 'shell' * 'shellxquote'
+ for shell in shell_tests
+ let &shell = shell
+ for sxq in sxq_tests
+ let &shellxquote = sxq
+
+ let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote)
+
+ try
+ let out = 'echo 123'->system()
+ catch
+ call assert_report(printf('%s: %s', msg, v:exception))
+ continue
+ endtry
+
+ " On Windows we may get a trailing space and CR.
+ if out != "123 \n"
+ call assert_equal("123\n", out, msg)
+ endif
+
+ endfor
+ endfor
+
+ finally
+ let &shell = shell_save
+ let &shellxquote = shellxquote_save
+ call delete('Xdir with spaces', 'rf')
+ endtry
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tab.vim b/src/testdir/test_tab.vim
new file mode 100644
index 0000000..da630f0
--- /dev/null
+++ b/src/testdir/test_tab.vim
@@ -0,0 +1,92 @@
+" Various tests for inserting a Tab.
+
+" Tests for "r<Tab>" with 'smarttab' and 'expandtab' set/not set.
+" Also test that dv_ works correctly
+func Test_smarttab()
+ enew!
+ set smarttab expandtab ts=8 sw=4
+ " make sure that backspace works, no matter what termcap is used
+ exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08"
+ call append(0, ['start text',
+ \ "\t\tsome test text",
+ \ 'test text',
+ \ "\t\tother test text",
+ \ ' a cde',
+ \ ' f ghi',
+ \ 'test text',
+ \ ' Second line beginning with whitespace'
+ \ ])
+ call cursor(1, 1)
+ exe "normal /some\<CR>"
+ exe "normal r\t"
+ call assert_equal("\t\t ome test text", getline('.'))
+ set noexpandtab
+ exe "normal /other\<CR>"
+ exe "normal r\t"
+ call assert_equal("\t\t ther test text", getline('.'))
+
+ " Test replacing with Tabs and then backspacing to undo it
+ exe "normal j0wR\t\t\t\<BS>\<BS>\<BS>"
+ call assert_equal(" a cde", getline('.'))
+ " Test replacing with Tabs
+ exe "normal j0wR\t\t\t"
+ call assert_equal(" \t\thi", getline('.'))
+
+ " Test that copyindent works with expandtab set
+ set expandtab smartindent copyindent ts=8 sw=8 sts=8
+ exe "normal jo{\<CR>x"
+ call assert_equal('{', getline(line('.') - 1))
+ call assert_equal(' x', getline('.'))
+ set nosol
+ exe "normal /Second line/\<CR>"
+ exe "normal fwdv_"
+ call assert_equal(' with whitespace', getline('.'))
+ enew!
+ set expandtab& smartindent& copyindent& ts& sw& sts&
+endfunc
+
+func Test_softtabstop()
+ new
+ set sts=0 sw=0
+ exe "normal ix\<Tab>x\<Esc>"
+ call assert_equal("x\tx", getline(1))
+
+ call setline(1, '')
+ set sts=4
+ exe "normal ix\<Tab>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, '')
+ set sts=-1 sw=4
+ exe "normal ix\<Tab>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x ')
+ set sts=0 sw=0 backspace=start
+ exe "normal A\<BS>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x ')
+ set sts=4
+ exe "normal A\<BS>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x ')
+ set sts=-1 sw=4
+ exe "normal A\<BS>x\<Esc>"
+ call assert_equal("x x", getline(1))
+
+ call setline(1, 'x')
+ set sts=-1 sw=0 smarttab
+ exe "normal I\<Tab>\<Esc>"
+ call assert_equal("\tx", getline(1))
+
+ call setline(1, 'x')
+ exe "normal I\<Tab>\<BS>\<Esc>"
+ call assert_equal("x", getline(1))
+
+ set sts=0 sw=0 backspace& nosmarttab
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tabline.vim b/src/testdir/test_tabline.vim
new file mode 100644
index 0000000..09adf2f
--- /dev/null
+++ b/src/testdir/test_tabline.vim
@@ -0,0 +1,205 @@
+" Test for tabline
+
+source shared.vim
+source view_util.vim
+source check.vim
+source screendump.vim
+
+func TablineWithCaughtError()
+ let s:func_in_tabline_called = 1
+ try
+ call eval('unknown expression')
+ catch
+ endtry
+ return ''
+endfunc
+
+func TablineWithError()
+ let s:func_in_tabline_called = 1
+ call eval('unknown expression')
+ return ''
+endfunc
+
+func Test_caught_error_in_tabline()
+ if has('gui')
+ set guioptions-=e
+ endif
+ let showtabline_save = &showtabline
+ set showtabline=2
+ let s:func_in_tabline_called = 0
+ let tabline = '%{TablineWithCaughtError()}'
+ let &tabline = tabline
+ redraw!
+ call assert_true(s:func_in_tabline_called)
+ call assert_equal(tabline, &tabline)
+ set tabline=
+ let &showtabline = showtabline_save
+endfunc
+
+func Test_tabline_will_be_disabled_with_error()
+ if has('gui')
+ set guioptions-=e
+ endif
+ let showtabline_save = &showtabline
+ set showtabline=2
+ let s:func_in_tabline_called = 0
+ let tabline = '%{TablineWithError()}'
+ try
+ let &tabline = tabline
+ redraw!
+ catch
+ endtry
+ call assert_true(s:func_in_tabline_called)
+ call assert_equal('', &tabline)
+ set tabline=
+ let &showtabline = showtabline_save
+endfunc
+
+func Test_redrawtabline()
+ if has('gui')
+ set guioptions-=e
+ endif
+ let showtabline_save = &showtabline
+ set showtabline=2
+ set tabline=%{bufnr('$')}
+ edit Xtabline1
+ edit Xtabline2
+ redraw
+ call assert_match(bufnr('$') . '', Screenline(1))
+ au BufAdd * redrawtabline
+ badd Xtabline3
+ call assert_match(bufnr('$') . '', Screenline(1))
+
+ set tabline=
+ let &showtabline = showtabline_save
+ au! Bufadd
+endfunc
+
+" Test for the "%T" and "%X" flags in the 'tabline' option
+func MyTabLine()
+ let s = ''
+ for i in range(tabpagenr('$'))
+ " set the tab page number (for mouse clicks)
+ let s .= '%' . (i + 1) . 'T'
+
+ " the label is made by MyTabLabel()
+ let s .= ' %{MyTabLabel(' . (i + 1) . ')} '
+ endfor
+
+ " after the last tab fill with TabLineFill and reset tab page nr
+ let s .= '%T'
+
+ " right-align the label to close the current tab page
+ if tabpagenr('$') > 1
+ let s .= '%=%Xclose'
+ endif
+
+ return s
+endfunc
+
+func MyTabLabel(n)
+ let buflist = tabpagebuflist(a:n)
+ let winnr = tabpagewinnr(a:n)
+ return bufname(buflist[winnr - 1])
+endfunc
+
+func Test_tabline_flags()
+ if has('gui')
+ set guioptions-=e
+ endif
+ set tabline=%!MyTabLine()
+ edit Xtabline1
+ tabnew Xtabline2
+ redrawtabline
+ call assert_match('^ Xtabline1 Xtabline2\s\+close$', Screenline(1))
+ set tabline=
+ %bw!
+endfunc
+
+function EmptyTabname()
+ return ""
+endfunction
+
+function MakeTabLine() abort
+ let titles = map(range(1, tabpagenr('$')), '"%( %" . v:val . "T%{EmptyTabname()}%T %)"')
+ let sep = 'ã‚'
+ let tabpages = join(titles, sep)
+ return tabpages .. sep .. '%=%999X X'
+endfunction
+
+func Test_tabline_empty_group()
+ " this was reading invalid memory
+ set tabline=%!MakeTabLine()
+ tabnew
+ redraw!
+
+ tabclose
+ set tabline=
+endfunc
+
+" When there are exactly 20 tabline format items (the exact size of the
+" initial tabline items array), test that we don't write beyond the size
+" of the array.
+func Test_tabline_20_format_items_no_overrun()
+ set showtabline=2
+
+ let tabline = repeat('%#StatColorHi2#', 20)
+ let &tabline = tabline
+ redrawtabline
+
+ set showtabline& tabline&
+endfunc
+
+func Test_mouse_click_in_tab()
+ " This used to crash because TabPageIdxs[] was not initialized
+ let lines =<< trim END
+ tabnew
+ set mouse=a
+ exe "norm \<LeftMouse>"
+ END
+ call writefile(lines, 'Xclickscript', 'D')
+ call RunVim([], [], "-e -s -S Xclickscript -c qa")
+endfunc
+
+func Test_tabline_showcmd()
+ CheckScreendump
+
+ let lines =<< trim END
+ func MyTabLine()
+ return '%S'
+ endfunc
+
+ set showtabline=2
+ set tabline=%!MyTabLine()
+ set showcmdloc=tabline
+ call setline(1, ['a', 'b', 'c'])
+ set foldopen+=jump
+ 1,2fold
+ 3
+ END
+ call writefile(lines, 'XTest_tabline', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_tabline', {'rows': 6})
+
+ call term_sendkeys(buf, "g")
+ call VerifyScreenDump(buf, 'Test_tabline_showcmd_1', {})
+
+ " typing "gg" should open the fold
+ call term_sendkeys(buf, "g")
+ call VerifyScreenDump(buf, 'Test_tabline_showcmd_2', {})
+
+ call term_sendkeys(buf, "\<C-V>Gl")
+ call VerifyScreenDump(buf, 'Test_tabline_showcmd_3', {})
+
+ call term_sendkeys(buf, "\<Esc>1234")
+ call VerifyScreenDump(buf, 'Test_tabline_showcmd_4', {})
+
+ call term_sendkeys(buf, "\<Esc>:set tabline=\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call term_sendkeys(buf, "1234")
+ call VerifyScreenDump(buf, 'Test_tabline_showcmd_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tabpage.vim b/src/testdir/test_tabpage.vim
new file mode 100644
index 0000000..83d6123
--- /dev/null
+++ b/src/testdir/test_tabpage.vim
@@ -0,0 +1,890 @@
+" Tests for tabpage
+
+source screendump.vim
+source check.vim
+
+function Test_tabpage()
+ CheckFeature quickfix
+
+ bw!
+ " Simple test for opening and closing a tab page
+ tabnew
+ call assert_equal(2, tabpagenr())
+ quit
+
+ " Open three tab pages and use ":tabdo"
+ 0tabnew
+ 1tabnew
+ $tabnew
+ %del
+ tabdo call append(line('$'), tabpagenr())
+ tabclose! 2
+ tabrewind
+ let line1 = getline('$')
+ undo
+ q
+ tablast
+ let line2 = getline('$')
+ q!
+ call append(line('$'), line1)
+ call append(line('$'), line2)
+ unlet line1 line2
+ call assert_equal(['', '3', '1', '4'], getline(1, '$'))
+ "
+ " Test for settabvar() and gettabvar() functions. Open a new tab page and
+ " set 3 variables to a number, string and a list. Verify that the variables
+ " are correctly set.
+ tabnew
+ tabfirst
+ call settabvar(2, 'val_num', 100)
+ eval 'SetTabVar test'->settabvar(2, 'val_str')
+ call settabvar(2, 'val_list', ['red', 'blue', 'green'])
+ "
+ call assert_true(gettabvar(2, 'val_num') == 100 && gettabvar(2, 'val_str') == 'SetTabVar test' && gettabvar(2, 'val_list') == ['red', 'blue', 'green'])
+
+ tabnext 2
+ call assert_true(t:val_num == 100 && t:val_str == 'SetTabVar test' && t:val_list == ['red', 'blue', 'green'])
+ tabclose
+
+ " Test for ":tab drop exist-file" to keep current window.
+ sp test1
+ tab drop test1
+ call assert_true(tabpagenr('$') == 1 && winnr('$') == 2 && winnr() == 1)
+ close
+ "
+ "
+ " Test for ":tab drop new-file" to keep current window of tabpage 1.
+ split
+ tab drop newfile
+ call assert_true(tabpagenr('$') == 2 && tabpagewinnr(1, '$') == 2 && tabpagewinnr(1) == 1)
+ tabclose
+ q
+ "
+ "
+ " Test for ":tab drop multi-opened-file" to keep current tabpage and window.
+ new test1
+ tabnew
+ new test1
+ tab drop test1
+ call assert_true(tabpagenr() == 2 && tabpagewinnr(2, '$') == 2 && tabpagewinnr(2) == 1)
+ tabclose
+ q
+ "
+ "
+ " Test for ":tab drop vertical-split-window" to jump test1 buffer
+ tabedit test1
+ vnew
+ tabfirst
+ tab drop test1
+ call assert_equal([2, 2, 2, 2], [tabpagenr('$'), tabpagenr(), tabpagewinnr(2, '$'), tabpagewinnr(2)])
+ 1tabonly
+ "
+ "
+ for i in range(9) | tabnew | endfor
+ normal! 1gt
+ call assert_equal(1, tabpagenr())
+ tabmove 5
+ call assert_equal(5, tabpagenr())
+ .tabmove
+ call assert_equal(5, tabpagenr())
+ tabmove -
+ call assert_equal(4, tabpagenr())
+ tabmove +
+ call assert_equal(5, tabpagenr())
+ tabmove -2
+ call assert_equal(3, tabpagenr())
+ tabmove +4
+ call assert_equal(7, tabpagenr())
+ tabmove
+ call assert_equal(10, tabpagenr())
+ 0tabmove
+ call assert_equal(1, tabpagenr())
+ $tabmove
+ call assert_equal(10, tabpagenr())
+ tabmove 0
+ call assert_equal(1, tabpagenr())
+ tabmove $
+ call assert_equal(10, tabpagenr())
+ 3tabmove
+ call assert_equal(4, tabpagenr())
+ 7tabmove 5
+ call assert_equal(5, tabpagenr())
+ -tabmove
+ call assert_equal(4, tabpagenr())
+ +tabmove
+ call assert_equal(5, tabpagenr())
+ -2tabmove
+ call assert_equal(3, tabpagenr())
+ +3tabmove
+ call assert_equal(6, tabpagenr())
+
+ " The following are a no-op
+ norm! 2gt
+ call assert_equal(2, tabpagenr())
+ tabmove 2
+ call assert_equal(2, tabpagenr())
+ 2tabmove
+ call assert_equal(2, tabpagenr())
+ tabmove 1
+ call assert_equal(2, tabpagenr())
+ 1tabmove
+ call assert_equal(2, tabpagenr())
+
+ call assert_fails('let t = tabpagenr("@")', 'E15:')
+ call assert_equal(0, tabpagewinnr(-1))
+ call assert_fails("99tabmove", 'E16:')
+ call assert_fails("+99tabmove", 'E16:')
+ call assert_fails("-99tabmove", 'E16:')
+ call assert_fails("tabmove foo", 'E475:')
+ call assert_fails("tabmove 99", 'E475:')
+ call assert_fails("tabmove +99", 'E475:')
+ call assert_fails("tabmove -99", 'E475:')
+ call assert_fails("tabmove -3+", 'E475:')
+ call assert_fails("tabmove $3", 'E475:')
+ call assert_fails("%tabonly", 'E16:')
+ 1tabonly!
+ tabmove 1
+ call assert_equal(1, tabpagenr())
+ tabnew
+ call assert_fails("-2tabmove", 'E16:')
+ tabonly!
+endfunc
+
+" Test autocommands
+function Test_tabpage_with_autocmd()
+ command -nargs=1 -bar C :call add(s:li, '=== ' . <q-args> . ' ===')|<args>
+ augroup TestTabpageGroup
+ au!
+ autocmd TabEnter * call add(s:li, 'TabEnter')
+ autocmd WinEnter * call add(s:li, 'WinEnter')
+ autocmd BufEnter * call add(s:li, 'BufEnter')
+ autocmd TabLeave * call add(s:li, 'TabLeave')
+ autocmd WinLeave * call add(s:li, 'WinLeave')
+ autocmd BufLeave * call add(s:li, 'BufLeave')
+ augroup END
+
+ let s:li = []
+ let t:a='a'
+ C tab split
+ call assert_equal(['=== tab split ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter'], s:li)
+ let s:li = []
+ let t:a='b'
+ C tabnew
+ call assert_equal(['=== tabnew ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufLeave', 'BufEnter'], s:li)
+ let t:a='c'
+ let s:li = split(join(map(range(1, tabpagenr('$')), 'gettabvar(v:val, "a")')) , '\s\+')
+ call assert_equal(['a', 'b', 'c'], s:li)
+
+ let s:li = []
+ C call map(range(1, tabpagenr('$')), 'settabvar(v:val, ''a'', v:val*2)')
+ call assert_equal(["=== call map(range(1, tabpagenr('$')), 'settabvar(v:val, ''a'', v:val*2)') ==="], s:li)
+ let s:li = split(join(map(range(1, tabpagenr('$')), 'gettabvar(v:val, "a")')) , '\s\+')
+ call assert_equal(['2', '4', '6'], s:li)
+
+ let s:li = []
+ let w:a='a'
+ C vsplit
+ call assert_equal(['=== vsplit ===', 'WinLeave', 'WinEnter'], s:li)
+ let s:li = []
+ let w:a='a'
+ let tabn=tabpagenr()
+ let winr=range(1, winnr('$'))
+ C tabnext 1
+ call assert_equal(['=== tabnext 1 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li)
+ let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+')
+ call assert_equal(['a', 'a'], s:li)
+ let s:li = []
+ C call map(copy(winr), '(v:val*2)->settabwinvar(' .. tabn .. ', v:val, ''a'')')
+ let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+')
+ call assert_equal(['2', '4'], s:li)
+
+ augroup TabDestructive
+ autocmd TabEnter * :C tabnext 2 | C tabclose 3
+ augroup END
+ let s:li = []
+ C tabnext 3
+ call assert_equal(['=== tabnext 3 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', '=== tabnext 2 ===', '=== tabclose 3 ==='], s:li)
+ call assert_equal(['2/2'], [tabpagenr().'/'.tabpagenr('$')])
+
+ autocmd! TabDestructive TabEnter
+ let s:li = []
+ C tabnew
+ call assert_equal(['=== tabnew ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufLeave', 'BufEnter'], s:li)
+ let s:li = []
+ C tabnext 1
+ call assert_equal(['=== tabnext 1 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li)
+
+ autocmd TabDestructive TabEnter * nested :C tabnext 2 | C tabclose 3
+ let s:li = []
+ call assert_equal(3, tabpagenr('$'))
+ C tabnext 2
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal(['=== tabnext 2 ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', '=== tabnext 2 ===', '=== tabclose 3 ==='], s:li)
+ call assert_equal(['2/2'], [tabpagenr().'/'.tabpagenr('$')])
+
+ delcommand C
+ autocmd! TabDestructive
+ augroup! TabDestructive
+ autocmd! TestTabpageGroup
+ augroup! TestTabpageGroup
+ 1tabonly!
+endfunction
+
+" Test autocommands on tab drop
+function Test_tabpage_with_autocmd_tab_drop()
+ augroup TestTabpageGroup
+ au!
+ autocmd TabEnter * call add(s:li, 'TabEnter')
+ autocmd WinEnter * call add(s:li, 'WinEnter')
+ autocmd BufEnter * call add(s:li, 'BufEnter')
+ autocmd TabLeave * call add(s:li, 'TabLeave')
+ autocmd WinLeave * call add(s:li, 'WinLeave')
+ autocmd BufLeave * call add(s:li, 'BufLeave')
+ augroup END
+
+ let s:li = []
+ tab drop test1
+ call assert_equal(['BufLeave', 'BufEnter'], s:li)
+
+ let s:li = []
+ tab drop test2 test3
+ call assert_equal([
+ \ 'TabLeave', 'TabEnter', 'TabLeave', 'TabEnter',
+ \ 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter',
+ \ 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li)
+
+ autocmd! TestTabpageGroup
+ augroup! TestTabpageGroup
+ 1tabonly!
+endfunction
+
+function Test_tabpage_with_tab_modifier()
+ CheckFeature quickfix
+
+ for n in range(4)
+ tabedit
+ endfor
+
+ function s:check_tab(pre_nr, cmd, post_nr)
+ exec 'tabnext ' . a:pre_nr
+ exec a:cmd
+ call assert_equal(a:post_nr, tabpagenr())
+ call assert_equal('help', &buftype)
+ helpclose
+ endfunc
+
+ call s:check_tab(1, 'tab help', 2)
+ call s:check_tab(1, '3tab help', 4)
+ call s:check_tab(1, '.tab help', 2)
+ call s:check_tab(1, '.+1tab help', 3)
+ call s:check_tab(1, '0tab help', 1)
+ call s:check_tab(2, '+tab help', 4)
+ call s:check_tab(2, '+2tab help', 5)
+ call s:check_tab(4, '-tab help', 4)
+ call s:check_tab(4, '-2tab help', 3)
+ call s:check_tab(3, '$tab help', 6)
+ call assert_fails('99tab help', 'E16:')
+ call assert_fails('+99tab help', 'E16:')
+ call assert_fails('-99tab help', 'E16:')
+
+ delfunction s:check_tab
+ 1tabonly!
+endfunction
+
+function Check_tab_count(pre_nr, cmd, post_nr)
+ exec 'tabnext' a:pre_nr
+ normal! G
+ exec a:cmd
+ call assert_equal(a:post_nr, tabpagenr(), a:cmd)
+endfunc
+
+" Test for [count] of tabnext
+function Test_tabpage_with_tabnext()
+ for n in range(4)
+ tabedit
+ call setline(1, ['', '', '3'])
+ endfor
+
+ call Check_tab_count(1, 'tabnext', 2)
+ call Check_tab_count(1, '3tabnext', 3)
+ call Check_tab_count(1, '.tabnext', 1)
+ call Check_tab_count(1, '.+1tabnext', 2)
+ call Check_tab_count(2, '+tabnext', 3)
+ call Check_tab_count(2, '+2tabnext', 4)
+ call Check_tab_count(4, '-tabnext', 3)
+ call Check_tab_count(4, '-2tabnext', 2)
+ call Check_tab_count(3, '$tabnext', 5)
+ call assert_fails('0tabnext', 'E16:')
+ call assert_fails('99tabnext', 'E16:')
+ call assert_fails('+99tabnext', 'E16:')
+ call assert_fails('-99tabnext', 'E16:')
+ call Check_tab_count(1, 'tabnext 3', 3)
+ call Check_tab_count(2, 'tabnext +', 3)
+ call Check_tab_count(2, 'tabnext +2', 4)
+ call Check_tab_count(4, 'tabnext -', 3)
+ call Check_tab_count(4, 'tabnext -2', 2)
+ call Check_tab_count(3, 'tabnext $', 5)
+ call assert_fails('tabnext 0', 'E475:')
+ call assert_fails('tabnext .', 'E475:')
+ call assert_fails('tabnext -+', 'E475:')
+ call assert_fails('tabnext +2-', 'E475:')
+ call assert_fails('tabnext $3', 'E475:')
+ call assert_fails('tabnext 99', 'E475:')
+ call assert_fails('tabnext +99', 'E475:')
+ call assert_fails('tabnext -99', 'E475:')
+
+ 1tabonly!
+endfunction
+
+" Test for [count] of tabprevious
+function Test_tabpage_with_tabprevious()
+ for n in range(5)
+ tabedit
+ call setline(1, ['', '', '3'])
+ endfor
+
+ for cmd in ['tabNext', 'tabprevious']
+ call Check_tab_count(6, cmd, 5)
+ call Check_tab_count(6, '3' . cmd, 3)
+ call Check_tab_count(6, '8' . cmd, 4)
+ call Check_tab_count(6, cmd . ' 3', 3)
+ call Check_tab_count(6, cmd . ' 8', 4)
+ for n in range(2)
+ for c in ['0', '.+3', '+', '+2', '-', '-2', '$', '+99', '-99']
+ if n == 0 " pre count
+ let entire_cmd = c . cmd
+ let err_code = 'E16:'
+ else
+ let entire_cmd = cmd . ' ' . c
+ let err_code = 'E475:'
+ endif
+ call assert_fails(entire_cmd, err_code)
+ endfor
+ endfor
+ endfor
+
+ 1tabonly!
+endfunction
+
+function s:reconstruct_tabpage_for_test(nr)
+ let n = (a:nr > 2) ? a:nr - 2 : 1
+ 1tabonly!
+ 0tabedit n0
+ for n in range(1, n)
+ exec '$tabedit n' . n
+ if n == 1
+ call setline(1, ['', '', '3'])
+ endif
+ endfor
+endfunc
+
+func Test_tabpage_ctrl_pgup_pgdown()
+ enew!
+ tabnew tab1
+ tabnew tab2
+
+ call assert_equal(3, tabpagenr())
+ exe "norm! \<C-PageUp>"
+ call assert_equal(2, tabpagenr())
+ exe "norm! \<C-PageDown>"
+ call assert_equal(3, tabpagenr())
+
+ " Check wrapping at last or first page.
+ exe "norm! \<C-PageDown>"
+ call assert_equal(1, tabpagenr())
+ exe "norm! \<C-PageUp>"
+ call assert_equal(3, tabpagenr())
+
+ " With a count, <C-PageUp> and <C-PageDown> are not symmetrical somehow:
+ " - {count}<C-PageUp> goes {count} pages downward (relative count)
+ " - {count}<C-PageDown> goes to page number {count} (absolute count)
+ exe "norm! 2\<C-PageUp>"
+ call assert_equal(1, tabpagenr())
+ exe "norm! 2\<C-PageDown>"
+ call assert_equal(2, tabpagenr())
+
+ 1tabonly!
+endfunc
+
+" Test for [count] of tabclose
+function Test_tabpage_with_tabclose()
+
+ " pre count
+ call s:reconstruct_tabpage_for_test(6)
+ call Check_tab_count(3, 'tabclose!', 3)
+ call Check_tab_count(1, '3tabclose', 1)
+ call Check_tab_count(4, '4tabclose', 3)
+ call Check_tab_count(3, '1tabclose', 2)
+ call Check_tab_count(2, 'tabclose', 1)
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('', bufname(''))
+
+ call s:reconstruct_tabpage_for_test(6)
+ call Check_tab_count(2, '$tabclose', 2)
+ call Check_tab_count(4, '.tabclose', 4)
+ call Check_tab_count(3, '.+tabclose', 3)
+ call Check_tab_count(3, '.-2tabclose', 2)
+ call Check_tab_count(1, '.+1tabclose!', 1)
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('', bufname(''))
+
+ " post count
+ call s:reconstruct_tabpage_for_test(6)
+ call Check_tab_count(3, 'tabclose!', 3)
+ call Check_tab_count(1, 'tabclose 3', 1)
+ call Check_tab_count(4, 'tabclose 4', 3)
+ call Check_tab_count(3, 'tabclose 1', 2)
+ call Check_tab_count(2, 'tabclose', 1)
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('', bufname(''))
+
+ call s:reconstruct_tabpage_for_test(6)
+ call Check_tab_count(2, 'tabclose $', 2)
+ call Check_tab_count(4, 'tabclose', 4)
+ call Check_tab_count(3, 'tabclose +', 3)
+ call Check_tab_count(3, 'tabclose -2', 2)
+ call Check_tab_count(1, 'tabclose! +1', 1)
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('', bufname(''))
+
+ call s:reconstruct_tabpage_for_test(6)
+ for n in range(2)
+ for c in ['0', '$3', '99', '+99', '-99']
+ if n == 0 " pre count
+ let entire_cmd = c . 'tabclose'
+ let err_code = 'E16:'
+ else
+ let entire_cmd = 'tabclose ' . c
+ let err_code = 'E475:'
+ endif
+ call assert_fails(entire_cmd, err_code)
+ call assert_equal(6, tabpagenr('$'))
+ endfor
+ endfor
+
+ call assert_fails('3tabclose', 'E37:')
+ call assert_fails('tabclose 3', 'E37:')
+ call assert_fails('tabclose -+', 'E475:')
+ call assert_fails('tabclose +2-', 'E475:')
+ call assert_equal(6, tabpagenr('$'))
+
+ 1tabonly!
+endfunction
+
+" Test for [count] of tabonly
+function Test_tabpage_with_tabonly()
+
+ " Test for the normal behavior (pre count only)
+ let tc = [ [4, '.', '!'], [2, '.+', ''], [3, '.-2', '!'], [1, '.+1', '!'] ]
+ for c in tc
+ call s:reconstruct_tabpage_for_test(6)
+ let entire_cmd = c[1] . 'tabonly' . c[2]
+ call Check_tab_count(c[0], entire_cmd, 1)
+ call assert_equal(1, tabpagenr('$'))
+ endfor
+
+ " Test for the normal behavior
+ let tc2 = [ [3, '', ''], [1, '3', ''], [4, '4', '!'], [3, '1', '!'],
+ \ [2, '', '!'],
+ \ [2, '$', '!'], [3, '+', '!'], [3, '-2', '!'], [3, '+1', '!']
+ \ ]
+ for n in range(2)
+ for c in tc2
+ call s:reconstruct_tabpage_for_test(6)
+ if n == 0 " pre count
+ let entire_cmd = c[1] . 'tabonly' . c[2]
+ else
+ let entire_cmd = 'tabonly' . c[2] . ' ' . c[1]
+ endif
+ call Check_tab_count(c[0], entire_cmd, 1)
+ call assert_equal(1, tabpagenr('$'))
+ endfor
+ endfor
+
+ " Test for the error behavior
+ for n in range(2)
+ for c in ['0', '$3', '99', '+99', '-99']
+ call s:reconstruct_tabpage_for_test(6)
+ if n == 0 " pre count
+ let entire_cmd = c . 'tabonly'
+ let err_code = 'E16:'
+ else
+ let entire_cmd = 'tabonly ' . c
+ let err_code = 'E475:'
+ endif
+ call assert_fails(entire_cmd, err_code)
+ call assert_equal(6, tabpagenr('$'))
+ endfor
+ endfor
+
+ " Test for the error behavior (post count only)
+ for c in tc
+ call s:reconstruct_tabpage_for_test(6)
+ let entire_cmd = 'tabonly' . c[2] . ' ' . c[1]
+ let err_code = 'E475:'
+ call assert_fails(entire_cmd, err_code)
+ call assert_equal(6, tabpagenr('$'))
+ endfor
+
+ call assert_fails('tabonly -+', 'E475:')
+ call assert_fails('tabonly +2-', 'E475:')
+ call assert_equal(6, tabpagenr('$'))
+
+ 1tabonly!
+ new
+ only!
+endfunction
+
+func Test_tabnext_on_buf_unload1()
+ " This once caused a crash
+ new
+ tabedit
+ tabfirst
+ au BufUnload <buffer> tabnext
+ q
+
+ while tabpagenr('$') > 1
+ bwipe!
+ endwhile
+endfunc
+
+func Test_tabnext_on_buf_unload2()
+ " This once caused a crash
+ tabedit
+ autocmd BufUnload <buffer> tabnext
+ file x
+ edit y
+
+ while tabpagenr('$') > 1
+ bwipe!
+ endwhile
+endfunc
+
+func Test_close_on_quitpre()
+ " This once caused a crash
+ edit Xtest
+ new
+ only
+ set bufhidden=delete
+ au QuitPre <buffer> close
+ tabnew tab1
+ tabnew tab2
+ 1tabn
+ q!
+ call assert_equal(1, tabpagenr())
+ call assert_equal(2, tabpagenr('$'))
+ " clean up
+ while tabpagenr('$') > 1
+ bwipe!
+ endwhile
+ buf Xtest
+endfunc
+
+func Test_tabs()
+ enew!
+ tabnew tab1
+ norm ixxx
+ let a=split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' [No Name]',
+ \ 'Tab page 2',
+ \ '> + tab1'], a)
+
+ 1tabonly!
+ bw!
+endfunc
+
+func Test_tabpage_cmdheight()
+ CheckRunVimInTerminal
+ call writefile([
+ \ 'set laststatus=2',
+ \ 'set cmdheight=2',
+ \ 'tabnew',
+ \ 'set cmdheight=3',
+ \ 'tabnext',
+ \ 'redraw!',
+ \ 'echo "hello\nthere"',
+ \ 'tabnext',
+ \ 'redraw',
+ \ ], 'XTest_tabpage_cmdheight', 'D')
+ " Check that cursor line is concealed
+ let buf = RunVimInTerminal('-S XTest_tabpage_cmdheight', {'statusoff': 3})
+ call VerifyScreenDump(buf, 'Test_tabpage_cmdheight', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for closing the tab page from a command window
+func Test_tabpage_close_cmdwin()
+ tabnew
+ call feedkeys("q/:tabclose\<CR>\<Esc>", 'xt')
+ call assert_equal(2, tabpagenr('$'))
+ call feedkeys("q/:tabonly\<CR>\<Esc>", 'xt')
+ call assert_equal(2, tabpagenr('$'))
+ tabonly
+endfunc
+
+" Pressing <C-PageUp> in insert mode should go to the previous tab page
+" and <C-PageDown> should go to the next tab page
+func Test_tabpage_Ctrl_Pageup()
+ tabnew
+ call feedkeys("i\<C-PageUp>", 'xt')
+ call assert_equal(1, tabpagenr())
+ call feedkeys("i\<C-PageDown>", 'xt')
+ call assert_equal(2, tabpagenr())
+ %bw!
+endfunc
+
+" Return the terminal key code for selecting a tab page from the tabline. This
+" sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0),
+" KS_FILLER (0x58) and then the tab page number.
+func TabLineSelectPageCode(tabnr)
+ return "\x9b\xf0\x58" .. nr2char(a:tabnr)
+endfunc
+
+" Return the terminal key code for opening a new tabpage from the tabpage
+" menu. This sequence consists of the following codes: a CSI (0x9b),
+" KS_TABMENU (0xef), KS_FILLER (0x58), the tab page number and
+" TABLINE_MENU_NEW (2).
+func TabMenuNewItemCode(tabnr)
+ return "\x9b\xef\x58" .. nr2char(a:tabnr) .. nr2char(2)
+endfunc
+
+" Return the terminal key code for closing a tabpage from the tabpage menu.
+" This sequence consists of the following codes: a CSI (0x9b), KS_TABMENU
+" (0xef), KS_FILLER (0x58), the tab page number and TABLINE_MENU_CLOSE (1).
+func TabMenuCloseItemCode(tabnr)
+ return "\x9b\xef\x58" .. nr2char(a:tabnr) .. nr2char(1)
+endfunc
+
+" Test for using the tabpage menu from the insert and normal modes
+func Test_tabline_tabmenu()
+ " only works in GUI
+ CheckGui
+
+ %bw!
+ tabnew
+ tabnew
+ call assert_equal(3, tabpagenr())
+
+ " go to tab page 2 in normal mode
+ call feedkeys(TabLineSelectPageCode(2), "Lx!")
+ call assert_equal(2, tabpagenr())
+
+ " close tab page 3 in normal mode
+ call feedkeys(TabMenuCloseItemCode(3), "Lx!")
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal(2, tabpagenr())
+
+ " open new tab page before tab page 1 in normal mode
+ call feedkeys(TabMenuNewItemCode(1), "Lx!")
+ call assert_equal(1, tabpagenr())
+ call assert_equal(3, tabpagenr('$'))
+
+ " go to tab page 2 in operator-pending mode (should beep)
+ call assert_beeps('call feedkeys("c" .. TabLineSelectPageCode(2), "Lx!")')
+ call assert_equal(2, tabpagenr())
+ call assert_equal(3, tabpagenr('$'))
+
+ " open new tab page before tab page 1 in operator-pending mode (should beep)
+ call assert_beeps('call feedkeys("c" .. TabMenuNewItemCode(1), "Lx!")')
+ call assert_equal(1, tabpagenr())
+ call assert_equal(4, tabpagenr('$'))
+
+ " open new tab page after tab page 3 in normal mode
+ call feedkeys(TabMenuNewItemCode(4), "Lx!")
+ call assert_equal(4, tabpagenr())
+ call assert_equal(5, tabpagenr('$'))
+
+ " go to tab page 2 in insert mode
+ call feedkeys("i" .. TabLineSelectPageCode(2) .. "\<C-C>", "Lx!")
+ call assert_equal(2, tabpagenr())
+
+ " close tab page 2 in insert mode
+ call feedkeys("i" .. TabMenuCloseItemCode(2) .. "\<C-C>", "Lx!")
+ call assert_equal(4, tabpagenr('$'))
+
+ " open new tab page before tab page 3 in insert mode
+ call feedkeys("i" .. TabMenuNewItemCode(3) .. "\<C-C>", "Lx!")
+ call assert_equal(3, tabpagenr())
+ call assert_equal(5, tabpagenr('$'))
+
+ " open new tab page after tab page 4 in insert mode
+ call feedkeys("i" .. TabMenuNewItemCode(5) .. "\<C-C>", "Lx!")
+ call assert_equal(5, tabpagenr())
+ call assert_equal(6, tabpagenr('$'))
+
+ %bw!
+endfunc
+
+" Test for changing the current tab page from an autocmd when closing a tab
+" page.
+func Test_tabpage_switchtab_on_close()
+ only
+ tabnew
+ tabnew
+ " Test for BufLeave
+ augroup T1
+ au!
+ au BufLeave * tabfirst
+ augroup END
+ tabclose
+ call assert_equal(1, tabpagenr())
+ augroup T1
+ au!
+ augroup END
+
+ " Test for WinLeave
+ $tabnew
+ augroup T1
+ au!
+ au WinLeave * tabfirst
+ augroup END
+ tabclose
+ call assert_equal(1, tabpagenr())
+ augroup T1
+ au!
+ augroup END
+
+ " Test for TabLeave
+ $tabnew
+ augroup T1
+ au!
+ au TabLeave * tabfirst
+ augroup END
+ tabclose
+ call assert_equal(1, tabpagenr())
+ augroup T1
+ au!
+ augroup END
+ augroup! T1
+ tabonly
+endfunc
+
+" Test for closing the destination tabpage when jumping from one to another.
+func Test_tabpage_close_on_switch()
+ tabnew
+ tabnew
+ edit Xtabfile
+ augroup T2
+ au!
+ au BufLeave Xtabfile 1tabclose
+ augroup END
+ tabfirst
+ call assert_equal(2, tabpagenr())
+ call assert_equal('Xtabfile', @%)
+ augroup T2
+ au!
+ augroup END
+ augroup! T2
+ %bw!
+endfunc
+
+" Test for jumping to last accessed tabpage
+func Test_lastused_tabpage()
+ tabonly!
+ call assert_equal(0, tabpagenr('#'))
+ call assert_beeps('call feedkeys("g\<Tab>", "xt")')
+ call assert_beeps('call feedkeys("\<C-Tab>", "xt")')
+ call assert_beeps('call feedkeys("\<C-W>g\<Tab>", "xt")')
+ call assert_fails('tabnext #', 'E475:')
+
+ " open four tab pages
+ tabnew
+ tabnew
+ tabnew
+
+ 2tabnext
+
+ " Test for g<Tab>
+ call assert_equal(4, tabpagenr('#'))
+ call feedkeys("g\<Tab>", "xt")
+ call assert_equal(4, tabpagenr())
+ call assert_equal(2, tabpagenr('#'))
+
+ " Test for <C-Tab>
+ call feedkeys("\<C-Tab>", "xt")
+ call assert_equal(2, tabpagenr())
+ call assert_equal(4, tabpagenr('#'))
+
+ " Test for <C-W>g<Tab>
+ call feedkeys("\<C-W>g\<Tab>", "xt")
+ call assert_equal(4, tabpagenr())
+ call assert_equal(2, tabpagenr('#'))
+
+ " Test for :tabnext #
+ tabnext #
+ call assert_equal(2, tabpagenr())
+ call assert_equal(4, tabpagenr('#'))
+
+ " Try to jump to a closed tab page
+ tabclose #
+ call assert_equal(0, tabpagenr('#'))
+ call feedkeys("g\<Tab>", "xt")
+ call assert_equal(2, tabpagenr())
+ call feedkeys("\<C-Tab>", "xt")
+ call assert_equal(2, tabpagenr())
+ call feedkeys("\<C-W>g\<Tab>", "xt")
+ call assert_equal(2, tabpagenr())
+ call assert_fails('tabnext #', 'E475:')
+ call assert_equal(2, tabpagenr())
+
+ " Test for :tabonly #
+ let wnum = win_getid()
+ $tabnew
+ tabonly #
+ call assert_equal(wnum, win_getid())
+ call assert_equal(1, tabpagenr('$'))
+
+ " Test for :tabmove #
+ tabnew
+ let wnum = win_getid()
+ tabnew
+ tabnew
+ tabnext 2
+ tabmove #
+ call assert_equal(4, tabpagenr())
+ call assert_equal(wnum, win_getid())
+
+ tabonly!
+endfunc
+
+" Test for tabpage allocation failure
+func Test_tabpage_alloc_failure()
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ call assert_fails('tabnew', 'E342:')
+
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ edit Xfile1
+ call assert_fails('tabedit Xfile2', 'E342:')
+ call assert_equal(1, winnr('$'))
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('Xfile1', @%)
+
+ new
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ call assert_fails('wincmd T', 'E342:')
+ bw!
+
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ call assert_fails('tab split', 'E342:')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, tabpagenr('$'))
+endfunc
+
+" this was giving ml_get errors
+func Test_tabpage_last_line()
+ enew
+ call setline(1, repeat(['a'], &lines + 5))
+ $
+ tabnew
+ call setline(1, repeat(['b'], &lines + 20))
+ $
+ tabNext
+ call assert_equal('a', getline('.'))
+
+ bwipe!
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tagcase.vim b/src/testdir/test_tagcase.vim
new file mode 100644
index 0000000..f03a5fd
--- /dev/null
+++ b/src/testdir/test_tagcase.vim
@@ -0,0 +1,75 @@
+" test 'tagcase' option
+
+func Test_tagcase()
+ call writefile(["Bar\tXtext\t3", "Foo\tXtext\t2", "foo\tXtext\t4"], 'Xtags', 'D')
+ set tags=Xtags
+ e Xtext
+
+ for &ic in [0, 1]
+ for &scs in [0, 1]
+ for &g:tc in ["followic", "ignore", "match", "followscs", "smart"]
+ for &l:tc in ["", "followic", "ignore", "match", "followscs", "smart"]
+ let smart = 0
+ if &l:tc != ''
+ let tc = &l:tc
+ else
+ let tc = &g:tc
+ endif
+ if tc == 'followic'
+ let ic = &ic
+ elseif tc == 'ignore'
+ let ic = 1
+ elseif tc == 'followscs'
+ let ic = &ic
+ let smart = &scs
+ elseif tc == 'smart'
+ let ic = 1
+ let smart = 1
+ else
+ let ic = 0
+ endif
+ if ic && smart
+ call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name}))
+ call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name}))
+ elseif ic
+ call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name}))
+ call assert_equal(['Foo', 'foo'], map(taglist("^Foo$"), {i, v -> v.name}))
+ else
+ call assert_equal(['foo'], map(taglist("^foo$"), {i, v -> v.name}))
+ call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name}))
+ endif
+ endfor
+ endfor
+ endfor
+ endfor
+
+ set tags&
+ set ic&
+ setg tc&
+ setl tc&
+ set scs&
+endfunc
+
+func Test_set_tagcase()
+ " Verify default values.
+ set ic&
+ setg tc&
+ setl tc&
+ call assert_equal(0, &ic)
+ call assert_equal('followic', &g:tc)
+ call assert_equal('followic', &l:tc)
+ call assert_equal('followic', &tc)
+
+ " Verify that the local setting accepts <empty> but that the global setting
+ " does not. The first of these (setting the local value to <empty>) should
+ " succeed; the other two should fail.
+ setl tc=
+ call assert_fails('setg tc=', 'E474:')
+ call assert_fails('set tc=', 'E474:')
+
+ set ic&
+ setg tc&
+ setl tc&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tagfunc.vim b/src/testdir/test_tagfunc.vim
new file mode 100644
index 0000000..d3cd053
--- /dev/null
+++ b/src/testdir/test_tagfunc.vim
@@ -0,0 +1,416 @@
+" Test 'tagfunc'
+
+import './vim9.vim' as v9
+source check.vim
+source screendump.vim
+
+func TagFunc(pat, flag, info)
+ let g:tagfunc_args = [a:pat, a:flag, a:info]
+ let tags = []
+ for num in range(1,10)
+ let tags += [{
+ \ 'cmd': '2', 'name': 'nothing'.num, 'kind': 'm',
+ \ 'filename': 'Xfile1', 'user_data': 'somedata'.num,
+ \}]
+ endfor
+ return tags
+endfunc
+
+func Test_tagfunc()
+ set tagfunc=TagFunc
+ new Xfile1
+ call setline(1, ['empty', 'one()', 'empty'])
+ write
+
+ call assert_equal({'cmd': '2', 'static': 0,
+ \ 'name': 'nothing2', 'user_data': 'somedata2',
+ \ 'kind': 'm', 'filename': 'Xfile1'}, taglist('.')[1])
+
+ call settagstack(win_getid(), {'items': []})
+
+ tag arbitrary
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata1', gettagstack().items[0].user_data)
+ 5tag arbitrary
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata5', gettagstack().items[1].user_data)
+ pop
+ tag
+ call assert_equal('arbitrary', g:tagfunc_args[0])
+ call assert_equal('', g:tagfunc_args[1])
+ call assert_equal('somedata5', gettagstack().items[1].user_data)
+
+ let g:tagfunc_args=[]
+ execute "normal! \<c-]>"
+ call assert_equal('one', g:tagfunc_args[0])
+ call assert_equal('c', g:tagfunc_args[1])
+
+ let g:tagfunc_args=[]
+ execute "tag /foo$"
+ call assert_equal('foo$', g:tagfunc_args[0])
+ call assert_equal('r', g:tagfunc_args[1])
+
+ set cpt=t
+ let g:tagfunc_args=[]
+ execute "normal! i\<c-n>\<c-y>"
+ call assert_equal('\<\k\k', g:tagfunc_args[0])
+ call assert_equal('cir', g:tagfunc_args[1])
+ call assert_equal('nothing1', getline('.')[0:7])
+
+ let g:tagfunc_args=[]
+ execute "normal! ono\<c-n>\<c-n>\<c-y>"
+ call assert_equal('\<no', g:tagfunc_args[0])
+ call assert_equal('cir', g:tagfunc_args[1])
+ call assert_equal('nothing2', getline('.')[0:7])
+
+ func BadTagFunc1(...)
+ return 0
+ endfunc
+ func BadTagFunc2(...)
+ return [1]
+ endfunc
+ func BadTagFunc3(...)
+ return [{'name': 'foo'}]
+ endfunc
+
+ for &tagfunc in ['BadTagFunc1', 'BadTagFunc2', 'BadTagFunc3']
+ try
+ tag nothing
+ call assert_false(1, 'tag command should have failed')
+ catch
+ call assert_exception('E987:')
+ endtry
+ exe 'delf' &tagfunc
+ endfor
+
+ func NullTagFunc(...)
+ return v:null
+ endfunc
+ set tags= tfu=NullTagFunc
+ call assert_fails('tag nothing', 'E433:')
+ delf NullTagFunc
+
+ bwipe!
+ set tags& tfu& cpt&
+ call delete('Xfile1')
+endfunc
+
+" Test for modifying the tag stack from a tag function and jumping to a tag
+" from a tag function
+func Test_tagfunc_settagstack()
+ func Mytagfunc1(pat, flags, info)
+ call settagstack(1, {'tagname' : 'mytag', 'from' : [0, 10, 1, 0]})
+ return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
+ endfunc
+ set tagfunc=Mytagfunc1
+ call writefile([''], 'Xtest', 'D')
+ call assert_fails('tag xyz', 'E986:')
+
+ func Mytagfunc2(pat, flags, info)
+ tag test_tag
+ return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
+ endfunc
+ set tagfunc=Mytagfunc2
+ call assert_fails('tag xyz', 'E986:')
+
+ set tagfunc&
+ delfunc Mytagfunc1
+ delfunc Mytagfunc2
+endfunc
+
+" Script local tagfunc callback function
+func s:ScriptLocalTagFunc(pat, flags, info)
+ let g:ScriptLocalFuncArgs = [a:pat, a:flags, a:info]
+ return v:null
+endfunc
+
+" Test for different ways of setting the 'tagfunc' option
+func Test_tagfunc_callback()
+ func TagFunc1(callnr, pat, flags, info)
+ let g:TagFunc1Args = [a:callnr, a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+ func TagFunc2(pat, flags, info)
+ let g:TagFunc2Args = [a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+
+ let lines =<< trim END
+ #" Test for using a function name
+ LET &tagfunc = 'g:TagFunc2'
+ new
+ LET g:TagFunc2Args = []
+ call assert_fails('tag a10', 'E433:')
+ call assert_equal(['a10', '', {}], g:TagFunc2Args)
+ bw!
+
+ #" Test for using a function()
+ set tagfunc=function('g:TagFunc1',\ [10])
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a11', 'E433:')
+ call assert_equal([10, 'a11', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'tagfunc'
+ VAR Fn = function('g:TagFunc1', [11])
+ LET &tagfunc = Fn
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a12', 'E433:')
+ call assert_equal([11, 'a12', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'tagfunc'
+ LET Fn = function('g:TagFunc1', [12])
+ LET &tagfunc = string(Fn)
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a12', 'E433:')
+ call assert_equal([12, 'a12', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Test for using a funcref()
+ set tagfunc=funcref('g:TagFunc1',\ [13])
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a13', 'E433:')
+ call assert_equal([13, 'a13', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a funcref variable to set 'tagfunc'
+ LET Fn = funcref('g:TagFunc1', [14])
+ LET &tagfunc = Fn
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a14', 'E433:')
+ call assert_equal([14, 'a14', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Using a string(funcref_variable) to set 'tagfunc'
+ LET Fn = funcref('g:TagFunc1', [15])
+ LET &tagfunc = string(Fn)
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a14', 'E433:')
+ call assert_equal([15, 'a14', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Test for using a lambda function
+ VAR optval = "LSTART a, b, c LMIDDLE g:TagFunc1(16, a, b, c) LEND"
+ LET optval = substitute(optval, ' ', '\\ ', 'g')
+ exe "set tagfunc=" .. optval
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a17', 'E433:')
+ call assert_equal([16, 'a17', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a lambda expression
+ LET &tagfunc = LSTART a, b, c LMIDDLE g:TagFunc1(17, a, b, c) LEND
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a18', 'E433:')
+ call assert_equal([17, 'a18', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a string(lambda expression)
+ LET &tagfunc = 'LSTART a, b, c LMIDDLE g:TagFunc1(18, a, b, c) LEND'
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails('tag a18', 'E433:')
+ call assert_equal([18, 'a18', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a variable with a lambda expression
+ VAR Lambda = LSTART a, b, c LMIDDLE g:TagFunc1(19, a, b, c) LEND
+ LET &tagfunc = Lambda
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails("tag a19", "E433:")
+ call assert_equal([19, 'a19', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Set 'tagfunc' to a string(variable with a lambda expression)
+ LET Lambda = LSTART a, b, c LMIDDLE g:TagFunc1(20, a, b, c) LEND
+ LET &tagfunc = string(Lambda)
+ new
+ LET g:TagFunc1Args = []
+ call assert_fails("tag a19", "E433:")
+ call assert_equal([20, 'a19', '', {}], g:TagFunc1Args)
+ bw!
+
+ #" Test for using a lambda function with incorrect return value
+ LET Lambda = LSTART a, b, c LMIDDLE strlen(a) LEND
+ LET &tagfunc = string(Lambda)
+ new
+ call assert_fails("tag a20", "E987:")
+ bw!
+
+ #" Test for clearing the 'tagfunc' option
+ set tagfunc=''
+ set tagfunc&
+ call assert_fails("set tagfunc=function('abc')", "E700:")
+ call assert_fails("set tagfunc=funcref('abc')", "E700:")
+
+ #" set 'tagfunc' to a non-existing function
+ LET &tagfunc = function('g:TagFunc2', [21])
+ LET g:TagFunc2Args = []
+ call assert_fails("set tagfunc=function('NonExistingFunc')", 'E700:')
+ call assert_fails("LET &tagfunc = function('NonExistingFunc')", 'E700:')
+ call assert_fails("tag axb123", 'E426:')
+ call assert_equal([], g:TagFunc2Args)
+ bw!
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+
+ " Test for using a script-local function name
+ func s:TagFunc3(pat, flags, info)
+ let g:TagFunc3Args = [a:pat, a:flags, a:info]
+ return v:null
+ endfunc
+ set tagfunc=s:TagFunc3
+ new
+ let g:TagFunc3Args = []
+ call assert_fails('tag a21', 'E433:')
+ call assert_equal(['a21', '', {}], g:TagFunc3Args)
+ bw!
+ let &tagfunc = 's:TagFunc3'
+ new
+ let g:TagFunc3Args = []
+ call assert_fails('tag a22', 'E433:')
+ call assert_equal(['a22', '', {}], g:TagFunc3Args)
+ bw!
+ delfunc s:TagFunc3
+
+ " invalid return value
+ let &tagfunc = "{a -> 'abc'}"
+ call assert_fails("echo taglist('a')", "E987:")
+
+ " Using Vim9 lambda expression in legacy context should fail
+ set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c)
+ new
+ let g:TagFunc1Args = []
+ call assert_fails("tag a17", "E117:")
+ call assert_equal([], g:TagFunc1Args)
+ bw!
+
+ " Test for using a script local function
+ set tagfunc=<SID>ScriptLocalTagFunc
+ new
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a15', 'E433:')
+ call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs)
+ bw!
+
+ " Test for using a script local funcref variable
+ let Fn = function("s:ScriptLocalTagFunc")
+ let &tagfunc= Fn
+ new
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a16', 'E433:')
+ call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
+ bw!
+
+ " Test for using a string(script local funcref variable)
+ let Fn = function("s:ScriptLocalTagFunc")
+ let &tagfunc= string(Fn)
+ new
+ let g:ScriptLocalFuncArgs = []
+ call assert_fails('tag a16', 'E433:')
+ call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs)
+ bw!
+
+ " set 'tagfunc' to a partial with dict. This used to cause a crash.
+ func SetTagFunc()
+ let params = {'tagfn': function('g:DictTagFunc')}
+ let &tagfunc = params.tagfn
+ endfunc
+ func g:DictTagFunc(_) dict
+ endfunc
+ call SetTagFunc()
+ new
+ call SetTagFunc()
+ bw
+ call test_garbagecollect_now()
+ new
+ set tagfunc=
+ wincmd w
+ set tagfunc=
+ :%bw!
+ delfunc g:DictTagFunc
+ delfunc SetTagFunc
+
+ " Vim9 tests
+ let lines =<< trim END
+ vim9script
+
+ def Vim9tagFunc(callnr: number, pat: string, flags: string, info: dict<any>): any
+ g:Vim9tagFuncArgs = [callnr, pat, flags, info]
+ return null
+ enddef
+
+ # Test for using a def function with completefunc
+ set tagfunc=function('Vim9tagFunc',\ [60])
+ new
+ g:Vim9tagFuncArgs = []
+ assert_fails('tag a10', 'E433:')
+ assert_equal([60, 'a10', '', {}], g:Vim9tagFuncArgs)
+
+ # Test for using a global function name
+ &tagfunc = g:TagFunc2
+ new
+ g:TagFunc2Args = []
+ assert_fails('tag a11', 'E433:')
+ assert_equal(['a11', '', {}], g:TagFunc2Args)
+ bw!
+
+ # Test for using a script-local function name
+ def LocalTagFunc(pat: string, flags: string, info: dict<any> ): any
+ g:LocalTagFuncArgs = [pat, flags, info]
+ return null
+ enddef
+ &tagfunc = LocalTagFunc
+ new
+ g:LocalTagFuncArgs = []
+ assert_fails('tag a12', 'E433:')
+ assert_equal(['a12', '', {}], g:LocalTagFuncArgs)
+ bw!
+ END
+ call v9.CheckScriptSuccess(lines)
+
+ " cleanup
+ delfunc TagFunc1
+ delfunc TagFunc2
+ set tagfunc&
+ %bw!
+endfunc
+
+func Test_tagfunc_wipes_buffer()
+ func g:Tag0unc0(t,f,o)
+ bwipe
+ endfunc
+ set tagfunc=g:Tag0unc0
+ new
+ cal assert_fails('tag 0', 'E987:')
+
+ delfunc g:Tag0unc0
+ set tagfunc=
+endfunc
+
+func Test_tagfunc_closes_window()
+ split any
+ func MytagfuncClose(pat, flags, info)
+ close
+ return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}]
+ endfunc
+ set tagfunc=MytagfuncClose
+ call assert_fails('tag xyz', 'E1299:')
+
+ set tagfunc=
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tagjump.vim b/src/testdir/test_tagjump.vim
new file mode 100644
index 0000000..8b85bd6
--- /dev/null
+++ b/src/testdir/test_tagjump.vim
@@ -0,0 +1,1548 @@
+" Tests for tagjump (tags and special searches)
+
+source check.vim
+source screendump.vim
+
+" SEGV occurs in older versions. (At least 7.4.1748 or older)
+func Test_ptag_with_notagstack()
+ CheckFeature quickfix
+
+ set notagstack
+ call assert_fails('ptag does_not_exist_tag_name', 'E433:')
+ set tagstack&vim
+endfunc
+
+func Test_ptjump()
+ CheckFeature quickfix
+
+ set tags=Xpttags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXptfile\t1",
+ \ "three\tXptfile\t3",
+ \ "two\tXptfile\t2"],
+ \ 'Xpttags', 'D')
+ call writefile(['one', 'two', 'three'], 'Xptfile', 'D')
+
+ %bw!
+ ptjump two
+ call assert_equal(2, winnr())
+ wincmd p
+ call assert_equal(1, &previewwindow)
+ call assert_equal('Xptfile', expand("%:p:t"))
+ call assert_equal(2, line('.'))
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, winnr())
+ close
+ call setline(1, ['one', 'two', 'three'])
+ exe "normal 3G\<C-W>g}"
+ call assert_equal(2, winnr())
+ wincmd p
+ call assert_equal(1, &previewwindow)
+ call assert_equal('Xptfile', expand("%:p:t"))
+ call assert_equal(3, line('.'))
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, winnr())
+ close
+ exe "normal 3G5\<C-W>\<C-G>}"
+ wincmd p
+ call assert_equal(5, winheight(0))
+ close
+
+ set tags&
+endfunc
+
+func Test_cancel_ptjump()
+ CheckFeature quickfix
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "word\tfile1\tcmd1",
+ \ "word\tfile2\tcmd2"],
+ \ 'Xtags', 'D')
+
+ only!
+ call feedkeys(":ptjump word\<CR>\<CR>", "xt")
+ help
+ call assert_equal(2, winnr('$'))
+
+ set tags&
+ quit
+endfunc
+
+func Test_static_tagjump()
+ set tags=Xtjtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXtjfile1\t/^one/;\"\tf\tfile:\tsignature:(void)",
+ \ "word\tXtjfile2\tcmd2"],
+ \ 'Xtjtags', 'D')
+ new Xtjfile1
+ call setline(1, ['empty', 'one()', 'empty'])
+ write
+ tag one
+ call assert_equal(2, line('.'))
+
+ bwipe!
+ set tags&
+ call delete('Xtjfile1')
+endfunc
+
+func Test_duplicate_tagjump()
+ set tags=Xdttags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "thesame\tXdtfile1\t1;\"\td\tfile:",
+ \ "thesame\tXdtfile1\t2;\"\td\tfile:",
+ \ "thesame\tXdtfile1\t3;\"\td\tfile:",
+ \ ],
+ \ 'Xdttags', 'D')
+ new Xdtfile1
+ call setline(1, ['thesame one', 'thesame two', 'thesame three'])
+ write
+ tag thesame
+ call assert_equal(1, line('.'))
+ tnext
+ call assert_equal(2, line('.'))
+ tnext
+ call assert_equal(3, line('.'))
+
+ bwipe!
+ set tags&
+ call delete('Xdtfile1')
+endfunc
+
+func Test_tagjump_switchbuf()
+ CheckFeature quickfix
+
+ set tags=Xswtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "second\tXsbfile1\t2",
+ \ "third\tXsbfile1\t3",],
+ \ 'Xswtags', 'D')
+ call writefile(['first', 'second', 'third'], 'Xsbfile1', 'D')
+
+ enew | only
+ set switchbuf=
+ stag second
+ call assert_equal(2, winnr('$'))
+ call assert_equal(2, line('.'))
+ stag third
+ call assert_equal(3, winnr('$'))
+ call assert_equal(3, line('.'))
+
+ enew | only
+ set switchbuf=useopen
+ stag second
+ call assert_equal(2, winnr('$'))
+ call assert_equal(2, line('.'))
+ stag third
+ call assert_equal(2, winnr('$'))
+ call assert_equal(3, line('.'))
+
+ enew | only
+ set switchbuf=usetab
+ tab stag second
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal(2, line('.'))
+ 1tabnext | stag third
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal(3, line('.'))
+
+ tabclose!
+ enew | only
+ set tags&
+ set switchbuf&vim
+endfunc
+
+" Tests for [ CTRL-I and CTRL-W CTRL-I commands
+function Test_keyword_jump()
+ call writefile(["#include Xinclude", "",
+ \ "",
+ \ "/* test text test tex start here",
+ \ " some text",
+ \ " test text",
+ \ " start OK if found this line",
+ \ " start found wrong line",
+ \ "test text"], 'Xtestfile', 'D')
+ call writefile(["/* test text test tex start here",
+ \ " some text",
+ \ " test text",
+ \ " start OK if found this line",
+ \ " start found wrong line",
+ \ "test text"], 'Xinclude', 'D')
+ new Xtestfile
+ call cursor(1,1)
+ call search("start")
+ exe "normal! 5[\<C-I>"
+ call assert_equal(" start OK if found this line", getline('.'))
+ call cursor(1,1)
+ call search("start")
+ exe "normal! 5\<C-W>\<C-I>"
+ call assert_equal(" start OK if found this line", getline('.'))
+
+ " invalid tag search pattern
+ call assert_fails('tag /\%(/', 'E426:')
+
+ enew! | only
+endfunction
+
+" Test for jumping to a tag with 'hidden' set, with symbolic link in path of
+" tag. This only works for Unix, because of the symbolic link.
+func Test_tag_symbolic()
+ CheckUnix
+
+ set hidden
+ call delete("Xtest.dir", "rf")
+ call system("ln -s . Xtest.dir")
+ " Create a tags file with the current directory name inserted.
+ call writefile([
+ \ "SECTION_OFF " . getcwd() . "/Xtest.dir/Xtest.c /^#define SECTION_OFF 3$/",
+ \ '',
+ \ ], 'Xsymtags', 'D')
+ call writefile(['#define SECTION_OFF 3',
+ \ '#define NUM_SECTIONS 3'], 'Xtest.c', 'D')
+
+ " Try jumping to a tag, but with a path that contains a symbolic link. When
+ " wrong, this will give the ATTENTION message. The next space will then be
+ " eaten by hit-return, instead of moving the cursor to 'd'.
+ set tags=Xsymtags
+ enew!
+ call append(0, 'SECTION_OFF')
+ call cursor(1,1)
+ exe "normal \<C-]> "
+ call assert_equal('Xtest.c', expand('%:t'))
+ call assert_equal(2, col('.'))
+
+ set hidden&
+ set tags&
+ enew!
+ call delete("Xtest.dir", "rf")
+ %bwipe!
+endfunc
+
+" Tests for tag search with !_TAG_FILE_ENCODING.
+func Test_tag_file_encoding()
+ if has('vms')
+ throw 'Skipped: does not work on VMS'
+ endif
+
+ if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21"
+ throw 'Skipped: iconv does not work'
+ endif
+
+ let save_enc = &encoding
+ set encoding=utf8
+
+ let content = ['text for tags1', 'abcdefghijklmnopqrs']
+ call writefile(content, 'Xtags1.txt', 'D')
+ let content = ['text for tags2', 'ABC']
+ call writefile(content, 'Xtags2.txt', 'D')
+ let content = ['text for tags3', 'ABC']
+ call writefile(content, 'Xtags3.txt', 'D')
+ let content = ['!_TAG_FILE_ENCODING utf-8 //', 'abcdefghijklmnopqrs Xtags1.txt /abcdefghijklmnopqrs']
+ call writefile(content, 'Xtags1', 'D')
+
+ " case1:
+ new
+ set tags=Xtags1
+ tag abcdefghijklmnopqrs
+ call assert_equal('Xtags1.txt', expand('%:t'))
+ call assert_equal('abcdefghijklmnopqrs', getline('.'))
+ close
+
+ " case2:
+ new
+ let content = ['!_TAG_FILE_ENCODING cp932 //',
+ \ "\x82`\x82a\x82b Xtags2.txt /\x82`\x82a\x82b"]
+ call writefile(content, 'Xenctags')
+ set tags=Xenctags
+ tag /.BC
+ call assert_equal('Xtags2.txt', expand('%:t'))
+ call assert_equal('ABC', getline('.'))
+ call delete('Xenctags')
+ close
+
+ " case3:
+ new
+ let contents = [
+ \ "!_TAG_FILE_SORTED 1 //",
+ \ "!_TAG_FILE_ENCODING cp932 //"]
+ for i in range(1, 100)
+ call add(contents, 'abc' .. i
+ \ .. " Xtags3.txt /\x82`\x82a\x82b")
+ endfor
+ call writefile(contents, 'Xenctags', 'D')
+ set tags=Xenctags
+ tag abc50
+ call assert_equal('Xtags3.txt', expand('%:t'))
+ call assert_equal('ABC', getline('.'))
+ close
+
+ set tags&
+ let &encoding = save_enc
+endfunc
+
+" Test for emacs-style tags file (TAGS)
+func Test_tagjump_etags()
+ CheckFeature emacs_tags
+
+ call writefile([
+ \ "void foo() {}",
+ \ "int main(int argc, char **argv)",
+ \ "{",
+ \ "\tfoo();",
+ \ "\treturn 0;",
+ \ "}",
+ \ ], 'Xmain.c', 'D')
+
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x011,0",
+ \ "int main(int argc, char **argv)\x7fmain\x012,14",
+ \ ], 'Xtetags', 'D')
+ set tags=Xtetags
+ ta foo
+ call assert_equal('void foo() {}', getline('.'))
+
+ " Test for including another tags file
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x011,0",
+ \ "\x0c",
+ \ "Xnonexisting,include",
+ \ "\x0c",
+ \ "Xtags2,include"
+ \ ], 'Xtetags')
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "int main(int argc, char **argv)\x7fmain\x012,14",
+ \ ], 'Xtags2', 'D')
+ tag main
+ call assert_equal(2, line('.'))
+ call assert_fails('tag bar', 'E426:')
+
+ " corrupted tag line
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,8",
+ \ "int main"
+ \ ], 'Xtetags', 'b')
+ call assert_fails('tag foo', 'E426:')
+
+ " invalid line number
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x0abc,0",
+ \ ], 'Xtetags')
+ call assert_fails('tag foo', 'E426:')
+
+ " invalid tag name
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ ";;;;\x7f1,0",
+ \ ], 'Xtetags')
+ call assert_fails('tag foo', 'E431:')
+
+ " end of file after a CTRL-L line
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}\x7ffoo\x011,0",
+ \ "\x0c",
+ \ ], 'Xtetags')
+ call assert_fails('tag main', 'E426:')
+
+ " error in an included tags file
+ call writefile([
+ \ "\x0c",
+ \ "Xtags2,include"
+ \ ], 'Xtetags')
+ call writefile([
+ \ "\x0c",
+ \ "Xmain.c,64",
+ \ "void foo() {}",
+ \ ], 'Xtags2')
+ call assert_fails('tag foo', 'E431:')
+
+ set tags&
+ bwipe!
+endfunc
+
+" Test for getting and modifying the tag stack
+func Test_getsettagstack()
+ call writefile(['line1', 'line2', 'line3'], 'Xstsfile1', 'D')
+ call writefile(['line1', 'line2', 'line3'], 'Xstsfile2', 'D')
+ call writefile(['line1', 'line2', 'line3'], 'Xstsfile3', 'D')
+
+ enew | only
+ call settagstack(1, {'items' : []})
+ call assert_equal(0, gettagstack(1).length)
+ call assert_equal([], 1->gettagstack().items)
+ " Error cases
+ call assert_equal({}, gettagstack(100))
+ call assert_equal(-1, settagstack(100, {'items' : []}))
+ call assert_fails('call settagstack(1, [1, 10])', 'E1206:')
+ call assert_fails("call settagstack(1, {'items' : 10})", 'E714:')
+ call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E1174:')
+ call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962:')
+ call assert_equal(-1, settagstack(0, test_null_dict()))
+
+ set tags=Xtsttags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXstsfile1\t1",
+ \ "three\tXstsfile3\t3",
+ \ "two\tXstsfile2\t2"],
+ \ 'Xtsttags', 'D')
+
+ let stk = []
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'one',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag one
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'two',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag two
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'three',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag three
+ call assert_equal(3, gettagstack(1).length)
+ call assert_equal(stk, gettagstack(1).items)
+ " Check for default - current window
+ call assert_equal(3, gettagstack().length)
+ call assert_equal(stk, gettagstack().items)
+
+ " Try to set current index to invalid values
+ call settagstack(1, {'curidx' : -1})
+ call assert_equal(1, gettagstack().curidx)
+ eval {'curidx' : 50}->settagstack(1)
+ call assert_equal(4, gettagstack().curidx)
+
+ " Try pushing invalid items onto the stack
+ call settagstack(1, {'items' : []})
+ call settagstack(1, {'items' : ["plate"]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"tagname" : "abc"}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"from" : 100}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"from" : [2, 1, 0, 0]}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+
+ " Push one item at a time to the stack
+ call settagstack(1, {'items' : []})
+ call settagstack(1, {'items' : [stk[0]]}, 'a')
+ call settagstack(1, {'items' : [stk[1]]}, 'a')
+ call settagstack(1, {'items' : [stk[2]]}, 'a')
+ call settagstack(1, {'curidx' : 4})
+ call assert_equal({'length' : 3, 'curidx' : 4, 'items' : stk},
+ \ gettagstack(1))
+
+ " Try pushing items onto a full stack
+ for i in range(7)
+ call settagstack(1, {'items' : stk}, 'a')
+ endfor
+ call assert_equal(20, gettagstack().length)
+ call settagstack(1,
+ \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a')
+ call assert_equal('abc', gettagstack().items[19].tagname)
+
+ " truncate the tag stack
+ call settagstack(1,
+ \ {'curidx' : 9,
+ \ 'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
+ let t = gettagstack()
+ call assert_equal(9, t.length)
+ call assert_equal(10, t.curidx)
+
+ " truncate the tag stack without pushing any new items
+ call settagstack(1, {'curidx' : 5}, 't')
+ let t = gettagstack()
+ call assert_equal(4, t.length)
+ call assert_equal(5, t.curidx)
+
+ " truncate an empty tag stack and push new items
+ call settagstack(1, {'items' : []})
+ call settagstack(1,
+ \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
+ let t = gettagstack()
+ call assert_equal(1, t.length)
+ call assert_equal(2, t.curidx)
+
+ " Tag with multiple matches
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "two\tXstsfile1\t1",
+ \ "two\tXstsfile2\t3",
+ \ "two\tXstsfile3\t2"],
+ \ 'Xtsttags')
+ call settagstack(1, {'items' : []})
+ tag two
+ tnext
+ tnext
+ call assert_equal(1, gettagstack().length)
+ call assert_equal(3, gettagstack().items[0].matchnr)
+
+ " Memory allocation failures
+ call test_alloc_fail(GetAllocId('tagstack_items'), 0, 0)
+ call assert_fails('call gettagstack()', 'E342:')
+ call test_alloc_fail(GetAllocId('tagstack_from'), 0, 0)
+ call assert_fails('call gettagstack()', 'E342:')
+ call test_alloc_fail(GetAllocId('tagstack_details'), 0, 0)
+ call assert_fails('call gettagstack()', 'E342:')
+
+ call settagstack(1, {'items' : []})
+ set tags&
+endfunc
+
+func Test_tag_with_count()
+ call writefile([
+ \ 'test Xtest.h /^void test();$/;" p typeref:typename:void signature:()',
+ \ ], 'Xtags', 'D')
+ call writefile([
+ \ 'main Xtest.c /^int main()$/;" f typeref:typename:int signature:()',
+ \ 'test Xtest.c /^void test()$/;" f typeref:typename:void signature:()',
+ \ ], 'Ytags', 'D')
+ cal writefile([
+ \ 'int main()',
+ \ 'void test()',
+ \ ], 'Xtest.c', 'D')
+ cal writefile([
+ \ 'void test();',
+ \ ], 'Xtest.h', 'D')
+ set tags=Xtags,Ytags
+
+ new Xtest.c
+ let tl = taglist('test', 'Xtest.c')
+ call assert_equal(tl[0].filename, 'Xtest.c')
+ call assert_equal(tl[1].filename, 'Xtest.h')
+
+ tag test
+ call assert_equal(bufname('%'), 'Xtest.c')
+ 1tag test
+ call assert_equal(bufname('%'), 'Xtest.c')
+ 2tag test
+ call assert_equal(bufname('%'), 'Xtest.h')
+
+ set tags&
+ bwipe Xtest.h
+ bwipe Xtest.c
+endfunc
+
+func Test_tagnr_recall()
+ call writefile([
+ \ 'test Xtest.h /^void test();$/;" p',
+ \ 'main Xtest.c /^int main()$/;" f',
+ \ 'test Xtest.c /^void test()$/;" f',
+ \ ], 'Xtags', 'D')
+ cal writefile([
+ \ 'int main()',
+ \ 'void test()',
+ \ ], 'Xtest.c', 'D')
+ cal writefile([
+ \ 'void test();',
+ \ ], 'Xtest.h', 'D')
+ set tags=Xtags
+
+ new Xtest.c
+ let tl = taglist('test', 'Xtest.c')
+ call assert_equal(tl[0].filename, 'Xtest.c')
+ call assert_equal(tl[1].filename, 'Xtest.h')
+
+ 2tag test
+ call assert_equal(bufname('%'), 'Xtest.h')
+ pop
+ call assert_equal(bufname('%'), 'Xtest.c')
+ tag
+ call assert_equal(bufname('%'), 'Xtest.h')
+
+ set tags&
+ bwipe Xtest.h
+ bwipe Xtest.c
+endfunc
+
+func Test_tag_line_toolong()
+ call writefile([
+ \ '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML'
+ \ ], 'Xtags', 'D')
+ set tags=Xtags
+ let old_vbs = &verbose
+ set verbose=5
+ " ":tjump" should give "tag not found" not "Format error in tags file"
+ call assert_fails('tj /foo', 'E426:')
+ try
+ tj /foo
+ catch /^Vim\%((\a\+)\)\=:E431/
+ call assert_report(v:exception)
+ catch /.*/
+ endtry
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
+ call writefile([
+ \ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML'
+ \ ], 'Xtags')
+ call assert_fails('tj /foo', 'E426:')
+ try
+ tj /foo
+ catch /^Vim\%((\a\+)\)\=:E431/
+ call assert_report(v:exception)
+ catch /.*/
+ endtry
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
+ " binary search works in file with long line
+ call writefile([
+ \ 'asdfasfd nowhere 16',
+ \ 'foobar Xsomewhere 3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567',
+ \ 'zasdfasfd nowhere 16',
+ \ ], 'Xtags')
+ call writefile([
+ \ 'one',
+ \ 'two',
+ \ 'trhee',
+ \ 'four',
+ \ ], 'Xsomewhere', 'D')
+ tag foobar
+ call assert_equal('Xsomewhere', expand('%'))
+ call assert_equal(3, getcurpos()[1])
+
+ " expansion on command line works with long lines when &wildoptions contains
+ " 'tagfile'
+ set wildoptions=tagfile
+ call writefile([
+ \ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa file /^pattern$/;" f'
+ \ ], 'Xtags')
+ call feedkeys(":tag \<Tab>", 'tx')
+ " Should not crash
+ call assert_true(v:true)
+
+ set tags&
+ let &verbose = old_vbs
+endfunc
+
+" Check that using :tselect does not run into the hit-enter prompt.
+" Requires a terminal to trigger that prompt.
+func Test_tselect()
+ CheckScreendump
+
+ call writefile([
+ \ 'main Xtest.h /^void test();$/;" f',
+ \ 'main Xtest.c /^int main()$/;" f',
+ \ 'main Xtest.x /^void test()$/;" f',
+ \ ], 'Xtags', 'D')
+ cal writefile([
+ \ 'int main()',
+ \ 'void test()',
+ \ ], 'Xtest.c', 'D')
+
+ let lines =<< trim [SCRIPT]
+ set tags=Xtags
+ [SCRIPT]
+ call writefile(lines, 'XTest_tselect', 'D')
+ let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50})
+
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":tselect main\<CR>2\<CR>")
+ call VerifyScreenDump(buf, 'Test_tselect_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_tagline()
+ call writefile([
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo',
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar',
+ \], 'Xtags', 'D')
+ call writefile([
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \], 'Xtest.py', 'D')
+
+ set tags=Xtags
+
+ 1tag provision
+ call assert_equal(line('.'), 1)
+ 2tag provision
+ call assert_equal(line('.'), 3)
+
+ set tags&
+endfunc
+
+" Test for expanding environment variable in a tag file name
+func Test_tag_envvar()
+ call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags', 'D')
+ set tags=Xtags
+
+ let $FOO='TagTestEnv'
+
+ let caught_exception = v:false
+ try
+ tag Func1
+ catch /E429:/
+ call assert_match('E429:.*"TagTestEnv".*', v:exception)
+ let caught_exception = v:true
+ endtry
+ call assert_true(caught_exception)
+
+ set tags&
+ unlet $FOO
+endfunc
+
+" Test for :ptag
+func Test_tag_preview()
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "second\tXtpfile1\t2",
+ \ "third\tXtpfile1\t3",],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ call writefile(['first', 'second', 'third'], 'Xtpfile1', 'D')
+
+ enew | only
+ ptag third
+ call assert_equal(2, winnr())
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, getwinvar(1, '&previewwindow'))
+ call assert_equal(0, getwinvar(2, '&previewwindow'))
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ " jump to the tag again
+ wincmd w
+ ptag third
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ " jump to the newer tag
+ wincmd w
+ ptag
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ " close the preview window
+ pclose
+ call assert_equal(1, winnr('$'))
+
+ set tags&
+endfunc
+
+" Tests for guessing the tag location
+func Test_tag_guess()
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "func1\tXfoo\t/^int func1(int x)/",
+ \ "func2\tXfoo\t/^int func2(int y)/",
+ \ "func3\tXfoo\t/^func3/",
+ \ "func4\tXfoo\t/^func4/"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+
+ int FUNC1 (int x) { }
+ int
+ func2 (int y) { }
+ int * func3 () { }
+
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ let v:statusmsg = ''
+ ta func1
+ call assert_match('E435:', v:statusmsg)
+ call assert_equal(2, line('.'))
+ let v:statusmsg = ''
+ ta func2
+ call assert_match('E435:', v:statusmsg)
+ call assert_equal(4, line('.'))
+ let v:statusmsg = ''
+ ta func3
+ call assert_match('E435:', v:statusmsg)
+ call assert_equal(5, line('.'))
+ call assert_fails('ta func4', 'E434:')
+
+ set tags&
+endfunc
+
+" Test for an unsorted tags file
+func Test_tag_sort()
+ let l = [
+ \ "first\tXfoo\t1",
+ \ "ten\tXfoo\t3",
+ \ "six\tXfoo\t2"]
+ call writefile(l, 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int six() {}
+ int ten() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ call assert_fails('tag first', 'E432:')
+
+ " When multiple tag files are not sorted, then message should be displayed
+ " multiple times
+ call writefile(l, 'Xtags2', 'D')
+ set tags=Xtags,Xtags2
+ call assert_fails('tag first', ['E432:', 'E432:'])
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for an unsorted tags file
+func Test_tag_fold()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/",
+ \ "first\tXfoo\t1",
+ \ "second\tXfoo\t2",
+ \ "third\tXfoo\t3"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ enew
+ tag second
+ call assert_equal('Xfoo', bufname(''))
+ call assert_equal(2, line('.'))
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for the :ltag command
+func Test_ltag()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1",
+ \ "second\tXfoo\t/^int second() {}$/",
+ \ "third\tXfoo\t3"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ enew
+ call setloclist(0, [], 'f')
+ ltag third
+ call assert_equal('Xfoo', bufname(''))
+ call assert_equal(3, line('.'))
+ call assert_equal([{'lnum': 3, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'),
+ \ 'col': 0, 'end_col': 0, 'pattern': '', 'valid': 1, 'vcol': 0,
+ \ 'nr': 0, 'type': '', 'module': '', 'text': 'third'}], getloclist(0))
+
+ ltag second
+ call assert_equal(2, line('.'))
+ call assert_equal([{'lnum': 0, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'),
+ \ 'col': 0, 'end_col': 0, 'pattern': '^\Vint second() {}\$',
+ \ 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', 'module': '',
+ \ 'text': 'second'}], getloclist(0))
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for setting the last search pattern to the tag search pattern
+" when cpoptions has 't'
+func Test_tag_last_search_pat()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t/^int first() {}/",
+ \ "second\tXfoo\t/^int second() {}/",
+ \ "third\tXfoo\t/^int third() {}/"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ enew
+ let save_cpo = &cpo
+ set cpo+=t
+ let @/ = ''
+ tag second
+ call assert_equal('^int second() {}', @/)
+ let &cpo = save_cpo
+
+ set tags&
+ %bwipe
+endfunc
+
+" Tag stack tests
+func Test_tag_stack()
+ let l = []
+ for i in range(10, 31)
+ let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"]
+ endfor
+ call writefile(l, 'Xtags', 'D')
+ set tags=Xtags
+
+ let l = []
+ for i in range(10, 31)
+ let l += ["int var" .. i .. ";"]
+ endfor
+ call writefile(l, 'Xfoo', 'D')
+
+ " Jump to a tag when the tag stack is full. Oldest entry should be removed.
+ enew
+ for i in range(10, 30)
+ exe "tag var" .. i
+ endfor
+ let l = gettagstack()
+ call assert_equal(20, l.length)
+ call assert_equal('var11', l.items[0].tagname)
+ tag var31
+ let l = gettagstack()
+ call assert_equal('var12', l.items[0].tagname)
+ call assert_equal('var31', l.items[19].tagname)
+
+ " Use tnext with a single match
+ call assert_fails('tnext', 'E427:')
+
+ " Jump to newest entry from the top of the stack
+ call assert_fails('tag', 'E556:')
+
+ " Pop with zero count from the top of the stack
+ call assert_fails('0pop', 'E556:')
+
+ " Pop from an unsaved buffer
+ enew!
+ call append(1, "sample text")
+ call assert_fails('pop', 'E37:')
+ call assert_equal(21, gettagstack().curidx)
+ enew!
+
+ " Pop all the entries in the tag stack
+ call assert_fails('30pop', 'E555:')
+
+ " Pop with a count when already at the bottom of the stack
+ call assert_fails('exe "normal 4\<C-T>"', 'E555:')
+ call assert_equal(1, gettagstack().curidx)
+
+ " Jump to newest entry from the bottom of the stack with zero count
+ call assert_fails('0tag', 'E555:')
+
+ " Pop the tag stack when it is empty
+ call settagstack(1, {'items' : []})
+ call assert_fails('pop', 'E73:')
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for browsing multiple matching tags
+func Test_tag_multimatch()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1",
+ \ "first\tXfoo\t2",
+ \ "first\tXfoo\t3"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int first() {}
+ int first() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ call settagstack(1, {'items' : []})
+ tag first
+ tlast
+ call assert_equal(3, line('.'))
+ call assert_fails('tnext', 'E428:')
+ tfirst
+ call assert_equal(1, line('.'))
+ call assert_fails('tprev', 'E425:')
+
+ tlast
+ call feedkeys("5\<CR>", 't')
+ tselect first
+ call assert_equal(2, gettagstack().curidx)
+
+ set ignorecase
+ tag FIRST
+ tnext
+ call assert_equal(2, line('.'))
+ tlast
+ tprev
+ call assert_equal(2, line('.'))
+ tNext
+ call assert_equal(1, line('.'))
+ set ignorecase&
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for previewing multiple matching tags
+func Test_preview_tag_multimatch()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1",
+ \ "first\tXfoo\t2",
+ \ "first\tXfoo\t3"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int first() {}
+ int first() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ enew | only
+ ptag first
+ ptlast
+ wincmd P
+ call assert_equal(3, line('.'))
+ wincmd w
+ call assert_fails('ptnext', 'E428:')
+ ptprev
+ wincmd P
+ call assert_equal(2, line('.'))
+ wincmd w
+ ptfirst
+ wincmd P
+ call assert_equal(1, line('.'))
+ wincmd w
+ call assert_fails('ptprev', 'E425:')
+ ptnext
+ wincmd P
+ call assert_equal(2, line('.'))
+ wincmd w
+ ptlast
+ call feedkeys("5\<CR>", 't')
+ ptselect first
+ wincmd P
+ call assert_equal(3, line('.'))
+
+ pclose
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for jumping to multiple matching tags across multiple :tags commands
+func Test_tnext_multimatch()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo1\t1",
+ \ "first\tXfoo2\t1",
+ \ "first\tXfoo3\t1"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ [CODE]
+ call writefile(code, 'Xfoo1', 'D')
+ call writefile(code, 'Xfoo2', 'D')
+ call writefile(code, 'Xfoo3', 'D')
+
+ tag first
+ tag first
+ pop
+ tnext
+ tnext
+ call assert_fails('tnext', 'E428:')
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for jumping to multiple matching tags in non-existing files
+func Test_multimatch_non_existing_files()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo1\t1",
+ \ "first\tXfoo2\t1",
+ \ "first\tXfoo3\t1"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+
+ call settagstack(1, {'items' : []})
+ call assert_fails('tag first', 'E429:')
+ call assert_equal(3, gettagstack().items[0].matchnr)
+
+ set tags&
+ %bwipe
+endfunc
+
+func Test_tselect_listing()
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:",
+ \ "first\tXfoo\t2" .. ';"' .. "\tkind:v\ttyperef:typename:char\tfile:"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+
+ let code =<< trim [CODE]
+ static int first;
+ static char first;
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ call feedkeys("\<CR>", "t")
+ let l = split(execute("tselect first"), "\n")
+ let expected =<< [DATA]
+ # pri kind tag file
+ 1 FS v first Xfoo
+ typeref:typename:int
+ 1
+ 2 FS v first Xfoo
+ typeref:typename:char
+ 2
+Type number and <Enter> (q or empty cancels):
+[DATA]
+ call assert_equal(expected, l)
+
+ set tags&
+ %bwipe
+endfunc
+
+" Test for :isearch, :ilist, :ijump and :isplit commands
+" Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands
+func Test_inc_search()
+ new
+ call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo', '==='])
+ call cursor(3, 1)
+
+ " Test for [i and ]i
+ call assert_equal('1:foo', execute('normal [i'))
+ call assert_equal('2:foo', execute('normal 2[i'))
+ call assert_fails('normal 3[i', 'E387:')
+ call assert_equal('3:foo', execute('normal ]i'))
+ call assert_equal('4:foo', execute('normal 2]i'))
+ call assert_fails('normal 3]i', 'E389:')
+ call assert_fails('normal G]i', 'E349:')
+ call assert_fails('normal [i', 'E349:')
+ call cursor(3, 1)
+
+ " Test for :isearch
+ call assert_equal('1:foo', execute('isearch foo'))
+ call assert_equal('3:foo', execute('isearch 4 /foo/'))
+ call assert_fails('isearch 3 foo', 'E387:')
+ call assert_equal('3:foo', execute('+1,$isearch foo'))
+ call assert_fails('1,.-1isearch 3 foo', 'E389:')
+ call assert_fails('isearch bar', 'E389:')
+ call assert_fails('isearch /foo/3', 'E488:')
+
+ " Test for [I and ]I
+ call assert_equal([
+ \ ' 1: 1 1:foo',
+ \ ' 2: 2 2:foo',
+ \ ' 3: 3 foo',
+ \ ' 4: 4 3:foo',
+ \ ' 5: 5 4:foo'], split(execute('normal [I'), "\n"))
+ call assert_equal([
+ \ ' 1: 4 3:foo',
+ \ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n"))
+ call assert_fails('normal G]I', 'E349:')
+ call assert_fails('normal [I', 'E349:')
+ call cursor(3, 1)
+
+ " Test for :ilist
+ call assert_equal([
+ \ ' 1: 1 1:foo',
+ \ ' 2: 2 2:foo',
+ \ ' 3: 3 foo',
+ \ ' 4: 4 3:foo',
+ \ ' 5: 5 4:foo'], split(execute('ilist foo'), "\n"))
+ call assert_equal([
+ \ ' 1: 4 3:foo',
+ \ ' 2: 5 4:foo'], split(execute('+1,$ilist /foo/'), "\n"))
+ call assert_fails('ilist bar', 'E389:')
+
+ " Test for [ CTRL-I and ] CTRL-I
+ exe "normal [\t"
+ call assert_equal([1, 3], [line('.'), col('.')])
+ exe "normal 2j4[\t"
+ call assert_equal([4, 3], [line('.'), col('.')])
+ call assert_fails("normal k3[\t", 'E387:')
+ call assert_fails("normal 6[\t", 'E389:')
+ exe "normal ]\t"
+ call assert_equal([4, 3], [line('.'), col('.')])
+ exe "normal k2]\t"
+ call assert_equal([5, 3], [line('.'), col('.')])
+ call assert_fails("normal 2k3]\t", 'E389:')
+ call assert_fails("normal G[\t", 'E349:')
+ call assert_fails("normal ]\t", 'E349:')
+ call cursor(3, 1)
+
+ " Test for :ijump
+ call cursor(3, 1)
+ ijump foo
+ call assert_equal([1, 3], [line('.'), col('.')])
+ call cursor(3, 1)
+ ijump 4 /foo/
+ call assert_equal([4, 3], [line('.'), col('.')])
+ call cursor(3, 1)
+ call assert_fails('ijump 3 foo', 'E387:')
+ +,$ijump 2 foo
+ call assert_equal([5, 3], [line('.'), col('.')])
+ call assert_fails('ijump bar', 'E389:')
+
+ " Test for CTRL-W i
+ call cursor(3, 1)
+ wincmd i
+ call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')])
+ close
+ 5wincmd i
+ call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')])
+ close
+ call assert_fails('3wincmd i', 'E387:')
+ call assert_fails('6wincmd i', 'E389:')
+ call assert_fails("normal G\<C-W>i", 'E349:')
+ call cursor(3, 1)
+
+ " Test for :isplit
+ isplit foo
+ call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')])
+ close
+ isplit 5 /foo/
+ call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')])
+ close
+ call assert_fails('isplit 3 foo', 'E387:')
+ call assert_fails('isplit 6 foo', 'E389:')
+ call assert_fails('isplit bar', 'E389:')
+
+ close!
+endfunc
+
+" this was using a line from ml_get() freed by the regexp
+func Test_isearch_copy_line()
+ new
+ norm o
+ norm 0
+ 0norm o
+ sil! norm bc0
+ sil! isearch \%')
+ bwipe!
+endfunc
+
+" Test for :dsearch, :dlist, :djump and :dsplit commands
+" Test for [d, ]d, [D, ]D, [ CTRL-D, ] CTRL-D and CTRL-W d commands
+func Test_macro_search()
+ new
+ call setline(1, ['#define FOO 1', '#define FOO 2', '#define FOO 3',
+ \ '#define FOO 4', '#define FOO 5'])
+ call cursor(3, 9)
+
+ " Test for [d and ]d
+ call assert_equal('#define FOO 1', execute('normal [d'))
+ call assert_equal('#define FOO 2', execute('normal 2[d'))
+ call assert_fails('normal 3[d', 'E387:')
+ call assert_equal('#define FOO 4', execute('normal ]d'))
+ call assert_equal('#define FOO 5', execute('normal 2]d'))
+ call assert_fails('normal 3]d', 'E388:')
+
+ " Test for :dsearch
+ call assert_equal('#define FOO 1', execute('dsearch FOO'))
+ call assert_equal('#define FOO 5', execute('dsearch 5 /FOO/'))
+ call assert_fails('dsearch 3 FOO', 'E387:')
+ call assert_equal('#define FOO 4', execute('+1,$dsearch FOO'))
+ call assert_fails('1,.-1dsearch 3 FOO', 'E388:')
+ call assert_fails('dsearch BAR', 'E388:')
+
+ " Test for [D and ]D
+ call assert_equal([
+ \ ' 1: 1 #define FOO 1',
+ \ ' 2: 2 #define FOO 2',
+ \ ' 3: 3 #define FOO 3',
+ \ ' 4: 4 #define FOO 4',
+ \ ' 5: 5 #define FOO 5'], split(execute('normal [D'), "\n"))
+ call assert_equal([
+ \ ' 1: 4 #define FOO 4',
+ \ ' 2: 5 #define FOO 5'], split(execute('normal ]D'), "\n"))
+
+ " Test for :dlist
+ call assert_equal([
+ \ ' 1: 1 #define FOO 1',
+ \ ' 2: 2 #define FOO 2',
+ \ ' 3: 3 #define FOO 3',
+ \ ' 4: 4 #define FOO 4',
+ \ ' 5: 5 #define FOO 5'], split(execute('dlist FOO'), "\n"))
+ call assert_equal([
+ \ ' 1: 4 #define FOO 4',
+ \ ' 2: 5 #define FOO 5'], split(execute('+1,$dlist /FOO/'), "\n"))
+ call assert_fails('dlist BAR', 'E388:')
+
+ " Test for [ CTRL-D and ] CTRL-D
+ exe "normal [\<C-D>"
+ call assert_equal([1, 9], [line('.'), col('.')])
+ exe "normal 2j4[\<C-D>"
+ call assert_equal([4, 9], [line('.'), col('.')])
+ call assert_fails("normal k3[\<C-D>", 'E387:')
+ call assert_fails("normal 6[\<C-D>", 'E388:')
+ exe "normal ]\<C-D>"
+ call assert_equal([4, 9], [line('.'), col('.')])
+ exe "normal k2]\<C-D>"
+ call assert_equal([5, 9], [line('.'), col('.')])
+ call assert_fails("normal 2k3]\<C-D>", 'E388:')
+
+ " Test for :djump
+ call cursor(3, 9)
+ djump FOO
+ call assert_equal([1, 9], [line('.'), col('.')])
+ call cursor(3, 9)
+ djump 4 /FOO/
+ call assert_equal([4, 9], [line('.'), col('.')])
+ call cursor(3, 9)
+ call assert_fails('djump 3 FOO', 'E387:')
+ +,$djump 2 FOO
+ call assert_equal([5, 9], [line('.'), col('.')])
+ call assert_fails('djump BAR', 'E388:')
+
+ " Test for CTRL-W d
+ call cursor(3, 9)
+ wincmd d
+ call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')])
+ close
+ 5wincmd d
+ call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')])
+ close
+ call assert_fails('3wincmd d', 'E387:')
+ call assert_fails('6wincmd d', 'E388:')
+ new
+ call assert_fails("normal \<C-W>d", 'E349:')
+ call assert_fails("normal \<C-W>\<C-D>", 'E349:')
+ close
+
+ " Test for :dsplit
+ dsplit FOO
+ call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')])
+ close
+ dsplit 5 /FOO/
+ call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')])
+ close
+ call assert_fails('dsplit 3 FOO', 'E387:')
+ call assert_fails('dsplit 6 FOO', 'E388:')
+ call assert_fails('dsplit BAR', 'E388:')
+
+ close!
+endfunc
+
+func Test_define_search()
+ " this was accessing freed memory
+ new
+ call setline(1, ['first line', '', '#define something 0'])
+ sil norm o0
+ sil! norm 
+ bwipe!
+
+ new somefile
+ call setline(1, ['first line', '', '#define something 0'])
+ sil norm 0o0
+ sil! norm ]d
+ bwipe!
+endfunc
+
+" Test for [*, [/, ]* and ]/
+func Test_comment_search()
+ new
+ call setline(1, ['', '/*', ' *', ' *', ' */'])
+ normal! 4gg[/
+ call assert_equal([2, 1], [line('.'), col('.')])
+ normal! 3gg[*
+ call assert_equal([2, 1], [line('.'), col('.')])
+ normal! 3gg]/
+ call assert_equal([5, 3], [line('.'), col('.')])
+ normal! 3gg]*
+ call assert_equal([5, 3], [line('.'), col('.')])
+ %d
+ call setline(1, ['', '/*', ' *', ' *'])
+ call assert_beeps('normal! 3gg]/')
+ %d
+ call setline(1, ['', ' *', ' *', ' */'])
+ call assert_beeps('normal! 4gg[/')
+ %d
+ call setline(1, ' /* comment */')
+ normal! 15|[/
+ call assert_equal(9, col('.'))
+ normal! 15|]/
+ call assert_equal(21, col('.'))
+ call setline(1, ' comment */')
+ call assert_beeps('normal! 15|[/')
+ call setline(1, ' /* comment')
+ call assert_beeps('normal! 15|]/')
+ close!
+endfunc
+
+" Test for the 'taglength' option
+func Test_tag_length()
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "tame\tXtlfile1\t1;",
+ \ "tape\tXtlfile2\t1;"], 'Xtags', 'D')
+ call writefile(['tame'], 'Xtlfile1', 'D')
+ call writefile(['tape'], 'Xtlfile2', 'D')
+
+ " Jumping to the tag 'tape', should instead jump to 'tame'
+ new
+ set taglength=2
+ tag tape
+ call assert_equal('Xtlfile1', @%)
+ " Tag search should jump to the right tag
+ enew
+ tag /^tape$
+ call assert_equal('Xtlfile2', @%)
+
+ set tags& taglength&
+endfunc
+
+" Tests for errors in a tags file
+func Test_tagfile_errors()
+ set tags=Xtags
+
+ " missing search pattern or line number for a tag
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "foo\tXfile\t"], 'Xtags', 'bD')
+ call writefile(['foo'], 'Xfile', 'D')
+
+ enew
+ tag foo
+ call assert_equal('', @%)
+ let caught_431 = v:false
+ try
+ eval taglist('.*')
+ catch /:E431:/
+ let caught_431 = v:true
+ endtry
+ call assert_equal(v:true, caught_431)
+
+ " tag name and file name are not separated by a tab
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "foo Xfile 1"], 'Xtags')
+ call assert_fails('tag foo', 'E431:')
+
+ " file name and search pattern are not separated by a tab
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "foo\tXfile 1;"], 'Xtags')
+ call assert_fails('tag foo', 'E431:')
+
+ set tags&
+endfunc
+
+" When :stag fails to open the file, should close the new window
+func Test_stag_close_window_on_error()
+ new | only
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "foo\tXfile\t1"], 'Xtags', 'D')
+ call writefile(['foo'], 'Xfile', 'D')
+ call writefile([], '.Xfile.swp', 'D')
+ " Remove the catch-all that runtest.vim adds
+ au! SwapExists
+ augroup StagTest
+ au!
+ autocmd SwapExists Xfile let v:swapchoice='q'
+ augroup END
+
+ stag foo
+ call assert_equal(1, winnr('$'))
+ call assert_equal('', @%)
+
+ augroup StagTest
+ au!
+ augroup END
+ set tags&
+endfunc
+
+" Test for 'tagbsearch' (binary search)
+func Test_tagbsearch()
+ " If a tags file header says the tags are sorted, but the tags are actually
+ " unsorted, then binary search should fail and linear search should work.
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/",
+ \ "third\tXfoo\t3",
+ \ "second\tXfoo\t2",
+ \ "first\tXfoo\t1"],
+ \ 'Xtags', 'D')
+ set tags=Xtags
+ let code =<< trim [CODE]
+ int first() {}
+ int second() {}
+ int third() {}
+ [CODE]
+ call writefile(code, 'Xfoo', 'D')
+
+ enew
+ set tagbsearch
+ call assert_fails('tag first', 'E426:')
+ call assert_equal('', bufname())
+ call assert_fails('tag second', 'E426:')
+ call assert_equal('', bufname())
+ tag third
+ call assert_equal('Xfoo', bufname())
+ call assert_equal(3, line('.'))
+ %bw!
+
+ set notagbsearch
+ tag first
+ call assert_equal('Xfoo', bufname())
+ call assert_equal(1, line('.'))
+ enew
+ tag second
+ call assert_equal('Xfoo', bufname())
+ call assert_equal(2, line('.'))
+ enew
+ tag third
+ call assert_equal('Xfoo', bufname())
+ call assert_equal(3, line('.'))
+ %bw!
+
+ " If a tags file header says the tags are unsorted, but the tags are
+ " actually sorted, then binary search should work.
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "!_TAG_FILE_SORTED\t0\t/0=unsorted, 1=sorted, 2=foldcase/",
+ \ "first\tXfoo\t1",
+ \ "second\tXfoo\t2",
+ \ "third\tXfoo\t3"],
+ \ 'Xtags')
+
+ set tagbsearch
+ tag first
+ call assert_equal('Xfoo', bufname())
+ call assert_equal(1, line('.'))
+ enew
+ tag second
+ call assert_equal('Xfoo', bufname())
+ call assert_equal(2, line('.'))
+ enew
+ tag third
+ call assert_equal('Xfoo', bufname())
+ call assert_equal(3, line('.'))
+ %bw!
+
+ " Binary search fails on EOF
+ call writefile([
+ \ "!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/",
+ \ "bar\tXfoo\t1",
+ \ "foo\tXfoo\t2"],
+ \ 'Xtags')
+ call assert_fails('tag bbb', 'E426:')
+
+ set tags& tagbsearch&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_taglist.vim b/src/testdir/test_taglist.vim
new file mode 100644
index 0000000..39e78bc
--- /dev/null
+++ b/src/testdir/test_taglist.vim
@@ -0,0 +1,263 @@
+" test taglist(), tagfiles() functions and :tags command
+
+source check.vim
+source view_util.vim
+
+func Test_taglist()
+ call writefile([
+ \ "FFoo\tXfoo\t1",
+ \ "FBar\tXfoo\t2",
+ \ "BFoo\tXbar\t1",
+ \ "BBar\tXbar\t2",
+ \ "Kindly\tXbar\t3;\"\tv\tfile:",
+ \ "Lambda\tXbar\t3;\"\tλ\tfile:",
+ \ "Command\tXbar\tcall cursor(3, 4)|;\"\td",
+ \ ], 'Xtags', 'D')
+ set tags=Xtags
+ split Xtext
+
+ call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo"), {i, v -> v.name}))
+ call assert_equal(['FFoo', 'BFoo'], map("Foo"->taglist("Xtext"), {i, v -> v.name}))
+ call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xfoo"), {i, v -> v.name}))
+ call assert_equal(['BFoo', 'FFoo'], map(taglist("Foo", "Xbar"), {i, v -> v.name}))
+
+ let kindly = taglist("Kindly")
+ call assert_equal(1, len(kindly))
+ call assert_equal('v', kindly[0]['kind'])
+ call assert_equal('3', kindly[0]['cmd'])
+ call assert_equal(1, kindly[0]['static'])
+ call assert_equal('Xbar', kindly[0]['filename'])
+
+ let lambda = taglist("Lambda")
+ call assert_equal(1, len(lambda))
+ call assert_equal('λ', lambda[0]['kind'])
+
+ let cmd = taglist("Command")
+ call assert_equal(1, len(cmd))
+ call assert_equal('d', cmd[0]['kind'])
+ call assert_equal('call cursor(3, 4)', cmd[0]['cmd'])
+
+ " Use characters with value > 127 in the tag extra field.
+ call writefile([
+ \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ta£££\tv",
+ \ ], 'Xtags', 'D')
+ call assert_equal('v', taglist('vFoo')[0].kind)
+
+ call assert_fails("let l=taglist([])", 'E730:')
+
+ set tags&
+ bwipe
+endfunc
+
+func Test_taglist_native_etags()
+ CheckFeature emacs_tags
+
+ call writefile([
+ \ "\x0c",
+ \ "src/os_unix.c,13491",
+ \ "set_signals(\x7f1335,32699",
+ \ "reset_signals(\x7f1407,34136",
+ \ ], 'Xtags', 'D')
+
+ set tags=Xtags
+
+ call assert_equal([['set_signals', '1335,32699'], ['reset_signals', '1407,34136']],
+ \ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]}))
+
+ set tags&
+endfunc
+
+func Test_taglist_ctags_etags()
+ CheckFeature emacs_tags
+
+ call writefile([
+ \ "\x0c",
+ \ "src/os_unix.c,13491",
+ \ "set_signals(void)\x7fset_signals\x011335,32699",
+ \ "reset_signals(void)\x7freset_signals\x011407,34136",
+ \ ], 'Xtags', 'D')
+
+ set tags=Xtags
+
+ call assert_equal([['set_signals', '1335,32699'], ['reset_signals', '1407,34136']],
+ \ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]}))
+
+ set tags&
+endfunc
+
+func Test_tags_too_long()
+ call assert_fails('tag ' . repeat('x', 1020), ['E433:', 'E426:'])
+ tags
+endfunc
+
+func Test_tagfiles()
+ call assert_equal([], tagfiles())
+
+ call writefile(["FFoo\tXfoo\t1"], 'Xtags1', 'D')
+ call writefile(["FBar\tXbar\t1"], 'Xtags2', 'D')
+ set tags=Xtags1,Xtags2
+ call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
+
+ help
+ let tf = tagfiles()
+ call assert_equal(1, len(tf))
+ call assert_equal(fnamemodify(expand('$VIMRUNTIME/doc/tags'), ':p:gs?\\?/?'),
+ \ fnamemodify(tf[0], ':p:gs?\\?/?'))
+ helpclose
+ call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
+ set tags&
+ call assert_equal([], tagfiles())
+
+ bd
+endfunc
+
+" For historical reasons we support a tags file where the last line is missing
+" the newline.
+func Test_tagsfile_without_trailing_newline()
+ call writefile(["Foo\tfoo\t1"], 'Xtags', 'bD')
+ set tags=Xtags
+
+ let tl = taglist('.*')
+ call assert_equal(1, len(tl))
+ call assert_equal('Foo', tl[0].name)
+
+ set tags&
+endfunc
+
+" Test for ignoring comments in a tags file
+func Test_tagfile_ignore_comments()
+ call writefile([
+ \ "!_TAG_PROGRAM_NAME /Test tags generator/",
+ \ "FBar\tXfoo\t2" .. ';"' .. "\textrafield\tf",
+ \ "!_TAG_FILE_FORMAT 2 /extended format/",
+ \ ], 'Xtags', 'D')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal(1, len(l))
+ call assert_equal('FBar', l[0].name)
+
+ set tags&
+endfunc
+
+" Test for using an excmd in a tags file to position the cursor (instead of a
+" search pattern or a line number)
+func Test_tagfile_excmd()
+ call writefile([
+ \ "vFoo\tXfoo\tcall cursor(3, 4)" .. '|;"' .. "\tv",
+ \ ], 'Xtags', 'D')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal([{
+ \ 'cmd' : 'call cursor(3, 4)',
+ \ 'static' : 0,
+ \ 'name' : 'vFoo',
+ \ 'kind' : 'v',
+ \ 'filename' : 'Xfoo'}], l)
+
+ set tags&
+endfunc
+
+" Test for duplicate fields in a tag in a tags file
+func Test_duplicate_field()
+ call writefile([
+ \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ttypename:int\tv",
+ \ ], 'Xtags', 'D')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal([{
+ \ 'cmd' : '4',
+ \ 'static' : 0,
+ \ 'name' : 'vFoo',
+ \ 'kind' : 'v',
+ \ 'typename' : 'int',
+ \ 'filename' : 'Xfoo'}], l)
+
+ set tags&
+endfunc
+
+" Test for tag address with ;
+func Test_tag_addr_with_semicolon()
+ call writefile([
+ \ "Func1\tXfoo\t6;/^Func1/" .. ';"' .. "\tf"
+ \ ], 'Xtags', 'D')
+ set tags=Xtags
+
+ let l = taglist('.*')
+ call assert_equal([{
+ \ 'cmd' : '6;/^Func1/',
+ \ 'static' : 0,
+ \ 'name' : 'Func1',
+ \ 'kind' : 'f',
+ \ 'filename' : 'Xfoo'}], l)
+
+ set tags&
+endfunc
+
+" Test for format error in a tags file
+func Test_format_error()
+ call writefile(['vFoo-Xfoo-4'], 'Xtags', 'D')
+ set tags=Xtags
+
+ let caught_exception = v:false
+ try
+ let l = taglist('.*')
+ catch /E431:/
+ " test succeeded
+ let caught_exception = v:true
+ catch
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+ call assert_true(caught_exception)
+
+ " no field after the filename for a tag
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "foo\tXfile"], 'Xtags')
+ call assert_fails("echo taglist('foo')", 'E431:')
+
+ set tags&
+endfunc
+
+" Test for :tag command completion with 'wildoptions' set to 'tagfile'
+func Test_tag_complete_wildoptions()
+ call writefile(["foo\ta.c\t10;\"\tf", "bar\tb.c\t20;\"\td"], 'Xtags', 'D')
+ set tags=Xtags
+ set wildoptions=tagfile
+
+ call feedkeys(":tag \<C-D>\<C-R>=Screenline(&lines - 1)\<CR> : "
+ \ .. "\<C-R>=Screenline(&lines - 2)\<CR>\<C-B>\"\<CR>", 'xt')
+
+ call assert_equal('"tag bar d b.c : foo f a.c', @:)
+
+ set wildoptions&
+ set tags&
+endfunc
+
+func Test_tag_complete_with_overlong_line()
+ let tagslines =<< trim END
+ !_TAG_FILE_FORMAT 2 //
+ !_TAG_FILE_SORTED 1 //
+ !_TAG_FILE_ENCODING utf-8 //
+ inboundGSV a 1;" r
+ inboundGovernor a 2;" kind:⊢ type:forall (muxMode :: MuxMode) socket peerAddr versionNumber m a b. (MonadAsync m, MonadCatch m, MonadEvaluate m, MonadThrow m, MonadThrow (STM m), MonadTime m, MonadTimer m, MonadMask m, Ord peerAddr, HasResponder muxMode ~ True) => Tracer m (RemoteTransitionTrace peerAddr) -> Tracer m (InboundGovernorTrace peerAddr) -> ServerControlChannel muxMode peerAddr ByteString m a b -> DiffTime -> MuxConnectionManager muxMode socket peerAddr versionNumber ByteString m a b -> StrictTVar m InboundGovernorObservableState -> m Void
+ inboundGovernorCounters a 3;" kind:⊢ type:InboundGovernorState muxMode peerAddr m a b -> InboundGovernorCounters
+ END
+ call writefile(tagslines, 'Xtags', 'D')
+ set tags=Xtags
+
+ " try with binary search
+ set tagbsearch
+ call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:)
+ " try with linear search
+ set notagbsearch
+ call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:)
+ set tagbsearch&
+
+ set tags&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_tcl.vim b/src/testdir/test_tcl.vim
new file mode 100644
index 0000000..4c6537f
--- /dev/null
+++ b/src/testdir/test_tcl.vim
@@ -0,0 +1,752 @@
+" Tests for the Tcl interface.
+
+source check.vim
+CheckFeature tcl
+
+" Helper function as there is no builtin tcleval() function similar
+" to perleval, luaeval(), pyeval(), etc.
+func TclEval(tcl_expr)
+ let s = split(execute('tcl ' . a:tcl_expr), "\n")
+ return (len(s) == 0) ? '' : s[-1]
+endfunc
+
+func Test_tcldo()
+ " Check deleting lines does not trigger ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ tcldo ::vim::command %d_
+ bwipe!
+
+ " Check that switching to another buffer does not trigger ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ tcldo ::vim::command new
+ call assert_equal(wincount + 1, winnr('$'))
+
+ " Try to run a command in a 'nomodifiable' buffer
+ call setline(1, ['one', 'two', 'three'])
+ set nomodifiable
+ call assert_fails('tcldo set line "abc"',
+ \ ['E21:', 'cannot save undo information'])
+ set modifiable
+
+ %bwipe!
+endfunc
+
+" Test :tcldo with a range
+func Test_tcldo_range()
+ new
+ call setline(1, ['line1', 'line2', 'line3', 'line4'])
+ 2,3tcldo set line [string toupper $line]
+ call assert_equal(['line1', 'LINE2', 'LINE3', 'line4'], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Test ::vim::beep
+func Test_vim_beep()
+ call assert_beeps('tcl ::vim::beep')
+ call assert_fails('tcl ::vim::beep x', 'wrong # args: should be "::vim::beep"')
+endfunc
+
+" Test ::vim::buffer
+func Test_vim_buffer()
+ " Test ::vim::buffer {nr}
+ e Xfoo1
+ call setline(1, ['foobar'])
+ let bn1 = bufnr('%')
+ let b1 = TclEval('::vim::buffer ' . bn1)
+ call assert_equal(b1, TclEval('set ::vim::current(buffer)'))
+
+ new Xfoo2
+ call setline(1, ['barfoo'])
+ let bn2 = bufnr('%')
+ let b2 = TclEval('::vim::buffer ' . bn2)
+ call assert_equal(b2, TclEval('set ::vim::current(buffer)'))
+
+ call assert_match('Xfoo1$', TclEval(b1 . ' name'))
+ call assert_match('Xfoo2$', TclEval(b2 . ' name'))
+
+ " Test ::vim::buffer exists {nr}
+ call assert_match('^[1-9]\d*$', TclEval('::vim::buffer exists ' . bn1))
+ call assert_match('^[1-9]\d*$', TclEval('::vim::buffer exists ' . bn2))
+ call assert_equal('0', TclEval('::vim::buffer exists 54321'))
+
+ " Test ::vim::buffer list
+ call assert_equal('2', TclEval('llength [::vim::buffer list]'))
+ call assert_equal(b1.' '.b2, TclEval('::vim::buffer list'))
+ tcl << trim EOF
+ proc eachbuf { cmd } {
+ foreach b [::vim::buffer list] { $b command $cmd }
+ }
+ EOF
+ tcl eachbuf %s/foo/FOO/g
+ b! Xfoo1
+ call assert_equal(['FOObar'], getline(1, '$'))
+ b! Xfoo2
+ call assert_equal(['barFOO'], getline(1, '$'))
+
+ call assert_fails('tcl ::vim::buffer',
+ \ 'wrong # args: should be "::vim::buffer option"')
+ call assert_fails('tcl ::vim::buffer ' . bn1 . ' x',
+ \ 'wrong # args: should be "::vim::buffer bufNumber"')
+ call assert_fails('tcl ::vim::buffer 4321', 'invalid buffer number')
+ call assert_fails('tcl ::vim::buffer x',
+ \ 'bad option "x": must be exists or list')
+ call assert_fails('tcl ::vim::buffer exists',
+ \ 'wrong # args: should be "::vim::buffer exists bufNumber"')
+ call assert_fails('tcl ::vim::buffer exists x',
+ \ 'expected integer but got "x"')
+ call assert_fails('tcl ::vim::buffer list x',
+ \ 'wrong # args: should be "::vim::buffer list "')
+ " Invalid buffer command
+ call assert_fails('tcl $::vim::current(buffer) abcd',
+ \ 'bad option "abcd":')
+
+ tcl rename eachbuf ""
+ %bwipe!
+endfunc
+
+" Test ::vim::option
+func Test_vim_option()
+ set cc=3,5
+
+ " Test getting option 'cc'
+ call assert_equal('3,5', TclEval('::vim::option cc'))
+ call assert_equal('3,5', &cc)
+
+ " Test setting option 'cc' (it returns the old option value)
+ call assert_equal('3,5', TclEval('::vim::option cc +4'))
+ call assert_equal('+4', &cc)
+ call assert_equal('+4', TclEval('::vim::option cc'))
+
+ " Test boolean option with 'toggle', 'on' and 'off' keywords.
+ call assert_equal('0', TclEval('::vim::option nu toggle'))
+ call assert_equal(1, &nu)
+ call assert_equal('1', TclEval('::vim::option nu toggle'))
+ call assert_equal(0, &nu)
+ call assert_equal('0', TclEval('::vim::option nu on'))
+ call assert_equal(1, &nu)
+ call assert_equal('1', TclEval('::vim::option nu off'))
+ call assert_equal(0, &nu)
+
+ call assert_fails('tcl ::vim::option nu x', 'expected integer but got "x"')
+ call assert_fails('tcl ::vim::option xxx', 'unknown vimOption')
+ call assert_fails('tcl ::vim::option',
+ \ 'wrong # args: should be "::vim::option vimOption ?value?"')
+
+ set cc&
+endfunc
+
+" Test ::vim::expr
+func Test_vim_expr()
+ call assert_equal(string(char2nr('X')),
+ \ TclEval('::vim::expr char2nr("X")'))
+
+ call assert_fails('tcl ::vim::expr x y',
+ \ 'wrong # args: should be "::vim::expr vimExpr"')
+ call assert_fails('tcl ::vim::expr 1-', 'E15: Invalid expression: "1-"')
+endfunc
+
+" Test ::vim::command
+func Test_vim_command()
+ call assert_equal('hello world',
+ \ TclEval('::vim::command {echo "hello world"}'))
+
+ " Check that if ::vim::command created a new Tcl interpreter, it is removed.
+ tcl set foo 123
+ call assert_equal('321', TclEval('::vim::command "tcl set foo 321"'))
+ call assert_equal('123', TclEval('set foo'))
+
+ " With the -quiet option, the error should silently be ignored.
+ call assert_equal('', TclEval('::vim::command -quiet xyz'))
+
+ call assert_fails('tcl ::vim::command',
+ \ 'wrong # args: should be "::vim::command ?-quiet? exCommand"')
+ call assert_fails('tcl ::vim::command -foo xyz', 'unknown flag: -foo')
+ call assert_fails('tcl ::vim::command xyz',
+ \ 'E492: Not an editor command: xyz')
+
+ " With the -quiet option, the error should silently be ignored.
+ call assert_equal('', TclEval('::vim::command -quiet xyz'))
+
+ tcl unset foo
+endfunc
+
+" Test ::vim::window list
+func Test_vim_window_list()
+ e Xfoo1
+ new Xfoo2
+ let w2 = TclEval('set ::vim::current(window)')
+ wincmd j
+ let w1 = TclEval('set ::vim::current(window)')
+
+ call assert_equal('2', TclEval('llength [::vim::window list]'))
+ call assert_equal(w2.' '.w1, TclEval('::vim::window list'))
+
+ call assert_fails('tcl ::vim::window x', 'unknown option')
+ call assert_fails('tcl ::vim::window list x',
+ \ 'wrong # args: should be "::vim::window option"')
+ call assert_fails('tcl $::vim::current(window) abcd',
+ \ 'bad option "abcd":')
+
+ %bwipe
+endfunc
+
+" Test output messages
+func Test_output()
+ call assert_fails('tcl puts vimerr "error #1"', 'error #1')
+ call assert_fails('tcl puts stderr "error #2"', 'error #2')
+ tcl puts vimout "message #1"
+ tcl puts stdout "message #2"
+ tcl puts "message #3"
+ let messages = split(execute('message'), "\n")
+ call assert_equal('message #3', messages[-1])
+ call assert_equal('message #2', messages[-2])
+ call assert_equal('message #1', messages[-3])
+
+ call assert_fails('tcl puts',
+ \ 'wrong # args: should be "puts ?-nonewline? ?channelId? string"')
+endfunc
+
+" Test $win height (get and set window height)
+func Test_window_height()
+ new
+
+ " Test setting window height
+ tcl $::vim::current(window) height 2
+ call assert_equal(2, winheight(0))
+
+ " Test getting window height
+ call assert_equal('2', TclEval('$::vim::current(window) height'))
+
+ call assert_fails('tcl $::vim::current(window) height 2 2', 'wrong # args:')
+ call assert_fails('tcl $::vim::current(window) height x',
+ \ 'expected integer but got "x"')
+ bwipe
+endfunc
+
+" Test $win cursor (get and set cursor)
+func Test_window_cursor()
+ new
+ call setline(1, ['line1', 'line2', 'line3', 'line5'])
+ tcl set win $::vim::current(window)
+
+ tcl $win cursor 2 4
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ call assert_equal('row 2 column 4', TclEval('$win cursor'))
+
+ " When setting ::vim::lbase to 0, line/col are counted from 0
+ " instead of 1.
+ tcl set ::vim::lbase 0
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ call assert_equal('row 1 column 3', TclEval('$win cursor'))
+ tcl $win cursor 2 4
+ call assert_equal([0, 3, 5, 0], getpos('.'))
+ call assert_equal('row 2 column 4', TclEval('$win cursor'))
+ tcl set ::vim::lbase 1
+ call assert_equal('row 3 column 5', TclEval('$win cursor'))
+ call assert_equal([0, 3, 5, 0], getpos('.'))
+
+ " test $win cursor {$var}
+ call cursor(2, 3)
+ tcl array set here [$win cursor]
+ call assert_equal([0, 2, 3, 0], getpos('.'))
+ call cursor(3, 1)
+ call assert_equal([0, 3, 1, 0], getpos('.'))
+ tcl $win cursor here
+ call assert_equal([0, 2, 3, 0], getpos('.'))
+ call cursor(3, 1)
+ call assert_equal([0, 3, 1, 0], getpos('.'))
+ tcl $win cursor $here(row) $here(column)
+ call assert_equal([0, 2, 3, 0], getpos('.'))
+
+ " Invalid values for the row and column
+ tcl array set pos {1 2}
+ call assert_fails('tcl $win cursor pos', "can't read \"pos(row)\":")
+ tcl array set pos {row '' abc 2}
+ call assert_fails('tcl $win cursor pos', "expected integer but got \"''\"")
+ tcl array set pos {row 1 abc 2}
+ call assert_fails('tcl $win cursor pos', "can't read \"pos(column)\":")
+ tcl array set pos {row 1 column ''}
+ call assert_fails('tcl $win cursor pos', "expected integer but got \"''\"")
+
+ call assert_fails("tcl $win cursor '' 2", "expected integer but got \"''\"")
+ call assert_fails("tcl $win cursor 1 ''", "expected integer but got \"''\"")
+
+ call assert_fails('tcl $win cursor 1 1 1', 'wrong # args:')
+
+ tcl unset win here
+ bwipe!
+endfunc
+
+" Test $win buffer
+func Test_window_buffer()
+ new Xfoo1
+ new Xfoo2
+ tcl set b2 $::vim::current(buffer)
+ tcl set w2 $::vim::current(window)
+ wincmd j
+ tcl set b1 $::vim::current(buffer)
+ tcl set w1 $::vim::current(window)
+
+ call assert_equal(TclEval('set b1'), TclEval('$w1 buffer'))
+ call assert_equal(TclEval('set b2'), TclEval('$w2 buffer'))
+ call assert_equal(string(bufnr('Xfoo1')), TclEval('[$w1 buffer] number'))
+ call assert_equal(string(bufnr('Xfoo2')), TclEval('[$w2 buffer] number'))
+
+ call assert_fails('tcl $w1 buffer x', 'wrong # args:')
+
+ tcl unset b1 b2 w1 w2
+ %bwipe
+endfunc
+
+" Test $win command
+func Test_window_command()
+ new Xfoo1
+ call setline(1, ['FOObar'])
+ new Xfoo2
+ call setline(1, ['fooBAR'])
+ tcl set w2 $::vim::current(window)
+ wincmd j
+ tcl set w1 $::vim::current(window)
+
+ tcl $w1 command "norm VU"
+ tcl $w2 command "norm Vu"
+ b! Xfoo1
+ call assert_equal('FOOBAR', getline(1))
+ b! Xfoo2
+ call assert_equal('foobar', getline(1))
+
+ call assert_fails('tcl $w1 command xyz',
+ \ 'E492: Not an editor command: xyz')
+ tcl $w1 command -quiet xyz
+
+ tcl unset w1 w2
+ %bwipe!
+endfunc
+
+" Test $win expr
+func Test_window_expr()
+ new Xfoo1
+ new Xfoo2
+ tcl set w2 $::vim::current(window)
+ wincmd j
+ tcl set w1 $::vim::current(window)
+
+ call assert_equal('Xfoo1', TclEval('$w1 expr bufname("%")'))
+ call assert_equal('Xfoo2', TclEval('$w2 expr bufname("%")'))
+
+ call assert_fails('tcl $w1 expr', 'wrong # args:')
+ call assert_fails('tcl $w1 expr x x', 'wrong # args:')
+
+ tcl unset w1 w2
+ %bwipe
+endfunc
+
+" Test $win option
+func Test_window_option()
+ new Xfoo1
+ new Xfoo2
+ tcl set w2 $::vim::current(window)
+ wincmd j
+ tcl set w1 $::vim::current(window)
+
+ " Test setting window option
+ tcl $w1 option syntax java
+ tcl $w2 option syntax rust
+
+ call assert_equal('java', &syntax)
+ wincmd k
+ call assert_equal('rust', &syntax)
+
+ " Test getting window option
+ call assert_equal('java', TclEval('$w1 option syntax'))
+ call assert_equal('rust', TclEval('$w2 option syntax'))
+
+ tcl unset w1 w2
+ %bwipe
+endfunc
+
+" Test $win delcmd {cmd}
+func Test_window_delcmd()
+ new
+ tcl $::vim::current(window) delcmd [list set msg "window deleted"]
+ call assert_fails('tcl set msg', "can't read \"msg\": no such variable")
+ q
+ call assert_equal('window deleted', TclEval('set msg'))
+
+ call assert_fails('tcl $::vim::current(window) delcmd', 'wrong # args')
+ call assert_fails('tcl $::vim::current(window) delcmd x x', 'wrong # args')
+
+ tcl unset msg
+ bwipe
+endfunc
+
+" Test $buf name
+func Test_buffer_name()
+ " Test buffer name with a named buffer
+ new Xfoo
+ call assert_equal(expand('%:p'), TclEval('$::vim::current(buffer) name'))
+ bwipe
+
+ " Test buffer name with an unnamed buffer
+ new
+ call assert_equal('', TclEval('$::vim::current(buffer) name'))
+
+ call assert_fails('tcl $::vim::current(buffer) name x', 'wrong # args:')
+
+ bwipe
+endfunc
+
+" Test $buf number
+func Test_buffer_number()
+ new
+ call assert_equal(string(bufnr('%')), TclEval('$::vim::current(buffer) number'))
+ new
+ call assert_equal(string(bufnr('%')), TclEval('$::vim::current(buffer) number'))
+
+ call assert_fails('tcl $::vim::current(buffer) number x', 'wrong # args:')
+
+ %bwipe
+endfunc
+
+" Test $buf count and $buf last
+func Test_buffer_count()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ call assert_equal('3', TclEval('$::vim::current(buffer) count'))
+ call assert_equal('3', TclEval('$::vim::current(buffer) last'))
+
+ " Check that $buf count and $buf last differ when ::vim::lbase is 0.
+ tcl set ::vim::lbase 0
+ call assert_equal('3', TclEval('$::vim::current(buffer) count'))
+ call assert_equal('2', TclEval('$::vim::current(buffer) last'))
+
+ call assert_fails('tcl $::vim::current(buffer) count x', 'wrong # args:')
+ call assert_fails('tcl $::vim::current(buffer) last x', 'wrong # args:')
+
+ tcl set ::vim::lbase 1
+ bwipe!
+endfunc
+
+" Test $buf delete (delete line(s) in buffer)
+func Test_buffer_delete()
+ new
+ call setline(1, ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'])
+ tcl $::vim::current(buffer) delete 4 6
+ tcl $::vim::current(buffer) delete 2
+ call assert_equal(['one', 'three', 'seven', 'eight'], getline(1, '$'))
+
+ call assert_fails('tcl $::vim::current(buffer) delete -1', 'line number out of range')
+ call assert_fails('tcl $::vim::current(buffer) delete 0', 'line number out of range')
+ call assert_fails('tcl $::vim::current(buffer) delete 5', 'line number out of range')
+
+ call assert_fails('tcl $::vim::current(buffer) delete', 'wrong # args:')
+ call assert_fails('tcl $::vim::current(buffer) delete 1 2 3', 'wrong # args:')
+ call assert_fails('tcl $::vim::current(buffer) delete 1 abc',
+ \ 'expected integer but got "abc"')
+
+ " Try to delete lines from an 'nomodifiable' buffer
+ set nomodifiable
+ call assert_fails('tcl $::vim::current(buffer) delete 2 1',
+ \ ['E21:', 'cannot save undo information'])
+ set modifiable
+
+ bwipe!
+endfunc
+
+" Test $buf insert (insert line(s) in buffer)
+func Test_buffer_insert()
+ new
+ tcl set buf $::vim::current(buffer)
+ tcl $buf insert 1 "first"
+ tcl $buf insert 2 "second"
+ tcl $buf insert 2 "third"
+ tcl $buf insert 4 "fourth"
+ tcl $buf insert 1 "fifth"
+ call assert_equal(['fifth', 'first', 'third', 'second', 'fourth', ''], getline(1, '$'))
+
+ call assert_fails('tcl $buf insert -1 "x"', 'line number out of range')
+ call assert_fails('tcl $buf insert 0 "x"', 'line number out of range')
+ call assert_fails('tcl $buf insert 7 "x"', 'line number out of range')
+
+ tcl unset buf
+ bwipe!
+endfunc
+
+" Test $buf append (append line in buffer)
+func Test_buffer_append()
+ new
+ tcl set buf $::vim::current(buffer)
+ tcl $buf append 1 "first"
+ tcl $buf append 2 "second"
+ tcl $buf append 2 "third"
+ tcl $buf append 4 "fourth"
+ tcl $buf append 1 "fifth"
+ call assert_equal(['', 'fifth', 'first', 'third', 'second', 'fourth'], getline(1, '$'))
+
+ call assert_fails('tcl $buf append -1 "x"', 'line number out of range')
+ call assert_fails('tcl $buf append 0 "x"', 'line number out of range')
+ call assert_fails('tcl $buf append 7 "x"', 'line number out of range')
+
+ call assert_fails('tcl $buf append', 'wrong # args:')
+ call assert_fails('tcl $buf append 1 x x', 'wrong # args:')
+
+ " Try to append lines to a 'nomodifiable' buffer
+ set nomodifiable
+ call assert_fails('tcl $buf append 1 "first"',
+ \ ['E21:', 'cannot save undo information'])
+ set modifiable
+
+ tcl unset buf
+ bwipe!
+endfunc
+
+" Test $buf set (replacing line(s) in a buffer)
+func Test_buffer_set()
+ new
+ call setline(1, ['line1', 'line2', 'line3', 'line4', 'line5'])
+ tcl $::vim::current(buffer) set 2 a
+ call assert_equal(['line1', 'a', 'line3', 'line4', 'line5'], getline(1, '$'))
+
+ " Test with fewer replacing lines than replaced lines: lines get deleted.
+ tcl $::vim::current(buffer) set 3 4 b
+ call assert_equal(['line1', 'a', 'b', 'line5'], getline(1, '$'))
+ tcl $::vim::current(buffer) set 4 3 c
+ call assert_equal(['line1', 'a', 'c'], getline(1, '$'))
+
+ " Test with more replacing lines than replaced lines: lines get added.
+ tcl $::vim::current(buffer) set 2 3 {x y z}
+ call assert_equal(['line1', 'x', 'y', 'z'], getline(1, '$'))
+ tcl $::vim::current(buffer) set 3 2 {X Y Z}
+ call assert_equal(['line1', 'X', 'Y', 'Z', 'z'], getline(1, '$'))
+
+ call assert_fails('tcl $::vim::current(buffer) set 0 "x"', 'line number out of range')
+ call assert_fails('tcl $::vim::current(buffer) set 6 "x"', 'line number out of range')
+
+ call assert_fails('tcl $::vim::current(buffer) set', 'wrong # args:')
+ call assert_fails('tcl $::vim::current(buffer) set 1 2 {[list "a" "b"]}',
+ \ 'list element in quotes followed by "]" instead of space')
+
+ " Try to modify a 'nomodifiable' buffer
+ set nomodifiable
+ call assert_fails('tcl $::vim::current(buffer) set 1 "x"',
+ \ ['E21:', 'cannot save undo information'])
+ call assert_fails('tcl $::vim::current(buffer) set 1 {a b}',
+ \ ['E21:', 'cannot save undo information'])
+ call assert_fails('tcl $::vim::current(buffer) set 1 2 {a b}',
+ \ ['E21:', 'cannot save undo information'])
+ set modifiable
+ bwipe!
+endfunc
+
+" Test $buf get (get line(s) from buffer)
+func Test_buffer_get()
+ new
+ call setline(1, ['first line', 'two', 'three', 'last line'])
+ tcl set buf $::vim::current(buffer)
+
+ call assert_equal('first line', TclEval('$buf get top'))
+ call assert_equal('first line', TclEval('$buf get begin'))
+ call assert_equal('last line', TclEval('$buf get bottom'))
+ call assert_equal('last line', TclEval('$buf get last'))
+
+ call assert_equal('first line', TclEval('$buf get 1'))
+ call assert_equal('two', TclEval('$buf get 2'))
+ call assert_equal('three', TclEval('$buf get 3'))
+ call assert_equal('last line', TclEval('$buf get 4'))
+
+ call assert_equal('two three', TclEval('$buf get 2 3'))
+ call assert_equal('two three', TclEval('$buf get 3 2'))
+ call assert_equal('three {last line}', TclEval('$buf get 3 last'))
+
+ call assert_fails('tcl $buf get -1', 'line number out of range')
+ call assert_fails('tcl $buf get 0', 'line number out of range')
+ call assert_fails('tcl $buf get 5', 'line number out of range')
+ call assert_fails('tcl $buf get 0 1', 'line number out of range')
+
+ call assert_fails('tcl $::vim::current(buffer) get x', 'expected integer but got "x"')
+ call assert_fails('tcl $::vim::current(buffer) get 1 x', 'expected integer but got "x"')
+ call assert_fails('tcl $::vim::current(buffer) get 1 1 1', 'wrong # args:')
+
+ tcl unset buf
+ bwipe!
+endfunc
+
+" Test $buf mark (get position of a mark)
+func Test_buffer_mark()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ /three
+ norm! ma
+ norm! jllmB
+
+ call assert_equal('row 3 column 1', TclEval('$::vim::current(buffer) mark a'))
+ call assert_equal('row 4 column 3', TclEval('$::vim::current(buffer) mark B'))
+
+ call assert_fails('tcl $::vim::current(buffer) mark /', 'invalid mark name')
+ call assert_fails('tcl $::vim::current(buffer) mark z', 'mark not set')
+ call assert_fails('tcl $::vim::current(buffer) mark', 'wrong # args:')
+
+ delmarks aB
+ bwipe!
+endfunc
+
+" Test $buf option (test and set option in context of a buffer)
+func Test_buffer_option()
+ new Xfoo1
+ tcl set b1 $::vim::current(buffer)
+ new Xfoo2
+ tcl set b2 $::vim::current(buffer)
+
+ tcl $b1 option foldcolumn 2
+ tcl $b2 option foldcolumn 3
+
+ call assert_equal(3, &foldcolumn)
+ wincmd j
+ call assert_equal(2, &foldcolumn)
+
+ call assert_equal('2', TclEval('$b1 option foldcolumn'))
+ call assert_equal('3', TclEval('$b2 option foldcolumn'))
+
+ call assert_fails('tcl $::vim::current(buffer) option', 'wrong # args:')
+
+ set foldcolumn&
+ tcl unset b1 b2
+ %bwipe
+endfunc
+
+" Test $buf expr (evaluate vim expression)
+func Test_buffer_expr()
+ new Xfoo1
+ norm ifoo1
+ tcl set b1 $::vim::current(buffer)
+
+ new Xfoo2
+ norm ifoo2
+ tcl set b2 $::vim::current(buffer)
+
+ call assert_equal('foo1', TclEval('$b1 expr getline(1)'))
+ call assert_equal('foo2', TclEval('$b2 expr getline(1)'))
+
+ call assert_fails('tcl expr', 'wrong # args:')
+
+ tcl unset b1 b2
+ %bwipe!
+endfunc
+
+" Test $buf delcmd {cmd} (command executed when buffer is deleted)
+func Test_buffer_delcmd()
+ new Xfoo
+ split
+ tcl $::vim::current(buffer) delcmd [list set msg "buffer deleted"]
+ q
+ call assert_fails('tcl set msg', "can't read \"msg\": no such variable")
+ q
+ call assert_equal('buffer deleted', TclEval('set msg'))
+
+ call assert_fails('tcl $::vim::current(buffer) delcmd', 'wrong # args')
+ call assert_fails('tcl $::vim::current(buffer) delcmd x x', 'wrong # args')
+
+ tcl unset msg
+ %bwipe
+endfunc
+
+func Test_vim_current()
+ " Only test errors as ::vim::current(...) is already indirectly
+ " tested by many other tests.
+ call assert_fails('tcl $::vim::current(buffer)', 'wrong # args:')
+ call assert_fails('tcl $::vim::current(window)', 'wrong # args:')
+endfunc
+
+" Test $buf windows (windows list of a buffer)
+func Test_buffer_windows()
+ new Xfoo
+ split
+ new Xbar
+ split
+ vsplit
+
+ tcl set bar_wl [$::vim::current(buffer) windows]
+ 2wincmd j
+ tcl set foo_wl [$::vim::current(buffer) windows]
+
+ call assert_equal('2', TclEval('llength $foo_wl'))
+ call assert_equal('3', TclEval('llength $bar_wl'))
+
+ call assert_fails('tcl $::vim::current(buffer) windows x', 'wrong # args:')
+
+ tcl unset bar_wl foo_wl
+ %bwipe
+endfunc
+
+" Test :tclfile
+func Test_tclfile()
+ call delete('Xtcl_file')
+ call writefile(['set pi [format "%.2f" [expr acos(-1.0)]]'], 'Xtcl_file', 'D')
+ call setfperm('Xtcl_file', 'r-xr-xr-x')
+
+ tclfile Xtcl_file
+ call assert_equal('3.14', TclEval('set pi'))
+
+ tcl unset pi
+endfunc
+
+" Test :tclfile with syntax error in tcl script
+func Test_tclfile_error()
+ call delete('Xtcl_file')
+ call writefile(['xyz'], 'Xtcl_file', 'D')
+ call setfperm('Xtcl_file', 'r-xr-xr-x')
+
+ call assert_fails('tclfile Xtcl_file', 'invalid command name "xyz"')
+endfunc
+
+" Test exiting current Tcl interpreter and re-creating one.
+func Test_tcl_exit()
+ call assert_fails('tcl exit 1 1', 'wrong # args: should be "exit ?returnCode?"')
+ call assert_fails('tcl exit x', 'expected integer but got "x"')
+
+ tcl set foo "foo"
+ call assert_fails('tcl exit 3', 'E572: Exit code 3')
+
+ " The Tcl interpreter should have been deleted and a new one
+ " is re-created with the next :tcl command.
+ call assert_fails('tcl set foo', "can't read \"foo\": no such variable")
+ tcl set bar "bar"
+ call assert_equal('bar', TclEval('set bar'))
+
+ tcl unset bar
+endfunc
+
+func Test_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ tcldo $::vim::current(window) cursor 1 5
+ call assert_equal([1, 5], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 5], [line('.'), col('.')])
+endfunc
+
+" Test for different syntax for ruby heredoc
+func Test_tcl_heredoc()
+ tcl << END
+::vim::command {let s = "A"}
+END
+ tcl <<
+::vim::command {let s ..= "B"}
+.
+ tcl << trim END
+ ::vim::command {let s ..= "C"}
+ END
+ tcl << trim
+ ::vim::command {let s ..= "D"}
+ .
+ call assert_equal('ABCD', s)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_termcodes.vim b/src/testdir/test_termcodes.vim
new file mode 100644
index 0000000..8e8be02
--- /dev/null
+++ b/src/testdir/test_termcodes.vim
@@ -0,0 +1,2726 @@
+" Tests for decoding escape sequences sent by the terminal.
+
+" This only works for Unix in a terminal
+source check.vim
+CheckNotGui
+CheckUnix
+
+source shared.vim
+source mouse.vim
+source view_util.vim
+source term_util.vim
+
+func Test_term_mouse_left_click()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ call setline(1, ['line 1', 'line 2', 'line 3 is a bit longer'])
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ go
+ call assert_equal([0, 1, 1, 0], getpos('.'), msg)
+ let row = 2
+ let col = 6
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([0, 2, 6, 0], getpos('.'), msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+func Test_xterm_mouse_right_click_extends_visual()
+ if has('mac')
+ throw "Skipped: test right click in visual mode does not work on macOs (why?)"
+ endif
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ for visual_mode in ["v", "V", "\<C-V>"]
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'visual=' .. visual_mode .. ' ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ call setline(1, repeat([repeat('-', 7)], 7))
+ call MouseLeftClick(4, 4)
+ call MouseLeftRelease(4, 4)
+ exe "norm! " .. visual_mode
+
+ " Right click extends top left of visual area.
+ call MouseRightClick(2, 2)
+ call MouseRightRelease(2, 2)
+
+ " Right click extends bottom right of visual area.
+ call MouseRightClick(6, 6)
+ call MouseRightRelease(6, 6)
+ norm! r1gv
+
+ " Right click shrinks top left of visual area.
+ call MouseRightClick(3, 3)
+ call MouseRightRelease(3, 3)
+
+ " Right click shrinks bottom right of visual area.
+ call MouseRightClick(5, 5)
+ call MouseRightRelease(5, 5)
+ norm! r2
+
+ if visual_mode ==# 'v'
+ call assert_equal(['-------',
+ \ '-111111',
+ \ '1122222',
+ \ '2222222',
+ \ '2222211',
+ \ '111111-',
+ \ '-------'], getline(1, '$'), msg)
+ elseif visual_mode ==# 'V'
+ call assert_equal(['-------',
+ \ '1111111',
+ \ '2222222',
+ \ '2222222',
+ \ '2222222',
+ \ '1111111',
+ \ '-------'], getline(1, '$'), msg)
+ else
+ call assert_equal(['-------',
+ \ '-11111-',
+ \ '-12221-',
+ \ '-12221-',
+ \ '-12221-',
+ \ '-11111-',
+ \ '-------'], getline(1, '$'), msg)
+ endif
+ endfor
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+" Test that <C-LeftMouse> jumps to help tag and <C-RightMouse> jumps back.
+" Also test for g<LeftMouse> and g<RightMouse>.
+func Test_xterm_mouse_tagjump()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ help
+ /usr_02.txt
+ norm! zt
+
+ " CTRL-left click to jump to a tag
+ let row = 1
+ let col = 1
+ call MouseCtrlLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_match('usr_02.txt$', bufname('%'), msg)
+ call assert_equal('*usr_02.txt*', expand('<cWORD>'), msg)
+
+ " CTRL-right click to pop a tag
+ call MouseCtrlRightClick(row, col)
+ call MouseRightRelease(row, col)
+ call assert_match('help.txt$', bufname('%'), msg)
+ call assert_equal('|usr_02.txt|', expand('<cWORD>'), msg)
+
+ " Jump to a tag
+ exe "normal \<C-]>"
+ call assert_match('usr_02.txt$', bufname('%'), msg)
+ call assert_equal('*usr_02.txt*', expand('<cWORD>'), msg)
+
+ " Use CTRL-right click in insert mode to pop the tag
+ new
+ let str = 'iHello' .. MouseCtrlRightClickCode(row, col)
+ \ .. MouseRightReleaseCode(row, col) .. "\<C-C>"
+ call assert_fails('call feedkeys(str, "Lx!")', 'E37:', msg)
+ close!
+
+ " CTRL-right click with a count
+ let str = "4" .. MouseCtrlRightClickCode(row, col)
+ \ .. MouseRightReleaseCode(row, col)
+ call assert_fails('call feedkeys(str, "Lx!")', 'E555:', msg)
+ call assert_match('help.txt$', bufname('%'), msg)
+ call assert_equal(1, line('.'), msg)
+
+ " g<LeftMouse> to jump to a tag
+ /usr_02.txt
+ norm! zt
+ call test_setmouse(row, col)
+ exe "normal g\<LeftMouse>"
+ call assert_match('usr_02.txt$', bufname('%'), msg)
+ call assert_equal('*usr_02.txt*', expand('<cWORD>'), msg)
+
+ " g<RightMouse> to pop to a tag
+ call test_setmouse(row, col)
+ exe "normal g\<RightMouse>"
+ call assert_match('help.txt$', bufname('%'), msg)
+ call assert_equal('|usr_02.txt|', expand('<cWORD>'), msg)
+
+ %bw!
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+endfunc
+
+func Test_term_mouse_middle_click()
+ CheckFeature clipboard_working
+
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ let save_quotestar = @*
+ let save_quoteplus = @+
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ call setline(1, ['123456789', '123456789'])
+ let @* = 'abc'
+
+ " Middle-click in the middle of the line pastes text where clicked.
+ let row = 1
+ let col = 6
+ call MouseMiddleClick(row, col)
+ call MouseMiddleRelease(row, col)
+ call assert_equal(['12345abc6789', '123456789'], getline(1, '$'), msg)
+
+ " Middle-click beyond end of the line pastes text at the end of the line.
+ let col = 20
+ call MouseMiddleClick(row, col)
+ call MouseMiddleRelease(row, col)
+ call assert_equal(['12345abc6789abc', '123456789'], getline(1, '$'), msg)
+
+ " Middle-click beyond the last line pastes in the last line.
+ let row = 5
+ let col = 3
+ call MouseMiddleClick(row, col)
+ call MouseMiddleRelease(row, col)
+ call assert_equal(['12345abc6789abc', '12abc3456789'], getline(1, '$'), msg)
+
+ " Middle mouse click in operator pending mode beeps
+ call assert_beeps('exe "normal c\<MiddleMouse>"')
+
+ " Clicking middle mouse in visual mode, yanks the selection and pastes the
+ " clipboard contents
+ let save_clipboard = &clipboard
+ set clipboard=
+ let @" = ''
+ call cursor(1, 1)
+ call feedkeys("v3l" ..
+ \ MouseMiddleClickCode(2, 7) .. MouseMiddleReleaseCode(2, 7), 'Lx!')
+ call assert_equal(['12345abc6789abc', '12abc3abc456789'],
+ \ getline(1, '$'), msg)
+ call assert_equal('1234', @", msg)
+ let &clipboard = save_clipboard
+
+ " Clicking middle mouse in select mode, replaces the selected text with
+ " the clipboard contents
+ let @+ = 'xyz'
+ call cursor(1, 3)
+ exe "normal gh\<Right>\<Right>\<MiddleMouse>"
+ call assert_equal(['12xyzabc6789abc', '12abc3abc456789'],
+ \ getline(1, '$'), msg)
+
+ " Prefixing middle click with [ or ] fixes the indent after pasting.
+ %d
+ call setline(1, " one two")
+ call setreg('r', 'red blue', 'l')
+ call test_setmouse(1, 5)
+ exe "normal \"r[\<MiddleMouse>"
+ call assert_equal(' red blue', getline(1), msg)
+ call test_setmouse(2, 5)
+ exe "normal \"r]\<MiddleMouse>"
+ call assert_equal(' red blue', getline(3), msg)
+ %d
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ let @* = save_quotestar
+ let @+ = save_quotestar
+ bwipe!
+endfunc
+
+" If clipboard is not working, then clicking the middle mouse button in visual
+" mode, will copy and paste the selected text.
+func Test_term_mouse_middle_click_no_clipboard()
+ if has('clipboard_working')
+ throw 'Skipped: clipboard support works'
+ endif
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ call setline(1, ['123456789', '123456789'])
+
+ " Clicking middle mouse in visual mode, yanks the selection and pastes it
+ call cursor(1, 1)
+ call feedkeys("v3l" ..
+ \ MouseMiddleClickCode(2, 7) .. MouseMiddleReleaseCode(2, 7), 'Lx!')
+ call assert_equal(['123456789', '1234561234789'],
+ \ getline(1, '$'), msg)
+ endfor
+
+ call test_override('no_query_mouse', 0)
+ let &ttymouse = save_ttymouse
+ let &term = save_term
+ let &mouse = save_mouse
+ close!
+endfunc
+
+func Test_term_mouse_middle_click_insert_mode()
+ CheckFeature clipboard_working
+
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ call setline(1, ['123456789', '123456789'])
+ let @* = 'abc'
+
+ " Middle-click in insert mode doesn't move the cursor but inserts the
+ " contents of a register
+ call cursor(1, 4)
+ call feedkeys('i' ..
+ \ MouseMiddleClickCode(2, 7) .. MouseMiddleReleaseCode(2, 7) ..
+ \ "\<C-C>", 'Lx!')
+ call assert_equal(['123abc456789', '123456789'],
+ \ getline(1, '$'), msg)
+ call assert_equal([1, 6], [line('.'), col('.')], msg)
+
+ " Middle-click in replace mode
+ call cursor(1, 1)
+ call feedkeys('$R' ..
+ \ MouseMiddleClickCode(2, 7) .. MouseMiddleReleaseCode(2, 7) ..
+ \ "\<C-C>", 'Lx!')
+ call assert_equal(['123abc45678abc', '123456789'],
+ \ getline(1, '$'), msg)
+ call assert_equal([1, 14], [line('.'), col('.')], msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for switching window using mouse in insert mode
+func Test_term_mouse_switch_win_insert_mode()
+ 5new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm ttymouse=xterm2
+ call WaitForResponses()
+
+ call feedkeys('ivim' ..
+ \ MouseLeftClickCode(8, 6) .. MouseLeftReleaseCode(8, 6) ..
+ \ "\<C-C>", 'Lx!')
+ call assert_equal(2, winnr())
+ wincmd w
+ call assert_equal('n', mode())
+ call assert_equal(['vim'], getline(1, '$'))
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for using the mouse to increase the height of the cmdline window
+func Test_mouse_cmdwin_resize()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm ttymouse=xterm2
+ call WaitForResponses()
+
+ 5new
+ redraw!
+
+ let h = 0
+ let row = &lines - &cmdwinheight - 2
+ call feedkeys("q:" ..
+ \ MouseLeftClickCode(row, 1) ..
+ \ MouseLeftDragCode(row - 1, 1) ..
+ \ MouseLeftReleaseCode(row - 2, 1) ..
+ \ "alet h = \<C-R>=winheight(0)\<CR>\<CR>", 'Lx!')
+ call assert_equal(&cmdwinheight + 2, h)
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" TODO: for unclear reasons this test fails if it comes after
+" Test_xterm_mouse_ctrl_click()
+func Test_1xterm_mouse_wheel()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_wrap = &wrap
+ let save_ttymouse = &ttymouse
+ set mouse=a term=xterm nowrap
+ call WaitForResponses()
+
+ call setline(1, range(100000000000000, 100000000000100))
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ go
+ call assert_equal(1, line('w0'), msg)
+ call assert_equal([0, 1, 1, 0], getpos('.'), msg)
+
+ call MouseWheelDown(1, 1)
+ call assert_equal(4, line('w0'), msg)
+ call assert_equal([0, 4, 1, 0], getpos('.'), msg)
+
+ call MouseWheelDown(1, 1)
+ call assert_equal(7, line('w0'), msg)
+ call assert_equal([0, 7, 1, 0], getpos('.'), msg)
+
+ call MouseWheelUp(1, 1)
+ call assert_equal(4, line('w0'), msg)
+ call assert_equal([0, 7, 1, 0], getpos('.'), msg)
+
+ call MouseWheelUp(1, 1)
+ call assert_equal(1, line('w0'), msg)
+ call assert_equal([0, 7, 1, 0], getpos('.'), msg)
+
+ call MouseWheelRight(1, 1)
+ call assert_equal(7, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 7, 0], getpos('.'), msg)
+
+ call MouseWheelRight(1, 1)
+ call assert_equal(13, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 13, 0], getpos('.'), msg)
+
+ call MouseWheelLeft(1, 1)
+ call assert_equal(7, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 13, 0], getpos('.'), msg)
+
+ call MouseWheelLeft(1, 1)
+ call assert_equal(1, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 13, 0], getpos('.'), msg)
+
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &wrap = save_wrap
+ let &ttymouse = save_ttymouse
+ bwipe!
+endfunc
+
+" Test that dragging beyond the window (at the bottom and at the top)
+" scrolls window content by the number of lines beyond the window.
+func Test_term_mouse_drag_beyond_window()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ let col = 1
+ call setline(1, range(1, 100))
+
+ " Split into 3 windows, and go into the middle window
+ " so we test dragging mouse below and above the window.
+ 2split
+ wincmd j
+ 2split
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ " Line #10 at the top.
+ norm! 10zt
+ redraw
+ call assert_equal(10, winsaveview().topline, msg)
+ call assert_equal(2, winheight(0), msg)
+
+ let row = 4
+ call MouseLeftClick(row, col)
+ call assert_equal(10, winsaveview().topline, msg)
+
+ " Drag downwards. We're still in the window so topline should
+ " not change yet.
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(10, winsaveview().topline, msg)
+
+ " We now leave the window at the bottom, so the window content should
+ " scroll by 1 line, then 2 lines (etc) as we drag further away.
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(11, winsaveview().topline, msg)
+
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(13, winsaveview().topline, msg)
+
+ " Now drag upwards.
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(14, winsaveview().topline, msg)
+
+ " We're now back in the window so the topline should not change.
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(14, winsaveview().topline, msg)
+
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(14, winsaveview().topline, msg)
+
+ " We now leave the window at the top so the window content should
+ " scroll by 1 line, then 2, then 3 (etc) in the opposite direction.
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(13, winsaveview().topline, msg)
+
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(11, winsaveview().topline, msg)
+
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(8, winsaveview().topline, msg)
+
+ call MouseLeftRelease(row, col)
+ call assert_equal(8, winsaveview().topline, msg)
+ call assert_equal(2, winheight(0), msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+func Test_term_mouse_drag_window_separator()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ " Split horizontally and test dragging the horizontal window separator.
+ split
+ let rowseparator = winheight(0) + 1
+ let row = rowseparator
+ let col = 1
+
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ if ttymouse_val !=# 'xterm2' || row <= 223
+ call MouseLeftClick(row, col)
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(rowseparator - 1, winheight(0) + 1, msg)
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(rowseparator, winheight(0) + 1, msg)
+ call MouseLeftRelease(row, col)
+ call assert_equal(rowseparator, winheight(0) + 1, msg)
+ endif
+ bwipe!
+
+ " Split vertically and test dragging the vertical window separator.
+ vsplit
+ let colseparator = winwidth(0) + 1
+ let row = 1
+ let col = colseparator
+
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ if ttymouse_val !=# 'xterm2' || col <= 223
+ call MouseLeftClick(row, col)
+ let col -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(colseparator - 1, winwidth(0) + 1, msg)
+ let col += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(colseparator, winwidth(0) + 1, msg)
+ call MouseLeftRelease(row, col)
+ call assert_equal(colseparator, winwidth(0) + 1, msg)
+ endif
+ bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+endfunc
+
+func Test_term_mouse_drag_statusline()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ let save_laststatus = &laststatus
+ set mouse=a term=xterm laststatus=2
+ call WaitForResponses()
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ call assert_equal(1, &cmdheight, msg)
+ let rowstatusline = winheight(0) + 1
+ let row = rowstatusline
+ let col = 1
+
+ if ttymouse_val ==# 'xterm2' && row > 223
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ continue
+ endif
+
+ call MouseLeftClick(row, col)
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(2, &cmdheight, msg)
+ call assert_equal(rowstatusline - 1, winheight(0) + 1, msg)
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(1, &cmdheight, msg)
+ call assert_equal(rowstatusline, winheight(0) + 1, msg)
+ call MouseLeftRelease(row, col)
+ call assert_equal(1, &cmdheight, msg)
+ call assert_equal(rowstatusline, winheight(0) + 1, msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ let &laststatus = save_laststatus
+endfunc
+
+func Test_term_mouse_click_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ let row = 1
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ e Xfoo
+ tabnew Xbar
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xfoo',
+ \ 'Tab page 2',
+ \ '> Xbar'], a, msg)
+
+ " Test clicking on tab names in the tabline at the top.
+ let col = 2
+ redraw
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '> Xfoo',
+ \ 'Tab page 2',
+ \ ' Xbar'], a, msg)
+
+ let col = 9
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xfoo',
+ \ 'Tab page 2',
+ \ '> Xbar'], a, msg)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+endfunc
+
+func Test_term_mouse_click_X_to_close_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ let row = 1
+ let col = &columns
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ if ttymouse_val ==# 'xterm2' && col > 223
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ continue
+ endif
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ e Xtab1
+ tabnew Xtab2
+ tabnew Xtab3
+ tabn 2
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2',
+ \ 'Tab page 3',
+ \ ' Xtab3'], a, msg)
+
+ " Click on "X" in tabline to close current tab i.e. Xtab2.
+ redraw
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab3'], a, msg)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+endfunc
+
+func Test_term_mouse_drag_to_move_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ " Set 'mousetime' to 1 to avoid recognizing a double-click in the loop
+ set mouse=a term=xterm mousetime=1
+ call WaitForResponses()
+
+ let row = 1
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ e Xtab1
+ tabnew Xtab2
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2'], a, msg)
+ redraw
+
+ " Click in tab2 and drag it to tab1.
+ " Check getcharmod() to verify that click is not
+ " interpreted as a spurious double-click.
+ call MouseLeftClick(row, 10)
+ call assert_equal(0, getcharmod(), msg)
+ for col in [9, 8, 7, 6]
+ call MouseLeftDrag(row, col)
+ endfor
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '> Xtab2',
+ \ 'Tab page 2',
+ \ ' Xtab1'], a, msg)
+
+ " Switch to tab1
+ tabnext
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab2',
+ \ 'Tab page 2',
+ \ '> Xtab1'], a, msg)
+
+ " Click in tab2 and drag it to tab1.
+ " This time it is non-current tab.
+ call MouseLeftClick(row, 6)
+ call assert_equal(0, getcharmod(), msg)
+ for col in [7, 8, 9, 10]
+ call MouseLeftDrag(row, col)
+ endfor
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2'], a, msg)
+
+ " Click elsewhere so that click in next iteration is not
+ " interpreted as unwanted double-click.
+ call MouseLeftClick(row, 11)
+ call MouseLeftRelease(row, 11)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ set mousetime&
+endfunc
+
+func Test_term_mouse_double_click_to_create_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ " Set 'mousetime' to a small value, so that double-click works but we don't
+ " have to wait long to avoid a triple-click.
+ set mouse=a term=xterm mousetime=200
+ call WaitForResponses()
+
+ let row = 1
+ let col = 10
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ e Xtab1
+ tabnew Xtab2
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2'], a, msg)
+
+ redraw
+ call MouseLeftClick(row, col)
+ " Check getcharmod() to verify that first click is not
+ " interpreted as a spurious double-click.
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(row, col)
+ call MouseLeftClick(row, col)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> [No Name]',
+ \ 'Tab page 3',
+ \ ' Xtab2'], a, msg)
+
+ " Click elsewhere so that click in next iteration is not
+ " interpreted as unwanted double click.
+ call MouseLeftClick(row, col + 1)
+ call MouseLeftRelease(row, col + 1)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ set mousetime&
+endfunc
+
+" Test double/triple/quadruple click in normal mode to visually select.
+func Test_term_mouse_multiple_clicks_to_visually_select()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+
+ " 'mousetime' must be sufficiently large, or else the test is flaky when
+ " using a ssh connection with X forwarding; i.e. ssh -X (issue #7563).
+ set mouse=a term=xterm mousetime=600
+ call WaitForResponses()
+
+ new
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+
+ " Double-click on word should visually select the word.
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call assert_equal('v', mode(), msg)
+ norm! r1
+ call assert_equal(['111 [foo bar] foo', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on opening square bracket should visually
+ " select the whole [foo bar].
+ call MouseLeftClick(1, 5)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 5)
+ call MouseLeftClick(1, 5)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 5)
+ call assert_equal('v', mode(), msg)
+ norm! r2
+ call assert_equal(['111 222222222 foo', 'foo'], getline(1, '$'), msg)
+
+ " Triple-click should visually select the whole line.
+ call MouseLeftClick(1, 3)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 3)
+ call assert_equal('V', mode(), msg)
+ norm! r3
+ call assert_equal(['33333333333333333', 'foo'], getline(1, '$'), msg)
+
+ " Quadruple-click should start visual block select.
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(96, getcharmod(), msg) " quadruple-click
+ call MouseLeftRelease(1, 2)
+ call assert_equal("\<c-v>", mode(), msg)
+ norm! r4
+ call assert_equal(['34333333333333333', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on a space character should visually select all the
+ " consecutive space characters.
+ %d
+ call setline(1, ' one two')
+ call MouseLeftClick(1, 2)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call MouseLeftRelease(1, 2)
+ call assert_equal('v', mode(), msg)
+ norm! r1
+ call assert_equal(['1111one two'], getline(1, '$'), msg)
+
+ " Double-click on a word with exclusive selection
+ set selection=exclusive
+ let @" = ''
+ call MouseLeftClick(1, 10)
+ call MouseLeftRelease(1, 10)
+ call MouseLeftClick(1, 10)
+ call MouseLeftRelease(1, 10)
+ norm! y
+ call assert_equal('two', @", msg)
+
+ " Double click to select a block of text with exclusive selection
+ %d
+ call setline(1, 'one (two) three')
+ call MouseLeftClick(1, 5)
+ call MouseLeftRelease(1, 5)
+ call MouseLeftClick(1, 5)
+ call MouseLeftRelease(1, 5)
+ norm! y
+ call assert_equal(5, col("'<"), msg)
+ call assert_equal(10, col("'>"), msg)
+
+ call MouseLeftClick(1, 9)
+ call MouseLeftRelease(1, 9)
+ call MouseLeftClick(1, 9)
+ call MouseLeftRelease(1, 9)
+ norm! y
+ call assert_equal(5, col("'<"), msg)
+ call assert_equal(10, col("'>"), msg)
+ set selection&
+
+ " Click somewhere else so that the clicks above is not combined with the
+ " clicks in the next iteration.
+ call MouseRightClick(3, 10)
+ call MouseRightRelease(3, 10)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+" Test for selecting text in visual blockwise mode using Alt-LeftClick
+func Test_mouse_alt_leftclick()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm mousetime=200
+ set mousemodel=popup
+ call WaitForResponses()
+
+ new
+ call setline(1, 'one (two) three')
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ " Left click with the Alt modifier key should extend the selection in
+ " blockwise visual mode.
+ let @" = ''
+ call MouseLeftClick(1, 3)
+ call MouseLeftRelease(1, 3)
+ call MouseAltLeftClick(1, 11)
+ call MouseLeftRelease(1, 11)
+ call assert_equal("\<C-V>", mode(), msg)
+ normal! y
+ call assert_equal('e (two) t', @")
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime& mousemodel&
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+func Test_xterm_mouse_click_in_fold_columns()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ let save_foldcolumn = &foldcolumn
+ set mouse=a term=xterm foldcolumn=3 ttymouse=xterm2
+ call WaitForResponses()
+
+ " Create 2 nested folds.
+ call setline(1, range(1, 7))
+ 2,6fold
+ norm! zR
+ 4,5fold
+ call assert_equal([-1, -1, -1, 4, 4, -1, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+
+ " Click in "+" of inner fold in foldcolumn should open it.
+ redraw
+ let row = 4
+ let col = 2
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([-1, -1, -1, -1, -1, -1, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+
+ " Click in "-" of outer fold in foldcolumn should close it.
+ redraw
+ let row = 2
+ let col = 1
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([-1, 2, 2, 2, 2, 2, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+ norm! zR
+
+ " Click in "|" of inner fold in foldcolumn should close it.
+ redraw
+ let row = 5
+ let col = 2
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([-1, -1, -1, 4, 4, -1, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+
+ let &foldcolumn = save_foldcolumn
+ let &ttymouse = save_ttymouse
+ let &term = save_term
+ let &mouse = save_mouse
+ bwipe!
+endfunc
+
+" Left or right click in Ex command line sets position of the cursor.
+func Test_term_mouse_click_in_cmdline_to_set_pos()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ let row = &lines
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ if ttymouse_val !=# 'xterm2' || row <= 223
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+
+ call feedkeys(':"3456789'
+ \ .. MouseLeftClickCode(row, 7)
+ \ .. MouseLeftReleaseCode(row, 7) .. 'L'
+ \ .. MouseRightClickCode(row, 4)
+ \ .. MouseRightReleaseCode(row, 4) .. 'R'
+ \ .. "\<CR>", 'Lx!')
+ call assert_equal('"3R456L789', @:, msg)
+ endif
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ call test_override('no_query_mouse', 0)
+endfunc
+
+" Middle click in command line pastes at position of cursor.
+func Test_term_mouse_middle_click_in_cmdline_to_paste()
+ CheckFeature clipboard_working
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm
+ call WaitForResponses()
+
+ let row = &lines
+ " Column values does not matter, paste is done at position of cursor.
+ let col = 1
+ let @* = 'paste'
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ call feedkeys(":\"->"
+ \ .. MouseMiddleReleaseCode(row, col)
+ \ .. MouseMiddleClickCode(row, col)
+ \ .. "<-"
+ \ .. MouseMiddleReleaseCode(row, col)
+ \ .. MouseMiddleClickCode(row, col)
+ \ .. "\<CR>", 'Lx!')
+ call assert_equal('"->paste<-paste', @:, msg)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ let @* = ''
+ call test_override('no_query_mouse', 0)
+endfunc
+
+" Test for making sure S-Middlemouse doesn't do anything
+func Test_term_mouse_shift_middle_click()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm ttymouse=xterm2 mousemodel=
+ call WaitForResponses()
+
+ call test_setmouse(1, 1)
+ exe "normal \<S-MiddleMouse>"
+ call assert_equal([''], getline(1, '$'))
+ call assert_equal(1, winnr())
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousemodel&
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for using mouse in visual mode
+func Test_term_mouse_visual_mode()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set term=xterm ttymouse=xterm2
+ call WaitForResponses()
+
+ " If visual mode is not present in 'mouse', then left click should not
+ " do anything in visal mode.
+ call setline(1, ['one two three four'])
+ set mouse=nci
+ call cursor(1, 5)
+ let @" = ''
+ call feedkeys("ve"
+ \ .. MouseLeftClickCode(1, 15) .. MouseLeftReleaseCode(1, 15)
+ \ .. 'y', 'Lx!')
+ call assert_equal(5, col('.'))
+ call assert_equal('two', @")
+
+ " Pressing right click in visual mode should change the visual selection
+ " if 'mousemodel' doesn't contain popup.
+ " Right click after the visual selection
+ set mousemodel=
+ set mouse=a
+ call test_setmouse(1, 13)
+ exe "normal 5|ve\<RightMouse>y"
+ call assert_equal('two three', @")
+ call assert_equal(5, col('.'))
+
+ " Right click before the visual selection
+ call test_setmouse(1, 9)
+ exe "normal 15|ve\<RightMouse>y"
+ call assert_equal('three four', @")
+ call assert_equal(9, col('.'))
+
+ " Right click inside the selection closer to the start of the selection
+ call test_setmouse(1, 7)
+ exe "normal 5|vee\<RightMouse>lly"
+ call assert_equal('three', @")
+ call assert_equal(9, col('.'))
+ call assert_equal(9, col("'<"))
+ call assert_equal(13, col("'>"))
+
+ " Right click inside the selection closer to the end of the selection
+ call test_setmouse(1, 11)
+ exe "normal 5|vee\<RightMouse>ly"
+ call assert_equal('two thre', @")
+ call assert_equal(5, col('.'))
+ call assert_equal(5, col("'<"))
+ call assert_equal(12, col("'>"))
+
+ " Multi-line selection. Right click inside the selection.
+ call setline(1, repeat(['aaaaaa'], 7))
+ call test_setmouse(3, 1)
+ exe "normal ggVG\<RightMouse>y"
+ call assert_equal(3, line("'<"))
+ call test_setmouse(5, 1)
+ exe "normal ggVG\<RightMouse>y"
+ call assert_equal(5, line("'>"))
+
+ " Click right in the middle line of the selection
+ call test_setmouse(4, 3)
+ exe "normal ggVG$\<RightMouse>y"
+ call assert_equal(4, line("'<"))
+ call test_setmouse(4, 4)
+ exe "normal ggVG$\<RightMouse>y"
+ call assert_equal(4, line("'>"))
+
+ set mousemodel&
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for displaying the popup menu using the right mouse click
+func Test_term_mouse_popup_menu()
+ CheckFeature menu
+ new
+ call setline(1, 'popup menu test')
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ let save_mousemodel = &mousemodel
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm mousemodel=popup
+ call WaitForResponses()
+
+ menu PopUp.foo :let g:menustr = 'foo'<CR>
+ menu PopUp.bar :let g:menustr = 'bar'<CR>
+ menu PopUp.baz :let g:menustr = 'baz'<CR>
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ let g:menustr = ''
+ call feedkeys(MouseRightClickCode(1, 4)
+ \ .. MouseRightReleaseCode(1, 4) .. "\<Down>\<Down>\<CR>", "x")
+ call assert_equal('bar', g:menustr, msg)
+ endfor
+
+ unmenu PopUp
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ let &mousemodel = save_mousemodel
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for 'mousemodel' set to popup_setpos to move the cursor where the popup
+" menu is displayed.
+func Test_term_mouse_popup_menu_setpos()
+ CheckFeature menu
+ 5new
+ call setline(1, ['the dish ran away with the spoon',
+ \ 'the cow jumped over the moon' ])
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ let save_mousemodel = &mousemodel
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm mousemodel=popup_setpos
+ call WaitForResponses()
+
+ nmenu PopUp.foo :let g:menustr = 'foo'<CR>
+ nmenu PopUp.bar :let g:menustr = 'bar'<CR>
+ nmenu PopUp.baz :let g:menustr = 'baz'<CR>
+ vmenu PopUp.foo y:<C-U>let g:menustr = 'foo'<CR>
+ vmenu PopUp.bar y:<C-U>let g:menustr = 'bar'<CR>
+ vmenu PopUp.baz y:<C-U>let g:menustr = 'baz'<CR>
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ let g:menustr = ''
+ call cursor(1, 1)
+ call feedkeys(MouseRightClickCode(1, 5)
+ \ .. MouseRightReleaseCode(1, 5) .. "\<Down>\<Down>\<CR>", "x")
+ call assert_equal('bar', g:menustr, msg)
+ call assert_equal([1, 5], [line('.'), col('.')], msg)
+
+ " Test for right click in visual mode inside the selection
+ let @" = ''
+ call cursor(1, 10)
+ call feedkeys('vee' .. MouseRightClickCode(1, 12)
+ \ .. MouseRightReleaseCode(1, 12) .. "\<Down>\<CR>", "x")
+ call assert_equal([1, 10], [line('.'), col('.')], msg)
+ call assert_equal('ran away', @", msg)
+
+ " Test for right click in visual mode right before the selection
+ let @" = ''
+ call cursor(1, 10)
+ call feedkeys('vee' .. MouseRightClickCode(1, 9)
+ \ .. MouseRightReleaseCode(1, 9) .. "\<Down>\<CR>", "x")
+ call assert_equal([1, 9], [line('.'), col('.')], msg)
+ call assert_equal('', @", msg)
+
+ " Test for right click in visual mode right after the selection
+ let @" = ''
+ call cursor(1, 10)
+ call feedkeys('vee' .. MouseRightClickCode(1, 18)
+ \ .. MouseRightReleaseCode(1, 18) .. "\<Down>\<CR>", "x")
+ call assert_equal([1, 18], [line('.'), col('.')], msg)
+ call assert_equal('', @", msg)
+
+ " Test for right click in block-wise visual mode inside the selection
+ let @" = ''
+ call cursor(1, 16)
+ call feedkeys("\<C-V>j3l" .. MouseRightClickCode(2, 17)
+ \ .. MouseRightReleaseCode(2, 17) .. "\<Down>\<CR>", "x")
+ call assert_equal([1, 16], [line('.'), col('.')], msg)
+ call assert_equal("\<C-V>4", getregtype('"'), msg)
+
+ " Test for right click in block-wise visual mode outside the selection
+ let @" = ''
+ call cursor(1, 16)
+ call feedkeys("\<C-V>j3l" .. MouseRightClickCode(2, 2)
+ \ .. MouseRightReleaseCode(2, 2) .. "\<Down>\<CR>", "x")
+ call assert_equal([2, 2], [line('.'), col('.')], msg)
+ call assert_equal('v', getregtype('"'), msg)
+ call assert_equal('', @", msg)
+
+ " Test for right click in line-wise visual mode inside the selection
+ let @" = ''
+ call cursor(1, 16)
+ call feedkeys("V" .. MouseRightClickCode(1, 10)
+ \ .. MouseRightReleaseCode(1, 10) .. "\<Down>\<CR>", "x")
+ call assert_equal([1, 1], [line('.'), col('.')], msg) " After yanking, the cursor goes to 1,1
+ call assert_equal("V", getregtype('"'), msg)
+ call assert_equal(1, len(getreg('"', 1, v:true)), msg)
+
+ " Test for right click in multi-line line-wise visual mode inside the selection
+ let @" = ''
+ call cursor(1, 16)
+ call feedkeys("Vj" .. MouseRightClickCode(2, 20)
+ \ .. MouseRightReleaseCode(2, 20) .. "\<Down>\<CR>", "x")
+ call assert_equal([1, 1], [line('.'), col('.')], msg) " After yanking, the cursor goes to 1,1
+ call assert_equal("V", getregtype('"'), msg)
+ call assert_equal(2, len(getreg('"', 1, v:true)), msg)
+
+ " Test for right click in line-wise visual mode outside the selection
+ let @" = ''
+ call cursor(1, 16)
+ call feedkeys("V" .. MouseRightClickCode(2, 10)
+ \ .. MouseRightReleaseCode(2, 10) .. "\<Down>\<CR>", "x")
+ call assert_equal([2, 10], [line('.'), col('.')], msg)
+ call assert_equal("", @", msg)
+
+ " Try clicking on the status line
+ let @" = ''
+ call cursor(1, 10)
+ call feedkeys('vee' .. MouseRightClickCode(6, 2)
+ \ .. MouseRightReleaseCode(6, 2) .. "\<Down>\<CR>", "x")
+ call assert_equal([1, 10], [line('.'), col('.')], msg)
+ call assert_equal('ran away', @", msg)
+
+ " Try clicking outside the window
+ let @" = ''
+ call cursor(2, 2)
+ call feedkeys('vee' .. MouseRightClickCode(7, 2)
+ \ .. MouseRightReleaseCode(7, 2) .. "\<Down>\<CR>", "x")
+ call assert_equal(2, winnr(), msg)
+ call assert_equal('', @", msg)
+ wincmd w
+ endfor
+
+ unmenu PopUp
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ let &mousemodel = save_mousemodel
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for searching for the word under the cursor using Shift-Right or
+" Shift-Left click.
+func Test_term_mouse_search()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm ttymouse=xterm2
+ set mousemodel=
+ call WaitForResponses()
+
+ " In normal mode, Shift-Left or Shift-Right click should search for the word
+ " under the cursor.
+ call setline(1, ['one two three four', 'four three two one'])
+ call test_setmouse(1, 4)
+ exe "normal \<S-LeftMouse>"
+ call assert_equal([2, 12], [line('.'), col('.')])
+ call test_setmouse(2, 16)
+ exe "normal \<S-RightMouse>"
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ " In visual mode, Shift-Left or Shift-Right click should search for the word
+ " under the cursor and extend the selection.
+ call test_setmouse(1, 4)
+ exe "normal 4|ve\<S-LeftMouse>y"
+ call assert_equal([2, 12], [line("'>"), col("'>")])
+ call test_setmouse(2, 16)
+ exe "normal 2G16|ve\<S-RightMouse>y"
+ call assert_equal([1, 1], [line("'<"), col("'<")])
+
+ set mousemodel&
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+" Test for selecting an entry in the quickfix/location list window using the
+" mouse.
+func Test_term_mouse_quickfix_window()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm ttymouse=xterm2
+ set mousemodel=
+ call WaitForResponses()
+
+ cgetexpr "Xfile1:1:L1"
+ copen 5
+ call test_setmouse(&lines - 7, 1)
+ exe "normal \<2-LeftMouse>"
+ call assert_equal('Xfile1', @%)
+ %bw!
+
+ lgetexpr "Xfile2:1:L1"
+ lopen 5
+ call test_setmouse(&lines - 7, 1)
+ exe "normal \<2-LeftMouse>"
+ call assert_equal('Xfile2', @%)
+ %bw!
+
+ set mousemodel&
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+endfunc
+
+" Test for the 'h' flag in the 'mouse' option. Using mouse in the help window.
+func Test_term_mouse_help_window()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=h term=xterm mousetime=200
+ call WaitForResponses()
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+ help
+ let @" = ''
+ call MouseLeftClick(2, 5)
+ call MouseLeftRelease(2, 5)
+ call MouseLeftClick(1, 1)
+ call MouseLeftDrag(1, 10)
+ call MouseLeftRelease(1, 10)
+ norm! y
+ call assert_equal('*help.txt*', @", msg)
+ helpclose
+
+ " Click somewhere else to make sure the left click above is not combined
+ " with the next left click and treated as a double click
+ call MouseRightClick(5, 10)
+ call MouseRightRelease(5, 10)
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ call test_override('no_query_mouse', 0)
+ %bw!
+endfunc
+
+" Test for the translation of various mouse terminal codes
+func Test_mouse_termcodes()
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=xterm mousetime=200
+ call WaitForResponses()
+
+ new
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ let msg = 'ttymouse=' .. ttymouse_val
+ exe 'set ttymouse=' .. ttymouse_val
+
+ let mouse_codes = [
+ \ ["\<LeftMouse>", "<LeftMouse>"],
+ \ ["\<MiddleMouse>", "<MiddleMouse>"],
+ \ ["\<RightMouse>", "<RightMouse>"],
+ \ ["\<S-LeftMouse>", "<S-LeftMouse>"],
+ \ ["\<S-MiddleMouse>", "<S-MiddleMouse>"],
+ \ ["\<S-RightMouse>", "<S-RightMouse>"],
+ \ ["\<C-LeftMouse>", "<C-LeftMouse>"],
+ \ ["\<C-MiddleMouse>", "<C-MiddleMouse>"],
+ \ ["\<C-RightMouse>", "<C-RightMouse>"],
+ \ ["\<M-LeftMouse>", "<M-LeftMouse>"],
+ \ ["\<M-MiddleMouse>", "<M-MiddleMouse>"],
+ \ ["\<M-RightMouse>", "<M-RightMouse>"],
+ \ ["\<2-LeftMouse>", "<2-LeftMouse>"],
+ \ ["\<2-MiddleMouse>", "<2-MiddleMouse>"],
+ \ ["\<2-RightMouse>", "<2-RightMouse>"],
+ \ ["\<3-LeftMouse>", "<3-LeftMouse>"],
+ \ ["\<3-MiddleMouse>", "<3-MiddleMouse>"],
+ \ ["\<3-RightMouse>", "<3-RightMouse>"],
+ \ ["\<4-LeftMouse>", "<4-LeftMouse>"],
+ \ ["\<4-MiddleMouse>", "<4-MiddleMouse>"],
+ \ ["\<4-RightMouse>", "<4-RightMouse>"],
+ \ ["\<LeftDrag>", "<LeftDrag>"],
+ \ ["\<MiddleDrag>", "<MiddleDrag>"],
+ \ ["\<RightDrag>", "<RightDrag>"],
+ \ ["\<LeftRelease>", "<LeftRelease>"],
+ \ ["\<MiddleRelease>", "<MiddleRelease>"],
+ \ ["\<RightRelease>", "<RightRelease>"],
+ \ ["\<ScrollWheelUp>", "<ScrollWheelUp>"],
+ \ ["\<S-ScrollWheelUp>", "<S-ScrollWheelUp>"],
+ \ ["\<C-ScrollWheelUp>", "<C-ScrollWheelUp>"],
+ \ ["\<ScrollWheelDown>", "<ScrollWheelDown>"],
+ \ ["\<S-ScrollWheelDown>", "<S-ScrollWheelDown>"],
+ \ ["\<C-ScrollWheelDown>", "<C-ScrollWheelDown>"],
+ \ ["\<ScrollWheelLeft>", "<ScrollWheelLeft>"],
+ \ ["\<S-ScrollWheelLeft>", "<S-ScrollWheelLeft>"],
+ \ ["\<C-ScrollWheelLeft>", "<C-ScrollWheelLeft>"],
+ \ ["\<ScrollWheelRight>", "<ScrollWheelRight>"],
+ \ ["\<S-ScrollWheelRight>", "<S-ScrollWheelRight>"],
+ \ ["\<C-ScrollWheelRight>", "<C-ScrollWheelRight>"]
+ \ ]
+
+ for [code, outstr] in mouse_codes
+ exe "normal ggC\<C-K>" . code
+ call assert_equal(outstr, getline(1), msg)
+ endfor
+ endfor
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime&
+ call test_override('no_query_mouse', 0)
+ %bw!
+endfunc
+
+" This only checks if the sequence is recognized.
+func Test_term_rgb_response()
+ set t_RF=x
+ set t_RB=y
+
+ " response to t_RF, 4 digits
+ let red = 0x12
+ let green = 0x34
+ let blue = 0x56
+ let seq = printf("\<Esc>]10;rgb:%02x00/%02x00/%02x00\x07", red, green, blue)
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termrfgresp)
+
+ " response to t_RF, 2 digits
+ let red = 0x78
+ let green = 0x9a
+ let blue = 0xbc
+ let seq = printf("\<Esc>]10;rgb:%02x/%02x/%02x\x07", red, green, blue)
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termrfgresp)
+
+ " response to t_RB, 4 digits, dark
+ set background=light
+ eval 'background'->test_option_not_set()
+ let red = 0x29
+ let green = 0x4a
+ let blue = 0x6b
+ let seq = printf("\<Esc>]11;rgb:%02x00/%02x00/%02x00\x07", red, green, blue)
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termrbgresp)
+ call assert_equal('dark', &background)
+
+ " response to t_RB, 4 digits, light
+ set background=dark
+ call test_option_not_set('background')
+ let red = 0x81
+ let green = 0x63
+ let blue = 0x65
+ let seq = printf("\<Esc>]11;rgb:%02x00/%02x00/%02x00\x07", red, green, blue)
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termrbgresp)
+ call assert_equal('light', &background)
+
+ " response to t_RB, 2 digits, dark
+ set background=light
+ call test_option_not_set('background')
+ let red = 0x47
+ let green = 0x59
+ let blue = 0x5b
+ let seq = printf("\<Esc>]11;rgb:%02x/%02x/%02x\x07", red, green, blue)
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termrbgresp)
+ call assert_equal('dark', &background)
+
+ " response to t_RB, 2 digits, light
+ set background=dark
+ call test_option_not_set('background')
+ let red = 0x83
+ let green = 0xa4
+ let blue = 0xc2
+ let seq = printf("\<Esc>]11;rgb:%02x/%02x/%02x\x07", red, green, blue)
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termrbgresp)
+ call assert_equal('light', &background)
+
+ set t_RF= t_RB=
+endfunc
+
+" This only checks if the sequence is recognized.
+" This must be after other tests, because it has side effects to xterm
+" properties.
+func Test_xx01_term_style_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ " send the termresponse to trigger requesting the XT codes
+ let seq = "\<Esc>[>41;337;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+
+ let seq = "\<Esc>P1$r2 q\<Esc>\\"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termstyleresp)
+
+ call assert_equal(#{
+ \ cursor_style: 'u',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'u',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+
+ set t_RV=
+ call test_override('term_props', 0)
+endfunc
+
+" This checks the iTerm2 version response.
+" This must be after other tests, because it has side effects to xterm
+" properties.
+func Test_xx02_iTerm2_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ " Old versions of iTerm2 used a different style term response.
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>0;95;c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('xterm', &ttymouse)
+
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>0;95;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'u',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+
+ set t_RV=
+ call test_override('term_props', 0)
+endfunc
+
+func Run_libvterm_konsole_response(code)
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>0;" .. a:code .. ";0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'u',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+endfunc
+
+" This checks the libvterm version response.
+" This must be after other tests, because it has side effects to xterm
+" properties.
+func Test_xx03_libvterm_konsole_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ " libvterm
+ call Run_libvterm_konsole_response(100)
+ " Konsole
+ call Run_libvterm_konsole_response(115)
+
+ set t_RV=
+ call test_override('term_props', 0)
+endfunc
+
+" This checks the Mac Terminal.app version response.
+" This must be after other tests, because it has side effects to xterm
+" properties.
+func Test_xx04_Mac_Terminal_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ set ttymouse=xterm
+ " t_8u is not reset
+ let &t_8u = "\<Esc>[58;2;%lu;%lu;%lum"
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>1;95;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'y',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+ call assert_equal("\<Esc>[58;2;%lu;%lu;%lum", &t_8u)
+
+ " Reset is_not_xterm and is_mac_terminal.
+ set t_RV=
+ set term=xterm
+ set t_RV=x
+ call test_override('term_props', 0)
+endfunc
+
+" This checks the mintty version response.
+" This must be after other tests, because it has side effects to xterm
+" properties.
+func Test_xx05_mintty_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>77;20905;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'y',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+
+ set t_RV=
+ call test_override('term_props', 0)
+endfunc
+
+" This checks the screen version response.
+" This must be after other tests, because it has side effects to xterm
+" properties.
+func Test_xx06_screen_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ " Old versions of screen don't support SGR mouse mode.
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>83;40500;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('xterm', &ttymouse)
+
+ " screen supports SGR mouse mode starting in version 4.7.
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>83;40700;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'n',
+ \ underline_rgb: 'y',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+
+ set t_RV=
+ call test_override('term_props', 0)
+endfunc
+
+func Do_check_t_8u_set_reset(set_by_user)
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let default_value = "\<Esc>[58;2;%lu;%lu;%lum"
+ let &t_8u = default_value
+ if !a:set_by_user
+ call test_option_not_set('t_8u')
+ endif
+ let seq = "\<Esc>[>0;279;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'u',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'u',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+ call assert_equal(a:set_by_user ? default_value : '', &t_8u)
+endfunc
+
+" This checks the xterm version response.
+" This must be after other tests, because it has side effects to xterm
+" properties.
+func Test_xx07_xterm_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ " Do Terminal.app first to check that is_mac_terminal is reset.
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>1;95;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ " xterm < 95: "xterm" (actually unmodified)
+ set t_RV=
+ set term=xterm
+ call WaitForResponses()
+
+ set t_RV=x
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>0;94;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('xterm', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'y',
+ \ mouse: 'u',
+ \ kitty: 'u',
+ \ }, terminalprops())
+
+ " xterm >= 95 < 277 "xterm2"
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>0;267;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('xterm2', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'u',
+ \ mouse: '2',
+ \ kitty: 'u',
+ \ }, terminalprops())
+
+ " xterm >= 277: "sgr"
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>0;277;0c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'n',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'u',
+ \ mouse: 's',
+ \ kitty: 'u',
+ \ }, terminalprops())
+
+ " xterm >= 279: "sgr" and cursor_style not reset; also check t_8u reset,
+ " except when it was set by the user
+ call Do_check_t_8u_set_reset(0)
+ call Do_check_t_8u_set_reset(1)
+
+ set t_RV=
+ call test_override('term_props', 0)
+endfunc
+
+func Test_xx08_kitty_response()
+ " Termresponse is only parsed when t_RV is not empty.
+ set t_RV=x
+ call test_override('term_props', 1)
+
+ set ttymouse=xterm
+ call test_option_not_set('ttymouse')
+ let seq = "\<Esc>[>1;4001;12c"
+ call feedkeys(seq, 'Lx!')
+ call assert_equal(seq, v:termresponse)
+ call assert_equal('sgr', &ttymouse)
+
+ call assert_equal(#{
+ \ cursor_style: 'u',
+ \ cursor_blink_mode: 'u',
+ \ underline_rgb: 'y',
+ \ mouse: 's',
+ \ kitty: 'y',
+ \ }, terminalprops())
+
+ set t_RV=
+ call test_override('term_props', 0)
+endfunc
+
+func Test_focus_events()
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ set term=xterm ttymouse=xterm2
+ call WaitForResponses()
+
+ au FocusGained * let g:focus_gained += 1
+ au FocusLost * let g:focus_lost += 1
+ let g:focus_gained = 0
+ let g:focus_lost = 0
+
+ call feedkeys("\<Esc>[O", "Lx!")
+ call assert_equal(1, g:focus_lost)
+ call feedkeys("\<Esc>[I", "Lx!")
+ call assert_equal(1, g:focus_gained)
+
+ " still works when 'ttymouse' is empty
+ set ttymouse=
+ call feedkeys("\<Esc>[O", "Lx!")
+ call assert_equal(2, g:focus_lost)
+ call feedkeys("\<Esc>[I", "Lx!")
+ call assert_equal(2, g:focus_gained)
+
+ au! FocusGained
+ au! FocusLost
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+endfunc
+
+func Test_get_termcode()
+ try
+ let k1 = &t_k1
+ catch /E113/
+ throw 'Skipped: Unable to query termcodes'
+ endtry
+ set t_k1=
+ set t_k1&
+ call assert_equal(k1, &t_k1)
+
+ " use external termcap first
+ set nottybuiltin
+ set t_k1=
+ set t_k1&
+ " when using external termcap may get something else, but it must not be
+ " empty, since we would fallback to the builtin one.
+ call assert_notequal('', &t_k1)
+
+ if &term =~ 'xterm'
+ " use internal termcap first
+ let term_save = &term
+ let &term = 'builtin_' .. &term
+ set t_k1=
+ set t_k1&
+ call assert_equal(k1, &t_k1)
+ let &term = term_save
+ endif
+
+ set ttybuiltin
+endfunc
+
+func Test_list_builtin_terminals()
+ CheckRunVimInTerminal
+
+ call RunVimInTerminal('', #{rows: 14})
+ call term_sendkeys('', ":set cmdheight=3\<CR>")
+ call TermWait('', 100)
+ call term_sendkeys('', ":set term=xxx\<CR>")
+ call TermWait('', 100)
+
+ " Check that the list ends in "builtin_dumb" and "builtin_debug".
+ let dumb_idx = 0
+ for n in range(8, 12)
+ if term_getline('', n) =~ 'builtin_dumb'
+ let dumb_idx = n
+ break
+ endif
+ endfor
+ call assert_notequal(0, dumb_idx, 'builtin_dumb not found')
+
+ call assert_match('builtin_dumb', term_getline('', dumb_idx))
+ call assert_match('builtin_debug', term_getline('', dumb_idx + 1))
+ call assert_match('Not found in termcap', term_getline('', dumb_idx + 2))
+
+ call StopVimInTerminal('')
+endfunc
+
+" This checks the CSI sequences when in modifyOtherKeys mode.
+" The mode doesn't need to be enabled, the codes are always detected.
+func RunTest_modifyOtherKeys(func)
+ new
+ set timeoutlen=10
+
+ " Shift-X is sent as 'X' with the shift modifier
+ call feedkeys('a' .. a:func('X', 2) .. "\<Esc>", 'Lx!')
+ call assert_equal('X', getline(1))
+
+ " Ctrl-i is Tab
+ call setline(1, '')
+ call feedkeys('a' .. a:func('i', 5) .. "\<Esc>", 'Lx!')
+ call assert_equal("\t", getline(1))
+
+ " Ctrl-I is also Tab
+ call setline(1, '')
+ call feedkeys('a' .. a:func('I', 5) .. "\<Esc>", 'Lx!')
+ call assert_equal("\t", getline(1))
+
+ " Alt-x is ø
+ call setline(1, '')
+ call feedkeys('a' .. a:func('x', 3) .. "\<Esc>", 'Lx!')
+ call assert_equal("ø", getline(1))
+
+ " Meta-x is also ø
+ call setline(1, '')
+ call feedkeys('a' .. a:func('x', 9) .. "\<Esc>", 'Lx!')
+ call assert_equal("ø", getline(1))
+
+ " Alt-X is Ø
+ call setline(1, '')
+ call feedkeys('a' .. a:func('X', 3) .. "\<Esc>", 'Lx!')
+ call assert_equal("Ø", getline(1))
+
+ " Meta-X is ø
+ call setline(1, '')
+ call feedkeys('a' .. a:func('X', 9) .. "\<Esc>", 'Lx!')
+ call assert_equal("Ø", getline(1))
+
+ " Ctrl-6 is Ctrl-^
+ split aaa
+ edit bbb
+ call feedkeys(a:func('6', 5), 'Lx!')
+ call assert_equal("aaa", bufname())
+ bwipe aaa
+ bwipe bbb
+
+ " Ctrl-V X 33 is 3
+ call setline(1, '')
+ call feedkeys("a\<C-V>" .. a:func('X', 2) .. "33\<Esc>", 'Lx!')
+ call assert_equal("3", getline(1))
+
+ " Ctrl-V U 12345 is Unicode 12345
+ call setline(1, '')
+ call feedkeys("a\<C-V>" .. a:func('U', 2) .. "12345\<Esc>", 'Lx!')
+ call assert_equal("\U12345", getline(1))
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func Test_modifyOtherKeys_basic()
+ call RunTest_modifyOtherKeys(function('GetEscCodeCSI27'))
+ call RunTest_modifyOtherKeys(function('GetEscCodeCSIu'))
+endfunc
+
+func Test_modifyOtherKeys_no_mapping()
+ set timeoutlen=10
+
+ let @a = 'aaa'
+ call feedkeys(":let x = '" .. GetEscCodeCSI27('R', 5) .. GetEscCodeCSI27('R', 5) .. "a'\<CR>", 'Lx!')
+ call assert_equal("let x = 'aaa'", @:)
+
+ new
+ call feedkeys("a" .. GetEscCodeCSI27('R', 5) .. GetEscCodeCSI27('R', 5) .. "a\<Esc>", 'Lx!')
+ call assert_equal("aaa", getline(1))
+ bwipe!
+
+ new
+ call feedkeys("axx\<CR>yy" .. GetEscCodeCSI27('G', 5) .. GetEscCodeCSI27('K', 5) .. "a\<Esc>", 'Lx!')
+ call assert_equal("axx", getline(1))
+ call assert_equal("yy", getline(2))
+ bwipe!
+
+ set timeoutlen&
+endfunc
+
+func Test_CSIu_keys_without_modifiers()
+ " make this execute faster
+ set timeoutlen=10
+
+ call WaitForResponses()
+
+ " Escape sent as `CSI 27 u` should act as normal escape and not undo
+ call setline(1, 'a')
+ call feedkeys('a' .. GetEscCodeCSIuWithoutModifier("\e"), 'Lx!')
+ call assert_equal('n', mode())
+ call assert_equal('a', getline(1))
+
+ " Tab sent as `CSI 9 u` should work
+ call setline(1, '')
+ call feedkeys('a' .. GetEscCodeCSIuWithoutModifier("\t") .. "\<Esc>", 'Lx!')
+ call assert_equal("\t", getline(1))
+
+ set timeoutlen&
+endfunc
+
+" Check that when DEC mouse codes are recognized a special key is handled.
+func Test_ignore_dec_mouse()
+ silent !infocmp gnome >/dev/null 2>&1
+ if v:shell_error != 0
+ throw 'Skipped: gnome entry missing in the terminfo db'
+ endif
+
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ call test_override('no_query_mouse', 1)
+ set mouse=a term=gnome ttymouse=
+ call WaitForResponses()
+
+ execute "set <xF1>=\<Esc>[1;*P"
+ nnoremap <S-F1> agot it<Esc>
+ call feedkeys("\<Esc>[1;2P", 'Lx!')
+ call assert_equal('got it', getline(1))
+
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+func RunTest_mapping_shift(key, func)
+ call setline(1, '')
+ if a:key == '|'
+ exe 'inoremap \| xyz'
+ else
+ exe 'inoremap ' .. a:key .. ' xyz'
+ endif
+ call feedkeys('a' .. a:func(a:key, 2) .. "\<Esc>", 'Lx!')
+ call assert_equal("xyz", getline(1))
+ if a:key == '|'
+ exe 'iunmap \|'
+ else
+ exe 'iunmap ' .. a:key
+ endif
+endfunc
+
+func Test_modifyOtherKeys_mapped()
+ set timeoutlen=10
+ imap ' <C-W>
+ imap <C-W><C-A> c-a
+ call setline(1, '')
+
+ " single quote is turned into single byte CTRL-W
+ " CTRL-A is added with a separate modifier, and needs to be simplified before
+ " the mapping can match.
+ call feedkeys("a'" .. GetEscCodeCSI27('A', 5) .. "\<Esc>", 'Lx!')
+ call assert_equal('c-a', getline(1))
+
+ iunmap '
+ iunmap <C-W><C-A>
+ set timeoutlen&
+endfunc
+
+func Test_modifyOtherKeys_ambiguous_mapping()
+ new
+ set timeoutlen=10
+ map <C-J> a
+ map <C-J>x <Nop>
+ call setline(1, 'x')
+
+ " CTRL-J b should have trigger the <C-J> mapping and then insert "b"
+ call feedkeys(GetEscCodeCSI27('J', 5) .. "b\<Esc>", 'Lx!')
+ call assert_equal('xb', getline(1))
+
+ unmap <C-J>
+ unmap <C-J>x
+
+ " if a special character is following there should be a check for a termcode
+ nnoremap s aX<Esc>
+ nnoremap s<BS> aY<Esc>
+ set t_kb=
+ call setline(1, 'x')
+ call feedkeys("s\x08", 'Lx!')
+ call assert_equal('xY', getline(1))
+
+ set timeoutlen&
+ bwipe!
+endfunc
+
+" Whether Shift-Tab sends "ESC [ Z" or "ESC [ 27 ; 2 ; 9 ~" is unpredictable,
+" both should work.
+func Test_modifyOtherKeys_shift_tab()
+ set timeoutlen=10
+
+ call setline(1, '')
+ call feedkeys("a\<C-K>" .. GetEscCodeCSI27("\t", '2') .. "\<Esc>", 'Lx!')
+ eval getline(1)->assert_equal('<S-Tab>')
+
+ call setline(1, '')
+ call feedkeys("a\<C-K>\<Esc>[Z\<Esc>", 'Lx!')
+ eval getline(1)->assert_equal('<S-Tab>')
+
+ set timeoutlen&
+ bwipe!
+endfunc
+
+func RunTest_mapping_works_with_shift(func)
+ new
+ set timeoutlen=10
+
+ call RunTest_mapping_shift('@', a:func)
+ call RunTest_mapping_shift('A', a:func)
+ call RunTest_mapping_shift('Z', a:func)
+ call RunTest_mapping_shift('^', a:func)
+ call RunTest_mapping_shift('_', a:func)
+ call RunTest_mapping_shift('{', a:func)
+ call RunTest_mapping_shift('|', a:func)
+ call RunTest_mapping_shift('}', a:func)
+ call RunTest_mapping_shift('~', a:func)
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func Test_mapping_works_with_shift_plain()
+ call RunTest_mapping_works_with_shift(function('GetEscCodeCSI27'))
+ call RunTest_mapping_works_with_shift(function('GetEscCodeCSIu'))
+endfunc
+
+func RunTest_mapping_mods(map, key, func, code)
+ call setline(1, '')
+ exe 'inoremap ' .. a:map .. ' xyz'
+ call feedkeys('a' .. a:func(a:key, a:code) .. "\<Esc>", 'Lx!')
+ call assert_equal("xyz", getline(1))
+ exe 'iunmap ' .. a:map
+endfunc
+
+func RunTest_mapping_works_with_mods(func, mods, code)
+ new
+ set timeoutlen=10
+
+ if a:mods !~ 'S'
+ " Shift by itself has no effect
+ call RunTest_mapping_mods('<' .. a:mods .. '-@>', '@', a:func, a:code)
+ endif
+ call RunTest_mapping_mods('<' .. a:mods .. '-A>', 'A', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-Z>', 'Z', a:func, a:code)
+ if a:mods !~ 'S'
+ " with Shift code is always upper case
+ call RunTest_mapping_mods('<' .. a:mods .. '-a>', 'a', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-z>', 'z', a:func, a:code)
+ endif
+ if a:mods != 'A'
+ " with Alt code is not in upper case
+ call RunTest_mapping_mods('<' .. a:mods .. '-a>', 'A', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-z>', 'Z', a:func, a:code)
+ endif
+ call RunTest_mapping_mods('<' .. a:mods .. '-á>', 'á', a:func, a:code)
+ if a:mods !~ 'S'
+ " Shift by itself has no effect
+ call RunTest_mapping_mods('<' .. a:mods .. '-^>', '^', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-_>', '_', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-{>', '{', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-\|>', '|', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-}>', '}', a:func, a:code)
+ call RunTest_mapping_mods('<' .. a:mods .. '-~>', '~', a:func, a:code)
+ endif
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func Test_mapping_works_with_shift()
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'S', 2)
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'S', 2)
+endfunc
+
+func Test_mapping_works_with_ctrl()
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C', 5)
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C', 5)
+
+ new
+ set timeoutlen=10
+
+ " CTRL-@ actually produces the code for CTRL-2, which is converted
+ call RunTest_mapping_mods('<C-@>', '2', function('GetEscCodeCSI27'), 5)
+ call RunTest_mapping_mods('<C-@>', '2', function('GetEscCodeCSIu'), 5)
+
+ " CTRL-^ actually produces the code for CTRL-6, which is converted
+ call RunTest_mapping_mods('<C-^>', '6', function('GetEscCodeCSI27'), 5)
+ call RunTest_mapping_mods('<C-^>', '6', function('GetEscCodeCSIu'), 5)
+
+ " CTRL-_ actually produces the code for CTRL--, which is converted
+ call RunTest_mapping_mods('<C-_>', '-', function('GetEscCodeCSI27'), 5)
+ call RunTest_mapping_mods('<C-_>', '-', function('GetEscCodeCSIu'), 5)
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func Test_mapping_works_with_shift_ctrl()
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C-S', 6)
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C-S', 6)
+
+ new
+ set timeoutlen=10
+
+ " Ctrl-Shift-[ actually produces CTRL-Shift-{ which is mapped as <C-{>
+ call RunTest_mapping_mods('<C-{>', '{', function('GetEscCodeCSI27'), 6)
+ call RunTest_mapping_mods('<C-{>', '{', function('GetEscCodeCSIu'), 6)
+
+ " Ctrl-Shift-] actually produces CTRL-Shift-} which is mapped as <C-}>
+ call RunTest_mapping_mods('<C-{>', '{', function('GetEscCodeCSI27'), 6)
+ call RunTest_mapping_mods('<C-{>', '{', function('GetEscCodeCSIu'), 6)
+
+ " Ctrl-Shift-\ actually produces CTRL-Shift-| which is mapped as <C-|>
+ call RunTest_mapping_mods('<C-\|>', '|', function('GetEscCodeCSI27'), 6)
+ call RunTest_mapping_mods('<C-\|>', '|', function('GetEscCodeCSIu'), 6)
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+" Below we also test the "u" code with Alt, This works, but libvterm would not
+" send the Alt key like this but by prefixing an Esc.
+
+func Test_mapping_works_with_alt()
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'A', 3)
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'A', 3)
+endfunc
+
+func Test_mapping_works_with_shift_alt()
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'S-A', 4)
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'S-A', 4)
+endfunc
+
+func Test_mapping_works_with_alt_and_shift()
+ new
+ set timeoutlen=10
+
+ " mapping <A-?> works even though the code is A-S-?
+ for c in ['!', '$', '+', ':', '?', '^', '~']
+ call RunTest_mapping_mods('<A-' .. c .. '>', c, function('GetEscCodeCSI27'), 4)
+ call RunTest_mapping_mods('<A-' .. c .. '>', c, function('GetEscCodeCSIu'), 4)
+ endfor
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func Test_mapping_works_with_ctrl_alt()
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C-A', 7)
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C-A', 7)
+endfunc
+
+func Test_mapping_works_with_shift_ctrl_alt()
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C-S-A', 8)
+ call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C-S-A', 8)
+endfunc
+
+func Test_mapping_works_with_unknown_modifiers()
+ new
+ set timeoutlen=10
+
+ for Func in [function('GetEscCodeCSI27'), function('GetEscCodeCSIu')]
+ call RunTest_mapping_mods('<C-z>', 'z', Func, 5)
+ " Add 16, 32, 64 or 128 for modifiers we currently don't support.
+ call RunTest_mapping_mods('<C-z>', 'z', Func, 5 + 16)
+ call RunTest_mapping_mods('<C-z>', 'z', Func, 5 + 32)
+ call RunTest_mapping_mods('<C-z>', 'z', Func, 5 + 64)
+ call RunTest_mapping_mods('<C-z>', 'z', Func, 5 + 128)
+
+ call RunTest_mapping_mods('<S-X>', 'X', Func, 2)
+ " Add 16, 32, 64 or 128 for modifiers we currently don't support.
+ call RunTest_mapping_mods('<S-X>', 'X', Func, 2 + 16)
+ call RunTest_mapping_mods('<S-X>', 'X', Func, 2 + 32)
+ call RunTest_mapping_mods('<S-X>', 'X', Func, 2 + 64)
+ call RunTest_mapping_mods('<S-X>', 'X', Func, 2 + 128)
+ endfor
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func RunTest_mapping_funckey(map, func, key, code)
+ call setline(1, '')
+ exe 'inoremap ' .. a:map .. ' xyz'
+ call feedkeys('a' .. a:func(a:key, a:code) .. "\<Esc>", 'Lx!')
+ call assert_equal("xyz", getline(1), 'mapping ' .. a:map)
+ exe 'iunmap ' .. a:map
+endfunc
+
+func Test_mapping_kitty_function_keys()
+ new
+ set timeoutlen=10
+
+ " Function keys made with CSI and ending in [ABCDEFHPQRS].
+ " 'E' is keypad BEGIN, not supported
+ let maps = [
+ \ ['<Up>', 'A', 0],
+ \ ['<S-Up>', 'A', 2],
+ \ ['<C-Up>', 'A', 5],
+ \ ['<C-S-Up>', 'A', 6],
+ \
+ \ ['<Down>', 'B', 0],
+ \ ['<S-Down>', 'B', 2],
+ \ ['<C-Down>', 'B', 5],
+ \ ['<C-S-Down>', 'B', 6],
+ \
+ \ ['<Right>', 'C', 0],
+ \ ['<S-Right>', 'C', 2],
+ \ ['<C-Right>', 'C', 5],
+ \ ['<C-S-Right>', 'C', 6],
+ \
+ \ ['<Left>', 'D', 0],
+ \ ['<S-Left>', 'D', 2],
+ \ ['<C-Left>', 'D', 5],
+ \ ['<C-S-Left>', 'D', 6],
+ \
+ \ ['<End>', 'F', 0],
+ \ ['<S-End>', 'F', 2],
+ \ ['<C-End>', 'F', 5],
+ \ ['<C-S-End>', 'F', 6],
+ \
+ \ ['<Home>', 'H', 0],
+ \ ['<S-Home>', 'H', 2],
+ \ ['<C-Home>', 'H', 5],
+ \ ['<C-S-Home>', 'H', 6],
+ \
+ \ ['<F1>', 'P', 0],
+ \ ['<S-F1>', 'P', 2],
+ \ ['<C-F1>', 'P', 5],
+ \ ['<C-S-F1>', 'P', 6],
+ \
+ \ ['<F2>', 'Q', 0],
+ \ ['<S-F2>', 'Q', 2],
+ \ ['<C-F2>', 'Q', 5],
+ \ ['<C-S-F2>', 'Q', 6],
+ \
+ \ ['<F3>', 'R', 0],
+ \ ['<S-F3>', 'R', 2],
+ \ ['<C-F3>', 'R', 5],
+ \ ['<C-S-F3>', 'R', 6],
+ \
+ \ ['<F4>', 'S', 0],
+ \ ['<S-F4>', 'S', 2],
+ \ ['<C-F4>', 'S', 5],
+ \ ['<C-S-F4>', 'S', 6],
+ \ ]
+
+ for map in maps
+ call RunTest_mapping_funckey(map[0], function('GetEscCodeFunckey'), map[1], map[2])
+ endfor
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func Test_insert_literal()
+ set timeoutlen=10
+
+ call WaitForResponses()
+
+ new
+ " CTRL-V CTRL-X inserts a ^X
+ call feedkeys('a' .. GetEscCodeCSIu('V', '5') .. GetEscCodeCSIu('X', '5') .. "\<Esc>", 'Lx!')
+ call assert_equal("\<C-X>", getline(1))
+
+ call setline(1, '')
+ call feedkeys('a' .. GetEscCodeCSI27('V', '5') .. GetEscCodeCSI27('X', '5') .. "\<Esc>", 'Lx!')
+ call assert_equal("\<C-X>", getline(1))
+
+ " CTRL-SHIFT-V CTRL-X inserts escape sequence
+ call setline(1, '')
+ call feedkeys('a' .. GetEscCodeCSIu('V', '6') .. GetEscCodeCSIu('X', '5') .. "\<Esc>", 'Lx!')
+ call assert_equal("\<Esc>[88;5u", getline(1))
+
+ call setline(1, '')
+ call feedkeys('a' .. GetEscCodeCSI27('V', '6') .. GetEscCodeCSI27('X', '5') .. "\<Esc>", 'Lx!')
+ call assert_equal("\<Esc>[27;5;88~", getline(1))
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
+func Test_cmdline_literal()
+ set timeoutlen=10
+
+ " CTRL-V CTRL-Y inserts a ^Y
+ call feedkeys(':' .. GetEscCodeCSIu('V', '5') .. GetEscCodeCSIu('Y', '5') .. "\<C-B>\"\<CR>", 'Lx!')
+ call assert_equal("\"\<C-Y>", @:)
+
+ call feedkeys(':' .. GetEscCodeCSI27('V', '5') .. GetEscCodeCSI27('Y', '5') .. "\<C-B>\"\<CR>", 'Lx!')
+ call assert_equal("\"\<C-Y>", @:)
+
+ " CTRL-SHIFT-V CTRL-Y inserts escape sequence
+ call feedkeys(':' .. GetEscCodeCSIu('V', '6') .. GetEscCodeCSIu('Y', '5') .. "\<C-B>\"\<CR>", 'Lx!')
+ call assert_equal("\"\<Esc>[89;5u", @:)
+
+ call setline(1, '')
+ call feedkeys(':' .. GetEscCodeCSI27('V', '6') .. GetEscCodeCSI27('Y', '5') .. "\<C-B>\"\<CR>", 'Lx!')
+ call assert_equal("\"\<Esc>[27;5;89~", @:)
+
+ set timeoutlen&
+endfunc
+
+func Test_mapping_esc()
+ set timeoutlen=10
+
+ new
+ nnoremap <Up> iHello<Esc>
+ nnoremap <Esc> <Nop>
+
+ call feedkeys(substitute(&t_ku, '\*', '', 'g'), 'Lx!')
+ call assert_equal("Hello", getline(1))
+
+ bwipe!
+ nunmap <Up>
+ nunmap <Esc>
+ set timeoutlen&
+endfunc
+
+" Test for translation of special key codes (<xF1>, <xF2>, etc.)
+func Test_Keycode_Translation()
+ let keycodes = [
+ \ ["<xUp>", "<Up>"],
+ \ ["<xDown>", "<Down>"],
+ \ ["<xLeft>", "<Left>"],
+ \ ["<xRight>", "<Right>"],
+ \ ["<xHome>", "<Home>"],
+ \ ["<xEnd>", "<End>"],
+ \ ["<zHome>", "<Home>"],
+ \ ["<zEnd>", "<End>"],
+ \ ["<xF1>", "<F1>"],
+ \ ["<xF2>", "<F2>"],
+ \ ["<xF3>", "<F3>"],
+ \ ["<xF4>", "<F4>"],
+ \ ["<S-xF1>", "<S-F1>"],
+ \ ["<S-xF2>", "<S-F2>"],
+ \ ["<S-xF3>", "<S-F3>"],
+ \ ["<S-xF4>", "<S-F4>"]]
+ for [k1, k2] in keycodes
+ exe "nnoremap " .. k1 .. " 2wx"
+ call assert_true(maparg(k1, 'n', 0, 1).lhs == k2)
+ exe "nunmap " .. k1
+ endfor
+endfunc
+
+" Test for terminal keycodes that doesn't have termcap entries
+func Test_special_term_keycodes()
+ new
+ " Test for <xHome>, <S-xHome> and <C-xHome>
+ " send <K_SPECIAL> <KS_EXTRA> keycode
+ call feedkeys("i\<C-K>\x80\xfd\x3f\n", 'xt')
+ " send <K_SPECIAL> <KS_MODIFIER> bitmap <K_SPECIAL> <KS_EXTRA> keycode
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3f\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3f\n", 'xt')
+ " Test for <xEnd>, <S-xEnd> and <C-xEnd>
+ call feedkeys("i\<C-K>\x80\xfd\x3d\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3d\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3d\n", 'xt')
+ " Test for <zHome>, <S-zHome> and <C-zHome>
+ call feedkeys("i\<C-K>\x80\xfd\x40\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x40\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x40\n", 'xt')
+ " Test for <zEnd>, <S-zEnd> and <C-zEnd>
+ call feedkeys("i\<C-K>\x80\xfd\x3e\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3e\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3e\n", 'xt')
+ " Test for <xUp>, <xDown>, <xLeft> and <xRight>
+ call feedkeys("i\<C-K>\x80\xfd\x41\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfd\x42\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfd\x43\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfd\x44\n", 'xt')
+ call assert_equal(['<Home>', '<S-Home>', '<C-Home>',
+ \ '<End>', '<S-End>', '<C-End>',
+ \ '<Home>', '<S-Home>', '<C-Home>',
+ \ '<End>', '<S-End>', '<C-End>',
+ \ '<Up>', '<Down>', '<Left>', '<Right>', ''], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_home_key_works()
+ " The '@' character in K_HOME must only match "1" when followed by ";",
+ " otherwise this code for Home is not recognized: "<Esc>[1~"
+ " Set termcap values like "xterm" uses them. Except using F2 for xHome,
+ " because that termcap entry can't be set here.
+ let save_K1 = exists('&t_K1') ? &t_K1 : ''
+ let save_kh = exists('&t_kh') ? &t_kh : ''
+ let save_k2 = exists('&t_k2') ? &t_k2 : ''
+ let save_k3 = exists('&t_k3') ? &t_k3 : ''
+ let save_end = exists('&t_@7') ? &t_@7 : ''
+
+ let &t_K1 = "\<Esc>[1;*~" " <kHome>
+ let &t_kh = "\<Esc>[@;*H" " <Home>
+ let &t_k2 = "\<Esc>O*H" " use <F2> for <xHome>
+ let &t_k3 = "\<Esc>[7;*~" " use <F3> for <zHome>
+ let &t_@7 = "\<Esc>[@;*F" " <End>
+
+ new
+ call feedkeys("i\<C-K>\<Esc>OH\n\<Esc>", 'tx')
+ call feedkeys("i\<C-K>\<Esc>[1~\n\<Esc>", 'tx')
+ call assert_equal([
+ \ '<F2>',
+ \ '<kHome>',
+ \ ''], getline(1, '$'))
+
+ bwipe!
+ let &t_K1 = save_K1
+ let &t_kh = save_kh
+ let &t_k2 = save_k2
+ let &t_k3 = save_k3
+ let &t_@7 = save_end
+endfunc
+
+func Test_terminal_builtin_without_gui()
+ CheckNotMSWindows
+
+ " builtin_gui should not be output by :set term=xxx
+ let output = systemlist("TERM=dumb " .. v:progpath .. " --clean -c ':set t_ti= t_te=' -c 'set term=xxx' -c ':q!'")
+ redraw!
+ call map(output, {_, val -> trim(val)})
+ call assert_equal(-1, index(output, 'builtin_gui'))
+ call assert_notequal(-1, index(output, 'builtin_dumb'))
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_termencoding.vim b/src/testdir/test_termencoding.vim
new file mode 100644
index 0000000..de812ce
--- /dev/null
+++ b/src/testdir/test_termencoding.vim
@@ -0,0 +1,38 @@
+" Test for setting 'encoding' to something else than the terminal uses, then
+" setting 'termencoding' to make it work.
+
+" This only works with "iconv".
+if !has('iconv')
+ throw 'Skipped: iconv feature missing'
+endif
+
+source screendump.vim
+if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+endif
+
+" This Vim is running with 'encoding' "utf-8", the Vim in the terminal is
+" running with 'encoding' "euc-jp". We need to make sure the text is in the
+" right encoding, this is a bit tricky.
+func Test_termencoding_euc_jp()
+ new
+ call setline(1, 'E89: ãƒãƒƒãƒ•ã‚¡ %ld ã®å¤‰æ›´ã¯ä¿å­˜ã•ã‚Œã¦ã„ã¾ã›ã‚“ (! ã§å¤‰æ›´ã‚’破棄)')
+ write ++enc=euc-jp Xeuc_jp.txt
+ quit
+
+ call writefile([
+ \ 'set encoding=euc-jp',
+ \ 'set termencoding=utf-8',
+ \ 'scriptencoding utf-8',
+ \ 'exe "normal aE83: ãƒãƒƒãƒ•ã‚¡ã‚’作æˆã§ããªã„ã®ã§ã€ä»–ã®ã‚’使用ã—ã¾ã™...\<Esc>"',
+ \ 'split Xeuc_jp.txt',
+ \ ], 'XTest_tenc_euc_jp', 'D')
+ let buf = RunVimInTerminal('-S XTest_tenc_euc_jp', {'rows': 10})
+ call VerifyScreenDump(buf, 'Test_tenc_euc_jp_01', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xeuc_jp.txt')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim
new file mode 100644
index 0000000..b0c29ec
--- /dev/null
+++ b/src/testdir/test_terminal.vim
@@ -0,0 +1,2353 @@
+" Tests for the terminal window.
+" This is split in two, because it can take a lot of time.
+" See test_terminal2.vim and test_terminal3.vim for further tests.
+
+source check.vim
+CheckFeature terminal
+
+source shared.vim
+source screendump.vim
+source mouse.vim
+source term_util.vim
+
+let s:python = PythonProg()
+let $PROMPT_COMMAND=''
+
+func Test_terminal_basic()
+ call test_override('vterm_title', 1)
+ au TerminalOpen * let b:done = 'yes'
+ let buf = Run_shell_in_terminal({})
+
+ call assert_equal('t', mode())
+ call assert_equal('yes', b:done)
+ call assert_match('%aR[^\n]*running]', execute('ls'))
+ call assert_match('%aR[^\n]*running]', execute('ls R'))
+ call assert_notmatch('%[^\n]*running]', execute('ls F'))
+ call assert_notmatch('%[^\n]*running]', execute('ls ?'))
+ call assert_fails('set modifiable', 'E946:')
+
+ call StopShellInTerminal(buf)
+ call assert_equal('n', mode())
+ call assert_match('%aF[^\n]*finished]', execute('ls'))
+ call assert_match('%aF[^\n]*finished]', execute('ls F'))
+ call assert_notmatch('%[^\n]*finished]', execute('ls R'))
+ call assert_notmatch('%[^\n]*finished]', execute('ls ?'))
+
+ " closing window wipes out the terminal buffer a with finished job
+ close
+ call assert_equal("", bufname(buf))
+
+ au! TerminalOpen
+ call test_override('ALL', 0)
+ unlet g:job
+endfunc
+
+func Test_terminal_no_name()
+ let buf = Run_shell_in_terminal({})
+ call assert_match('^!', bufname(buf))
+ 0file
+ call assert_equal("", bufname(buf))
+ call assert_match('\[No Name\]', execute('file'))
+ call StopShellInTerminal(buf)
+endfunc
+
+func Test_terminal_TerminalWinOpen()
+ au TerminalWinOpen * let b:done = 'yes'
+ let buf = Run_shell_in_terminal({})
+ call assert_equal('yes', b:done)
+ call StopShellInTerminal(buf)
+ " closing window wipes out the terminal buffer with the finished job
+ close
+
+ if has("unix")
+ terminal ++hidden ++open sleep 1
+ sleep 1
+ call assert_fails("echo b:done", 'E121:')
+ endif
+
+ au! TerminalWinOpen
+endfunc
+
+func Test_terminal_make_change()
+ let buf = Run_shell_in_terminal({})
+ call StopShellInTerminal(buf)
+
+ setlocal modifiable
+ exe "normal Axxx\<Esc>"
+ call assert_fails(buf . 'bwipe', 'E89:')
+ undo
+
+ exe buf . 'bwipe'
+ unlet g:job
+endfunc
+
+func Test_terminal_paste_register()
+ let @" = "text to paste"
+
+ let buf = Run_shell_in_terminal({})
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+
+ call feedkeys("echo \<C-W>\"\" \<C-W>\"=37 + 5\<CR>\<CR>", 'xt')
+ call WaitForAssert({-> assert_match("echo text to paste 42$", getline(1))})
+ call WaitForAssert({-> assert_equal('text to paste 42', 2->getline())})
+
+ exe buf . 'bwipe!'
+ unlet g:job
+endfunc
+
+func Test_terminal_unload_buffer()
+ let buf = Run_shell_in_terminal({})
+ call assert_fails(buf . 'bunload', 'E948:')
+ exe buf . 'bunload!'
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+endfunc
+
+func Test_terminal_wipe_buffer()
+ let buf = Run_shell_in_terminal({})
+ call assert_fails(buf . 'bwipe', 'E948:')
+ exe buf . 'bwipe!'
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+endfunc
+
+" Test that using ':confirm bwipe' on terminal works
+func Test_terminal_confirm_wipe_buffer()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf = Run_shell_in_terminal({})
+ call assert_fails(buf . 'bwipe', 'E948:')
+ call feedkeys('n', 'L')
+ call assert_fails('confirm ' .. buf .. 'bwipe', 'E517:')
+ call assert_equal(buf, bufnr())
+ call assert_equal(1, &modified)
+ call feedkeys('y', 'L')
+ exe 'confirm ' .. buf .. 'bwipe'
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+endfunc
+
+" Test that using :b! will hide the terminal
+func Test_terminal_goto_buffer()
+ let buf_mod = bufnr()
+ let buf_term = Run_shell_in_terminal({})
+ call assert_equal(buf_term, bufnr())
+ call assert_fails(buf_mod . 'b', 'E948:')
+ exe buf_mod . 'b!'
+ call assert_equal(buf_mod, bufnr())
+ call assert_equal('run', job_status(g:job))
+ call assert_notequal('', bufname(buf_term))
+ exec buf_mod .. 'bwipe!'
+ exec buf_term .. 'bwipe!'
+
+ unlet g:job
+endfunc
+
+" Test that using ':confirm :b' will kill terminal
+func Test_terminal_confirm_goto_buffer()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf_mod = bufnr()
+ let buf_term = Run_shell_in_terminal({})
+ call feedkeys('n', 'L')
+ exe 'confirm ' .. buf_mod .. 'b'
+ call assert_equal(buf_term, bufnr())
+ call feedkeys('y', 'L')
+ exec 'confirm ' .. buf_mod .. 'b'
+ call assert_equal(buf_mod, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf_term))
+ exec buf_mod .. 'bwipe!'
+
+ unlet g:job
+endfunc
+
+" Test that using :close! will hide the terminal
+func Test_terminal_close_win()
+ let buf = Run_shell_in_terminal({})
+ call assert_equal(buf, bufnr())
+ call assert_fails('close', 'E948:')
+ close!
+ call assert_notequal(buf, bufnr())
+ call assert_equal('run', job_status(g:job))
+ call assert_notequal('', bufname(buf))
+ exec buf .. 'bwipe!'
+
+ unlet g:job
+endfunc
+
+" Test that using ':confirm close' will kill terminal
+func Test_terminal_confirm_close_win()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf = Run_shell_in_terminal({})
+ call feedkeys('n', 'L')
+ confirm close
+ call assert_equal(buf, bufnr())
+ call feedkeys('y', 'L')
+ confirm close
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+endfunc
+
+" Test that using :quit! will kill the terminal
+func Test_terminal_quit()
+ let buf = Run_shell_in_terminal({})
+ call assert_equal(buf, bufnr())
+ call assert_fails('quit', 'E948:')
+ quit!
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+endfunc
+
+" Test that using ':confirm quit' will kill terminal
+func Test_terminal_confirm_quit()
+ CheckUnix
+ CheckNotGui
+ CheckFeature dialog_con
+ let buf = Run_shell_in_terminal({})
+ call feedkeys('n', 'L')
+ confirm quit
+ call assert_equal(buf, bufnr())
+ call feedkeys('y', 'L')
+ confirm quit
+ call assert_notequal(buf, bufnr())
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+
+ unlet g:job
+endfunc
+
+" Test :q or :next
+
+func Test_terminal_split_quit()
+ let buf = Run_shell_in_terminal({})
+ split
+ quit!
+ call TermWait(buf)
+ sleep 50m
+ call assert_equal('run', job_status(g:job))
+
+ quit!
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+
+ call assert_equal("", bufname(buf))
+ unlet g:job
+endfunc
+
+func Test_terminal_hide_buffer_job_running()
+ let buf = Run_shell_in_terminal({})
+ setlocal bufhidden=hide
+ quit
+ for nr in range(1, winnr('$'))
+ call assert_notequal(winbufnr(nr), buf)
+ endfor
+ call assert_true(bufloaded(buf))
+ call assert_true(buflisted(buf))
+
+ exe 'split ' . buf . 'buf'
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+
+ unlet g:job
+endfunc
+
+func Test_terminal_hide_buffer_job_finished()
+ term echo hello
+ let buf = bufnr()
+ call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
+
+ call assert_true(bufloaded(buf))
+ call assert_true(buflisted(buf))
+
+ " Test :hide
+ hide
+ call assert_true(bufloaded(buf))
+ call assert_true(buflisted(buf))
+ split
+ exe buf .. 'buf'
+ call assert_equal(buf, bufnr())
+
+ " Test bufhidden, which exercises a different code path
+ setlocal bufhidden=hide
+ edit Xasdfasdf
+ call assert_true(bufloaded(buf))
+ call assert_true(buflisted(buf))
+ exe buf .. 'buf'
+ call assert_equal(buf, bufnr())
+ setlocal bufhidden=
+
+ edit Xasdfasdf
+ call assert_false(bufloaded(buf))
+ call assert_false(buflisted(buf))
+ bwipe Xasdfasdf
+endfunc
+
+func Test_terminal_rename_buffer()
+ let cmd = Get_cat_123_cmd()
+ let buf = term_start(cmd, {'term_name': 'foo'})
+ call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
+ call assert_equal('foo', bufname())
+ call assert_match('foo.*finished', execute('ls'))
+ file bar
+ call assert_equal('bar', bufname())
+ call assert_match('bar.*finished', execute('ls'))
+ exe 'bwipe! ' .. buf
+endfunc
+
+func s:Nasty_exit_cb(job, st)
+ exe g:buf . 'bwipe!'
+ let g:buf = 0
+endfunc
+
+func Get_cat_123_cmd()
+ if has('win32')
+ if !has('conpty')
+ return 'cmd /c "cls && color 2 && echo 123"'
+ else
+ " When clearing twice, extra sequence is not output.
+ return 'cmd /c "cls && cls && color 2 && echo 123"'
+ endif
+ else
+ call writefile(["\<Esc>[32m123"], 'Xtext')
+ return "cat Xtext"
+ endif
+endfunc
+
+func Test_terminal_nasty_cb()
+ let cmd = Get_cat_123_cmd()
+ let g:buf = term_start(cmd, {'exit_cb': function('s:Nasty_exit_cb')})
+ let g:job = term_getjob(g:buf)
+
+ call WaitForAssert({-> assert_equal("dead", job_status(g:job))})
+ call WaitForAssert({-> assert_equal(0, g:buf)})
+ unlet g:job
+ unlet g:buf
+ call delete('Xtext')
+endfunc
+
+func Check_123(buf)
+ let l = term_scrape(a:buf, 0)
+ call assert_true(len(l) == 0)
+ let l = term_scrape(a:buf, 999)
+ call assert_true(len(l) == 0)
+ let l = a:buf->term_scrape(1)
+ call assert_true(len(l) > 0)
+ call assert_equal('1', l[0].chars)
+ call assert_equal('2', l[1].chars)
+ call assert_equal('3', l[2].chars)
+ call assert_equal('#00e000', l[0].fg)
+ call assert_equal(0, term_getattr(l[0].attr, 'bold'))
+ call assert_equal(0, l[0].attr->term_getattr('italic'))
+ if has('win32')
+ " On Windows 'background' always defaults to dark, even though the terminal
+ " may use a light background. Therefore accept both white and black.
+ call assert_match('#ffffff\|#000000', l[0].bg)
+ else
+ if &background == 'light'
+ call assert_equal('#ffffff', l[0].bg)
+ else
+ call assert_equal('#000000', l[0].bg)
+ endif
+ endif
+
+ let l = term_getline(a:buf, -1)
+ call assert_equal('', l)
+ let l = term_getline(a:buf, 0)
+ call assert_equal('', l)
+ let l = term_getline(a:buf, 999)
+ call assert_equal('', l)
+ let l = term_getline(a:buf, 1)
+ call assert_equal('123', l)
+endfunc
+
+func Test_terminal_scrape_123()
+ let cmd = Get_cat_123_cmd()
+ let buf = term_start(cmd)
+
+ let termlist = term_list()
+ call assert_equal(1, len(termlist))
+ call assert_equal(buf, termlist[0])
+
+ " Nothing happens with invalid buffer number
+ call term_wait(1234)
+
+ call TermWait(buf)
+ " On MS-Windows we first get a startup message of two lines, wait for the
+ " "cls" to happen, after that we have one line with three characters.
+ call WaitForAssert({-> assert_equal(3, len(term_scrape(buf, 1)))})
+ call Check_123(buf)
+
+ " Must still work after the job ended.
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call TermWait(buf)
+ call Check_123(buf)
+
+ exe buf . 'bwipe'
+ call delete('Xtext')
+endfunc
+
+func Test_terminal_scrape_multibyte()
+ call writefile(["léttã¾rs"], 'Xtext', 'D')
+ if has('win32')
+ " Run cmd with UTF-8 codepage to make the type command print the expected
+ " multibyte characters.
+ let buf = term_start("cmd /K chcp 65001")
+ call term_sendkeys(buf, "type Xtext\<CR>")
+ eval buf->term_sendkeys("exit\<CR>")
+ let line = 4
+ else
+ let buf = term_start("cat Xtext")
+ let line = 1
+ endif
+
+ call WaitFor({-> len(term_scrape(buf, line)) >= 7 && term_scrape(buf, line)[0].chars == "l"})
+ let l = term_scrape(buf, line)
+ call assert_true(len(l) >= 7)
+ call assert_equal('l', l[0].chars)
+ call assert_equal('é', l[1].chars)
+ call assert_equal(1, l[1].width)
+ call assert_equal('t', l[2].chars)
+ call assert_equal('t', l[3].chars)
+ call assert_equal('ã¾', l[4].chars)
+ call assert_equal(2, l[4].width)
+ call assert_equal('r', l[5].chars)
+ call assert_equal('s', l[6].chars)
+
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call TermWait(buf)
+
+ exe buf . 'bwipe'
+endfunc
+
+func Test_terminal_one_column()
+ " This creates a terminal, displays a double-wide character and makes the
+ " window one column wide. This used to cause a crash.
+ let width = &columns
+ botright vert term
+ let buf = bufnr('$')
+ call TermWait(buf, 100)
+ exe "set columns=" .. (width / 2)
+ redraw
+ call term_sendkeys(buf, "ã‚­")
+ call TermWait(buf, 10)
+ exe "set columns=" .. width
+ exe buf . 'bwipe!'
+endfunc
+
+func Test_terminal_scroll()
+ call writefile(range(1, 200), 'Xtext', 'D')
+ if has('win32')
+ let cmd = 'cmd /c "type Xtext"'
+ else
+ let cmd = "cat Xtext"
+ endif
+ let buf = term_start(cmd)
+
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call TermWait(buf)
+
+ " wait until the scrolling stops
+ while 1
+ let scrolled = buf->term_getscrolled()
+ sleep 20m
+ if scrolled == buf->term_getscrolled()
+ break
+ endif
+ endwhile
+
+ call assert_equal('1', getline(1))
+ call assert_equal('1', term_getline(buf, 1 - scrolled))
+ call assert_equal('49', getline(49))
+ call assert_equal('49', term_getline(buf, 49 - scrolled))
+ call assert_equal('200', getline(200))
+ call assert_equal('200', term_getline(buf, 200 - scrolled))
+
+ exe buf . 'bwipe'
+endfunc
+
+func Test_terminal_scrollback()
+ let buf = Run_shell_in_terminal({'term_rows': 15})
+ set termwinscroll=100
+ call writefile(range(150), 'Xtext', 'D')
+ if has('win32')
+ call term_sendkeys(buf, "type Xtext\<CR>")
+ else
+ call term_sendkeys(buf, "cat Xtext\<CR>")
+ endif
+ let rows = term_getsize(buf)[0]
+ " On MS-Windows there is an empty line, check both last line and above it.
+ call WaitForAssert({-> assert_match( '149', term_getline(buf, rows - 1) . term_getline(buf, rows - 2))})
+ let lines = line('$')
+ call assert_inrange(91, 100, lines)
+
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+ set termwinscroll&
+endfunc
+
+func Test_terminal_postponed_scrollback()
+ " tail -f only works on Unix
+ CheckUnix
+
+ call writefile(range(50), 'Xtext', 'D')
+ call writefile([
+ \ 'set shell=/bin/sh noruler',
+ \ 'terminal',
+ \ 'sleep 200m',
+ \ 'call feedkeys("tail -n 100 -f Xtext\<CR>", "xt")',
+ \ 'sleep 100m',
+ \ 'call feedkeys("\<C-W>N", "xt")',
+ \ ], 'XTest_postponed', 'D')
+ let buf = RunVimInTerminal('-S XTest_postponed', {})
+ " Check that the Xtext lines are displayed and in Terminal-Normal mode
+ call VerifyScreenDump(buf, 'Test_terminal_scrollback_1', {})
+
+ silent !echo 'one more line' >>Xtext
+ " Screen will not change, move cursor to get a different dump
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_terminal_scrollback_2', {})
+
+ " Back to Terminal-Job mode, text will scroll and show the extra line.
+ call term_sendkeys(buf, "a")
+ call VerifyScreenDump(buf, 'Test_terminal_scrollback_3', {})
+
+ " stop "tail -f"
+ call term_sendkeys(buf, "\<C-C>")
+ call TermWait(buf, 25)
+ " stop shell
+ call term_sendkeys(buf, "exit\<CR>")
+ call TermWait(buf, 50)
+ " close terminal window
+ let tsk_ret = term_sendkeys(buf, ":q\<CR>")
+
+ " check type of term_sendkeys() return value
+ echo type(tsk_ret)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Run diff on two dumps with different size.
+func Test_terminal_dumpdiff_size()
+ call assert_equal(1, winnr('$'))
+ call term_dumpdiff('dumps/Test_incsearch_search_01.dump', 'dumps/Test_popup_command_01.dump')
+ call assert_equal(2, winnr('$'))
+ call assert_match('Test_incsearch_search_01.dump', getline(10))
+ call assert_match(' +++++$', getline(11))
+ call assert_match('Test_popup_command_01.dump', getline(31))
+ call assert_equal(repeat('+', 75), getline(30))
+ quit
+endfunc
+
+func Test_terminal_size()
+ let cmd = Get_cat_123_cmd()
+
+ exe 'terminal ++rows=5 ' . cmd
+ let size = term_getsize('')
+ bwipe!
+ call assert_equal(5, size[0])
+
+ call term_start(cmd, {'term_rows': 6})
+ let size = term_getsize('')
+ bwipe!
+ call assert_equal(6, size[0])
+
+ vsplit
+ exe 'terminal ++rows=5 ++cols=33 ' . cmd
+ call assert_equal([5, 33], ''->term_getsize())
+
+ call term_setsize('', 6, 0)
+ call assert_equal([6, 33], term_getsize(''))
+
+ eval ''->term_setsize(0, 35)
+ call assert_equal([6, 35], term_getsize(''))
+
+ call term_setsize('', 7, 30)
+ call assert_equal([7, 30], term_getsize(''))
+
+ bwipe!
+ call assert_fails("call term_setsize('', 7, 30)", "E955:")
+
+ call term_start(cmd, {'term_rows': 6, 'term_cols': 36})
+ let size = term_getsize('')
+ bwipe!
+ call assert_equal([6, 36], size)
+
+ exe 'vertical terminal ++cols=20 ' . cmd
+ let size = term_getsize('')
+ bwipe!
+ call assert_equal(20, size[1])
+
+ eval cmd->term_start({'vertical': 1, 'term_cols': 26})
+ let size = term_getsize('')
+ bwipe!
+ call assert_equal(26, size[1])
+
+ split
+ exe 'vertical terminal ++rows=6 ++cols=20 ' . cmd
+ let size = term_getsize('')
+ bwipe!
+ call assert_equal([6, 20], size)
+
+ call term_start(cmd, {'vertical': 1, 'term_rows': 7, 'term_cols': 27})
+ let size = term_getsize('')
+ bwipe!
+ call assert_equal([7, 27], size)
+
+ call assert_fails("call term_start(cmd, {'term_rows': -1})", 'E475:')
+ call assert_fails("call term_start(cmd, {'term_rows': 1001})", 'E475:')
+ call assert_fails("call term_start(cmd, {'term_rows': 10.0})", 'E805:')
+
+ call delete('Xtext')
+endfunc
+
+func Test_terminal_zero_height()
+ split
+ wincmd j
+ anoremenu 1.1 WinBar.test :
+ terminal ++curwin
+ wincmd k
+ wincmd _
+ redraw
+
+ call term_sendkeys(bufnr(), "exit\r")
+ bwipe!
+endfunc
+
+func Test_terminal_curwin()
+ let cmd = Get_cat_123_cmd()
+ call assert_equal(1, winnr('$'))
+
+ split Xdummy
+ call setline(1, 'dummy')
+ write
+ call assert_equal(1, getbufinfo('Xdummy')[0].loaded)
+ exe 'terminal ++curwin ' . cmd
+ call assert_equal(2, winnr('$'))
+ call assert_equal(0, getbufinfo('Xdummy')[0].loaded)
+ bwipe!
+
+ split Xdummy
+ call term_start(cmd, {'curwin': 1})
+ call assert_equal(2, winnr('$'))
+ bwipe!
+
+ split Xdummy
+ call setline(1, 'change')
+ call assert_fails('terminal ++curwin ' . cmd, 'E37:')
+ call assert_equal(2, winnr('$'))
+ exe 'terminal! ++curwin ' . cmd
+ call assert_equal(2, winnr('$'))
+ bwipe!
+
+ split Xdummy
+ call setline(1, 'change')
+ call assert_fails("call term_start(cmd, {'curwin': 1})", 'E37:')
+ call assert_equal(2, winnr('$'))
+ bwipe!
+
+ split Xdummy
+ bwipe!
+ call delete('Xtext')
+ call delete('Xdummy')
+endfunc
+
+func s:get_sleep_cmd()
+ if s:python != ''
+ let cmd = s:python . " test_short_sleep.py"
+ " 500 was not enough for Travis
+ let waittime = 900
+ else
+ echo 'This will take five seconds...'
+ let waittime = 2000
+ if has('win32')
+ let cmd = $windir . '\system32\timeout.exe 1'
+ else
+ let cmd = 'sleep 1'
+ endif
+ endif
+ return [cmd, waittime]
+endfunc
+
+func Test_terminal_finish_open_close()
+ call assert_equal(1, winnr('$'))
+
+ let [cmd, waittime] = s:get_sleep_cmd()
+
+ " shell terminal closes automatically
+ terminal
+ let buf = bufnr('%')
+ call assert_equal(2, winnr('$'))
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+ call StopShellInTerminal(buf)
+ call WaitForAssert({-> assert_equal(1, winnr('$'))}, waittime)
+
+ " shell terminal that does not close automatically
+ terminal ++noclose
+ let buf = bufnr('%')
+ call assert_equal(2, winnr('$'))
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+ call StopShellInTerminal(buf)
+ call assert_equal(2, winnr('$'))
+ quit
+ call assert_equal(1, winnr('$'))
+
+ exe 'terminal ++close ' . cmd
+ call assert_equal(2, winnr('$'))
+ wincmd p
+ call WaitForAssert({-> assert_equal(1, winnr('$'))}, waittime)
+
+ call term_start(cmd, {'term_finish': 'close'})
+ call assert_equal(2, winnr('$'))
+ wincmd p
+ call WaitForAssert({-> assert_equal(1, winnr('$'))}, waittime)
+ call assert_equal(1, winnr('$'))
+
+ exe 'terminal ++open ' . cmd
+ close!
+ call WaitForAssert({-> assert_equal(2, winnr('$'))}, waittime)
+ bwipe
+
+ call term_start(cmd, {'term_finish': 'open'})
+ close!
+ call WaitForAssert({-> assert_equal(2, winnr('$'))}, waittime)
+ bwipe
+
+ exe 'terminal ++hidden ++open ' . cmd
+ call assert_equal(1, winnr('$'))
+ call WaitForAssert({-> assert_equal(2, winnr('$'))}, waittime)
+ bwipe
+
+ call term_start(cmd, {'term_finish': 'open', 'hidden': 1})
+ call assert_equal(1, winnr('$'))
+ call WaitForAssert({-> assert_equal(2, winnr('$'))}, waittime)
+ bwipe
+
+ call assert_fails("call term_start(cmd, {'term_opencmd': 'open'})", 'E475:')
+ call assert_fails("call term_start(cmd, {'term_opencmd': 'split %x'})", 'E475:')
+ call assert_fails("call term_start(cmd, {'term_opencmd': 'split %d and %s'})", 'E475:')
+ call assert_fails("call term_start(cmd, {'term_opencmd': 'split % and %d'})", 'E475:')
+
+ call term_start(cmd, {'term_finish': 'open', 'term_opencmd': '4split | buffer %d | let g:result = "opened the buffer in a window"'})
+ close!
+ call WaitForAssert({-> assert_equal(2, winnr('$'))}, waittime)
+ call assert_equal(4, winheight(0))
+ call assert_equal('opened the buffer in a window', g:result)
+ unlet g:result
+ bwipe
+endfunc
+
+func Test_terminal_cwd()
+ if has('win32')
+ let cmd = 'cmd /c cd'
+ else
+ CheckExecutable pwd
+ let cmd = 'pwd'
+ endif
+ call mkdir('Xtermdir')
+ let buf = term_start(cmd, {'cwd': 'Xtermdir'})
+ " if the path is very long it may be split over two lines, join them
+ " together
+ call WaitForAssert({-> assert_equal('Xtermdir', fnamemodify(getline(1) .. getline(2), ":t"))})
+
+ exe buf . 'bwipe'
+ call delete('Xtermdir', 'rf')
+endfunc
+
+func Test_terminal_cwd_failure()
+ " Case 1: Provided directory is not actually a directory. Attempt to make
+ " the file executable as well.
+ call writefile([], 'Xtcfile', 'D')
+ call setfperm('Xtcfile', 'rwx------')
+ call assert_fails("call term_start(&shell, {'cwd': 'Xtcfile'})", 'E475:')
+
+ " Case 2: Directory does not exist.
+ call assert_fails("call term_start(&shell, {'cwd': 'Xdir'})", 'E475:')
+
+ " Case 3: Directory exists but is not accessible.
+ " Skip this for root, it will be accessible anyway.
+ if !IsRoot()
+ call mkdir('XdirNoAccess', '', '0600')
+ " return early if the directory permissions could not be set properly
+ if getfperm('XdirNoAccess')[2] == 'x'
+ call delete('XdirNoAccess', 'rf')
+ return
+ endif
+ call assert_fails("call term_start(&shell, {'cwd': 'XdirNoAccess'})", 'E475:')
+ call delete('XdirNoAccess', 'rf')
+ endif
+endfunc
+
+func Test_terminal_servername()
+ CheckFeature clientserver
+ call s:test_environment("VIM_SERVERNAME", v:servername)
+endfunc
+
+func Test_terminal_version()
+ call s:test_environment("VIM_TERMINAL", string(v:version))
+endfunc
+
+func s:test_environment(name, value)
+ let buf = Run_shell_in_terminal({})
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+ if has('win32')
+ call term_sendkeys(buf, "echo %" . a:name . "%\r")
+ else
+ call term_sendkeys(buf, "echo $" . a:name . "\r")
+ endif
+ call TermWait(buf)
+ call StopShellInTerminal(buf)
+ call WaitForAssert({-> assert_equal(a:value, getline(2))})
+
+ exe buf . 'bwipe'
+ unlet buf
+endfunc
+
+func Test_terminal_env()
+ let buf = Run_shell_in_terminal({'env': {'TESTENV': 'correct'}})
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+ if has('win32')
+ call term_sendkeys(buf, "echo %TESTENV%\r")
+ else
+ call term_sendkeys(buf, "echo $TESTENV\r")
+ endif
+ eval buf->TermWait()
+ call StopShellInTerminal(buf)
+ call WaitForAssert({-> assert_equal('correct', getline(2))})
+
+ exe buf . 'bwipe'
+endfunc
+
+func Test_terminal_list_args()
+ let buf = term_start([&shell, &shellcmdflag, 'echo "123"'])
+ call assert_fails(buf . 'bwipe', 'E948:')
+ exe buf . 'bwipe!'
+ call assert_equal("", bufname(buf))
+endfunction
+
+func Test_terminal_noblock()
+ let g:test_is_flaky = 1
+ let buf = term_start(&shell)
+ " Starting a terminal can be slow, esp. on busy CI machines.
+ let wait_time = 7500
+ let letters = 'abcdefghijklmnopqrstuvwxyz'
+ if has('bsd') || has('mac') || has('sun')
+ " The shell or something else has a problem dealing with more than 1000
+ " characters at the same time. It's very slow too.
+ let len = 1000
+ let wait_time = 15000
+ let letters = 'abcdefghijklm'
+ " NPFS is used in Windows, nonblocking mode does not work properly.
+ elseif has('win32')
+ let len = 1
+ else
+ let len = 5000
+ endif
+
+ " Send a lot of text lines, should be buffered properly.
+ for c in split(letters, '\zs')
+ call term_sendkeys(buf, 'echo ' . repeat(c, len) . "\<cr>")
+ endfor
+ call term_sendkeys(buf, "echo done\<cr>")
+
+ " On MS-Windows there is an extra empty line below "done". Find "done" in
+ " the last-but-one or the last-but-two line.
+ let lnum = term_getsize(buf)[0] - 1
+ call WaitForAssert({-> assert_match('done', term_getline(buf, lnum - 1) .. '//' .. term_getline(buf, lnum))}, wait_time)
+ let line = term_getline(buf, lnum)
+ if line !~ 'done'
+ let line = term_getline(buf, lnum - 1)
+ endif
+ call assert_match('done', line)
+
+ let g:job = term_getjob(buf)
+ call StopShellInTerminal(buf)
+ unlet g:job
+ bwipe
+endfunc
+
+func Test_terminal_write_stdin()
+ " TODO: enable once writing to stdin works on MS-Windows
+ CheckNotMSWindows
+ CheckExecutable wc
+ let g:test_is_flaky = 1
+
+ call setline(1, ['one', 'two', 'three'])
+ %term wc
+ call WaitForAssert({-> assert_match('3', getline("$"))})
+ let nrs = split(getline('$'))
+ call assert_equal(['3', '3', '14'], nrs)
+ %bwipe!
+
+ call setline(1, ['one', 'two', 'three', 'four'])
+ 2,3term wc
+ call WaitForAssert({-> assert_match('2', getline("$"))})
+ let nrs = split(getline('$'))
+ call assert_equal(['2', '2', '10'], nrs)
+ %bwipe!
+endfunc
+
+func Test_terminal_eof_arg()
+ call CheckPython(s:python)
+ let g:test_is_flaky = 1
+
+ call setline(1, ['print("hello")'])
+ exe '1term ++eof=exit(123) ' .. s:python
+ " MS-Windows echoes the input, Unix doesn't.
+ if has('win32')
+ call WaitFor({-> getline('$') =~ 'exit(123)'})
+ call assert_equal('hello', getline(line('$') - 1))
+ else
+ call WaitFor({-> getline('$') =~ 'hello'})
+ call assert_equal('hello', getline('$'))
+ endif
+ call assert_equal(123, bufnr()->term_getjob()->job_info().exitval)
+ %bwipe!
+endfunc
+
+func Test_terminal_eof_arg_win32_ctrl_z()
+ CheckMSWindows
+ call CheckPython(s:python)
+ let g:test_is_flaky = 1
+
+ call setline(1, ['print("hello")'])
+ exe '1term ++eof=<C-Z> ' .. s:python
+ call WaitForAssert({-> assert_match('\^Z', getline(line('$') - 1))})
+ call assert_match('\^Z', getline(line('$') - 1))
+ %bwipe!
+endfunc
+
+func Test_terminal_duplicate_eof_arg()
+ call CheckPython(s:python)
+ let g:test_is_flaky = 1
+
+ " Check the last specified ++eof arg is used and does not leak memory.
+ new
+ call setline(1, ['print("hello")'])
+ exe '1term ++eof=<C-Z> ++eof=exit(123) ' .. s:python
+ " MS-Windows echoes the input, Unix doesn't.
+ if has('win32')
+ call WaitFor({-> getline('$') =~ 'exit(123)'})
+ call assert_equal('hello', getline(line('$') - 1))
+ else
+ call WaitFor({-> getline('$') =~ 'hello'})
+ call assert_equal('hello', getline('$'))
+ endif
+ call assert_equal(123, bufnr()->term_getjob()->job_info().exitval)
+ %bwipe!
+endfunc
+
+func Test_terminal_no_cmd()
+ let g:test_is_flaky = 1
+ let buf = term_start('NONE', {})
+ call assert_notequal(0, buf)
+
+ let pty = job_info(term_getjob(buf))['tty_out']
+ call assert_notequal('', pty)
+ if has('gui_running') && !has('win32')
+ " In the GUI job_start() doesn't work, it does not read from the pty.
+ call system('echo "look here" > ' . pty)
+ else
+ " Otherwise using a job works on all systems.
+ call job_start([&shell, &shellcmdflag, 'echo "look here" > ' . pty])
+ endif
+ call WaitForAssert({-> assert_match('look here', term_getline(buf, 1))})
+
+ bwipe!
+endfunc
+
+func Test_terminal_special_chars()
+ " this file name only works on Unix
+ CheckUnix
+
+ call mkdir('Xdir with spaces', 'R')
+ call writefile(['x'], 'Xdir with spaces/quoted"file')
+ term ls Xdir\ with\ spaces/quoted\"file
+ call WaitForAssert({-> assert_match('quoted"file', term_getline('', 1))})
+ " make sure the job has finished
+ call WaitForAssert({-> assert_match('finish', term_getstatus(bufnr()))})
+
+ bwipe
+endfunc
+
+func Test_terminal_wrong_options()
+ call assert_fails('call term_start(&shell, {
+ \ "in_io": "file",
+ \ "in_name": "xxx",
+ \ "out_io": "file",
+ \ "out_name": "xxx",
+ \ "err_io": "file",
+ \ "err_name": "xxx"
+ \ })', 'E474:')
+ call assert_fails('call term_start(&shell, {
+ \ "out_buf": bufnr("%")
+ \ })', 'E474:')
+ call assert_fails('call term_start(&shell, {
+ \ "err_buf": bufnr("%")
+ \ })', 'E474:')
+endfunc
+
+func Test_terminal_redir_file()
+ let g:test_is_flaky = 1
+ let cmd = Get_cat_123_cmd()
+ let buf = term_start(cmd, {'out_io': 'file', 'out_name': 'Xtrfile'})
+ call TermWait(buf)
+ " ConPTY may precede escape sequence. There are things that are not so.
+ if !has('conpty')
+ call WaitForAssert({-> assert_notequal(0, len(readfile("Xtrfile")))})
+ call assert_match('123', readfile('Xtrfile')[0])
+ endif
+ let g:job = term_getjob(buf)
+ call WaitForAssert({-> assert_equal("dead", job_status(g:job))})
+
+ if has('win32')
+ " On Windows we cannot delete a file being used by a process. When
+ " job_status() returns "dead", the process remains for a short time.
+ " Just wait for a moment.
+ sleep 50m
+ endif
+ call delete('Xtrfile')
+ bwipe
+
+ if has('unix')
+ call writefile(['one line'], 'Xtrfile', 'D')
+ let buf = term_start('cat', {'in_io': 'file', 'in_name': 'Xtrfile'})
+ call TermWait(buf)
+ call WaitForAssert({-> assert_equal('one line', term_getline(buf, 1))})
+ let g:job = term_getjob(buf)
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ bwipe
+ endif
+endfunc
+
+func TerminalTmap(remap)
+ let buf = Run_shell_in_terminal({})
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+ call assert_equal('t', mode())
+
+ if a:remap
+ tmap 123 456
+ else
+ tnoremap 123 456
+ endif
+ " don't use abcde, it's an existing command
+ tmap 456 abxde
+ call assert_equal('456', maparg('123', 't'))
+ call assert_equal('abxde', maparg('456', 't'))
+ call feedkeys("123", 'tx')
+ call WaitForAssert({-> assert_match('abxde\|456', term_getline(buf, term_getcursor(buf)[0]))})
+ let lnum = term_getcursor(buf)[0]
+ if a:remap
+ call assert_match('abxde', term_getline(buf, lnum))
+ else
+ call assert_match('456', term_getline(buf, lnum))
+ endif
+
+ call term_sendkeys(buf, "\r")
+ call StopShellInTerminal(buf)
+
+ tunmap 123
+ tunmap 456
+ call assert_equal('', maparg('123', 't'))
+ exe buf . 'bwipe'
+ unlet g:job
+endfunc
+
+func Test_terminal_tmap()
+ call TerminalTmap(1)
+ call TerminalTmap(0)
+endfunc
+
+func Test_terminal_wall()
+ let buf = Run_shell_in_terminal({})
+ wall
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+ unlet g:job
+endfunc
+
+func Test_terminal_wqall()
+ let buf = Run_shell_in_terminal({})
+ call assert_fails('wqall', 'E948:')
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+ unlet g:job
+endfunc
+
+func Test_terminal_composing_unicode()
+ let g:test_is_flaky = 1
+ let save_enc = &encoding
+ set encoding=utf-8
+
+ if has('win32')
+ let cmd = "cmd /K chcp 65001"
+ let lnum = [3, 6, 9]
+ else
+ let cmd = &shell
+ let lnum = [1, 3, 5]
+ endif
+
+ enew
+ let buf = term_start(cmd, {'curwin': 1})
+ let g:job = term_getjob(buf)
+ call WaitFor({-> term_getline(buf, 1) !=# ''}, 1000)
+
+ if has('win32')
+ call assert_equal('cmd', job_info(g:job).cmd[0])
+ else
+ call assert_equal(&shell, job_info(g:job).cmd[0])
+ endif
+
+ " ascii + composing
+ let txt = "a\u0308bc"
+ call term_sendkeys(buf, "echo " . txt)
+ call TermWait(buf, 25)
+ call assert_match("echo " . txt, term_getline(buf, lnum[0]))
+ call term_sendkeys(buf, "\<cr>")
+ call WaitForAssert({-> assert_equal(txt, term_getline(buf, lnum[0] + 1))}, 1000)
+ let l = term_scrape(buf, lnum[0] + 1)
+ call assert_equal("a\u0308", l[0].chars)
+ call assert_equal("b", l[1].chars)
+ call assert_equal("c", l[2].chars)
+
+ " multibyte + composing: ã‹ã‚™ãŽãã‚™ã’ã“ã‚™
+ let txt = "\u304b\u3099\u304e\u304f\u3099\u3052\u3053\u3099"
+ call term_sendkeys(buf, "echo " . txt)
+ call TermWait(buf, 25)
+ call assert_match("echo " . txt, term_getline(buf, lnum[1]))
+ call term_sendkeys(buf, "\<cr>")
+ call WaitForAssert({-> assert_equal(txt, term_getline(buf, lnum[1] + 1))}, 1000)
+ let l = term_scrape(buf, lnum[1] + 1)
+ call assert_equal("\u304b\u3099", l[0].chars)
+ call assert_equal(2, l[0].width)
+ call assert_equal("\u304e", l[1].chars)
+ call assert_equal(2, l[1].width)
+ call assert_equal("\u304f\u3099", l[2].chars)
+ call assert_equal(2, l[2].width)
+ call assert_equal("\u3052", l[3].chars)
+ call assert_equal(2, l[3].width)
+ call assert_equal("\u3053\u3099", l[4].chars)
+ call assert_equal(2, l[4].width)
+
+ " \u00a0 + composing
+ let txt = "abc\u00a0\u0308"
+ call term_sendkeys(buf, "echo " . txt)
+ call TermWait(buf, 25)
+ call assert_match("echo " . txt, term_getline(buf, lnum[2]))
+ call term_sendkeys(buf, "\<cr>")
+ call WaitForAssert({-> assert_equal(txt, term_getline(buf, lnum[2] + 1))}, 1000)
+ let l = term_scrape(buf, lnum[2] + 1)
+ call assert_equal("\u00a0\u0308", l[3].chars)
+
+ call term_sendkeys(buf, "exit\r")
+ call WaitForAssert({-> assert_equal('dead', job_status(g:job))})
+ bwipe!
+ unlet g:job
+ let &encoding = save_enc
+endfunc
+
+func Test_terminal_aucmd_on_close()
+ fun Nop()
+ let s:called = 1
+ endfun
+
+ aug repro
+ au!
+ au BufWinLeave * call Nop()
+ aug END
+
+ let [cmd, waittime] = s:get_sleep_cmd()
+
+ call assert_equal(1, winnr('$'))
+ new
+ call setline(1, ['one', 'two'])
+ exe 'term ++close ' . cmd
+ wincmd p
+ call WaitForAssert({-> assert_equal(2, winnr('$'))}, waittime)
+ call assert_equal(1, s:called)
+ bwipe!
+
+ unlet s:called
+ au! repro
+ delfunc Nop
+endfunc
+
+func Test_terminal_term_start_empty_command()
+ let cmd = "call term_start('', {'curwin' : 1, 'term_finish' : 'close'})"
+ call assert_fails(cmd, 'E474:')
+ let cmd = "call term_start('', {'curwin' : 1, 'term_finish' : 'close'})"
+ call assert_fails(cmd, 'E474:')
+ let cmd = "call term_start({}, {'curwin' : 1, 'term_finish' : 'close'})"
+ call assert_fails(cmd, 'E474:')
+ let cmd = "call term_start(0, {'curwin' : 1, 'term_finish' : 'close'})"
+ call assert_fails(cmd, 'E474:')
+ let cmd = "call term_start('', {'term_name' : []})"
+ call assert_fails(cmd, 'E730:')
+ let cmd = "call term_start('', {'term_finish' : 'axby'})"
+ call assert_fails(cmd, 'E475:')
+ let cmd = "call term_start('', {'eof_chars' : []})"
+ call assert_fails(cmd, 'E730:')
+ let cmd = "call term_start('', {'term_kill' : []})"
+ call assert_fails(cmd, 'E730:')
+ let cmd = "call term_start('', {'tty_type' : []})"
+ call assert_fails(cmd, 'E730:')
+ let cmd = "call term_start('', {'tty_type' : 'abc'})"
+ call assert_fails(cmd, 'E475:')
+ let cmd = "call term_start('', {'term_highlight' : []})"
+ call assert_fails(cmd, 'E730:')
+ if has('gui') || has('termguicolors')
+ let cmd = "call term_start('', {'ansi_colors' : 'abc'})"
+ call assert_fails(cmd, 'E475:')
+ let cmd = "call term_start('', {'ansi_colors' : [[]]})"
+ call assert_fails(cmd, 'E730:')
+ let cmd = "call term_start('', {'ansi_colors' : repeat(['blue'], 18)})"
+ if has('gui_running') || has('termguicolors')
+ call assert_fails(cmd, 'E475:')
+ else
+ call assert_fails(cmd, 'E254:')
+ endif
+ endif
+endfunc
+
+func Test_terminal_response_to_control_sequence()
+ CheckUnix
+
+ let buf = Run_shell_in_terminal({})
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+
+ call term_sendkeys(buf, "cat\<CR>")
+ call WaitForAssert({-> assert_match('cat', term_getline(buf, 1))})
+
+ " Request the cursor position.
+ call term_sendkeys(buf, "\x1b[6n\<CR>")
+
+ " Wait for output from tty to display, below an empty line.
+ call WaitForAssert({-> assert_match('3;1R', term_getline(buf, 4))})
+
+ " End "cat" gently.
+ call term_sendkeys(buf, "\<CR>\<C-D>")
+
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+ unlet g:job
+endfunc
+
+" Run this first, it fails when run after other tests.
+func Test_aa_terminal_focus_events()
+ CheckNotGui
+ CheckUnix
+ CheckRunVimInTerminal
+
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ set term=xterm ttymouse=xterm2
+
+ let lines =<< trim END
+ set term=xterm ttymouse=xterm2
+ au FocusLost * call setline(1, 'I am lost') | set nomod
+ au FocusGained * call setline(1, 'I am back') | set nomod
+ END
+ call writefile(lines, 'XtermFocus', 'D')
+ let buf = RunVimInTerminal('-S XtermFocus', #{rows: 6})
+
+ " Send a focus event to ourselves, it should be forwarded to the terminal
+ call feedkeys("\<Esc>[O", "Lx!")
+ call VerifyScreenDump(buf, 'Test_terminal_focus_1', {})
+
+ call feedkeys("\<Esc>[I", "Lx!")
+ call VerifyScreenDump(buf, 'Test_terminal_focus_2', {})
+
+ " check that a command line being edited is redrawn in place
+ call term_sendkeys(buf, ":" .. repeat('x', 80))
+ call TermWait(buf)
+ call feedkeys("\<Esc>[O", "Lx!")
+ call VerifyScreenDump(buf, 'Test_terminal_focus_3', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+endfunc
+
+" Run Vim, start a terminal in that Vim with the kill argument,
+" :qall works.
+func Run_terminal_qall_kill(line1, line2)
+ " 1. Open a terminal window and wait for the prompt to appear
+ " 2. set kill using term_setkill()
+ " 3. make Vim exit, it will kill the shell
+ let after = [
+ \ a:line1,
+ \ 'let buf = bufnr("%")',
+ \ 'while term_getline(buf, 1) =~ "^\\s*$"',
+ \ ' sleep 10m',
+ \ 'endwhile',
+ \ a:line2,
+ \ 'au VimLeavePre * call writefile(["done"], "Xdone")',
+ \ 'qall',
+ \ ]
+ if !RunVim([], after, '')
+ return
+ endif
+ call assert_equal("done", readfile("Xdone")[0])
+ call delete("Xdone")
+endfunc
+
+" Run Vim in a terminal, then start a terminal in that Vim with a kill
+" argument, check that :qall works.
+func Test_terminal_qall_kill_arg()
+ call Run_terminal_qall_kill('term ++kill=kill', '')
+endfunc
+
+" Run Vim, start a terminal in that Vim, set the kill argument with
+" term_setkill(), check that :qall works.
+func Test_terminal_qall_kill_func()
+ call Run_terminal_qall_kill('term', 'eval buf->term_setkill("kill")')
+endfunc
+
+" Run Vim, start a terminal in that Vim without the kill argument,
+" check that :qall does not exit, :qall! does.
+func Test_terminal_qall_exit()
+ let after =<< trim [CODE]
+ term
+ let buf = bufnr("%")
+ while term_getline(buf, 1) =~ "^\\s*$"
+ sleep 10m
+ endwhile
+ set nomore
+ au VimLeavePre * call writefile(["too early"], "Xdone")
+ qall
+ au! VimLeavePre * exe buf . "bwipe!" | call writefile(["done"], "Xdone")
+ cquit
+ [CODE]
+
+ if !RunVim([], after, '')
+ return
+ endif
+ call assert_equal("done", readfile("Xdone")[0])
+ call delete("Xdone")
+endfunc
+
+" Run Vim in a terminal, then start a terminal in that Vim without a kill
+" argument, check that :confirm qall works.
+func Test_terminal_qall_prompt()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', {})
+
+ " the shell may set the window title, we don't want that here
+ call term_sendkeys(buf, ":call test_override('vterm_title', 1)\<CR>")
+
+ " Open a terminal window and wait for the prompt to appear
+ call term_sendkeys(buf, ":term\<CR>")
+ call WaitForAssert({-> assert_match('\[running]', term_getline(buf, 10))})
+ call WaitForAssert({-> assert_notmatch('^\s*$', term_getline(buf, 1))})
+
+ " make Vim exit, it will prompt to kill the shell
+ call term_sendkeys(buf, "\<C-W>:confirm qall\<CR>")
+ call WaitForAssert({-> assert_match('\[Y\]es, (N)o:', term_getline(buf, 20))})
+ call term_sendkeys(buf, "y")
+ call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))})
+
+ " close the terminal window where Vim was running
+ quit
+endfunc
+
+" Run Vim in a terminal, then start a terminal window with a shell and check
+" that Vim exits if it is closed.
+func Test_terminal_exit()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ let winid = win_getid()
+ help
+ term
+ let termid = win_getid()
+ call win_gotoid(winid)
+ close
+ call win_gotoid(termid)
+ END
+ call writefile(lines, 'XtermExit', 'D')
+ let buf = RunVimInTerminal('-S XtermExit', #{rows: 10})
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_equal("run", job_status(job))})
+
+ " quit the shell, it will make Vim exit
+ call term_sendkeys(buf, "exit\<CR>")
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+endfunc
+
+func Test_terminal_open_autocmd()
+ augroup repro
+ au!
+ au TerminalOpen * let s:called += 1
+ augroup END
+
+ let s:called = 0
+
+ " Open a terminal window with :terminal
+ terminal
+ call assert_equal(1, s:called)
+ bwipe!
+
+ " Open a terminal window with term_start()
+ call term_start(&shell)
+ call assert_equal(2, s:called)
+ bwipe!
+
+ " Open a hidden terminal buffer with :terminal
+ terminal ++hidden
+ call assert_equal(3, s:called)
+ for buf in term_list()
+ exe buf . "bwipe!"
+ endfor
+
+ " Open a hidden terminal buffer with term_start()
+ let buf = term_start(&shell, {'hidden': 1})
+ call assert_equal(4, s:called)
+ exe buf . "bwipe!"
+
+ unlet s:called
+ au! repro
+endfunc
+
+func Test_open_term_from_cmd()
+ CheckUnix
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['a', 'b', 'c'])
+ 3
+ set incsearch
+ cnoremap <F3> <Cmd>call term_start(['/bin/sh', '-c', ':'])<CR>
+ END
+ call writefile(lines, 'Xopenterm', 'D')
+ let buf = RunVimInTerminal('-S Xopenterm', {})
+
+ " this opens a window, incsearch should not use the old cursor position
+ call term_sendkeys(buf, "/\<F3>")
+ call VerifyScreenDump(buf, 'Test_terminal_from_cmd', {})
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, ":q\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_combining_double_width()
+ CheckUnix
+ CheckRunVimInTerminal
+
+ call writefile(["\xe3\x83\x9b\xe3\x82\x9a"], 'Xonedouble', 'D')
+ let lines =<< trim END
+ call term_start(['/bin/sh', '-c', 'cat Xonedouble'])
+ END
+ call writefile(lines, 'Xcombining', 'D')
+ let buf = RunVimInTerminal('-S Xcombining', #{rows: 9})
+
+ " this opens a window, incsearch should not use the old cursor position
+ call VerifyScreenDump(buf, 'Test_terminal_combining', {})
+ call term_sendkeys(buf, ":q\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_popup_with_cmd()
+ " this was crashing
+ let buf = term_start(&shell, #{hidden: v:true})
+ let s:winid = popup_create(buf, {})
+ tnoremap <F3> <Cmd>call popup_close(s:winid)<CR>
+ call feedkeys("\<F3>", 'xt')
+
+ tunmap <F3>
+ exe 'bwipe! ' .. buf
+ unlet s:winid
+endfunc
+
+func Test_terminal_popup_bufload()
+ let termbuf = term_start(&shell, #{hidden: v:true, term_finish: 'close'})
+ let winid = popup_create(termbuf, {})
+ sleep 50m
+
+ let newbuf = bufadd('')
+ call bufload(newbuf)
+ call setbufline(newbuf, 1, 'foobar')
+
+ " must not have switched to another window
+ call assert_equal(winid, win_getid())
+
+ call StopShellInTerminal(termbuf)
+ call WaitFor({-> win_getid() != winid})
+ exe 'bwipe! ' .. newbuf
+endfunc
+
+func Test_terminal_popup_two_windows()
+ CheckRunVimInTerminal
+ CheckUnix
+
+ " use "sh" instead of "&shell" in the hope it will use a short prompt
+ let lines =<< trim END
+ let termbuf = term_start('sh', #{hidden: v:true, term_finish: 'close'})
+ exe 'buffer ' .. termbuf
+
+ let winid = popup_create(termbuf, #{line: 2, minwidth: 30, border: []})
+ sleep 50m
+
+ call term_sendkeys(termbuf, "echo 'test'")
+ END
+ call writefile(lines, 'XpopupScript', 'D')
+ let buf = RunVimInTerminal('-S XpopupScript', {})
+
+ " typed text appears both in normal window and in popup
+ call WaitForAssert({-> assert_match("echo 'test'", term_getline(buf, 1))})
+ call WaitForAssert({-> assert_match("echo 'test'", term_getline(buf, 3))})
+
+ call term_sendkeys(buf, "\<CR>\<CR>exit\<CR>")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":q\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_popup_insert_cmd()
+ CheckUnix
+
+ inoremap <F3> <Cmd>call StartTermInPopup()<CR>
+ func StartTermInPopup()
+ call term_start(['/bin/sh', '-c', 'cat'], #{hidden: v:true, term_finish: 'close'})->popup_create(#{highlight: 'Pmenu'})
+ endfunc
+ call feedkeys("i\<F3>")
+ sleep 10m
+ call assert_equal('n', mode())
+
+ call feedkeys("\<C-D>", 'xt')
+ call WaitFor({-> popup_list() == []})
+ delfunc StartTermInPopup
+ iunmap <F3>
+endfunc
+
+func Check_dump01(off)
+ call assert_equal('one two three four five', trim(getline(a:off + 1)))
+ call assert_equal('~ Select Word', trim(getline(a:off + 7)))
+ call assert_equal(':popup PopUp', trim(getline(a:off + 20)))
+endfunc
+
+func Test_terminal_dumpwrite_composing()
+ CheckRunVimInTerminal
+
+ let save_enc = &encoding
+ set encoding=utf-8
+ call assert_equal(1, winnr('$'))
+
+ let text = " a\u0300 e\u0302 o\u0308"
+ call writefile([text], 'Xcomposing', 'D')
+ let buf = RunVimInTerminal('--cmd "set encoding=utf-8" Xcomposing', {})
+ call WaitForAssert({-> assert_match(text, term_getline(buf, 1))})
+ eval 'Xdump'->term_dumpwrite(buf)
+ let dumpline = readfile('Xdump')[0]
+ call assert_match('|à| |ê| |ö', dumpline)
+
+ call StopVimInTerminal(buf)
+ call delete('Xdump')
+ let &encoding = save_enc
+endfunc
+
+" Tests for failures in the term_dumpwrite() function
+func Test_terminal_dumpwrite_errors()
+ CheckRunVimInTerminal
+ call assert_fails("call term_dumpwrite({}, 'Xtest.dump')", 'E728:')
+ let buf = RunVimInTerminal('', {})
+ call TermWait(buf)
+ call assert_fails("call term_dumpwrite(buf, 'Xtest.dump', '')", 'E1206:')
+ call assert_fails("call term_dumpwrite(buf, [])", 'E730:')
+ call writefile([], 'Xtest.dump')
+ call assert_fails("call term_dumpwrite(buf, 'Xtest.dump')", 'E953:')
+ call delete('Xtest.dump')
+ call assert_fails("call term_dumpwrite(buf, '')", 'E482:')
+ call assert_fails("call term_dumpwrite(buf, test_null_string())", 'E482:')
+ call test_garbagecollect_now()
+ call StopVimInTerminal(buf, 0)
+ call TermWait(buf)
+ call assert_fails("call term_dumpwrite(buf, 'Xtest.dump')", 'E958:')
+ call assert_fails('call term_sendkeys([], ":q\<CR>")', 'E745:')
+ call assert_equal(0, term_sendkeys(buf, ":q\<CR>"))
+endfunc
+
+" just testing basic functionality.
+func Test_terminal_dumpload()
+ let curbuf = winbufnr('')
+ call assert_equal(1, winnr('$'))
+ let buf = term_dumpload('dumps/Test_popup_command_01.dump')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(20, line('$'))
+ call Check_dump01(0)
+
+ " Load another dump in the same window
+ let buf2 = 'dumps/Test_diff_01.dump'->term_dumpload({'bufnr': buf})
+ call assert_equal(buf, buf2)
+ call assert_notequal('one two three four five', trim(getline(1)))
+
+ " Load the first dump again in the same window
+ let buf2 = term_dumpload('dumps/Test_popup_command_01.dump', {'bufnr': buf})
+ call assert_equal(buf, buf2)
+ call Check_dump01(0)
+
+ call assert_fails("call term_dumpload('dumps/Test_popup_command_01.dump', {'bufnr': curbuf})", 'E475:')
+ call assert_fails("call term_dumpload('dumps/Test_popup_command_01.dump', {'bufnr': 9999})", 'E86:')
+ new
+ let closedbuf = winbufnr('')
+ quit
+ call assert_fails("call term_dumpload('dumps/Test_popup_command_01.dump', {'bufnr': closedbuf})", 'E475:')
+ call assert_fails('call term_dumpload([])', 'E730:')
+ call assert_fails('call term_dumpload("xabcy.dump")', 'E485:')
+
+ quit
+endfunc
+
+func Test_terminal_dumpload_dump()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call term_dumpload('dumps/Test_popupwin_22.dump', #{term_rows: 12})
+ END
+ call writefile(lines, 'XtermDumpload', 'D')
+ let buf = RunVimInTerminal('-S XtermDumpload', #{rows: 15})
+ call VerifyScreenDump(buf, 'Test_terminal_dumpload', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_dumpdiff()
+ call assert_equal(1, winnr('$'))
+ eval 'dumps/Test_popup_command_01.dump'->term_dumpdiff('dumps/Test_popup_command_02.dump')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(62, line('$'))
+ call Check_dump01(0)
+ call Check_dump01(42)
+ call assert_equal(' bbbbbbbbbbbbbbbbbb ', getline(26)[0:29])
+ quit
+
+ call assert_fails('call term_dumpdiff("X1.dump", [])', 'E730:')
+ call assert_fails('call term_dumpdiff("X1.dump", "X2.dump")', 'E485:')
+ call writefile([], 'X1.dump', 'D')
+ call assert_fails('call term_dumpdiff("X1.dump", "X2.dump")', 'E485:')
+endfunc
+
+func Test_terminal_dumpdiff_swap()
+ call assert_equal(1, winnr('$'))
+ call term_dumpdiff('dumps/Test_popup_command_01.dump', 'dumps/Test_popup_command_03.dump')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(62, line('$'))
+ call assert_match('Test_popup_command_01.dump', getline(21))
+ call assert_match('Test_popup_command_03.dump', getline(42))
+ call assert_match('Undo', getline(3))
+ call assert_match('three four five', getline(45))
+
+ normal s
+ call assert_match('Test_popup_command_03.dump', getline(21))
+ call assert_match('Test_popup_command_01.dump', getline(42))
+ call assert_match('three four five', getline(3))
+ call assert_match('Undo', getline(45))
+ quit
+
+ " Diff two terminal dump files with different number of rows
+ " Swap the diffs
+ call term_dumpdiff('dumps/Test_popup_command_01.dump', 'dumps/Test_winline_rnu.dump')
+ call assert_match('Test_popup_command_01.dump', getline(21))
+ call assert_match('Test_winline_rnu.dump', getline(42))
+ normal s
+ call assert_match('Test_winline_rnu.dump', getline(6))
+ call assert_match('Test_popup_command_01.dump', getline(27))
+ quit
+endfunc
+
+func Test_terminal_dumpdiff_options()
+ set laststatus=0
+ call assert_equal(1, winnr('$'))
+ let height = winheight(0)
+ call term_dumpdiff('dumps/Test_popup_command_01.dump', 'dumps/Test_popup_command_02.dump', {'vertical': 1, 'term_cols': 33})
+ call assert_equal(2, winnr('$'))
+ call assert_equal(height, winheight(winnr()))
+ call assert_equal(33, winwidth(winnr()))
+ call assert_equal('dump diff dumps/Test_popup_command_01.dump', bufname('%'))
+ quit
+
+ call assert_equal(1, winnr('$'))
+ call term_dumpdiff('dumps/Test_popup_command_01.dump', 'dumps/Test_popup_command_02.dump', {'vertical': 0, 'term_rows': 13, 'term_name': 'something else'})
+ call assert_equal(2, winnr('$'))
+ call assert_equal(&columns, winwidth(0))
+ call assert_equal(13, winheight(0))
+ call assert_equal('something else', bufname('%'))
+ quit
+
+ call assert_equal(1, winnr('$'))
+ call term_dumpdiff('dumps/Test_popup_command_01.dump', 'dumps/Test_popup_command_02.dump', {'curwin': 1})
+ call assert_equal(1, winnr('$'))
+ call assert_fails("call term_dumpdiff('dumps/Test_popup_command_01.dump', 'dumps/Test_popup_command_02.dump', {'bufnr': -1})", 'E475:')
+ bwipe
+
+ set laststatus&
+endfunc
+
+" When drawing the statusline the cursor position may not have been updated
+" yet.
+" 1. create a terminal, make it show 2 lines
+" 2. 0.5 sec later: leave terminal window, execute "i"
+" 3. 0.5 sec later: clear terminal window, now it's 1 line
+" 4. 0.5 sec later: redraw, including statusline (used to trigger bug)
+" 4. 0.5 sec later: should be done, clean up
+func Test_terminal_statusline()
+ CheckUnix
+ CheckFeature timers
+
+ set statusline=x
+ terminal
+ let tbuf = bufnr('')
+ call term_sendkeys(tbuf, "clear; echo a; echo b; sleep 1; clear\n")
+ call timer_start(500, { tid -> feedkeys("\<C-w>j", 'tx') })
+ call timer_start(1500, { tid -> feedkeys("\<C-l>", 'tx') })
+ au BufLeave * if &buftype == 'terminal' | silent! normal i | endif
+
+ sleep 2
+ exe tbuf . 'bwipe!'
+ au! BufLeave
+ set statusline=
+endfunc
+
+func CheckTerminalWindowWorks(buf)
+ call WaitForAssert({-> assert_match('!sh \[running\]', term_getline(a:buf, 10))})
+ call term_sendkeys(a:buf, "exit\<CR>")
+ call WaitForAssert({-> assert_match('!sh \[finished\]', term_getline(a:buf, 10))})
+ call term_sendkeys(a:buf, ":q\<CR>")
+ call WaitForAssert({-> assert_match('^\~', term_getline(a:buf, 10))})
+endfunc
+
+func Test_start_terminal_from_timer()
+ CheckUnix
+ CheckFeature timers
+
+ " Open a terminal window from a timer, typed text goes to the terminal
+ call writefile(["call timer_start(100, { -> term_start('sh') })"], 'XtimerTerm', 'D')
+ let buf = RunVimInTerminal('-S XtimerTerm', {})
+ call CheckTerminalWindowWorks(buf)
+
+ " do the same in Insert mode
+ call term_sendkeys(buf, ":call timer_start(200, { -> term_start('sh') })\<CR>a")
+ call CheckTerminalWindowWorks(buf)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_window_focus()
+ let winid1 = win_getid()
+ terminal
+ let winid2 = win_getid()
+ call feedkeys("\<C-W>j", 'xt')
+ call assert_equal(winid1, win_getid())
+ call feedkeys("\<C-W>k", 'xt')
+ call assert_equal(winid2, win_getid())
+ " can use a cursor key here
+ call feedkeys("\<C-W>\<Down>", 'xt')
+ call assert_equal(winid1, win_getid())
+ call feedkeys("\<C-W>\<Up>", 'xt')
+ call assert_equal(winid2, win_getid())
+
+ bwipe!
+endfunc
+
+func Api_drop_common(options)
+ call assert_equal(1, winnr('$'))
+
+ " Use the title termcap entries to output the escape sequence.
+ call writefile([
+ \ 'set title',
+ \ 'exe "set t_ts=\<Esc>]51; t_fs=\x07"',
+ \ 'let &titlestring = ''["drop","Xtextfile"' . a:options . ']''',
+ \ 'redraw',
+ \ "set t_ts=",
+ \ ], 'Xscript')
+ let buf = RunVimInTerminal('-S Xscript', {})
+ call WaitFor({-> bufnr('Xtextfile') > 0})
+ call assert_equal('Xtextfile', expand('%:t'))
+ call assert_true(winnr('$') >= 3)
+ return buf
+endfunc
+
+func Test_terminal_api_drop_newwin()
+ CheckRunVimInTerminal
+ let buf = Api_drop_common('')
+ call assert_equal(0, &bin)
+ call assert_equal('', &fenc)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+endfunc
+
+func Test_terminal_api_drop_newwin_bin()
+ CheckRunVimInTerminal
+ let buf = Api_drop_common(',{"bin":1}')
+ call assert_equal(1, &bin)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+endfunc
+
+func Test_terminal_api_drop_newwin_binary()
+ CheckRunVimInTerminal
+ let buf = Api_drop_common(',{"binary":1}')
+ call assert_equal(1, &bin)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+endfunc
+
+func Test_terminal_api_drop_newwin_nobin()
+ CheckRunVimInTerminal
+ set binary
+ let buf = Api_drop_common(',{"nobin":1}')
+ call assert_equal(0, &bin)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+ set nobinary
+endfunc
+
+func Test_terminal_api_drop_newwin_nobinary()
+ CheckRunVimInTerminal
+ set binary
+ let buf = Api_drop_common(',{"nobinary":1}')
+ call assert_equal(0, &bin)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+ set nobinary
+endfunc
+
+func Test_terminal_api_drop_newwin_ff()
+ CheckRunVimInTerminal
+ let buf = Api_drop_common(',{"ff":"dos"}')
+ call assert_equal("dos", &ff)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+endfunc
+
+func Test_terminal_api_drop_newwin_fileformat()
+ CheckRunVimInTerminal
+ let buf = Api_drop_common(',{"fileformat":"dos"}')
+ call assert_equal("dos", &ff)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+endfunc
+
+func Test_terminal_api_drop_newwin_enc()
+ CheckRunVimInTerminal
+ let buf = Api_drop_common(',{"enc":"utf-16"}')
+ call assert_equal("utf-16", &fenc)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+endfunc
+
+func Test_terminal_api_drop_newwin_encoding()
+ CheckRunVimInTerminal
+ let buf = Api_drop_common(',{"encoding":"utf-16"}')
+ call assert_equal("utf-16", &fenc)
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ bwipe Xtextfile
+endfunc
+
+func Test_terminal_api_drop_oldwin()
+ CheckRunVimInTerminal
+ let firstwinid = win_getid()
+ split Xtextfile
+ let textfile_winid = win_getid()
+ call assert_equal(2, winnr('$'))
+ call win_gotoid(firstwinid)
+
+ " Use the title termcap entries to output the escape sequence.
+ call writefile([
+ \ 'set title',
+ \ 'exe "set t_ts=\<Esc>]51; t_fs=\x07"',
+ \ 'let &titlestring = ''["drop","Xtextfile"]''',
+ \ 'redraw',
+ \ "set t_ts=",
+ \ ], 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 10})
+ call WaitForAssert({-> assert_equal('Xtextfile', expand('%:t'))})
+ call assert_equal(textfile_winid, win_getid())
+
+ call StopVimInTerminal(buf)
+ bwipe Xtextfile
+endfunc
+
+func Tapi_TryThis(bufnum, arg)
+ let g:called_bufnum = a:bufnum
+ let g:called_arg = a:arg
+endfunc
+
+func WriteApiCall(funcname)
+ " Use the title termcap entries to output the escape sequence.
+ call writefile([
+ \ 'set title',
+ \ 'exe "set t_ts=\<Esc>]51; t_fs=\x07"',
+ \ 'let &titlestring = ''["call","' . a:funcname . '",["hello",123]]''',
+ \ 'redraw',
+ \ "set t_ts=",
+ \ ], 'Xscript')
+endfunc
+
+func Test_terminal_api_call()
+ CheckRunVimInTerminal
+
+ unlet! g:called_bufnum
+ unlet! g:called_arg
+
+ call WriteApiCall('Tapi_TryThis')
+
+ " Default
+ let buf = RunVimInTerminal('-S Xscript', {})
+ call WaitFor({-> exists('g:called_bufnum')})
+ call assert_equal(buf, g:called_bufnum)
+ call assert_equal(['hello', 123], g:called_arg)
+ call StopVimInTerminal(buf)
+
+ unlet! g:called_bufnum
+ unlet! g:called_arg
+
+ " Enable explicitly
+ let buf = RunVimInTerminal('-S Xscript', {'term_api': 'Tapi_Try'})
+ call WaitFor({-> exists('g:called_bufnum')})
+ call assert_equal(buf, g:called_bufnum)
+ call assert_equal(['hello', 123], g:called_arg)
+ call StopVimInTerminal(buf)
+
+ unlet! g:called_bufnum
+ unlet! g:called_arg
+
+ func! ApiCall_TryThis(bufnum, arg)
+ let g:called_bufnum2 = a:bufnum
+ let g:called_arg2 = a:arg
+ endfunc
+
+ call WriteApiCall('ApiCall_TryThis')
+
+ " Use prefix match
+ let buf = RunVimInTerminal('-S Xscript', {'term_api': 'ApiCall_'})
+ call WaitFor({-> exists('g:called_bufnum2')})
+ call assert_equal(buf, g:called_bufnum2)
+ call assert_equal(['hello', 123], g:called_arg2)
+ call StopVimInTerminal(buf)
+
+ call assert_fails("call term_start('ls', {'term_api' : []})", 'E730:')
+
+ unlet! g:called_bufnum2
+ unlet! g:called_arg2
+
+ call delete('Xscript')
+ delfunction! ApiCall_TryThis
+ unlet! g:called_bufnum2
+ unlet! g:called_arg2
+endfunc
+
+func Test_terminal_api_call_fails()
+ CheckRunVimInTerminal
+
+ func! TryThis(bufnum, arg)
+ let g:called_bufnum3 = a:bufnum
+ let g:called_arg3 = a:arg
+ endfunc
+
+ call WriteApiCall('TryThis')
+
+ unlet! g:called_bufnum3
+ unlet! g:called_arg3
+
+ " Not permitted
+ call ch_logfile('Xlog', 'w')
+ let buf = RunVimInTerminal('-S Xscript', {'term_api': ''})
+ call WaitForAssert({-> assert_match('Unpermitted function: TryThis', string(readfile('Xlog')))})
+ call assert_false(exists('g:called_bufnum3'))
+ call assert_false(exists('g:called_arg3'))
+ call StopVimInTerminal(buf)
+
+ " No match
+ call ch_logfile('Xlog', 'w')
+ let buf = RunVimInTerminal('-S Xscript', {'term_api': 'TryThat'})
+ call WaitFor({-> string(readfile('Xlog')) =~ 'Unpermitted function: TryThis'})
+ call assert_false(exists('g:called_bufnum3'))
+ call assert_false(exists('g:called_arg3'))
+ call StopVimInTerminal(buf)
+
+ call delete('Xscript')
+ call ch_logfile('')
+ call delete('Xlog')
+ delfunction! TryThis
+ unlet! g:called_bufnum3
+ unlet! g:called_arg3
+endfunc
+
+let s:caught_e937 = 0
+
+func Tapi_Delete(bufnum, arg)
+ try
+ execute 'bdelete!' a:bufnum
+ catch /E937:/
+ let s:caught_e937 = 1
+ endtry
+endfunc
+
+func Test_terminal_api_call_fail_delete()
+ CheckRunVimInTerminal
+
+ call WriteApiCall('Tapi_Delete')
+ let buf = RunVimInTerminal('-S Xscript', {})
+ call WaitForAssert({-> assert_equal(1, s:caught_e937)})
+
+ call StopVimInTerminal(buf)
+ call delete('Xscript')
+ call ch_logfile('', '')
+endfunc
+
+func Test_terminal_setapi_and_call()
+ CheckRunVimInTerminal
+
+ call WriteApiCall('Tapi_TryThis')
+ call ch_logfile('Xlog', 'w')
+
+ unlet! g:called_bufnum
+ unlet! g:called_arg
+
+ let buf = RunVimInTerminal('-S Xscript', {'term_api': ''})
+ call WaitForAssert({-> assert_match('Unpermitted function: Tapi_TryThis', string(readfile('Xlog')))})
+ call assert_false(exists('g:called_bufnum'))
+ call assert_false(exists('g:called_arg'))
+
+ eval buf->term_setapi('Tapi_')
+ call term_sendkeys(buf, ":set notitle\<CR>")
+ call term_sendkeys(buf, ":source Xscript\<CR>")
+ call WaitFor({-> exists('g:called_bufnum')})
+ call assert_equal(buf, g:called_bufnum)
+ call assert_equal(['hello', 123], g:called_arg)
+
+ call StopVimInTerminal(buf)
+
+ call delete('Xscript')
+ call ch_logfile('')
+ call delete('Xlog')
+ unlet! g:called_bufnum
+ unlet! g:called_arg
+endfunc
+
+func Test_terminal_api_arg()
+ CheckRunVimInTerminal
+
+ call WriteApiCall('Tapi_TryThis')
+ call ch_logfile('Xlog', 'w')
+
+ unlet! g:called_bufnum
+ unlet! g:called_arg
+
+ execute 'term ++api= ' .. GetVimCommandCleanTerm() .. '-S Xscript'
+ let buf = bufnr('%')
+ call WaitForAssert({-> assert_match('Unpermitted function: Tapi_TryThis', string(readfile('Xlog')))})
+ call assert_false(exists('g:called_bufnum'))
+ call assert_false(exists('g:called_arg'))
+
+ call StopVimInTerminal(buf)
+
+ call ch_logfile('Xlog', 'w')
+
+ execute 'term ++api=Tapi_ ' .. GetVimCommandCleanTerm() .. '-S Xscript'
+ let buf = bufnr('%')
+ call WaitFor({-> exists('g:called_bufnum')})
+ call assert_equal(buf, g:called_bufnum)
+ call assert_equal(['hello', 123], g:called_arg)
+
+ call StopVimInTerminal(buf)
+
+ call delete('Xscript')
+ call ch_logfile('')
+ call delete('Xlog')
+ unlet! g:called_bufnum
+ unlet! g:called_arg
+endfunc
+
+func Test_terminal_ansicolors_default()
+ CheckFunction term_getansicolors
+
+ let colors = [
+ \ '#000000', '#e00000',
+ \ '#00e000', '#e0e000',
+ \ '#0000e0', '#e000e0',
+ \ '#00e0e0', '#e0e0e0',
+ \ '#808080', '#ff4040',
+ \ '#40ff40', '#ffff40',
+ \ '#4040ff', '#ff40ff',
+ \ '#40ffff', '#ffffff',
+ \]
+
+ let buf = Run_shell_in_terminal({})
+ call assert_equal(colors, term_getansicolors(buf))
+ call StopShellInTerminal(buf)
+ call assert_equal([], term_getansicolors(buf))
+
+ exe buf . 'bwipe'
+endfunc
+
+let s:test_colors = [
+ \ '#616e64', '#0d0a79',
+ \ '#6d610d', '#0a7373',
+ \ '#690d0a', '#6d696e',
+ \ '#0d0a6f', '#616e0d',
+ \ '#0a6479', '#6d0d0a',
+ \ '#617373', '#0d0a69',
+ \ '#6d690d', '#0a6e6f',
+ \ '#610d0a', '#6e6479',
+ \]
+
+func Test_terminal_ansicolors_global()
+ CheckFeature termguicolors
+ CheckFunction term_getansicolors
+
+ if has('vtp') && !has('vcon') && !has('gui_running')
+ throw 'Skipped: does not support termguicolors'
+ endif
+
+ set tgc
+ let g:terminal_ansi_colors = reverse(copy(s:test_colors))
+ let buf = Run_shell_in_terminal({})
+ call assert_equal(g:terminal_ansi_colors, term_getansicolors(buf))
+ call StopShellInTerminal(buf)
+ set tgc&
+
+ exe buf . 'bwipe'
+ unlet g:terminal_ansi_colors
+endfunc
+
+func Test_terminal_ansicolors_func()
+ CheckFeature termguicolors
+ CheckFunction term_getansicolors
+
+ if has('vtp') && !has('vcon') && !has('gui_running')
+ throw 'Skipped: does not support termguicolors'
+ endif
+
+ set tgc
+ let g:terminal_ansi_colors = reverse(copy(s:test_colors))
+ let buf = Run_shell_in_terminal({'ansi_colors': s:test_colors})
+ call assert_equal(s:test_colors, term_getansicolors(buf))
+
+ call term_setansicolors(buf, g:terminal_ansi_colors)
+ call assert_equal(g:terminal_ansi_colors, buf->term_getansicolors())
+
+ let colors = [
+ \ 'ivory', 'AliceBlue',
+ \ 'grey67', 'dark goldenrod',
+ \ 'SteelBlue3', 'PaleVioletRed4',
+ \ 'MediumPurple2', 'yellow2',
+ \ 'RosyBrown3', 'OrangeRed2',
+ \ 'white smoke', 'navy blue',
+ \ 'grey47', 'gray97',
+ \ 'MistyRose2', 'DodgerBlue4',
+ \]
+ eval buf->term_setansicolors(colors)
+
+ let colors[4] = 'Invalid'
+ call assert_fails('call term_setansicolors(buf, colors)', 'E254:')
+ call assert_fails('call term_setansicolors(buf, {})', 'E1211:')
+ set tgc&
+
+ call StopShellInTerminal(buf)
+ call assert_equal(0, term_setansicolors(buf, []))
+ exe buf . 'bwipe'
+endfunc
+
+func Test_terminal_all_ansi_colors()
+ CheckRunVimInTerminal
+
+ " Use all the ANSI colors.
+ call writefile([
+ \ 'call setline(1, "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPP XXYYZZ")',
+ \ 'hi Tblack ctermfg=0 ctermbg=8',
+ \ 'hi Tdarkred ctermfg=1 ctermbg=9',
+ \ 'hi Tdarkgreen ctermfg=2 ctermbg=10',
+ \ 'hi Tbrown ctermfg=3 ctermbg=11',
+ \ 'hi Tdarkblue ctermfg=4 ctermbg=12',
+ \ 'hi Tdarkmagenta ctermfg=5 ctermbg=13',
+ \ 'hi Tdarkcyan ctermfg=6 ctermbg=14',
+ \ 'hi Tlightgrey ctermfg=7 ctermbg=15',
+ \ 'hi Tdarkgrey ctermfg=8 ctermbg=0',
+ \ 'hi Tred ctermfg=9 ctermbg=1',
+ \ 'hi Tgreen ctermfg=10 ctermbg=2',
+ \ 'hi Tyellow ctermfg=11 ctermbg=3',
+ \ 'hi Tblue ctermfg=12 ctermbg=4',
+ \ 'hi Tmagenta ctermfg=13 ctermbg=5',
+ \ 'hi Tcyan ctermfg=14 ctermbg=6',
+ \ 'hi Twhite ctermfg=15 ctermbg=7',
+ \ 'hi TdarkredBold ctermfg=1 cterm=bold',
+ \ 'hi TgreenBold ctermfg=10 cterm=bold',
+ \ 'hi TmagentaBold ctermfg=13 cterm=bold ctermbg=5',
+ \ '',
+ \ 'call matchadd("Tblack", "A")',
+ \ 'call matchadd("Tdarkred", "B")',
+ \ 'call matchadd("Tdarkgreen", "C")',
+ \ 'call matchadd("Tbrown", "D")',
+ \ 'call matchadd("Tdarkblue", "E")',
+ \ 'call matchadd("Tdarkmagenta", "F")',
+ \ 'call matchadd("Tdarkcyan", "G")',
+ \ 'call matchadd("Tlightgrey", "H")',
+ \ 'call matchadd("Tdarkgrey", "I")',
+ \ 'call matchadd("Tred", "J")',
+ \ 'call matchadd("Tgreen", "K")',
+ \ 'call matchadd("Tyellow", "L")',
+ \ 'call matchadd("Tblue", "M")',
+ \ 'call matchadd("Tmagenta", "N")',
+ \ 'call matchadd("Tcyan", "O")',
+ \ 'call matchadd("Twhite", "P")',
+ \ 'call matchadd("TdarkredBold", "X")',
+ \ 'call matchadd("TgreenBold", "Y")',
+ \ 'call matchadd("TmagentaBold", "Z")',
+ \ 'redraw',
+ \ ], 'Xcolorscript', 'D')
+ let buf = RunVimInTerminal('-S Xcolorscript', {'rows': 10})
+ call VerifyScreenDump(buf, 'Test_terminal_all_ansi_colors', {})
+
+ call term_sendkeys(buf, ":q\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+function On_BufFilePost()
+ doautocmd <nomodeline> User UserEvent
+endfunction
+
+func Test_terminal_nested_autocmd()
+ new
+ call setline(1, range(500))
+ $
+ let lastline = line('.')
+
+ augroup TermTest
+ autocmd BufFilePost * call On_BufFilePost()
+ autocmd User UserEvent silent
+ augroup END
+
+ let cmd = Get_cat_123_cmd()
+ let buf = term_start(cmd, #{term_finish: 'close', hidden: 1})
+ call assert_equal(lastline, line('.'))
+
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+ call delete('Xtext')
+ augroup TermTest
+ au!
+ augroup END
+endfunc
+
+func Test_terminal_adds_jump()
+ clearjumps
+ call term_start("ls", #{curwin: 1})
+ call assert_equal(1, getjumplist()[0]->len())
+ bwipe!
+endfunc
+
+func Close_cb(ch, ctx)
+ call term_wait(a:ctx.bufnr)
+ let g:close_done = 'done'
+endfunc
+
+func Test_term_wait_in_close_cb()
+ let g:close_done = ''
+ let ctx = {}
+ let ctx.bufnr = term_start('echo "HELLO WORLD"',
+ \ {'close_cb': {ch -> Close_cb(ch, ctx)}})
+
+ call WaitForAssert({-> assert_equal("done", g:close_done)})
+
+ unlet g:close_done
+ bwipe!
+endfunc
+
+func Test_term_TextChangedT()
+ augroup TermTest
+ autocmd TextChangedT * ++once
+ \ execute expand('<abuf>') . 'buffer' |
+ \ let b:called = 1 |
+ \ split |
+ \ enew
+ augroup END
+
+ terminal
+
+ let term_buf = bufnr()
+
+ let b:called = 0
+
+ call term_sendkeys(term_buf, "aaabbc\r")
+ call TermWait(term_buf)
+
+ call assert_equal(1, getbufvar(term_buf, 'called'))
+
+ " Current buffer will be restored
+ call assert_equal(bufnr(), term_buf)
+
+ bwipe!
+ augroup TermTest
+ au!
+ augroup END
+endfunc
+
+func Test_term_TextChangedT_close()
+ augroup TermTest
+ autocmd TextChangedT * ++once split | enew | 1close!
+ augroup END
+
+ terminal
+
+ let term_buf = bufnr()
+
+ call term_sendkeys(term_buf, "aaabbc\r")
+ call TermWait(term_buf)
+
+ " Current buffer will be restored
+ call assert_equal(bufnr(), term_buf)
+
+ bwipe!
+ augroup TermTest
+ au!
+ augroup END
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_terminal2.vim b/src/testdir/test_terminal2.vim
new file mode 100644
index 0000000..8615bf5
--- /dev/null
+++ b/src/testdir/test_terminal2.vim
@@ -0,0 +1,570 @@
+" Tests for the terminal window.
+" This is split in two, because it can take a lot of time.
+" See test_terminal.vim and test_terminal3.vim for further tests.
+
+source check.vim
+CheckFeature terminal
+
+source shared.vim
+source screendump.vim
+source mouse.vim
+source term_util.vim
+
+let $PROMPT_COMMAND=''
+
+func Test_terminal_termwinsize_option_fixed()
+ CheckRunVimInTerminal
+ set termwinsize=6x40
+ let text = []
+ for n in range(10)
+ call add(text, repeat(n, 50))
+ endfor
+ call writefile(text, 'Xwinsize', 'D')
+ let buf = RunVimInTerminal('Xwinsize', {})
+ let win = bufwinid(buf)
+ call assert_equal([6, 40], term_getsize(buf))
+ call assert_equal(6, winheight(win))
+ call assert_equal(40, winwidth(win))
+
+ " resizing the window doesn't resize the terminal.
+ resize 10
+ vertical resize 60
+ call assert_equal([6, 40], term_getsize(buf))
+ call assert_equal(10, winheight(win))
+ call assert_equal(60, winwidth(win))
+
+ call StopVimInTerminal(buf)
+
+ call assert_fails('set termwinsize=40', 'E474:')
+ call assert_fails('set termwinsize=10+40', 'E474:')
+ call assert_fails('set termwinsize=abc', 'E474:')
+
+ set termwinsize=
+endfunc
+
+func Test_terminal_termwinsize_option_zero()
+ set termwinsize=0x0
+ let buf = Run_shell_in_terminal({})
+ let win = bufwinid(buf)
+ call assert_equal([winheight(win), winwidth(win)], term_getsize(buf))
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+
+ set termwinsize=7x0
+ let buf = Run_shell_in_terminal({})
+ let win = bufwinid(buf)
+ call assert_equal([7, winwidth(win)], term_getsize(buf))
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+
+ set termwinsize=0x33
+ let buf = Run_shell_in_terminal({})
+ let win = bufwinid(buf)
+ call assert_equal([winheight(win), 33], term_getsize(buf))
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+
+ set termwinsize=
+endfunc
+
+func Test_terminal_termwinsize_minimum()
+ set termwinsize=10*50
+ vsplit
+ let buf = Run_shell_in_terminal({})
+ let win = bufwinid(buf)
+ call assert_inrange(10, 1000, winheight(win))
+ call assert_inrange(50, 1000, winwidth(win))
+ call assert_equal([winheight(win), winwidth(win)], term_getsize(buf))
+
+ resize 15
+ vertical resize 60
+ redraw
+ call assert_equal([15, 60], term_getsize(buf))
+ call assert_equal(15, winheight(win))
+ call assert_equal(60, winwidth(win))
+
+ resize 7
+ vertical resize 30
+ redraw
+ call assert_equal([10, 50], term_getsize(buf))
+ call assert_equal(7, winheight(win))
+ call assert_equal(30, winwidth(win))
+
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+
+ set termwinsize=0*0
+ let buf = Run_shell_in_terminal({})
+ let win = bufwinid(buf)
+ call assert_equal([winheight(win), winwidth(win)], term_getsize(buf))
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+
+ set termwinsize=
+endfunc
+
+func Test_terminal_termwinsize_overruled()
+ let cmd = GetDummyCmd()
+ set termwinsize=5x43
+ let buf = term_start(cmd, #{term_rows: 7, term_cols: 50})
+ call TermWait(buf)
+ call assert_equal([7, 50], term_getsize(buf))
+ exe "bwipe! " .. buf
+
+ let buf = term_start(cmd, #{term_cols: 50})
+ call TermWait(buf)
+ call assert_equal([5, 50], term_getsize(buf))
+ exe "bwipe! " .. buf
+
+ let buf = term_start(cmd, #{term_rows: 7})
+ call TermWait(buf)
+ call assert_equal([7, 43], term_getsize(buf))
+ exe "bwipe! " .. buf
+
+ set termwinsize=
+endfunc
+
+" hidden terminal must not change current window size
+func Test_terminal_hidden_winsize()
+ let cmd = GetDummyCmd()
+ let rows = winheight(0)
+ let buf = term_start(cmd, #{hidden: 1, term_rows: 10})
+ call TermWait(buf)
+ call assert_equal(rows, winheight(0))
+ call assert_equal([10, &columns], term_getsize(buf))
+ exe "bwipe! " .. buf
+endfunc
+
+func Test_terminal_termwinkey()
+ " make three tabpages, terminal in the middle
+ 0tabnew
+ tabnext
+ tabnew
+ tabprev
+ call assert_equal(1, winnr('$'))
+ call assert_equal(2, tabpagenr())
+ let thiswin = win_getid()
+
+ let buf = Run_shell_in_terminal({})
+ let termwin = bufwinid(buf)
+ set termwinkey=<C-L>
+ call feedkeys("\<C-L>w", 'tx')
+ call assert_equal(thiswin, win_getid())
+ call feedkeys("\<C-W>w", 'tx')
+ call assert_equal(termwin, win_getid())
+
+ if has('langmap')
+ set langmap=xjyk
+ call feedkeys("\<C-L>x", 'tx')
+ call assert_equal(thiswin, win_getid())
+ call feedkeys("\<C-W>y", 'tx')
+ call assert_equal(termwin, win_getid())
+ set langmap=
+ endif
+
+ call feedkeys("\<C-L>gt", "xt")
+ call assert_equal(3, tabpagenr())
+ tabprev
+ call assert_equal(2, tabpagenr())
+ call assert_equal(termwin, win_getid())
+
+ call feedkeys("\<C-L>gT", "xt")
+ call assert_equal(1, tabpagenr())
+ tabnext
+ call assert_equal(2, tabpagenr())
+ call assert_equal(termwin, win_getid())
+
+ let job = term_getjob(buf)
+ call feedkeys("\<C-L>\<C-C>", 'tx')
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+
+ set termwinkey&
+ tabnext
+ tabclose
+ tabprev
+ tabclose
+endfunc
+
+func Test_terminal_out_err()
+ CheckUnix
+
+ call writefile([
+ \ '#!/bin/sh',
+ \ 'echo "this is standard error" >&2',
+ \ 'echo "this is standard out" >&1',
+ \ ], 'Xechoerrout.sh', 'D')
+ call setfperm('Xechoerrout.sh', 'rwxrwx---')
+
+ let outfile = 'Xtermstdout'
+ let buf = term_start(['./Xechoerrout.sh'], {'out_io': 'file', 'out_name': outfile})
+ call TermWait(buf)
+
+ call WaitFor({-> !empty(readfile(outfile)) && !empty(term_getline(buf, 1))})
+ call assert_equal(['this is standard out'], readfile(outfile))
+ call assert_equal('this is standard error', term_getline(buf, 1))
+
+ call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))})
+ exe buf . 'bwipe'
+ call delete(outfile)
+endfunc
+
+func Test_termwinscroll()
+ CheckUnix
+ " TODO: Somehow this test sometimes hangs in the GUI
+ CheckNotGui
+ let g:test_is_flaky = 1
+
+ " Let the terminal output more than 'termwinscroll' lines, some at the start
+ " will be dropped.
+ exe 'set termwinscroll=' . &lines
+ let buf = term_start('/bin/sh')
+ call TermWait(buf)
+ for i in range(1, &lines)
+ call feedkeys("echo " . i . "\<CR>", 'xt')
+ call WaitForAssert({-> assert_match(string(i), term_getline(buf, term_getcursor(buf)[0] - 1))})
+ endfor
+ " Go to Terminal-Normal mode to update the buffer.
+ call feedkeys("\<C-W>N", 'xt')
+ call assert_inrange(&lines, &lines * 110 / 100 + winheight(0), line('$'))
+
+ " Every "echo nr" must only appear once
+ let lines = getline(1, line('$'))
+ for i in range(&lines - len(lines) / 2 + 2, &lines)
+ let filtered = filter(copy(lines), {idx, val -> val =~ 'echo ' . i . '\>'})
+ call assert_equal(1, len(filtered), 'for "echo ' . i . '"')
+ endfor
+
+ exe buf . 'bwipe!'
+endfunc
+
+" Resizing the terminal window caused an ml_get error.
+" TODO: This does not reproduce the original problem.
+func Test_terminal_resize()
+ set statusline=x
+ terminal
+ call assert_equal(2, winnr('$'))
+ let buf = bufnr()
+
+ " Wait for the shell to display a prompt
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+
+ " Fill the terminal with text.
+ if has('win32')
+ call feedkeys("dir\<CR>", 'xt')
+ else
+ call feedkeys("ls\<CR>", 'xt')
+ endif
+ " Wait for some output
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 3))})
+
+ " Go to Terminal-Normal mode for a moment.
+ call feedkeys("\<C-W>N", 'xt')
+ " Open a new window
+ call feedkeys("i\<C-W>n", 'xt')
+ call assert_equal(3, winnr('$'))
+ redraw
+
+ close
+ call assert_equal(2, winnr('$'))
+ call feedkeys("exit\<CR>", 'xt')
+ call TermWait(buf)
+ set statusline&
+endfunc
+
+" must be nearly the last, we can't go back from GUI to terminal
+func Test_zz1_terminal_in_gui()
+ CheckCanRunGui
+
+ " Ignore the "failed to create input context" error.
+ call test_ignore_error('E285:')
+
+ gui -f
+
+ call assert_equal(1, winnr('$'))
+ let buf = Run_shell_in_terminal({'term_finish': 'close'})
+ call StopShellInTerminal(buf)
+
+ " closing window wipes out the terminal buffer a with finished job
+ call WaitForAssert({-> assert_equal(1, winnr('$'))})
+ call assert_equal("", bufname(buf))
+
+ unlet g:job
+endfunc
+
+func Test_zz2_terminal_guioptions_bang()
+ CheckGui
+ set guioptions+=!
+
+ let filename = 'Xtestscript'
+ if has('win32')
+ let filename .= '.bat'
+ let prefix = ''
+ let contents = ['@echo off', 'exit %1']
+ else
+ let filename .= '.sh'
+ let prefix = './'
+ let contents = ['#!/bin/sh', 'exit $1']
+ endif
+ call writefile(contents, filename, 'D')
+ call setfperm(filename, 'rwxrwx---')
+
+ " Check if v:shell_error is equal to the exit status.
+ let exitval = 0
+ execute printf(':!%s%s %d', prefix, filename, exitval)
+ call assert_equal(exitval, v:shell_error)
+
+ let exitval = 9
+ execute printf(':!%s%s %d', prefix, filename, exitval)
+ call assert_equal(exitval, v:shell_error)
+
+ set guioptions&
+endfunc
+
+func Test_terminal_hidden()
+ CheckUnix
+
+ term ++hidden cat
+ let bnr = bufnr('$')
+ call assert_equal('terminal', getbufvar(bnr, '&buftype'))
+ exe 'sbuf ' . bnr
+ call assert_equal('terminal', &buftype)
+ call term_sendkeys(bnr, "asdf\<CR>")
+ call WaitForAssert({-> assert_match('asdf', term_getline(bnr, 2))})
+ call term_sendkeys(bnr, "\<C-D>")
+ call WaitForAssert({-> assert_equal('finished', bnr->term_getstatus())})
+ bwipe!
+endfunc
+
+func Test_terminal_switch_mode()
+ term
+ let bnr = bufnr('$')
+ call WaitForAssert({-> assert_equal('running', term_getstatus(bnr))})
+ " In the GUI the first switch sometimes doesn't work. Switch twice to avoid
+ " flakiness.
+ call feedkeys("\<C-W>N", 'xt')
+ call feedkeys("A", 'xt')
+ call WaitForAssert({-> assert_equal('running', term_getstatus(bnr))})
+ call feedkeys("\<C-W>N", 'xt')
+ call WaitForAssert({-> assert_equal('running,normal', term_getstatus(bnr))})
+ call feedkeys("A", 'xt')
+ call WaitForAssert({-> assert_equal('running', term_getstatus(bnr))})
+ call feedkeys("\<C-\>\<C-N>", 'xt')
+ call WaitForAssert({-> assert_equal('running,normal', term_getstatus(bnr))})
+ call feedkeys("I", 'xt')
+ call WaitForAssert({-> assert_equal('running', term_getstatus(bnr))})
+ call feedkeys("\<C-W>Nv", 'xt')
+ call WaitForAssert({-> assert_equal('running,normal', term_getstatus(bnr))})
+ call feedkeys("I", 'xt')
+ call WaitForAssert({-> assert_equal('running', term_getstatus(bnr))})
+ call feedkeys("\<C-W>Nv", 'xt')
+ call WaitForAssert({-> assert_equal('running,normal', term_getstatus(bnr))})
+ call feedkeys("A", 'xt')
+ call WaitForAssert({-> assert_equal('running', term_getstatus(bnr))})
+ bwipe!
+endfunc
+
+func Test_terminal_normal_mode()
+ CheckRunVimInTerminal
+
+ " Run Vim in a terminal and open a terminal window to run Vim in.
+ let lines =<< trim END
+ call setline(1, range(11111, 11122))
+ 3
+ END
+ call writefile(lines, 'XtermNormal', 'D')
+ let buf = RunVimInTerminal('-S XtermNormal', {'rows': 8})
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "\<C-W>N")
+ call term_sendkeys(buf, ":set number cursorline culopt=both\r")
+ call VerifyScreenDump(buf, 'Test_terminal_normal_1', {})
+
+ call term_sendkeys(buf, ":set culopt=number\r")
+ call VerifyScreenDump(buf, 'Test_terminal_normal_2', {})
+
+ call term_sendkeys(buf, ":set culopt=line\r")
+ call VerifyScreenDump(buf, 'Test_terminal_normal_3', {})
+
+ call assert_fails('call term_sendkeys(buf, [])', 'E730:')
+ call term_sendkeys(buf, "a:q!\<CR>:q\<CR>:q\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_hidden_and_close()
+ CheckUnix
+
+ call assert_equal(1, winnr('$'))
+ term ++hidden ++close ls
+ let bnr = bufnr('$')
+ call assert_equal('terminal', getbufvar(bnr, '&buftype'))
+ call WaitForAssert({-> assert_false(bufexists(bnr))})
+ call assert_equal(1, winnr('$'))
+endfunc
+
+func Test_terminal_does_not_truncate_last_newlines()
+ if has('conpty')
+ throw 'Skipped: fail on ConPTY'
+ endif
+ let g:test_is_flaky = 1
+ let contents = [
+ \ [ 'One', '', 'X' ],
+ \ [ 'Two', '', '' ],
+ \ [ 'Three' ] + repeat([''], 30)
+ \ ]
+
+ for c in contents
+ call writefile(c, 'Xdntfile', 'D')
+ if has('win32')
+ term cmd /c type Xdntfile
+ else
+ term cat Xdntfile
+ endif
+ let bnr = bufnr('$')
+ call assert_equal('terminal', getbufvar(bnr, '&buftype'))
+ call WaitForAssert({-> assert_equal('finished', term_getstatus(bnr))})
+ sleep 100m
+ call assert_equal(c, getline(1, line('$')))
+ quit
+ endfor
+endfunc
+
+func GetDummyCmd()
+ if has('win32')
+ return 'cmd /c ""'
+ else
+ CheckExecutable false
+ return 'false'
+ endif
+endfunc
+
+func Test_terminal_no_job()
+ let cmd = GetDummyCmd()
+ let term = term_start(cmd, {'term_finish': 'close'})
+ call WaitForAssert({-> assert_equal(v:null, term_getjob(term)) })
+endfunc
+
+func Test_term_getcursor()
+ CheckUnix
+
+ let buf = Run_shell_in_terminal({})
+
+ " Wait for the shell to display a prompt.
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+
+ " Hide the cursor.
+ call term_sendkeys(buf, "echo -e '\\033[?25l'\r")
+ call WaitForAssert({-> assert_equal(0, term_getcursor(buf)[2].visible)})
+
+ " Show the cursor.
+ call term_sendkeys(buf, "echo -e '\\033[?25h'\r")
+ call WaitForAssert({-> assert_equal(1, buf->term_getcursor()[2].visible)})
+
+ " Change color of cursor.
+ call WaitForAssert({-> assert_equal('', term_getcursor(buf)[2].color)})
+ call term_sendkeys(buf, "echo -e '\\033]12;blue\\007'\r")
+ call WaitForAssert({-> assert_equal('blue', term_getcursor(buf)[2].color)})
+ call term_sendkeys(buf, "echo -e '\\033]12;green\\007'\r")
+ call WaitForAssert({-> assert_equal('green', term_getcursor(buf)[2].color)})
+
+ " Make cursor a blinking block.
+ call term_sendkeys(buf, "echo -e '\\033[1 q'\r")
+ call WaitForAssert({-> assert_equal([1, 1],
+ \ [term_getcursor(buf)[2].blink, term_getcursor(buf)[2].shape])})
+
+ " Make cursor a steady block.
+ call term_sendkeys(buf, "echo -e '\\033[2 q'\r")
+ call WaitForAssert({-> assert_equal([0, 1],
+ \ [term_getcursor(buf)[2].blink, term_getcursor(buf)[2].shape])})
+
+ " Make cursor a blinking underline.
+ call term_sendkeys(buf, "echo -e '\\033[3 q'\r")
+ call WaitForAssert({-> assert_equal([1, 2],
+ \ [term_getcursor(buf)[2].blink, term_getcursor(buf)[2].shape])})
+
+ " Make cursor a steady underline.
+ call term_sendkeys(buf, "echo -e '\\033[4 q'\r")
+ call WaitForAssert({-> assert_equal([0, 2],
+ \ [term_getcursor(buf)[2].blink, term_getcursor(buf)[2].shape])})
+
+ " Make cursor a blinking vertical bar.
+ call term_sendkeys(buf, "echo -e '\\033[5 q'\r")
+ call WaitForAssert({-> assert_equal([1, 3],
+ \ [term_getcursor(buf)[2].blink, term_getcursor(buf)[2].shape])})
+
+ " Make cursor a steady vertical bar.
+ call term_sendkeys(buf, "echo -e '\\033[6 q'\r")
+ call WaitForAssert({-> assert_equal([0, 3],
+ \ [term_getcursor(buf)[2].blink, term_getcursor(buf)[2].shape])})
+
+ call StopShellInTerminal(buf)
+endfunc
+
+" Test for term_gettitle()
+func Test_term_gettitle()
+ " term_gettitle() returns an empty string for a non-terminal buffer
+ " and for a non-existing buffer.
+ call assert_equal('', bufnr('%')->term_gettitle())
+ call assert_equal('', term_gettitle(bufnr('$') + 1))
+
+ if !has('title') || empty(&t_ts)
+ throw "Skipped: can't get/set title"
+ endif
+
+ let term = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', '-c', 'set title'])
+ call TermWait(term)
+ " When Vim is running as a server then the title ends in VIM{number}, thus
+ " optionally match a number after "VIM".
+ call WaitForAssert({-> assert_match('^\[No Name\] - VIM\d*$', term_gettitle(term)) })
+ call term_sendkeys(term, ":e Xfoo\r")
+ call WaitForAssert({-> assert_match('^Xfoo (.*[/\\]testdir) - VIM\d*$', term_gettitle(term)) })
+
+ call term_sendkeys(term, ":set titlestring=foo\r")
+ call WaitForAssert({-> assert_equal('foo', term_gettitle(term)) })
+
+ exe term . 'bwipe!'
+endfunc
+
+func Test_term_gettty()
+ let buf = Run_shell_in_terminal({})
+ let gettty = term_gettty(buf)
+
+ if has('unix') && executable('tty')
+ " Find tty using the tty shell command.
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 1))})
+ call term_sendkeys(buf, "tty\r")
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 3))})
+ let tty = term_getline(buf, 2)
+ call assert_equal(tty, gettty)
+ endif
+
+ let gettty0 = term_gettty(buf, 0)
+ let gettty1 = term_gettty(buf, 1)
+
+ call assert_equal(gettty, gettty0)
+ call assert_equal(job_info(g:job).tty_out, gettty0)
+ call assert_equal(job_info(g:job).tty_in, gettty1)
+
+ if has('unix')
+ " For unix, term_gettty(..., 0) and term_gettty(..., 1)
+ " are identical according to :help term_gettty()
+ call assert_equal(gettty0, gettty1)
+ call assert_match('^/dev/', gettty)
+ else
+ " ConPTY works on anonymous pipe.
+ if !has('conpty')
+ call assert_match('^\\\\.\\pipe\\', gettty0)
+ call assert_match('^\\\\.\\pipe\\', gettty1)
+ endif
+ endif
+
+ call assert_fails('call term_gettty(buf, 2)', 'E475:')
+ call assert_fails('call term_gettty(buf, -1)', 'E475:')
+
+ call assert_equal('', term_gettty(buf + 1))
+
+ call StopShellInTerminal(buf)
+ exe buf . 'bwipe'
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_terminal3.vim b/src/testdir/test_terminal3.vim
new file mode 100644
index 0000000..96a9e63
--- /dev/null
+++ b/src/testdir/test_terminal3.vim
@@ -0,0 +1,935 @@
+" Tests for the terminal window.
+" This is split in two, because it can take a lot of time.
+" See test_terminal.vim and test_terminal2.vim for further tests.
+
+source check.vim
+CheckFeature terminal
+
+source shared.vim
+source screendump.vim
+source mouse.vim
+source term_util.vim
+
+let $PROMPT_COMMAND=''
+
+func Test_terminal_altscreen()
+ " somehow doesn't work on MS-Windows
+ CheckUnix
+ let cmd = "cat Xtext\<CR>"
+
+ let buf = term_start(&shell, {})
+ call TermWait(buf)
+ call writefile(["\<Esc>[?1047h"], 'Xtext', 'D')
+ call term_sendkeys(buf, cmd)
+ call WaitForAssert({-> assert_equal(1, term_getaltscreen(buf))})
+
+ call writefile(["\<Esc>[?1047l"], 'Xtext')
+ call term_sendkeys(buf, cmd)
+ call WaitForAssert({-> assert_equal(0, term_getaltscreen(buf))})
+
+ call term_sendkeys(buf, "exit\r")
+ exe buf . "bwipe!"
+endfunc
+
+func Test_terminal_shell_option()
+ if has('unix')
+ " exec is a shell builtin command, should fail without a shell.
+ term exec ls runtest.vim
+ call WaitForAssert({-> assert_match('job failed', term_getline(bufnr(), 1))})
+ bwipe!
+
+ term ++shell exec ls runtest.vim
+ call WaitForAssert({-> assert_match('runtest.vim', term_getline(bufnr(), 1))})
+ bwipe!
+ elseif has('win32')
+ " dir is a shell builtin command, should fail without a shell.
+ " However, if dir.exe (which might be provided by Cygwin/MSYS2) exists in
+ " the %PATH%, "term dir" succeeds unintentionally. Use dir.com instead.
+ try
+ term dir.com /b runtest.vim
+ call WaitForAssert({-> assert_match('job failed', term_getline(bufnr(), 1))})
+ catch /CreateProcess/
+ " ignore
+ endtry
+ bwipe!
+
+ " This should execute the dir builtin command even with ".com".
+ term ++shell dir.com /b runtest.vim
+ call WaitForAssert({-> assert_match('runtest.vim', term_getline(bufnr(), 1))})
+ bwipe!
+ else
+ throw 'Skipped: does not work on this platform'
+ endif
+endfunc
+
+func Test_terminal_invalid_arg()
+ call assert_fails('terminal ++xyz', 'E181:')
+endfunc
+
+" Check a terminal with different colors
+func Terminal_color(group_name, highlight_cmds, highlight_opt, open_cmds)
+ CheckRunVimInTerminal
+ CheckUnix
+
+ let lines = [
+ \ 'call setline(1, range(20))',
+ \ 'func OpenTerm()',
+ \ ' set noruler',
+ \ " call term_start('cat', #{vertical: 1, " .. a:highlight_opt .. "})",
+ \ ] + a:open_cmds + [
+ \ 'endfunc',
+ \ ] + a:highlight_cmds
+ call writefile(lines, 'XtermStart', 'D')
+ let buf = RunVimInTerminal('-S XtermStart', #{rows: 15})
+ call TermWait(buf, 100)
+ call term_sendkeys(buf, ":call OpenTerm()\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "hello\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_color_' .. a:group_name, {})
+
+ call term_sendkeys(buf, "\<C-D>")
+ call TermWait(buf, 50)
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_color_Terminal()
+ call Terminal_color("Terminal", [
+ \ "highlight Terminal ctermfg=blue ctermbg=yellow",
+ \ ], "", [])
+endfunc
+
+func Test_terminal_color_group()
+ call Terminal_color("MyTermCol", [
+ \ "highlight MyTermCol ctermfg=darkgreen ctermbg=lightblue",
+ \ ], "term_highlight: 'MyTermCol',", [])
+endfunc
+
+func Test_terminal_color_wincolor()
+ call Terminal_color("MyWinCol", [
+ \ "highlight MyWinCol ctermfg=red ctermbg=darkyellow",
+ \ ], "", [
+ \ 'set wincolor=MyWinCol',
+ \ ])
+endfunc
+
+func Test_terminal_color_group_over_Terminal()
+ call Terminal_color("MyTermCol_over_Terminal", [
+ \ "highlight Terminal ctermfg=blue ctermbg=yellow",
+ \ "highlight MyTermCol ctermfg=darkgreen ctermbg=lightblue",
+ \ ], "term_highlight: 'MyTermCol',", [])
+endfunc
+
+func Test_terminal_color_wincolor_over_group()
+ call Terminal_color("MyWinCol_over_group", [
+ \ "highlight MyTermCol ctermfg=darkgreen ctermbg=lightblue",
+ \ "highlight MyWinCol ctermfg=red ctermbg=darkyellow",
+ \ ], "term_highlight: 'MyTermCol',", [
+ \ 'set wincolor=MyWinCol',
+ \ ])
+endfunc
+
+func Test_terminal_color_wincolor_split()
+ CheckRunVimInTerminal
+ CheckUnix
+
+ let lines = [
+ \ 'call setline(1, range(20))',
+ \ 'func OpenTerm()',
+ \ ' set noruler',
+ \ " call term_start('cat', #{vertical: 1, term_highlight: 'MyTermCol'})",
+ \ 'endfunc',
+ \ 'highlight MyTermCol ctermfg=darkgreen ctermbg=lightblue',
+ \ 'highlight MyWinCol ctermfg=red ctermbg=darkyellow',
+ \ 'highlight MyWinCol2 ctermfg=black ctermbg=blue',
+ \ ]
+ call writefile(lines, 'XtermStart', 'D')
+ let buf = RunVimInTerminal('-S XtermStart', #{rows: 15})
+ call TermWait(buf, 100)
+ call term_sendkeys(buf, ":call OpenTerm()\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "hello\<CR>")
+ call TermWait(buf, 50)
+
+ call term_sendkeys(buf, "\<C-W>:split\<CR>")
+ call term_sendkeys(buf, "\<C-W>:set wincolor=MyWinCol\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_wincolor_split_MyWinCol', {})
+
+ call term_sendkeys(buf, "\<C-W>b:2sb\<CR>")
+ call term_sendkeys(buf, "\<C-W>:set wincolor=MyWinCol2\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_wincolor_split_MyWinCol2', {})
+
+ call term_sendkeys(buf, "\<C-D>")
+ call TermWait(buf, 50)
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_color_transp_Terminal()
+ call Terminal_color("transp_Terminal", [
+ \ "highlight Terminal ctermfg=blue",
+ \ ], "", [])
+endfunc
+
+func Test_terminal_color_transp_group()
+ call Terminal_color("transp_MyTermCol", [
+ \ "highlight MyTermCol ctermfg=darkgreen",
+ \ ], "term_highlight: 'MyTermCol',", [])
+endfunc
+
+func Test_terminal_color_transp_wincolor()
+ call Terminal_color("transp_MyWinCol", [
+ \ "highlight MyWinCol ctermfg=red",
+ \ ], "", [
+ \ 'set wincolor=MyWinCol',
+ \ ])
+endfunc
+
+func Test_terminal_color_gui_Terminal()
+ CheckFeature termguicolors
+ call Terminal_color("gui_Terminal", [
+ \ "set termguicolors",
+ \ "highlight Terminal guifg=#3344ff guibg=#b0a700",
+ \ ], "", [])
+endfunc
+
+func Test_terminal_color_gui_group()
+ CheckFeature termguicolors
+ call Terminal_color("gui_MyTermCol", [
+ \ "set termguicolors",
+ \ "highlight MyTermCol guifg=#007800 guibg=#6789ff",
+ \ ], "term_highlight: 'MyTermCol',", [])
+endfunc
+
+func Test_terminal_color_gui_wincolor()
+ CheckFeature termguicolors
+ call Terminal_color("gui_MyWinCol", [
+ \ "set termguicolors",
+ \ "highlight MyWinCol guifg=#fe1122 guibg=#818100",
+ \ ], "", [
+ \ 'set wincolor=MyWinCol',
+ \ ])
+endfunc
+
+func Test_terminal_color_gui_transp_Terminal()
+ CheckFeature termguicolors
+ call Terminal_color("gui_transp_Terminal", [
+ \ "set termguicolors",
+ \ "highlight Terminal guifg=#3344ff",
+ \ ], "", [])
+endfunc
+
+func Test_terminal_color_gui_transp_group()
+ CheckFeature termguicolors
+ call Terminal_color("gui_transp_MyTermCol", [
+ \ "set termguicolors",
+ \ "highlight MyTermCol guifg=#007800",
+ \ ], "term_highlight: 'MyTermCol',", [])
+endfunc
+
+func Test_terminal_color_gui_transp_wincolor()
+ CheckFeature termguicolors
+ call Terminal_color("gui_transp_MyWinCol", [
+ \ "set termguicolors",
+ \ "highlight MyWinCol guifg=#fe1122",
+ \ ], "", [
+ \ 'set wincolor=MyWinCol',
+ \ ])
+endfunc
+
+func Test_terminal_in_popup()
+ CheckRunVimInTerminal
+
+ let text =<< trim END
+ some text
+ to edit
+ in a popup window
+ END
+ call writefile(text, 'Xtext', 'D')
+ let cmd = GetVimCommandCleanTerm()
+ let lines = [
+ \ 'call setline(1, range(20))',
+ \ 'hi PopTerm ctermbg=grey',
+ \ 'func OpenTerm(setColor)',
+ \ " set noruler",
+ \ " let s:buf = term_start('" .. cmd .. " Xtext', #{hidden: 1, term_finish: 'close'})",
+ \ ' let g:winid = popup_create(s:buf, #{minwidth: 45, minheight: 7, border: [], drag: 1, resize: 1})',
+ \ ' if a:setColor',
+ \ ' call win_execute(g:winid, "set wincolor=PopTerm")',
+ \ ' endif',
+ \ 'endfunc',
+ \ 'func HidePopup()',
+ \ ' call popup_hide(g:winid)',
+ \ 'endfunc',
+ \ 'func ClosePopup()',
+ \ ' call popup_close(g:winid)',
+ \ 'endfunc',
+ \ 'func ReopenPopup()',
+ \ ' call popup_create(s:buf, #{minwidth: 40, minheight: 6, border: []})',
+ \ 'endfunc',
+ \ ]
+ call writefile(lines, 'XtermPopup', 'D')
+ let buf = RunVimInTerminal('-S XtermPopup', #{rows: 15})
+ call TermWait(buf, 200)
+ call term_sendkeys(buf, ":call OpenTerm(0)\<CR>")
+ call TermWait(buf, 800)
+ call term_sendkeys(buf, ":\<CR>")
+ call TermWait(buf, 500)
+ call term_sendkeys(buf, "\<C-W>:echo getwinvar(g:winid, \"&buftype\") win_gettype(g:winid)\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_1', {})
+
+ call term_sendkeys(buf, ":q\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_2', {})
+
+ call term_sendkeys(buf, ":call OpenTerm(1)\<CR>")
+ call TermWait(buf, 800)
+ call term_sendkeys(buf, ":set hlsearch\<CR>")
+ call TermWait(buf, 500)
+ call term_sendkeys(buf, "/edit\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_3', {})
+
+ call term_sendkeys(buf, "\<C-W>:call HidePopup()\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_4', {})
+ call term_sendkeys(buf, "\<CR>")
+ call TermWait(buf, 50)
+
+ call term_sendkeys(buf, "\<C-W>:call ClosePopup()\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_5', {})
+
+ call term_sendkeys(buf, "\<C-W>:call ReopenPopup()\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_6', {})
+
+ " Go to terminal-Normal mode and visually select text.
+ call term_sendkeys(buf, "\<C-W>Ngg/in\<CR>vww")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_7', {})
+
+ " Back to job mode, redraws
+ call term_sendkeys(buf, "A")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_8', {})
+
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":q\<CR>")
+ call TermWait(buf, 250) " wait for terminal to vanish
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Check a terminal in popup window uses the default minimum size.
+func Test_terminal_in_popup_min_size()
+ CheckRunVimInTerminal
+
+ let text =<< trim END
+ another text
+ to show
+ in a popup window
+ END
+ call writefile(text, 'Xtext', 'D')
+ let lines = [
+ \ 'call setline(1, range(20))',
+ \ 'func OpenTerm()',
+ \ " let s:buf = term_start('cat Xtext', #{hidden: 1})",
+ \ ' let g:winid = popup_create(s:buf, #{ border: []})',
+ \ 'endfunc',
+ \ ]
+ call writefile(lines, 'XtermPopup', 'D')
+ let buf = RunVimInTerminal('-S XtermPopup', #{rows: 15})
+ call TermWait(buf, 100)
+ call term_sendkeys(buf, ":set noruler\<CR>")
+ call term_sendkeys(buf, ":call OpenTerm()\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_m1', {})
+
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":q\<CR>")
+ call TermWait(buf, 50) " wait for terminal to vanish
+ call StopVimInTerminal(buf)
+endfunc
+
+" Check a terminal in popup window with different colors
+func Terminal_in_popup_color(group_name, highlight_cmds, highlight_opt, popup_cmds, popup_opt)
+ CheckRunVimInTerminal
+ CheckUnix
+
+ let lines = [
+ \ 'call setline(1, range(20))',
+ \ 'func OpenTerm()',
+ \ " let s:buf = term_start('cat', #{hidden: 1, "
+ \ .. a:highlight_opt .. "})",
+ \ ' let g:winid = popup_create(s:buf, #{border: [], '
+ \ .. a:popup_opt .. '})',
+ \ ] + a:popup_cmds + [
+ \ 'endfunc',
+ \ ] + a:highlight_cmds
+ call writefile(lines, 'XtermPopup', 'D')
+ let buf = RunVimInTerminal('-S XtermPopup', #{rows: 15})
+ call TermWait(buf, 100)
+ call term_sendkeys(buf, ":set noruler\<CR>")
+ call term_sendkeys(buf, ":call OpenTerm()\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "hello\<CR>")
+ call VerifyScreenDump(buf, 'Test_terminal_popup_' .. a:group_name, {})
+
+ call term_sendkeys(buf, "\<C-D>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":q\<CR>")
+ call TermWait(buf, 50) " wait for terminal to vanish
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_terminal_in_popup_color_Terminal()
+ call Terminal_in_popup_color("Terminal", [
+ \ "highlight Terminal ctermfg=blue ctermbg=yellow",
+ \ ], "", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_group()
+ call Terminal_in_popup_color("MyTermCol", [
+ \ "highlight MyTermCol ctermfg=darkgreen ctermbg=lightblue",
+ \ ], "term_highlight: 'MyTermCol',", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_wincolor()
+ call Terminal_in_popup_color("MyWinCol", [
+ \ "highlight MyWinCol ctermfg=red ctermbg=darkyellow",
+ \ ], "", [
+ \ 'call setwinvar(g:winid, "&wincolor", "MyWinCol")',
+ \ ], "")
+endfunc
+
+func Test_terminal_in_popup_color_popup_highlight()
+ call Terminal_in_popup_color("MyPopupHlCol", [
+ \ "highlight MyPopupHlCol ctermfg=cyan ctermbg=green",
+ \ ], "", [], "highlight: 'MyPopupHlCol'")
+endfunc
+
+func Test_terminal_in_popup_color_group_over_Terminal()
+ call Terminal_in_popup_color("MyTermCol_over_Terminal", [
+ \ "highlight Terminal ctermfg=blue ctermbg=yellow",
+ \ "highlight MyTermCol ctermfg=darkgreen ctermbg=lightblue",
+ \ ], "term_highlight: 'MyTermCol',", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_wincolor_over_group()
+ call Terminal_in_popup_color("MyWinCol_over_group", [
+ \ "highlight MyTermCol ctermfg=darkgreen ctermbg=lightblue",
+ \ "highlight MyWinCol ctermfg=red ctermbg=darkyellow",
+ \ ], "term_highlight: 'MyTermCol',", [
+ \ 'call setwinvar(g:winid, "&wincolor", "MyWinCol")',
+ \ ], "")
+endfunc
+
+func Test_terminal_in_popup_color_transp_Terminal()
+ call Terminal_in_popup_color("transp_Terminal", [
+ \ "highlight Terminal ctermfg=blue",
+ \ ], "", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_transp_group()
+ call Terminal_in_popup_color("transp_MyTermCol", [
+ \ "highlight MyTermCol ctermfg=darkgreen",
+ \ ], "term_highlight: 'MyTermCol',", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_transp_wincolor()
+ call Terminal_in_popup_color("transp_MyWinCol", [
+ \ "highlight MyWinCol ctermfg=red",
+ \ ], "", [
+ \ 'call setwinvar(g:winid, "&wincolor", "MyWinCol")',
+ \ ], "")
+endfunc
+
+func Test_terminal_in_popup_color_transp_popup_highlight()
+ call Terminal_in_popup_color("transp_MyPopupHlCol", [
+ \ "highlight MyPopupHlCol ctermfg=cyan",
+ \ ], "", [], "highlight: 'MyPopupHlCol'")
+endfunc
+
+func Test_terminal_in_popup_color_gui_Terminal()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_Terminal", [
+ \ "set termguicolors",
+ \ "highlight Terminal guifg=#3344ff guibg=#b0a700",
+ \ ], "", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_gui_group()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_MyTermCol", [
+ \ "set termguicolors",
+ \ "highlight MyTermCol guifg=#007800 guibg=#6789ff",
+ \ ], "term_highlight: 'MyTermCol',", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_gui_wincolor()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_MyWinCol", [
+ \ "set termguicolors",
+ \ "highlight MyWinCol guifg=#fe1122 guibg=#818100",
+ \ ], "", [
+ \ 'call setwinvar(g:winid, "&wincolor", "MyWinCol")',
+ \ ], "")
+endfunc
+
+func Test_terminal_in_popup_color_gui_popup_highlight()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_MyPopupHlCol", [
+ \ "set termguicolors",
+ \ "highlight MyPopupHlCol guifg=#00e8f0 guibg=#126521",
+ \ ], "", [], "highlight: 'MyPopupHlCol'")
+endfunc
+
+func Test_terminal_in_popup_color_gui_transp_Terminal()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_transp_Terminal", [
+ \ "set termguicolors",
+ \ "highlight Terminal guifg=#3344ff",
+ \ ], "", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_gui_transp_group()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_transp_MyTermCol", [
+ \ "set termguicolors",
+ \ "highlight MyTermCol guifg=#007800",
+ \ ], "term_highlight: 'MyTermCol',", [], "")
+endfunc
+
+func Test_terminal_in_popup_color_gui_transp_wincolor()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_transp_MyWinCol", [
+ \ "set termguicolors",
+ \ "highlight MyWinCol guifg=#fe1122",
+ \ ], "", [
+ \ 'call setwinvar(g:winid, "&wincolor", "MyWinCol")',
+ \ ], "")
+endfunc
+
+func Test_terminal_in_popup_color_gui_transp_popup_highlight()
+ CheckFeature termguicolors
+ call Terminal_in_popup_color("gui_transp_MyPopupHlCol", [
+ \ "set termguicolors",
+ \ "highlight MyPopupHlCol guifg=#00e8f0",
+ \ ], "", [], "highlight: 'MyPopupHlCol'")
+endfunc
+
+func Test_double_popup_terminal()
+ let buf1 = term_start(&shell, #{hidden: 1})
+ let win1 = popup_create(buf1, {})
+ let buf2 = term_start(&shell, #{hidden: 1})
+ call assert_fails('call popup_create(buf2, {})', 'E861:')
+ call popup_close(win1)
+ exe buf1 .. 'bwipe!'
+ exe buf2 .. 'bwipe!'
+endfunc
+
+func Test_escape_popup_terminal()
+ set hidden
+
+ " Cannot escape a terminal popup window using win_gotoid
+ let prev_win = win_getid()
+ eval term_start('sh', #{hidden: 1, term_finish: 'close'})->popup_create({})
+ call assert_fails("call win_gotoid(" .. prev_win .. ")", 'E863:')
+
+ call popup_clear(1)
+ set hidden&
+endfunc
+
+func Test_issue_5607()
+ let wincount = winnr('$')
+ exe 'terminal' &shell &shellcmdflag 'exit'
+ let job = term_getjob(bufnr())
+ call WaitForAssert({-> assert_equal("dead", job_status(job))})
+
+ let old_wincolor = &wincolor
+ try
+ set wincolor=
+ finally
+ let &wincolor = old_wincolor
+ bw!
+ endtry
+endfunc
+
+func Test_hidden_terminal()
+ let buf = term_start(&shell, #{hidden: 1})
+ call assert_equal('', bufname('^$'))
+ call StopShellInTerminal(buf)
+endfunc
+
+func Test_term_nasty_callback()
+ CheckExecutable sh
+
+ set hidden
+ let g:buf0 = term_start('sh', #{hidden: 1, term_finish: 'close'})
+ call popup_create(g:buf0, {})
+ call assert_fails("call term_start(['sh', '-c'], #{curwin: 1})", 'E863:')
+
+ call popup_clear(1)
+ set hidden&
+endfunc
+
+func Test_term_and_startinsert()
+ CheckRunVimInTerminal
+ CheckUnix
+
+ let lines =<< trim EOL
+ put='some text'
+ term
+ startinsert
+ EOL
+ call writefile(lines, 'XTest_startinsert', 'D')
+ let buf = RunVimInTerminal('-S XTest_startinsert', {})
+
+ call term_sendkeys(buf, "exit\r")
+ call WaitForAssert({-> assert_equal("some text", term_getline(buf, 1))})
+ call term_sendkeys(buf, "0l")
+ call term_sendkeys(buf, "A<\<Esc>")
+ call WaitForAssert({-> assert_equal("some text<", term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for passing invalid arguments to terminal functions
+func Test_term_func_invalid_arg()
+ call assert_fails('let b = term_getaltscreen([])', 'E745:')
+ call assert_fails('let a = term_getattr(1, [])', 'E730:')
+ call assert_fails('let c = term_getcursor([])', 'E745:')
+ call assert_fails('let l = term_getline([], 1)', 'E745:')
+ call assert_fails('let l = term_getscrolled([])', 'E745:')
+ call assert_fails('let s = term_getsize([])', 'E745:')
+ call assert_fails('let s = term_getstatus([])', 'E745:')
+ call assert_fails('let s = term_scrape([], 1)', 'E745:')
+ call assert_fails('call term_sendkeys([], "a")', 'E745:')
+ call assert_fails('call term_setapi([], "")', 'E745:')
+ call assert_fails('call term_setrestore([], "")', 'E745:')
+ call assert_fails('call term_setkill([], "")', 'E745:')
+ if has('gui') || has('termguicolors')
+ call assert_fails('let p = term_getansicolors([])', 'E745:')
+ call assert_fails('call term_setansicolors([], [])', 'E745:')
+ endif
+ let buf = term_start('echo')
+ call assert_fails('call term_setapi(' .. buf .. ', {})', 'E731:')
+ call assert_fails('call term_setkill(' .. buf .. ', {})', 'E731:')
+ call assert_fails('call term_setrestore(' .. buf .. ', {})', 'E731:')
+ exe buf . "bwipe!"
+endfunc
+
+" Test for sending various special keycodes to a terminal
+func Test_term_keycode_translation()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', {})
+ call term_sendkeys(buf, ":set nocompatible\<CR>")
+ call term_sendkeys(buf, ":set timeoutlen=20\<CR>")
+
+ let keys = ["\<F1>", "\<F2>", "\<F3>", "\<F4>", "\<F5>", "\<F6>", "\<F7>",
+ \ "\<F8>", "\<F9>", "\<F10>", "\<F11>", "\<F12>", "\<Home>",
+ \ "\<S-Home>", "\<C-Home>", "\<End>", "\<S-End>", "\<C-End>",
+ \ "\<Ins>", "\<Del>", "\<Left>", "\<S-Left>", "\<C-Left>", "\<Right>",
+ \ "\<S-Right>", "\<C-Right>", "\<Up>", "\<S-Up>", "\<Down>",
+ \ "\<S-Down>"]
+ let output = ['<F1>', '<F2>', '<F3>', '<F4>', '<F5>', '<F6>', '<F7>',
+ \ '<F8>', '<F9>', '<F10>', '<F11>', '<F12>', '<Home>', '<S-Home>',
+ \ '<C-Home>', '<End>', '<S-End>', '<C-End>', '<Insert>', '<Del>',
+ \ '<Left>', '<S-Left>', '<C-Left>', '<Right>', '<S-Right>',
+ \ '<C-Right>', '<Up>', '<S-Up>', '<Down>', '<S-Down>']
+
+ call term_sendkeys(buf, "i")
+ for i in range(len(keys))
+ call term_sendkeys(buf, "\<C-U>\<C-K>" .. keys[i])
+ call WaitForAssert({-> assert_equal(output[i], term_getline(buf, 1))}, 200)
+ endfor
+
+ let keypad_keys = ["\<k0>", "\<k1>", "\<k2>", "\<k3>", "\<k4>", "\<k5>",
+ \ "\<k6>", "\<k7>", "\<k8>", "\<k9>", "\<kPoint>", "\<kPlus>",
+ \ "\<kMinus>", "\<kMultiply>", "\<kDivide>"]
+ let keypad_output = ['0', '1', '2', '3', '4', '5',
+ \ '6', '7', '8', '9', '.', '+',
+ \ '-', '*', '/']
+ for i in range(len(keypad_keys))
+ " TODO: Mysteriously keypad 3 and 9 do not work on some systems.
+ if keypad_output[i] == '3' || keypad_output[i] == '9'
+ continue
+ endif
+ call term_sendkeys(buf, "\<C-U>" .. keypad_keys[i])
+ call WaitForAssert({-> assert_equal(keypad_output[i], term_getline(buf, 1))}, 100)
+ endfor
+
+ call feedkeys("\<C-U>\<kEnter>\<BS>one\<C-W>.two", 'xt')
+ call WaitForAssert({-> assert_equal('two', term_getline(buf, 1))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for using the mouse in a terminal
+func Test_term_mouse()
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ let save_clipboard = &clipboard
+ set mouse=a term=xterm ttymouse=sgr mousetime=200 clipboard=
+
+ let lines =<< trim END
+ one two three four five
+ red green yellow red blue
+ vim emacs sublime nano
+ END
+ call writefile(lines, 'Xtest_mouse', 'D')
+
+ " Create a terminal window running Vim for the test with mouse enabled
+ let prev_win = win_getid()
+ let buf = RunVimInTerminal('Xtest_mouse -n', {})
+ call term_sendkeys(buf, ":set nocompatible\<CR>")
+ call term_sendkeys(buf, ":set mouse=a term=xterm ttymouse=sgr\<CR>")
+ call term_sendkeys(buf, ":set clipboard=\<CR>")
+ call term_sendkeys(buf, ":set mousemodel=extend\<CR>")
+ call TermWait(buf)
+ redraw!
+
+ " Funcref used in WaitFor() to check that the "Xbuf" file is readable and
+ " has some contents. This avoids a "List index out of range" error when the
+ " file hasn't been written yet.
+ let XbufNotEmpty = {-> filereadable('Xbuf') && len(readfile('Xbuf')) > 0}
+
+ " Use the mouse to enter the terminal window
+ call win_gotoid(prev_win)
+ call feedkeys(MouseLeftClickCode(1, 1), 'x')
+ call feedkeys(MouseLeftReleaseCode(1, 1), 'x')
+ call assert_equal(1, getwininfo(win_getid())[0].terminal)
+
+ " Test for <LeftMouse> click/release
+ call test_setmouse(2, 5)
+ call feedkeys("\<LeftMouse>\<LeftRelease>", 'xt')
+ call test_setmouse(3, 8)
+ call term_sendkeys(buf, "\<LeftMouse>\<LeftRelease>")
+ call TermWait(buf, 50)
+ call delete('Xbuf')
+ call term_sendkeys(buf, ":call writefile([json_encode(getpos('.'))], 'Xbuf')\<CR>")
+ call TermWait(buf, 50)
+ call WaitFor(XbufNotEmpty)
+ let pos = json_decode(readfile('Xbuf')[0])
+ call assert_equal([3, 8], pos[1:2])
+ call delete('Xbuf')
+
+ " Test for selecting text using mouse
+ call test_setmouse(2, 11)
+ call term_sendkeys(buf, "\<LeftMouse>")
+ call test_setmouse(2, 16)
+ call term_sendkeys(buf, "\<LeftRelease>y")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":call writefile([@\"], 'Xbuf')\<CR>")
+ call WaitFor(XbufNotEmpty)
+ call WaitForAssert({-> assert_equal('yellow', readfile('Xbuf')[0])})
+ call delete('Xbuf')
+
+ " Test for selecting text using double click
+ call test_setmouse(1, 11)
+ call term_sendkeys(buf, "\<LeftMouse>\<LeftRelease>\<LeftMouse>")
+ call test_setmouse(1, 17)
+ call term_sendkeys(buf, "\<LeftRelease>y")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":call writefile([@\"], 'Xbuf')\<CR>")
+ call WaitFor(XbufNotEmpty)
+ call assert_equal('three four', readfile('Xbuf')[0])
+ call delete('Xbuf')
+
+ " Test for selecting a line using triple click
+ call test_setmouse(3, 2)
+ call term_sendkeys(buf, "\<LeftMouse>\<LeftRelease>\<LeftMouse>\<LeftRelease>\<LeftMouse>\<LeftRelease>y")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":call writefile([@\"], 'Xbuf')\<CR>")
+ call WaitFor(XbufNotEmpty)
+ call assert_equal("vim emacs sublime nano\n", readfile('Xbuf')[0])
+ call delete('Xbuf')
+
+ " Test for selecting a block using quadruple click
+ call test_setmouse(1, 11)
+ call term_sendkeys(buf, "\<LeftMouse>\<LeftRelease>\<LeftMouse>\<LeftRelease>\<LeftMouse>\<LeftRelease>\<LeftMouse>")
+ call test_setmouse(3, 13)
+ call term_sendkeys(buf, "\<LeftRelease>y")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":call writefile([@\"], 'Xbuf')\<CR>")
+ call WaitFor(XbufNotEmpty)
+ call assert_equal("ree\nyel\nsub", readfile('Xbuf')[0])
+ call delete('Xbuf')
+
+ " Test for extending a selection using right click
+ call test_setmouse(2, 9)
+ call term_sendkeys(buf, "\<LeftMouse>\<LeftRelease>")
+ call test_setmouse(2, 16)
+ call term_sendkeys(buf, "\<RightMouse>\<RightRelease>y")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":call writefile([@\"], 'Xbuf')\<CR>")
+ call WaitFor(XbufNotEmpty)
+ call assert_equal("n yellow", readfile('Xbuf')[0])
+ call delete('Xbuf')
+
+ " Test for pasting text using middle click
+ call term_sendkeys(buf, ":let @r='bright '\<CR>")
+ call test_setmouse(2, 22)
+ call term_sendkeys(buf, "\"r\<MiddleMouse>\<MiddleRelease>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":call writefile([getline(2)], 'Xbuf')\<CR>")
+ call WaitFor(XbufNotEmpty)
+ call assert_equal("red bright blue", readfile('Xbuf')[0][-15:])
+ call delete('Xbuf')
+
+ " cleanup
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ let &clipboard = save_clipboard
+ set mousetime&
+ call delete('Xbuf')
+endfunc
+
+" Test for sync buffer cwd with shell's pwd
+func Test_terminal_sync_shell_dir()
+ CheckUnix
+ " The test always use sh (see src/testdir/unix.vim).
+ " BSD's sh doesn't seem to play well with the OSC 7 escape sequence.
+ CheckNotBSD
+
+ set asd
+ " , is
+ " 1. a valid character for directory names
+ " 2. a reserved character in url-encoding
+ let chars = ",a"
+ " "," is url-encoded as '%2C'
+ let chars_url = "%2Ca"
+ let tmpfolder = fnamemodify(tempname(),':h') .. '/' .. chars
+ let tmpfolder_url = fnamemodify(tempname(),':h') .. '/' .. chars_url
+ call mkdir(tmpfolder, "p")
+ let buf = Run_shell_in_terminal({})
+ call term_sendkeys(buf, "echo $'\\e\]7;file://" .. tmpfolder_url .. "\\a'\<CR>")
+ "call term_sendkeys(buf, "cd " .. tmpfolder .. "\<CR>")
+ call TermWait(buf)
+ if has("mac")
+ let expected = "/private" .. tmpfolder
+ else
+ let expected = tmpfolder
+ endif
+ call assert_equal(expected, getcwd(winnr()))
+
+ set noasd
+endfunc
+
+" Test for modeless selection in a terminal
+func Test_term_modeless_selection()
+ CheckUnix
+ CheckNotGui
+ CheckRunVimInTerminal
+ CheckFeature clipboard_working
+
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_ttymouse = &ttymouse
+ set mouse=a term=xterm ttymouse=sgr mousetime=200
+ set clipboard=autoselectml
+
+ let lines =<< trim END
+ one two three four five
+ red green yellow red blue
+ vim emacs sublime nano
+ END
+ call writefile(lines, 'Xtest_modeless', 'D')
+
+ " Create a terminal window running Vim for the test with mouse disabled
+ let prev_win = win_getid()
+ let buf = RunVimInTerminal('Xtest_modeless -n', {})
+ call term_sendkeys(buf, ":set nocompatible\<CR>")
+ call term_sendkeys(buf, ":set mouse=\<CR>")
+ call TermWait(buf)
+ redraw!
+
+ " Use the mouse to enter the terminal window
+ call win_gotoid(prev_win)
+ call feedkeys(MouseLeftClickCode(1, 1), 'x')
+ call feedkeys(MouseLeftReleaseCode(1, 1), 'x')
+ call TermWait(buf)
+ call assert_equal(1, getwininfo(win_getid())[0].terminal)
+
+ " Test for copying a modeless selection to clipboard
+ let @* = 'clean'
+ " communicating with X server may take a little time
+ sleep 100m
+ call feedkeys(MouseLeftClickCode(2, 3), 'x')
+ call feedkeys(MouseLeftDragCode(2, 11), 'x')
+ call feedkeys(MouseLeftReleaseCode(2, 11), 'x')
+ call assert_equal("d green y", @*)
+
+ " cleanup
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+ let &mouse = save_mouse
+ let &term = save_term
+ let &ttymouse = save_ttymouse
+ set mousetime& clipboard&
+ new | only!
+endfunc
+
+func Test_terminal_getwinpos()
+ CheckRunVimInTerminal
+
+ " split, go to the bottom-right window
+ split
+ wincmd j
+ set splitright
+
+ let buf = RunVimInTerminal('', {'cols': 60})
+ call TermWait(buf, 100)
+ call term_sendkeys(buf, ":echo getwinpos(500)\<CR>")
+
+ " Find the output of getwinpos() in the bottom line.
+ let rows = term_getsize(buf)[0]
+ call WaitForAssert({-> assert_match('\[\d\+, \d\+\]', term_getline(buf, rows))})
+ let line = term_getline(buf, rows)
+ let xpos = str2nr(substitute(line, '\[\(\d\+\), \d\+\]', '\1', ''))
+ let ypos = str2nr(substitute(line, '\[\d\+, \(\d\+\)\]', '\1', ''))
+
+ " Position must be bigger than the getwinpos() result of Vim itself.
+ " The calculation in the console assumes a 10 x 7 character cell.
+ " In the GUI it can be more, let's assume a 20 x 14 cell.
+ " And then add 100 / 200 tolerance.
+ let [xroot, yroot] = getwinpos()
+ let winpos = 50->getwinpos()
+ call assert_equal(xroot, winpos[0])
+ call assert_equal(yroot, winpos[1])
+ let [winrow, wincol] = win_screenpos(0)
+ let xoff = wincol * (has('gui_running') ? 14 : 7) + 100
+ let yoff = winrow * (has('gui_running') ? 20 : 10) + 200
+ call assert_inrange(xroot + 2, xroot + xoff, xpos)
+ call assert_inrange(yroot + 2, yroot + yoff, ypos)
+
+ call TermWait(buf)
+ call term_sendkeys(buf, ":q\<CR>")
+ call StopVimInTerminal(buf)
+ set splitright&
+ only!
+endfunc
+
+func Test_terminal_term_start_error()
+ func s:term_start_error() abort
+ try
+ return term_start([[]])
+ catch
+ return v:exception
+ finally
+ "
+ endtry
+ endfunc
+ autocmd WinEnter * call type(0)
+
+ " Must not crash in s:term_start_error, nor the exception thrown.
+ let result = s:term_start_error()
+ call assert_match('^Vim(return):E730:', result)
+
+ autocmd! WinEnter
+ delfunc s:term_start_error
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_terminal_fail.vim b/src/testdir/test_terminal_fail.vim
new file mode 100644
index 0000000..e430bfe
--- /dev/null
+++ b/src/testdir/test_terminal_fail.vim
@@ -0,0 +1,22 @@
+" This test is in a separate file, because it usually causes reports for memory
+" leaks under valgrind. That is because when fork/exec fails memory is not
+" freed. Since the process exits right away it's not a real leak.
+
+source check.vim
+CheckFeature terminal
+
+source shared.vim
+
+func Test_terminal_redir_fails()
+ CheckUnix
+
+ let buf = term_start('xyzabc', {'err_io': 'file', 'err_name': 'Xfile'})
+ call TermWait(buf)
+ call WaitFor('len(readfile("Xfile")) > 0')
+ call assert_match('executing job failed', readfile('Xfile')[0])
+ call WaitFor('!&modified')
+ call delete('Xfile')
+ bwipe
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_textformat.vim b/src/testdir/test_textformat.vim
new file mode 100644
index 0000000..c5f5975
--- /dev/null
+++ b/src/testdir/test_textformat.vim
@@ -0,0 +1,1306 @@
+" Tests for the various 'formatoptions' settings
+
+source check.vim
+
+func Test_text_format()
+ enew!
+
+ setl noai tw=2 fo=t
+ call append('$', [
+ \ '{',
+ \ ' ',
+ \ '',
+ \ '}'])
+ exe "normal /^{/+1\n0"
+ normal gRa b
+ let lnum = line('.')
+ call assert_equal([
+ \ 'a',
+ \ 'b'], getline(lnum - 1, lnum))
+
+ normal ggdG
+ setl ai tw=2 fo=tw
+ call append('$', [
+ \ '{',
+ \ 'a b ',
+ \ '',
+ \ 'a ',
+ \ '}'])
+ exe "normal /^{/+1\n0"
+ normal gqgqjjllab
+ let lnum = line('.')
+ call assert_equal([
+ \ 'a ',
+ \ 'b ',
+ \ '',
+ \ 'a ',
+ \ 'b'], getline(lnum - 4, lnum))
+
+ normal ggdG
+ setl tw=3 fo=t
+ call append('$', [
+ \ '{',
+ \ "a \<C-A>",
+ \ '}'])
+ exe "normal /^{/+1\n0"
+ exe "normal gqgqo\na \<C-V>\<C-A>"
+ let lnum = line('.')
+ call assert_equal([
+ \ 'a',
+ \ "\<C-A>",
+ \ '',
+ \ 'a',
+ \ "\<C-A>"], getline(lnum - 4, lnum))
+
+ normal ggdG
+ setl tw=2 fo=tcq1 comments=:#
+ call append('$', [
+ \ '{',
+ \ 'a b',
+ \ '#a b',
+ \ '}'])
+ exe "normal /^{/+1\n0"
+ exe "normal gqgqjgqgqo\na b\n#a b"
+ let lnum = line('.')
+ call assert_equal([
+ \ 'a b',
+ \ '#a b',
+ \ '',
+ \ 'a b',
+ \ '#a b'], getline(lnum - 4, lnum))
+
+ normal ggdG
+ setl tw=5 fo=tcn comments=:#
+ call append('$', [
+ \ '{',
+ \ ' 1 a',
+ \ '# 1 a',
+ \ '}'])
+ exe "normal /^{/+1\n0"
+ exe "normal A b\<Esc>jA b"
+ let lnum = line('.')
+ call assert_equal([
+ \ ' 1 a',
+ \ ' b',
+ \ '# 1 a',
+ \ '# b'], getline(lnum - 3, lnum))
+
+ normal ggdG
+ setl tw=5 fo=t2a si
+ call append('$', [
+ \ '{',
+ \ '',
+ \ ' x a',
+ \ ' b',
+ \ ' c',
+ \ '',
+ \ '}'])
+ exe "normal /^{/+3\n0"
+ exe "normal i \<Esc>A_"
+ let lnum = line('.')
+ call assert_equal([
+ \ '',
+ \ ' x a',
+ \ ' b_',
+ \ ' c',
+ \ ''], getline(lnum - 2, lnum + 2))
+
+ normal ggdG
+ setl tw=5 fo=qn comments=:#
+ call append('$', [
+ \ '{',
+ \ '# 1 a b',
+ \ '}'])
+ exe "normal /^{/+1\n5|"
+ normal gwap
+ call assert_equal(5, col('.'))
+ let lnum = line('.')
+ call assert_equal([
+ \ '# 1 a',
+ \ '# b'], getline(lnum, lnum + 1))
+
+ normal ggdG
+ setl tw=5 fo=q2 comments=:#
+ call append('$', [
+ \ '{',
+ \ '# x',
+ \ '# a b',
+ \ '}'])
+ exe "normal /^{/+1\n0"
+ normal gwap
+ let lnum = line('.')
+ call assert_equal([
+ \ '# x a',
+ \ '# b'], getline(lnum, lnum + 1))
+
+ normal ggdG
+ setl tw& fo=a
+ call append('$', [
+ \ '{',
+ \ ' 1aa',
+ \ ' 2bb',
+ \ '}'])
+ exe "normal /^{/+2\n0"
+ normal I^^
+ call assert_equal('{ 1aa ^^2bb }', getline('.'))
+
+ normal ggdG
+ setl tw=20 fo=an12wcq comments=s1:/*,mb:*,ex:*/
+ call append('$', [
+ \ '/* abc def ghi jkl ',
+ \ ' * mno pqr stu',
+ \ ' */'])
+ exe "normal /mno pqr/\n"
+ normal A vwx yz
+ let lnum = line('.')
+ call assert_equal([
+ \ ' * mno pqr stu ',
+ \ ' * vwx yz',
+ \ ' */'], getline(lnum - 1, lnum + 1))
+
+ normal ggdG
+ setl tw=12 fo=tqnc comments=:#
+ call setline('.', '# 1 xxxxx')
+ normal A foobar
+ call assert_equal([
+ \ '# 1 xxxxx',
+ \ '# foobar'], getline(1, 2))
+
+ " Test the 'p' flag for 'formatoptions'
+ " First test without the flag: that it will break "Mr. Feynman" at the space
+ normal ggdG
+ setl tw=28 fo=tcq
+ call setline('.', 'Surely you''re joking, Mr. Feynman!')
+ normal gqq
+ call assert_equal([
+ \ 'Surely you''re joking, Mr.',
+ \ 'Feynman!'], getline(1, 2))
+ " Now test with the flag: that it will push the name with the title onto the
+ " next line
+ normal ggdG
+ setl fo+=p
+ call setline('.', 'Surely you''re joking, Mr. Feynman!')
+ normal gqq
+ call assert_equal([
+ \ 'Surely you''re joking,',
+ \ 'Mr. Feynman!'], getline(1, 2))
+ " Ensure that it will still break if two spaces are entered
+ normal ggdG
+ call setline('.', 'Surely you''re joking, Mr. Feynman!')
+ normal gqq
+ call assert_equal([
+ \ 'Surely you''re joking, Mr.',
+ \ 'Feynman!'], getline(1, 2))
+
+ setl ai& tw& fo& si& comments&
+ enew!
+endfunc
+
+func Test_format_c_comment()
+ new
+ setl ai cindent tw=40 et fo=croql
+ let text =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
+ END
+ call setline(1, text)
+ normal gql
+ let expected =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf
+ // asdf asdf asdf asdf asdf
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ %del
+ let text =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf
+ END
+ call setline(1, text)
+ normal gql
+ let expected =<< trim END
+ var = 2345; // asdf asdf asdf asdf asdf
+ // asdf asdf asdf asdf asdf
+ // asdf asdf
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ %del
+ let text =<< trim END
+ #if 0 // This is another long end of
+ // line comment that
+ // wraps.
+ END
+ call setline(1, text)
+ normal gq2j
+ let expected =<< trim END
+ #if 0 // This is another long
+ // end of line comment
+ // that wraps.
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using either "o" or "O" repeats a line comment occupying a whole line.
+ %del
+ let text =<< trim END
+ nop;
+ // This is a comment
+ val = val;
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+ normal 2GO
+ let expected =<< trim END
+ nop;
+ //
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" repeats a line comment after a statement, "O" does not.
+ %del
+ let text =<< trim END
+ nop;
+ val = val; // This is a comment
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ val = val; // This is a comment
+ //
+ END
+ call assert_equal(expected, getline(1, '$'))
+ 3delete
+
+ " No comment repeated with a slash in 'formatoptions'
+ set fo+=/
+ normal 2Gox
+ let expected =<< trim END
+ nop;
+ val = val; // This is a comment
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Comment is formatted when it wraps
+ normal 2GA with some more text added
+ let expected =<< trim END
+ nop;
+ val = val; // This is a comment
+ // with some more text
+ // added
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set fo-=/
+
+ " using 'indentexpr' instead of 'cindent' does not repeat a comment
+ setl nocindent indentexpr=2
+ %del
+ let text =<< trim END
+ nop;
+ val = val; // This is a comment
+ END
+ call setline(1, text)
+ normal 2Gox
+ let expected =<< trim END
+ nop;
+ val = val; // This is a comment
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+ setl cindent indentexpr=
+ 3delete
+
+ normal 2GO
+ let expected =<< trim END
+ nop;
+
+ val = val; // This is a comment
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" does not repeat a comment in a string
+ %del
+ let text =<< trim END
+ nop;
+ val = " // This is not a comment";
+ END
+ call setline(1, text)
+ normal 2Gox
+ let expected =<< trim END
+ nop;
+ val = " // This is not a comment";
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using CTRL-U after "o" fixes the indent
+ %del
+ let text =<< trim END
+ {
+ val = val; // This is a comment
+ END
+ call setline(1, text)
+ exe "normal! 2Go\<C-U>x\<Esc>"
+ let expected =<< trim END
+ {
+ val = val; // This is a comment
+ x
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " typing comment text auto-wraps
+ %del
+ call setline(1, text)
+ exe "normal! 2GA blah more text blah.\<Esc>"
+ let expected =<< trim END
+ {
+ val = val; // This is a comment
+ // blah more text
+ // blah.
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Tests for :right, :center and :left on text with embedded TAB.
+func Test_format_align()
+ enew!
+ set tw=65
+
+ " :left alignment
+ call append(0, [
+ \ " test for :left",
+ \ " a a",
+ \ " fa a",
+ \ " dfa a",
+ \ " sdfa a",
+ \ " asdfa a",
+ \ " xasdfa a",
+ \ "asxxdfa a",
+ \ ])
+ %left
+ call assert_equal([
+ \ "test for :left",
+ \ "a a",
+ \ "fa a",
+ \ "dfa a",
+ \ "sdfa a",
+ \ "asdfa a",
+ \ "xasdfa a",
+ \ "asxxdfa a",
+ \ ""
+ \ ], getline(1, '$'))
+ enew!
+
+ " :center alignment
+ call append(0, [
+ \ " test for :center",
+ \ " a a",
+ \ " fa afd asdf",
+ \ " dfa a",
+ \ " sdfa afd asdf",
+ \ " asdfa a",
+ \ " xasdfa asdfasdfasdfasdfasdf",
+ \ "asxxdfa a"
+ \ ])
+ %center
+ call assert_equal([
+ \ " test for :center",
+ \ " a a",
+ \ " fa afd asdf",
+ \ " dfa a",
+ \ " sdfa afd asdf",
+ \ " asdfa a",
+ \ " xasdfa asdfasdfasdfasdfasdf",
+ \ " asxxdfa a",
+ \ ""
+ \ ], getline(1, '$'))
+ enew!
+
+ " :right alignment
+ call append(0, [
+ \ " test for :right",
+ \ " a a",
+ \ " fa a",
+ \ " dfa a",
+ \ " sdfa a",
+ \ " asdfa a",
+ \ " xasdfa a",
+ \ " asxxdfa a",
+ \ " asxa;ofa a",
+ \ " asdfaqwer a",
+ \ " a ax",
+ \ " fa ax",
+ \ " dfa ax",
+ \ " sdfa ax",
+ \ " asdfa ax",
+ \ " xasdfa ax",
+ \ " asxxdfa ax",
+ \ " asxa;ofa ax",
+ \ " asdfaqwer ax",
+ \ " a axx",
+ \ " fa axx",
+ \ " dfa axx",
+ \ " sdfa axx",
+ \ " asdfa axx",
+ \ " xasdfa axx",
+ \ " asxxdfa axx",
+ \ " asxa;ofa axx",
+ \ " asdfaqwer axx",
+ \ " a axxx",
+ \ " fa axxx",
+ \ " dfa axxx",
+ \ " sdfa axxx",
+ \ " asdfa axxx",
+ \ " xasdfa axxx",
+ \ " asxxdfa axxx",
+ \ " asxa;ofa axxx",
+ \ " asdfaqwer axxx",
+ \ " a axxxo",
+ \ " fa axxxo",
+ \ " dfa axxxo",
+ \ " sdfa axxxo",
+ \ " asdfa axxxo",
+ \ " xasdfa axxxo",
+ \ " asxxdfa axxxo",
+ \ " asxa;ofa axxxo",
+ \ " asdfaqwer axxxo",
+ \ " a axxxoi",
+ \ " fa axxxoi",
+ \ " dfa axxxoi",
+ \ " sdfa axxxoi",
+ \ " asdfa axxxoi",
+ \ " xasdfa axxxoi",
+ \ " asxxdfa axxxoi",
+ \ " asxa;ofa axxxoi",
+ \ " asdfaqwer axxxoi",
+ \ " a axxxoik",
+ \ " fa axxxoik",
+ \ " dfa axxxoik",
+ \ " sdfa axxxoik",
+ \ " asdfa axxxoik",
+ \ " xasdfa axxxoik",
+ \ " asxxdfa axxxoik",
+ \ " asxa;ofa axxxoik",
+ \ " asdfaqwer axxxoik",
+ \ " a axxxoike",
+ \ " fa axxxoike",
+ \ " dfa axxxoike",
+ \ " sdfa axxxoike",
+ \ " asdfa axxxoike",
+ \ " xasdfa axxxoike",
+ \ " asxxdfa axxxoike",
+ \ " asxa;ofa axxxoike",
+ \ " asdfaqwer axxxoike",
+ \ " a axxxoikey",
+ \ " fa axxxoikey",
+ \ " dfa axxxoikey",
+ \ " sdfa axxxoikey",
+ \ " asdfa axxxoikey",
+ \ " xasdfa axxxoikey",
+ \ " asxxdfa axxxoikey",
+ \ " asxa;ofa axxxoikey",
+ \ " asdfaqwer axxxoikey",
+ \ ])
+ %right
+ call assert_equal([
+ \ "\t\t\t\t test for :right",
+ \ "\t\t\t\t a a",
+ \ "\t\t\t\t fa a",
+ \ "\t\t\t\t dfa a",
+ \ "\t\t\t\t sdfa a",
+ \ "\t\t\t\t asdfa a",
+ \ "\t\t\t\t xasdfa a",
+ \ "\t\t\t\t asxxdfa a",
+ \ "\t\t\t\t asxa;ofa a",
+ \ "\t\t\t\t asdfaqwer a",
+ \ "\t\t\t\t a ax",
+ \ "\t\t\t\t fa ax",
+ \ "\t\t\t\t dfa ax",
+ \ "\t\t\t\t sdfa ax",
+ \ "\t\t\t\t asdfa ax",
+ \ "\t\t\t\t xasdfa ax",
+ \ "\t\t\t\t asxxdfa ax",
+ \ "\t\t\t\t asxa;ofa ax",
+ \ "\t\t\t\t asdfaqwer ax",
+ \ "\t\t\t\t a axx",
+ \ "\t\t\t\t fa axx",
+ \ "\t\t\t\t dfa axx",
+ \ "\t\t\t\t sdfa axx",
+ \ "\t\t\t\t asdfa axx",
+ \ "\t\t\t\t xasdfa axx",
+ \ "\t\t\t\t asxxdfa axx",
+ \ "\t\t\t\t asxa;ofa axx",
+ \ "\t\t\t\t asdfaqwer axx",
+ \ "\t\t\t\t a axxx",
+ \ "\t\t\t\t fa axxx",
+ \ "\t\t\t\t dfa axxx",
+ \ "\t\t\t\t sdfa axxx",
+ \ "\t\t\t\t asdfa axxx",
+ \ "\t\t\t\t xasdfa axxx",
+ \ "\t\t\t\t asxxdfa axxx",
+ \ "\t\t\t\t asxa;ofa axxx",
+ \ "\t\t\t\t asdfaqwer axxx",
+ \ "\t\t\t\t a axxxo",
+ \ "\t\t\t\t fa axxxo",
+ \ "\t\t\t\t dfa axxxo",
+ \ "\t\t\t\t sdfa axxxo",
+ \ "\t\t\t\t asdfa axxxo",
+ \ "\t\t\t\t xasdfa axxxo",
+ \ "\t\t\t\t asxxdfa axxxo",
+ \ "\t\t\t\t asxa;ofa axxxo",
+ \ "\t\t\t\t asdfaqwer axxxo",
+ \ "\t\t\t\t a axxxoi",
+ \ "\t\t\t\t fa axxxoi",
+ \ "\t\t\t\t dfa axxxoi",
+ \ "\t\t\t\t sdfa axxxoi",
+ \ "\t\t\t\t asdfa axxxoi",
+ \ "\t\t\t\t xasdfa axxxoi",
+ \ "\t\t\t\t asxxdfa axxxoi",
+ \ "\t\t\t\t asxa;ofa axxxoi",
+ \ "\t\t\t\t asdfaqwer axxxoi",
+ \ "\t\t\t\t a axxxoik",
+ \ "\t\t\t\t fa axxxoik",
+ \ "\t\t\t\t dfa axxxoik",
+ \ "\t\t\t\t sdfa axxxoik",
+ \ "\t\t\t\t asdfa axxxoik",
+ \ "\t\t\t\t xasdfa axxxoik",
+ \ "\t\t\t\t asxxdfa axxxoik",
+ \ "\t\t\t\t asxa;ofa axxxoik",
+ \ "\t\t\t\t asdfaqwer axxxoik",
+ \ "\t\t\t\t a axxxoike",
+ \ "\t\t\t\t fa axxxoike",
+ \ "\t\t\t\t dfa axxxoike",
+ \ "\t\t\t\t sdfa axxxoike",
+ \ "\t\t\t\t asdfa axxxoike",
+ \ "\t\t\t\t xasdfa axxxoike",
+ \ "\t\t\t\t asxxdfa axxxoike",
+ \ "\t\t\t\t asxa;ofa axxxoike",
+ \ "\t\t\t\t asdfaqwer axxxoike",
+ \ "\t\t\t\t a axxxoikey",
+ \ "\t\t\t\t fa axxxoikey",
+ \ "\t\t\t\t dfa axxxoikey",
+ \ "\t\t\t\t sdfa axxxoikey",
+ \ "\t\t\t\t asdfa axxxoikey",
+ \ "\t\t\t\t xasdfa axxxoikey",
+ \ "\t\t\t\t asxxdfa axxxoikey",
+ \ "\t\t\t\t asxa;ofa axxxoikey",
+ \ "\t\t\t\t asdfaqwer axxxoikey",
+ \ ""
+ \ ], getline(1, '$'))
+ enew!
+
+ " align text with 'wrapmargin'
+ 50vnew
+ call setline(1, ['Vim'])
+ setl textwidth=0
+ setl wrapmargin=30
+ right
+ call assert_equal("\t\t Vim", getline(1))
+ q!
+
+ " align text with 'rightleft'
+ if has('rightleft')
+ new
+ call setline(1, 'Vim')
+ setlocal rightleft
+ left 20
+ setlocal norightleft
+ call assert_equal("\t\t Vim", getline(1))
+ setlocal rightleft
+ right
+ setlocal norightleft
+ call assert_equal("Vim", getline(1))
+ close!
+ endif
+
+ set tw&
+endfunc
+
+" Test formatting a paragraph.
+func Test_format_para()
+ enew!
+ set fo+=tcroql tw=72
+
+ call append(0, [
+ \ "xxxxx xx xxxxxx ",
+ \ "xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx",
+ \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx",
+ \ "xx xxxxxxx. xxxx xxxx.",
+ \ "",
+ \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx",
+ \ "> xxxxxx xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx"
+ \ ])
+ exe "normal /xxxxxxxx$\<CR>"
+ normal 0gq6kk
+ call assert_equal([
+ \ "xxxxx xx xxxxxx xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx",
+ \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx xx xxxxxxx.",
+ \ "xxxx xxxx.",
+ \ "",
+ \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx xxxxxx",
+ \ "> xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx",
+ \ ""
+ \ ], getline(1, '$'))
+
+ set fo& tw&
+ enew!
+endfunc
+
+" Test undo after ":%s" and formatting.
+func Test_format_undo()
+ enew!
+ map gg :.,.+2s/^/x/<CR>kk:set tw=3<CR>gqq
+
+ call append(0, [
+ \ "aa aa aa aa",
+ \ "bb bb bb bb",
+ \ "cc cc cc cc"
+ \ ])
+ " undo/redo here to make the next undo only work on the following changes
+ exe "normal i\<C-G>u"
+ call cursor(1,1)
+ normal ggu
+ call assert_equal([
+ \ "aa aa aa aa",
+ \ "bb bb bb bb",
+ \ "cc cc cc cc",
+ \ ""
+ \ ], getline(1, '$'))
+
+ unmap gg
+ set tw&
+ enew!
+endfunc
+
+func Test_format_list_auto()
+ new
+ call setline(1, ['1. abc', '2. def', '3. ghi'])
+ set fo=tan ai bs=2
+ call feedkeys("3G0lli\<BS>\<BS>x\<Esc>", 'tx')
+ call assert_equal('2. defx ghi', getline(2))
+ bwipe!
+ set fo& ai& bs&
+endfunc
+
+func Test_crash_github_issue_5095()
+ CheckFeature autocmd
+
+ " This used to segfault, see https://github.com/vim/vim/issues/5095
+ augroup testing
+ au BufNew x center
+ augroup END
+
+ next! x
+
+ bw
+ augroup testing
+ au!
+ augroup END
+ augroup! testing
+endfunc
+
+" Test for formatting multi-byte text with 'fo=t'
+func Test_tw_2_fo_t()
+ new
+ let t =<< trim END
+ {
+ XYZ
+ abc XYZ
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=t
+ let t =<< trim END
+ XYZ
+ abc XYZ
+ END
+ exe "normal gqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ XYZ
+ abc
+ XYZ
+
+ XYZ
+ abc
+ XYZ
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm' and 'tw=1'
+func Test_tw_1_fo_tm()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ X a
+ XY
+ X Y
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=1 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ X a
+ XY
+ X Y
+ END
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm' and 'tw=2'
+func Test_tw_2_fo_tm()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ X a
+ XY
+ X Y
+ aX
+ abX
+ abcX
+ abX c
+ abXY
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ X a
+ XY
+ X Y
+ aX
+ abX
+ abcX
+ abX c
+ abXY
+ END
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+ a
+ X
+ ab
+ X
+ abc
+ X
+ ab
+ X
+ c
+ ab
+ X
+ ï¼¹
+
+ X
+ X
+ a
+ X
+ a
+ X
+ ï¼¹
+ X
+ ï¼¹
+ a
+ X
+ ab
+ X
+ abc
+ X
+ ab
+ X
+ c
+ ab
+ X
+ ï¼¹
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'autoindent'.
+func Test_tw_2_fo_tm_ai()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set ai tw=2 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ END
+ exe "normal gqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+
+ X
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& ai&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'noai'.
+func Test_tw_2_fo_tm_noai()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set noai tw=2 fo=tm
+ exe "normal gqgqjgqgqo\n X\n Xa"
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+
+ X
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& ai&
+ bwipe!
+endfunc
+
+func Test_tw_2_fo_tm_replace()
+ new
+ let t =<< trim END
+ {
+
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=tm
+ exe "normal RXa"
+
+ let expected =<< trim END
+ {
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for 'matchpairs' with multibyte chars
+func Test_mps_multibyte()
+ new
+ let t =<< trim END
+ {
+ ‘ two three ’ four
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ exe "set mps+=\u2018:\u2019"
+ normal d%
+
+ let expected =<< trim END
+ {
+ four
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set mps&
+ bwipe!
+endfunc
+
+" Test for 'matchpairs' in latin1 encoding
+func Test_mps_latin1()
+ new
+ let save_enc = &encoding
+ set encoding=latin1
+ call setline(1, 'abc(def)ghi')
+ normal %
+ call assert_equal(8, col('.'))
+ normal %
+ call assert_equal(4, col('.'))
+ call cursor(1, 6)
+ normal [(
+ call assert_equal(4, col('.'))
+ normal %
+ call assert_equal(8, col('.'))
+ call cursor(1, 6)
+ normal ])
+ call assert_equal(8, col('.'))
+ normal %
+ call assert_equal(4, col('.'))
+ let &encoding = save_enc
+ close!
+endfunc
+
+func Test_empty_matchpairs()
+ split
+ set matchpairs= showmatch
+ call assert_nobeep('call feedkeys("ax\tx\t\<Esc>", "xt")')
+ set matchpairs& noshowmatch
+ bwipe!
+endfunc
+
+func Test_mps_error()
+ let encoding_save = &encoding
+
+ for e in ['utf-8', 'latin1']
+ exe 'set encoding=' .. e
+
+ call assert_fails('set mps=<:', 'E474:', e)
+ call assert_fails('set mps=:>', 'E474:', e)
+ call assert_fails('set mps=<>', 'E474:', e)
+ call assert_fails('set mps=<:>_', 'E474:', e)
+ endfor
+
+ let &encoding = encoding_save
+endfunc
+
+" Test for ra on multi-byte characters
+func Test_ra_multibyte()
+ new
+ let t =<< trim END
+ ra test
+ ï½bbï½
+ ï½ï½b
+ END
+ call setline(1, t)
+ call cursor(1, 1)
+
+ normal jVjra
+
+ let expected =<< trim END
+ ra test
+ aaaa
+ aaa
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for 'whichwrap' with multi-byte character
+func Test_whichwrap_multi_byte()
+ new
+ let t =<< trim END
+ á
+ x
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set whichwrap+=h
+ normal dh
+ set whichwrap&
+
+ let expected =<< trim END
+ áx
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for 'a' and 'w' flags in 'formatoptions'
+func Test_fo_a_w()
+ new
+ setlocal fo+=aw tw=10
+ call feedkeys("iabc abc a abc\<Esc>k0weade", 'xt')
+ call assert_equal(['abc abcde ', 'a abc'], getline(1, '$'))
+
+ " when a line ends with space, it is not broken up.
+ %d
+ call feedkeys("ione two to ", 'xt')
+ call assert_equal('one two to ', getline(1))
+
+ " when a line ends with spaces and backspace is used in the next line, the
+ " last space in the previous line should be removed.
+ %d
+ set backspace=indent,eol,start
+ call setline(1, ['one ', 'two'])
+ exe "normal 2Gi\<BS>"
+ call assert_equal(['one two'], getline(1, '$'))
+ set backspace&
+
+ " Test for 'a', 'w' and '1' options.
+ setlocal textwidth=0
+ setlocal fo=1aw
+ %d
+ call setline(1, '. foo')
+ normal 72ig
+ call feedkeys('a uu uu uu', 'xt')
+ call assert_equal('g uu uu ', getline(1)[-8:])
+ call assert_equal(['uu. foo'], getline(2, '$'))
+
+ " using backspace or "x" triggers reformat
+ call setline(1, ['1 2 3 4 5 ', '6 7 8 9'])
+ set tw=10
+ set fo=taw
+ set bs=indent,eol,start
+ exe "normal 1G4la\<BS>\<BS>\<Esc>"
+ call assert_equal(['1 2 4 5 6 ', '7 8 9'], getline(1, 2))
+ exe "normal f4xx"
+ call assert_equal(['1 2 5 6 7 ', '8 9'], getline(1, 2))
+
+ " using "cw" leaves cursor in right spot
+ call setline(1, ['Now we g whether that nation, or',
+ \ 'any nation so conceived and,'])
+ set fo=tcqa tw=35
+ exe "normal 2G0cwx\<Esc>"
+ call assert_equal(['Now we g whether that nation, or x', 'nation so conceived and,'], getline(1, 2))
+
+ set tw=0
+ set fo&
+ %bw!
+endfunc
+
+" Test for formatting lines using gq in visual mode
+func Test_visual_gq_format()
+ new
+ call setline(1, ['one two three four', 'five six', 'one two'])
+ setl textwidth=10
+ call feedkeys('ggv$jj', 'xt')
+ redraw!
+ normal gq
+ %d
+ call setline(1, ['one two three four', 'five six', 'one two'])
+ normal G$
+ call feedkeys('v0kk', 'xt')
+ redraw!
+ normal gq
+ setl textwidth&
+ close!
+endfunc
+
+" Test for 'n' flag in 'formatoptions' to format numbered lists
+func Test_fo_n()
+ new
+ setlocal autoindent
+ setlocal textwidth=12
+ setlocal fo=n
+ call setline(1, [' 1) one two three four', ' 2) two'])
+ normal gggqG
+ call assert_equal([' 1) one two', ' three', ' four', ' 2) two'],
+ \ getline(1, '$'))
+ close!
+endfunc
+
+" Test for 'formatlistpat' option
+func Test_formatlistpat()
+ new
+ setlocal autoindent
+ setlocal textwidth=10
+ setlocal fo=n
+ setlocal formatlistpat=^\\s*-\\s*
+ call setline(1, [' - one two three', ' - two'])
+ normal gggqG
+ call assert_equal([' - one', ' two', ' three', ' - two'],
+ \ getline(1, '$'))
+ close!
+endfunc
+
+" Test for the 'b' and 'v' flags in 'formatoptions'
+" Text should wrap only if a space character is inserted at or before
+" 'textwidth'
+func Test_fo_b()
+ new
+ setlocal textwidth=20
+
+ setlocal formatoptions=t
+ call setline(1, 'one two three four')
+ call feedkeys('Amore', 'xt')
+ call assert_equal(['one two three', 'fourmore'], getline(1, '$'))
+
+ setlocal formatoptions=bt
+ %d
+ call setline(1, 'one two three four')
+ call feedkeys('Amore five', 'xt')
+ call assert_equal(['one two three fourmore five'], getline(1, '$'))
+
+ setlocal formatoptions=bt
+ %d
+ call setline(1, 'one two three four')
+ call feedkeys('A five', 'xt')
+ call assert_equal(['one two three four', 'five'], getline(1, '$'))
+
+ setlocal formatoptions=vt
+ %d
+ call setline(1, 'one two three four')
+ call feedkeys('Amore five', 'xt')
+ call assert_equal(['one two three fourmore', 'five'], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for the '1' flag in 'formatoptions'. Don't wrap text after a one letter
+" word.
+func Test_fo_1()
+ new
+ setlocal textwidth=20
+
+ setlocal formatoptions=t
+ call setline(1, 'one two three four')
+ call feedkeys('A a bird', 'xt')
+ call assert_equal(['one two three four a', 'bird'], getline(1, '$'))
+
+ %d
+ setlocal formatoptions=t1
+ call setline(1, 'one two three four')
+ call feedkeys('A a bird', 'xt')
+ call assert_equal(['one two three four', 'a bird'], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for 'l' flag in 'formatoptions'. When starting insert mode, if a line
+" is longer than 'textwidth', then it is not broken.
+func Test_fo_l()
+ new
+ setlocal textwidth=20
+
+ setlocal formatoptions=t
+ call setline(1, 'one two three four five')
+ call feedkeys('A six', 'xt')
+ call assert_equal(['one two three four', 'five six'], getline(1, '$'))
+
+ %d
+ setlocal formatoptions=tl
+ call setline(1, 'one two three four five')
+ call feedkeys('A six', 'xt')
+ call assert_equal(['one two three four five six'], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for the '2' flag in 'formatoptions'
+func Test_fo_2()
+ new
+ setlocal autoindent
+ setlocal formatoptions=t2
+ setlocal textwidth=30
+ call setline(1, ["\tfirst line of a paragraph.",
+ \ "second line of the same paragraph.",
+ \ "third line."])
+ normal gggqG
+ call assert_equal(["\tfirst line of a",
+ \ "paragraph. second line of the",
+ \ "same paragraph. third line."], getline(1, '$'))
+ close!
+endfunc
+
+" This was leaving the cursor after the end of a line. Complicated way to
+" have the problem show up with valgrind.
+func Test_correct_cursor_position()
+ set encoding=iso8859
+ new
+ norm a000“0
+ sil! norm gggg0i0gw0gg
+
+ bwipe!
+ set encoding=utf8
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_textobjects.vim b/src/testdir/test_textobjects.vim
new file mode 100644
index 0000000..746b326
--- /dev/null
+++ b/src/testdir/test_textobjects.vim
@@ -0,0 +1,644 @@
+" Test for textobjects
+
+source check.vim
+
+func CpoM(line, useM, expected)
+ new
+
+ if a:useM
+ set cpoptions+=M
+ else
+ set cpoptions-=M
+ endif
+
+ call setline(1, a:line)
+
+ call setreg('"', '')
+ normal! ggfrmavi)y
+ call assert_equal(getreg('"'), a:expected[0])
+
+ call setreg('"', '')
+ normal! `afbmavi)y
+ call assert_equal(getreg('"'), a:expected[1])
+
+ call setreg('"', '')
+ normal! `afgmavi)y
+ call assert_equal(getreg('"'), a:expected[2])
+
+ q!
+endfunc
+
+func Test_inner_block_without_cpo_M()
+ call CpoM('(red \(blue) green)', 0, ['red \(blue', 'red \(blue', ''])
+endfunc
+
+func Test_inner_block_with_cpo_M_left_backslash()
+ call CpoM('(red \(blue) green)', 1, ['red \(blue) green', 'blue', 'red \(blue) green'])
+endfunc
+
+func Test_inner_block_with_cpo_M_right_backslash()
+ call CpoM('(red (blue\) green)', 1, ['red (blue\) green', 'blue\', 'red (blue\) green'])
+endfunc
+
+func Test_inner_block_single_char()
+ new
+ call setline(1, "(a)")
+
+ set selection=inclusive
+ let @" = ''
+ call assert_nobeep('norm! 0faviby')
+ call assert_equal('a', @")
+
+ set selection=exclusive
+ let @" = ''
+ call assert_nobeep('norm! 0faviby')
+ call assert_equal('a', @")
+
+ set selection&
+ bwipe!
+endfunc
+
+func Test_quote_selection_selection_exclusive()
+ new
+ call setline(1, "a 'bcde' f")
+ set selection=exclusive
+
+ exe "norm! fdvhi'y"
+ call assert_equal('bcde', @")
+
+ let @" = 'dummy'
+ exe "norm! $gevi'y"
+ call assert_equal('bcde', @")
+
+ let @" = 'dummy'
+ exe "norm! 0fbhvi'y"
+ call assert_equal('bcde', @")
+
+ set selection&vim
+ bw!
+endfunc
+
+func Test_quote_selection_selection_exclusive_abort()
+ new
+ set selection=exclusive
+ call setline(1, "'abzzc'")
+ let exp_curs = [0, 1, 6, 0]
+ call cursor(1,1)
+ exe 'norm! fcdvi"'
+ " make sure to end visual mode to have a clear state
+ exe "norm! \<esc>"
+ call assert_equal(exp_curs, getpos('.'))
+ call cursor(1,1)
+ exe 'norm! fcvi"'
+ exe "norm! \<esc>"
+ call assert_equal(exp_curs, getpos('.'))
+ call cursor(1,2)
+ exe 'norm! vfcoi"'
+ exe "norm! \<esc>"
+ let exp_curs = [0, 1, 2, 0]
+ let exp_visu = [0, 1, 7, 0]
+ call assert_equal(exp_curs, getpos('.'))
+ call assert_equal(exp_visu, getpos("'>"))
+ set selection&vim
+ bw!
+endfunc
+
+" Tests for string and html text objects
+func Test_string_html_objects()
+
+ for e in ['utf-8', 'latin1', 'cp932']
+ enew!
+ exe 'set enc=' .. e
+
+ let t = '"wo\"rd\\" foo'
+ put =t
+ normal! da"
+ call assert_equal('foo', getline('.'), e)
+
+ let t = "'foo' 'bar' 'piep'"
+ put =t
+ normal! 0va'a'rx
+ call assert_equal("xxxxxxxxxxxx'piep'", getline('.'), e)
+
+ let t = "bla bla `quote` blah"
+ put =t
+ normal! 02f`da`
+ call assert_equal("bla bla blah", getline('.'), e)
+
+ let t = 'out " in "noXno"'
+ put =t
+ normal! 0fXdi"
+ call assert_equal('out " in ""', getline('.'), e)
+
+ let t = "\"'\" 'blah' rep 'buh'"
+ put =t
+ normal! 03f'vi'ry
+ call assert_equal("\"'\" 'blah'yyyyy'buh'", getline('.'), e)
+
+ set quoteescape=+*-
+ let t = "bla `s*`d-`+++`l**` b`la"
+ put =t
+ normal! di`
+ call assert_equal("bla `` b`la", getline('.'), e)
+
+ let t = 'voo "nah" sdf " asdf" sdf " sdf" sd'
+ put =t
+ normal! $F"va"oha"i"rz
+ call assert_equal('voo "zzzzzzzzzzzzzzzzzzzzzzzzzzzzsd', getline('.'), e)
+
+ let t = "-<b>asdf<i>Xasdf</i>asdf</b>-"
+ put =t
+ normal! fXdit
+ call assert_equal('-<b>asdf<i></i>asdf</b>-', getline('.'), e)
+
+ let t = "-<b>asdX<i>a<i />sdf</i>asdf</b>-"
+ put =t
+ normal! 0fXdit
+ call assert_equal('-<b></b>-', getline('.'), e)
+
+ let t = "-<b>asdf<i>Xasdf</i>asdf</b>-"
+ put =t
+ normal! fXdat
+ call assert_equal('-<b>asdfasdf</b>-', getline('.'), e)
+
+ let t = "-<b>asdX<i>as<b />df</i>asdf</b>-"
+ put =t
+ normal! 0fXdat
+ call assert_equal('--', getline('.'), e)
+
+ let t = "-<b>\ninnertext object\n</b>"
+ put =t
+ normal! dit
+ call assert_equal('-<b></b>', getline('.'), e)
+
+ " copy the tag block from leading indentation before the start tag
+ let t = " <b>\ntext\n</b>"
+ $put =t
+ normal! 2kvaty
+ call assert_equal("<b>\ntext\n</b>", @", e)
+
+ " copy the tag block from the end tag
+ let t = "<title>\nwelcome\n</title>"
+ $put =t
+ normal! $vaty
+ call assert_equal("<title>\nwelcome\n</title>", @", e)
+
+ " copy the outer tag block from a tag without an end tag
+ let t = "<html>\n<title>welcome\n</html>"
+ $put =t
+ normal! k$vaty
+ call assert_equal("<html>\n<title>welcome\n</html>", @", e)
+
+ " nested tag that has < in a different line from >
+ let t = "<div><div\n></div></div>"
+ $put =t
+ normal! k0vaty
+ call assert_equal("<div><div\n></div></div>", @", e)
+
+ " nested tag with attribute that has < in a different line from >
+ let t = "<div><div\nattr=\"attr\"\n></div></div>"
+ $put =t
+ normal! 2k0vaty
+ call assert_equal("<div><div\nattr=\"attr\"\n></div></div>", @", e)
+
+ set quoteescape&
+
+ " this was going beyond the end of the line
+ %del
+ sil! norm i"\
+ sil! norm i"\
+ sil! norm i"\
+ call assert_equal('"\', getline(1))
+
+ bwipe!
+ endfor
+
+ set enc=utf-8
+endfunc
+
+func Test_empty_html_tag()
+ new
+ call setline(1, '<div></div>')
+ normal 0citxxx
+ call assert_equal('<div>xxx</div>', getline(1))
+
+ call setline(1, '<div></div>')
+ normal 0f<cityyy
+ call assert_equal('<div>yyy</div>', getline(1))
+
+ call setline(1, '<div></div>')
+ normal 0f<vitsaaa
+ call assert_equal('aaa', getline(1))
+
+ " selecting a tag block in a non-empty blank line should fail
+ call setline(1, ' ')
+ call assert_beeps('normal $vaty')
+
+ bwipe!
+endfunc
+
+" Tests for match() and matchstr()
+func Test_match()
+ call assert_equal("b", matchstr("abcd", ".", 0, 2))
+ call assert_equal("bc", matchstr("abcd", "..", 0, 2))
+ call assert_equal("c", matchstr("abcd", ".", 2, 0))
+ call assert_equal("a", matchstr("abcd", ".", 0, -1))
+ call assert_equal(-1, match("abcd", ".", 0, 5))
+ call assert_equal(0, match("abcd", ".", 0, -1))
+ call assert_equal(0, match('abc', '.', 0, 1))
+ call assert_equal(1, match('abc', '.', 0, 2))
+ call assert_equal(2, match('abc', '.', 0, 3))
+ call assert_equal(-1, match('abc', '.', 0, 4))
+ call assert_equal(1, match('abc', '.', 1, 1))
+ call assert_equal(2, match('abc', '.', 2, 1))
+ call assert_equal(-1, match('abc', '.', 3, 1))
+ call assert_equal(3, match('abc', '$', 0, 1))
+ call assert_equal(-1, match('abc', '$', 0, 2))
+ call assert_equal(3, match('abc', '$', 1, 1))
+ call assert_equal(3, match('abc', '$', 2, 1))
+ call assert_equal(3, match('abc', '$', 3, 1))
+ call assert_equal(-1, match('abc', '$', 4, 1))
+ call assert_equal(0, match('abc', '\zs', 0, 1))
+ call assert_equal(1, match('abc', '\zs', 0, 2))
+ call assert_equal(2, match('abc', '\zs', 0, 3))
+ call assert_equal(3, match('abc', '\zs', 0, 4))
+ call assert_equal(-1, match('abc', '\zs', 0, 5))
+ call assert_equal(1, match('abc', '\zs', 1, 1))
+ call assert_equal(2, match('abc', '\zs', 2, 1))
+ call assert_equal(3, match('abc', '\zs', 3, 1))
+ call assert_equal(-1, match('abc', '\zs', 4, 1))
+endfunc
+
+" This was causing an illegal memory access
+func Test_inner_tag()
+ new
+ norm ixxx
+ call feedkeys("v", 'xt')
+ insert
+x
+x
+.
+ norm it
+ q!
+endfunc
+
+func Test_sentence()
+ enew!
+ call setline(1, 'A sentence. A sentence? A sentence!')
+
+ normal yis
+ call assert_equal('A sentence.', @")
+ normal yas
+ call assert_equal('A sentence. ', @")
+
+ normal )
+
+ normal yis
+ call assert_equal('A sentence?', @")
+ normal yas
+ call assert_equal('A sentence? ', @")
+
+ normal )
+
+ normal yis
+ call assert_equal('A sentence!', @")
+ normal yas
+ call assert_equal(' A sentence!', @")
+
+ normal 0
+ normal 2yis
+ call assert_equal('A sentence. ', @")
+ normal 3yis
+ call assert_equal('A sentence. A sentence?', @")
+ normal 2yas
+ call assert_equal('A sentence. A sentence? ', @")
+
+ %delete _
+endfunc
+
+func Test_sentence_with_quotes()
+ enew!
+ call setline(1, 'A "sentence." A sentence.')
+
+ normal yis
+ call assert_equal('A "sentence."', @")
+ normal yas
+ call assert_equal('A "sentence." ', @")
+
+ normal )
+
+ normal yis
+ call assert_equal('A sentence.', @")
+ normal yas
+ call assert_equal(' A sentence.', @")
+
+ %delete _
+endfunc
+
+func Test_sentence_with_cursor_on_delimiter()
+ enew!
+ call setline(1, "A '([sentence.])' A sentence.")
+
+ normal! 15|yis
+ call assert_equal("A '([sentence.])'", @")
+ normal! 15|yas
+ call assert_equal("A '([sentence.])' ", @")
+
+ normal! 16|yis
+ call assert_equal("A '([sentence.])'", @")
+ normal! 16|yas
+ call assert_equal("A '([sentence.])' ", @")
+
+ normal! 17|yis
+ call assert_equal("A '([sentence.])'", @")
+ normal! 17|yas
+ call assert_equal("A '([sentence.])' ", @")
+
+ " don't get stuck on a quote at the start of a sentence
+ %delete _
+ call setline(1, ['A sentence.', '"A sentence"?', 'A sentence!'])
+ normal gg))
+ call assert_equal(3, getcurpos()[1])
+
+ %delete _
+ call setline(1, ['A sentence.', "'A sentence'?", 'A sentence!'])
+ normal gg))
+ call assert_equal(3, getcurpos()[1])
+
+ %delete _
+endfunc
+
+" Test for the paragraph (ap) text object
+func Test_paragraph()
+ new
+ call setline(1, ['First line.', 'Second line.', 'Third line.'])
+ call cursor(2, 1)
+ normal vapy
+ call assert_equal("First line.\nSecond line.\nThird line.\n", @")
+
+ call cursor(2, 1)
+ call assert_beeps('normal vapapy')
+
+ call setline(1, ['First line.', 'Second line.', ' ', ''])
+ call cursor(1, 1)
+ normal vapy
+ call assert_equal("First line.\nSecond line.\n \n\n", @")
+
+ call setline(1, ['', '', '', 'First line.', 'Second line.'])
+ call cursor(2, 1)
+ normal yap
+ call assert_equal("\n\n\nFirst line.\nSecond line.\n", @")
+ call assert_beeps('normal 3yap')
+ exe "normal \<C-C>"
+
+ %d
+ call setline(1, [' ', ' ', ' '])
+ call cursor(2, 1)
+ normal Vipy
+ call assert_equal(" \n \n \n", @")
+ call cursor(2, 1)
+ call assert_beeps("normal Vipip")
+ exe "normal \<C-C>"
+
+ close!
+endfunc
+
+" Tests for text object aw
+func Test_textobj_a_word()
+ new
+ call append(0, ['foobar,eins,foobar', 'foo,zwei,foo '])
+ " diw
+ norm! 1gg0diw
+ call assert_equal([',eins,foobar', 'foo,zwei,foo ', ''], getline(1,'$'))
+ " daw
+ norm! 2ggEdaw
+ call assert_equal([',eins,foobar', 'foo,zwei,', ''], getline(1, '$'))
+ " daw the last word in a line
+ call setline(1, ['foo bar', 'foo bar', ''])
+ call cursor(1, 5)
+ normal daw
+ call assert_equal('foo', getline(1))
+ " aw in visual mode
+ call cursor(2, 5)
+ normal! vawx
+ call assert_equal('foo', getline(2))
+ %d
+ call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "])
+ " diW
+ norm! 2ggwd2iW
+ call assert_equal(['foo eins foobar', 'foo foo ', ''], getline(1,'$'))
+ " daW
+ norm! 1ggd2aW
+ call assert_equal(['foobar', 'foo foo ', ''], getline(1,'$'))
+
+ %d
+ call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "])
+ " aw in visual line mode switches to characterwise mode
+ norm! 2gg$Vawd
+ call assert_equal(['foo eins foobar', 'foo zwei foo'], getline(1,'$'))
+ norm! 1gg$Viwd
+ call assert_equal(['foo eins ', 'foo zwei foo'], getline(1,'$'))
+
+ " visually selecting a tab before a word with 'selection' set to 'exclusive'
+ set selection=exclusive
+ normal gg3lvlawy
+ call assert_equal("\teins", @")
+ " visually selecting a tab before a word with 'selection' set to 'inclusive'
+ set selection=inclusive
+ normal gg3lvlawy
+ call assert_equal("\teins\t", @")
+ set selection&
+
+ " selecting a word with no non-space characters in a buffer fails
+ %d
+ call setline(1, ' ')
+ call assert_beeps('normal 3lyaw')
+
+ " visually selecting words backwards with no more words to select
+ call setline(1, 'one two')
+ call assert_beeps('normal 2lvh2aw')
+ exe "normal \<C-C>"
+ call assert_beeps('normal $vh3aw')
+ exe "normal \<C-C>"
+ call setline(1, ['', 'one two'])
+ call assert_beeps('normal 2G2lvh3aw')
+ exe "normal \<C-C>"
+
+ " selecting words forward with no more words to select
+ %d
+ call setline(1, 'one a')
+ call assert_beeps('normal 0y3aw')
+ call setline(1, 'one two ')
+ call assert_beeps('normal 0y3aw')
+ call assert_beeps('normal 03ly2aw')
+
+ " clean up
+ bw!
+endfunc
+
+" Test for is and as text objects
+func Test_textobj_sentence()
+ new
+ call append(0, ['This is a test. With some sentences!', '',
+ \ 'Even with a question? And one more. And no sentence here'])
+ " Test for dis - does not remove trailing whitespace
+ norm! 1gg0dis
+ call assert_equal([' With some sentences!', '',
+ \ 'Even with a question? And one more. And no sentence here', ''],
+ \ getline(1,'$'))
+ " Test for das - removes leading whitespace
+ norm! 3ggf?ldas
+ call assert_equal([' With some sentences!', '',
+ \ 'Even with a question? And no sentence here', ''], getline(1,'$'))
+ " when used in visual mode, is made characterwise
+ norm! 3gg$Visy
+ call assert_equal('v', visualmode())
+ " reset visualmode()
+ norm! 3ggVy
+ norm! 3gg$Vasy
+ call assert_equal('v', visualmode())
+ " basic testing for textobjects a< and at
+ %d
+ call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' '])
+ " a<
+ norm! 1gg0da<
+ call assert_equal([' ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
+ norm! 1pj
+ call assert_equal([' <div>', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
+ " at
+ norm! d2at
+ call assert_equal([' '], getline(1,'$'))
+ %d
+ call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' '])
+ " i<
+ norm! 1gg0di<
+ call assert_equal(['<> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
+ norm! 1Pj
+ call assert_equal(['<div> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$'))
+ norm! d2it
+ call assert_equal(['<div></div>',' '], getline(1,'$'))
+ " basic testing for a[ and i[ text object
+ %d
+ call setline(1, [' ', '[', 'one [two]', 'thre', ']'])
+ norm! 3gg0di[
+ call assert_equal([' ', '[', ']'], getline(1,'$'))
+ call setline(1, [' ', '[', 'one [two]', 'thre', ']'])
+ norm! 3gg0ftd2a[
+ call assert_equal([' '], getline(1,'$'))
+
+ " clean up
+ bw!
+endfunc
+
+" Test for quote (', " and `) textobjects
+func Test_textobj_quote()
+ new
+
+ " Test for i" when cursor is in front of a quoted object
+ call append(0, 'foo "bar"')
+ norm! 1gg0di"
+ call assert_equal(['foo ""', ''], getline(1,'$'))
+
+ " Test for visually selecting an inner quote
+ %d
+ " extend visual selection from one quote to the next
+ call setline(1, 'color "red" color "blue"')
+ call cursor(1, 7)
+ normal v4li"y
+ call assert_equal('"red" color "blue', @")
+
+ " try to extend visual selection from one quote to a non-existing quote
+ call setline(1, 'color "red" color blue')
+ call cursor(1, 7)
+ call feedkeys('v4li"y', 'xt')
+ call assert_equal('"red"', @")
+
+ " try to extend visual selection from one quote to a next partial quote
+ call setline(1, 'color "red" color "blue')
+ call cursor(1, 7)
+ normal v4li"y
+ call assert_equal('"red" color ', @")
+
+ " select a quote backwards in visual mode
+ call cursor(1, 12)
+ normal vhi"y
+ call assert_equal('red" ', @")
+ call assert_equal(8, col('.'))
+
+ " select a quote backwards in visual mode from outside the quote
+ call cursor(1, 17)
+ normal v2hi"y
+ call assert_equal('red', @")
+ call assert_equal(8, col('.'))
+
+ " visually selecting a quote with 'selection' set to 'exclusive'
+ call setline(1, 'He said "How are you?"')
+ set selection=exclusive
+ normal 012lv2li"y
+ call assert_equal('How are you?', @")
+ set selection&
+
+ " try copy a quote object with a single quote in the line
+ call setline(1, "Smith's car")
+ call cursor(1, 6)
+ call assert_beeps("normal yi'")
+ call assert_beeps("normal 2lyi'")
+
+ " selecting space before and after a quoted string
+ call setline(1, "some 'special' string")
+ normal 0ya'
+ call assert_equal("'special' ", @")
+ call setline(1, "some 'special'string")
+ normal 0ya'
+ call assert_equal(" 'special'", @")
+
+ " quoted string with odd or even number of backslashes.
+ call setline(1, 'char *s = "foo\"bar"')
+ normal $hhyi"
+ call assert_equal('foo\"bar', @")
+ call setline(1, 'char *s = "foo\\"bar"')
+ normal $hhyi"
+ call assert_equal('bar', @")
+ call setline(1, 'char *s = "foo\\\"bar"')
+ normal $hhyi"
+ call assert_equal('foo\\\"bar', @")
+ call setline(1, 'char *s = "foo\\\\"bar"')
+ normal $hhyi"
+ call assert_equal('bar', @")
+
+ close!
+endfunc
+
+" Test for i(, i<, etc. when cursor is in front of a block
+func Test_textobj_find_paren_forward()
+ new
+
+ " i< and a> when cursor is in front of a block
+ call setline(1, '#include <foo.h>')
+ normal 0yi<
+ call assert_equal('foo.h', @")
+ normal 0ya>
+ call assert_equal('<foo.h>', @")
+
+ " 2i(, 3i( in front of a block enters second/third nested '('
+ call setline(1, 'foo (bar (baz (quux)))')
+ normal 0yi)
+ call assert_equal('bar (baz (quux))', @")
+ normal 02yi)
+ call assert_equal('baz (quux)', @")
+ normal 03yi)
+ call assert_equal('quux', @")
+
+ " 3i( in front of a block doesn't enter third but un-nested '('
+ call setline(1, 'foo (bar (baz) (quux))')
+ normal 03di)
+ call assert_equal('foo (bar (baz) (quux))', getline(1))
+ normal 02di)
+ call assert_equal('foo (bar () (quux))', getline(1))
+ normal 0di)
+ call assert_equal('foo ()', getline(1))
+
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_textprop.vim b/src/testdir/test_textprop.vim
new file mode 100644
index 0000000..cd141eb
--- /dev/null
+++ b/src/testdir/test_textprop.vim
@@ -0,0 +1,3884 @@
+" Tests for defining text property types and adding text properties to the
+" buffer.
+
+source check.vim
+CheckFeature textprop
+
+source screendump.vim
+import './vim9.vim' as v9
+
+func Test_proptype_global()
+ call prop_type_add('comment', {'highlight': 'Directory', 'priority': 123, 'start_incl': 1, 'end_incl': 1})
+ let proptypes = prop_type_list()
+ call assert_equal(1, len(proptypes))
+ call assert_equal('comment', proptypes[0])
+
+ let proptype = prop_type_get('comment')
+ call assert_equal('Directory', proptype['highlight'])
+ call assert_equal(123, proptype['priority'])
+ call assert_equal(1, proptype['start_incl'])
+ call assert_equal(1, proptype['end_incl'])
+
+ call prop_type_delete('comment')
+ call assert_equal(0, len(prop_type_list()))
+
+ call prop_type_add('one', {})
+ call assert_equal(1, len(prop_type_list()))
+ let proptype = 'one'->prop_type_get()
+ call assert_false(has_key(proptype, 'highlight'))
+ call assert_equal(0, proptype['priority'])
+ call assert_equal(0, proptype['start_incl'])
+ call assert_equal(0, proptype['end_incl'])
+
+ call prop_type_add('two', {})
+ call assert_equal(2, len(prop_type_list()))
+ call prop_type_delete('one')
+ call assert_equal(1, len(prop_type_list()))
+ call prop_type_delete('two')
+ call assert_equal(0, len(prop_type_list()))
+endfunc
+
+func Test_proptype_buf()
+ let bufnr = bufnr('')
+ call prop_type_add('comment', #{bufnr: bufnr, highlight: 'Directory', priority: 123, start_incl: 1, end_incl: 1})
+ let proptypes = prop_type_list({'bufnr': bufnr})
+ call assert_equal(1, len(proptypes))
+ call assert_equal('comment', proptypes[0])
+
+ let proptype = prop_type_get('comment', {'bufnr': bufnr})
+ call assert_equal('Directory', proptype['highlight'])
+ call assert_equal(123, proptype['priority'])
+ call assert_equal(1, proptype['start_incl'])
+ call assert_equal(1, proptype['end_incl'])
+
+ call prop_type_delete('comment', {'bufnr': bufnr})
+ call assert_equal(0, len({'bufnr': bufnr}->prop_type_list()))
+
+ call prop_type_add('one', {'bufnr': bufnr})
+ let proptype = prop_type_get('one', {'bufnr': bufnr})
+ call assert_false(has_key(proptype, 'highlight'))
+ call assert_equal(0, proptype['priority'])
+ call assert_equal(0, proptype['start_incl'])
+ call assert_equal(0, proptype['end_incl'])
+
+ call prop_type_add('two', {'bufnr': bufnr})
+ call assert_equal(2, len(prop_type_list({'bufnr': bufnr})))
+ call prop_type_delete('one', {'bufnr': bufnr})
+ call assert_equal(1, len(prop_type_list({'bufnr': bufnr})))
+ call prop_type_delete('two', {'bufnr': bufnr})
+ call assert_equal(0, len(prop_type_list({'bufnr': bufnr})))
+
+ call assert_fails("call prop_type_add('one', {'bufnr': 98764})", "E158:")
+endfunc
+
+def Test_proptype_add_remove()
+ # add and remove a prop type so that the array is empty
+ prop_type_add('local', {bufnr: bufnr('%')})
+ prop_type_delete('local', {bufnr: bufnr('%')})
+ prop_type_add('global', {highlight: 'ErrorMsg'})
+ prop_add(1, 1, {length: 1, type: 'global'})
+ redraw
+
+ prop_clear(1)
+ prop_type_delete('global')
+enddef
+
+def Test_proptype_buf_list()
+ new
+ var bufnr = bufnr('')
+ try
+ prop_type_add('global', {})
+ prop_type_add('local', {bufnr: bufnr})
+
+ prop_add(1, 1, {type: 'global'})
+ prop_add(1, 1, {type: 'local'})
+
+ assert_equal([
+ {type: 'local', type_bufnr: bufnr, id: 0, col: 1, end: 1, length: 0, start: 1},
+ {type: 'global', type_bufnr: 0, id: 0, col: 1, end: 1, length: 0, start: 1},
+ ], prop_list(1))
+ assert_equal(
+ {lnum: 1, id: 0, col: 1, type_bufnr: bufnr, end: 1, type: 'local', length: 0, start: 1},
+ prop_find({lnum: 1, type: 'local'}))
+ assert_equal(
+ {lnum: 1, id: 0, col: 1, type_bufnr: 0, end: 1, type: 'global', length: 0, start: 1},
+ prop_find({lnum: 1, type: 'global'}))
+
+ prop_remove({type: 'global'}, 1)
+ prop_remove({type: 'local'}, 1)
+ finally
+ prop_type_delete('global')
+ prop_type_delete('local', {bufnr: bufnr})
+ bwipe!
+ endtry
+enddef
+
+func AddPropTypes()
+ call prop_type_add('one', {})
+ call prop_type_add('two', {})
+ call prop_type_add('three', {})
+ call prop_type_add('whole', {})
+endfunc
+
+func DeletePropTypes()
+ call prop_type_delete('one')
+ call prop_type_delete('two')
+ call prop_type_delete('three')
+ call prop_type_delete('whole')
+endfunc
+
+func SetupPropsInFirstLine()
+ call setline(1, 'one two three')
+ call prop_add(1, 1, {'length': 3, 'id': 11, 'type': 'one'})
+ eval 1->prop_add(5, {'length': 3, 'id': 12, 'type': 'two'})
+ call prop_add(1, 9, {'length': 5, 'id': 13, 'type': 'three'})
+ call prop_add(1, 1, {'length': 13, 'id': 14, 'type': 'whole'})
+endfunc
+
+func Get_expected_props()
+ return [
+ \ #{type_bufnr: 0, col: 1, length: 13, id: 14, type: 'whole', start: 1, end: 1},
+ \ #{type_bufnr: 0, col: 1, length: 3, id: 11, type: 'one', start: 1, end: 1},
+ \ #{type_bufnr: 0, col: 5, length: 3, id: 12, type: 'two', start: 1, end: 1},
+ \ #{type_bufnr: 0, col: 9, length: 5, id: 13, type: 'three', start: 1, end: 1},
+ \ ]
+endfunc
+
+func Test_prop_find()
+ new
+ call setline(1, ['one one one', 'twotwo', 'three', 'fourfour', 'five', 'sixsix'])
+
+ " Add two text props on lines 1 and 5, and one spanning lines 2 to 4.
+ call prop_type_add('prop_name', {'highlight': 'Directory'})
+ call prop_add(1, 5, {'type': 'prop_name', 'id': 10, 'length': 3})
+ call prop_add(2, 4, {'type': 'prop_name', 'id': 11, 'end_lnum': 4, 'end_col': 9})
+ call prop_add(5, 4, {'type': 'prop_name', 'id': 12, 'length': 1})
+
+ let expected = [
+ \ #{type_bufnr: 0, lnum: 1, col: 5, length: 3, id: 10, type: 'prop_name', start: 1, end: 1},
+ \ #{type_bufnr: 0, lnum: 2, col: 4, id: 11, type: 'prop_name', start: 1, end: 0},
+ \ #{type_bufnr: 0, lnum: 5, col: 4, length: 1, id: 12, type: 'prop_name', start: 1, end: 1}
+ \ ]
+
+ " Starting at line 5 col 1 this should find the prop at line 5 col 4.
+ call cursor(5, 1)
+ let result = prop_find({'type': 'prop_name'}, 'f')
+ call assert_equal(expected[2], result)
+
+ " With skipstart left at false (default), this should find the prop at line
+ " 5 col 4.
+ let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4}, 'b')
+ call assert_equal(expected[2], result)
+
+ " With skipstart set to true, this should skip the prop at line 5 col 4.
+ let result = prop_find({'type': 'prop_name', 'lnum': 5, 'col': 4, 'skipstart': 1}, 'b')
+ unlet result.length
+ call assert_equal(expected[1], result)
+
+ " Search backwards from line 1 col 10 to find the prop on the same line.
+ let result = prop_find({'type': 'prop_name', 'lnum': 1, 'col': 10}, 'b')
+ call assert_equal(expected[0], result)
+
+ " with skipstart set to false, if the start position is anywhere between the
+ " start and end lines of a text prop (searching forward or backward), the
+ " result should be the prop on the first line (the line with 'start' set to 1).
+ call cursor(3, 1)
+ let result = prop_find({'type': 'prop_name'}, 'f')
+ unlet result.length
+ call assert_equal(expected[1], result)
+ let result = prop_find({'type': 'prop_name'}, 'b')
+ unlet result.length
+ call assert_equal(expected[1], result)
+
+ " with skipstart set to true, if the start position is anywhere between the
+ " start and end lines of a text prop (searching forward or backward), all lines
+ " of the prop will be skipped.
+ let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'b')
+ call assert_equal(expected[0], result)
+ let result = prop_find({'type': 'prop_name', 'skipstart': 1}, 'f')
+ call assert_equal(expected[2], result)
+
+ " Use skipstart to search through all props with type name 'prop_name'.
+ " First forward...
+ let lnum = 1
+ let col = 1
+ let i = 0
+ for exp in expected
+ let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'f')
+ if !has_key(exp, "length")
+ unlet result.length
+ endif
+ call assert_equal(exp, result)
+ let lnum = result.lnum
+ let col = result.col
+ let i = i + 1
+ endfor
+
+ " ...then backwards.
+ let lnum = 6
+ let col = 4
+ let i = 2
+ while i >= 0
+ let result = prop_find({'type': 'prop_name', 'lnum': lnum, 'col': col, 'skipstart': 1}, 'b')
+ if !has_key(expected[i], "length")
+ unlet result.length
+ endif
+ call assert_equal(expected[i], result)
+ let lnum = result.lnum
+ let col = result.col
+ let i = i - 1
+ endwhile
+
+ " Starting from line 6 col 1 search backwards for prop with id 10.
+ call cursor(6, 1)
+ let result = prop_find({'id': 10, 'skipstart': 1}, 'b')
+ call assert_equal(expected[0], result)
+
+ " Starting from line 1 col 1 search forwards for prop with id 12.
+ call cursor(1, 1)
+ let result = prop_find({'id': 12}, 'f')
+ call assert_equal(expected[2], result)
+
+ " Search for a prop with an unknown id.
+ let result = prop_find({'id': 999}, 'f')
+ call assert_equal({}, result)
+
+ " Search backwards from the proceeding position of the prop with id 11
+ " (at line num 2 col 4). This should return an empty dict.
+ let result = prop_find({'id': 11, 'lnum': 2, 'col': 3}, 'b')
+ call assert_equal({}, result)
+
+ " When lnum is given and col is omitted, use column 1.
+ let result = prop_find({'type': 'prop_name', 'lnum': 1}, 'f')
+ call assert_equal(expected[0], result)
+
+ " Negative ID is possible, just like prop is not found.
+ call assert_equal({}, prop_find({'id': -1}))
+ call assert_equal({}, prop_find({'id': -2}))
+
+ call prop_clear(1, 6)
+
+ " Default ID is zero
+ call prop_add(5, 4, {'type': 'prop_name', 'length': 1})
+ call assert_equal(#{lnum: 5, id: 0, col: 4, type_bufnr: 0, end: 1, type: 'prop_name', length: 1, start: 1}, prop_find({'id': 0}))
+
+ call prop_type_delete('prop_name')
+ call prop_clear(1, 6)
+ bwipe!
+endfunc
+
+def Test_prop_find2()
+ # Multiple props per line, start on the first, should find the second.
+ new
+ ['the quikc bronw fox jumsp over the layz dog']->repeat(2)->setline(1)
+ prop_type_add('misspell', {highlight: 'ErrorMsg'})
+ for lnum in [1, 2]
+ for col in [8, 14, 24, 38]
+ prop_add(lnum, col, {type: 'misspell', length: 2})
+ endfor
+ endfor
+ cursor(1, 8)
+ var expected = {type_bufnr: 0, lnum: 1, id: 0, col: 14, end: 1, type: 'misspell', length: 2, start: 1}
+ var result = prop_find({type: 'misspell', skipstart: true}, 'f')
+ assert_equal(expected, result)
+
+ prop_type_delete('misspell')
+ bwipe!
+enddef
+
+func Test_prop_find_smaller_len_than_match_col()
+ new
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call setline(1, ['xxxx', 'x'])
+ call prop_add(1, 4, {'type': 'test'})
+ call assert_equal(
+ \ #{type_bufnr: 0, id: 0, lnum: 1, col: 4, type: 'test', length: 0, start: 1, end: 1},
+ \ prop_find({'type': 'test', 'lnum': 2, 'col': 1}, 'b'))
+ bwipe!
+ call prop_type_delete('test')
+endfunc
+
+func Test_prop_find_with_both_option_enabled()
+ " Initialize
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let props = Get_expected_props()->map({_, v -> extend(v, {'lnum': 1})})
+ " Test
+ call assert_fails("call prop_find({'both': 1})", 'E968:')
+ call assert_fails("call prop_find({'id': 11, 'both': 1})", 'E860:')
+ call assert_fails("call prop_find({'type': 'three', 'both': 1})", 'E860:')
+ call assert_equal({}, prop_find({'id': 11, 'type': 'three', 'both': 1}))
+ call assert_equal({}, prop_find({'id': 130000, 'type': 'one', 'both': 1}))
+ call assert_equal(props[2], prop_find({'id': 12, 'type': 'two', 'both': 1}))
+ call assert_equal(props[0], prop_find({'id': 14, 'type': 'whole', 'both': 1}))
+ " Clean up
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_add()
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let expected_props = Get_expected_props()
+ call assert_equal(expected_props, prop_list(1))
+ call assert_fails("call prop_add(10, 1, {'length': 1, 'id': 14, 'type': 'whole'})", 'E966:')
+ call assert_fails("call prop_add(1, 22, {'length': 1, 'id': 14, 'type': 'whole'})", 'E964:')
+
+ " Insert a line above, text props must still be there.
+ call append(0, 'empty')
+ call assert_equal(expected_props, prop_list(2))
+ " Delete a line above, text props must still be there.
+ 1del
+ call assert_equal(expected_props, prop_list(1))
+
+ " Prop without length or end column is zero length
+ call prop_clear(1)
+ call prop_type_add('included', {'start_incl': 1, 'end_incl': 1})
+ call prop_add(1, 5, #{type: 'included'})
+ let expected = [#{type_bufnr: 0, col: 5, length: 0, type: 'included', id: 0, start: 1, end: 1}]
+ call assert_equal(expected, prop_list(1))
+
+ " Inserting text makes the prop bigger.
+ exe "normal 5|ixx\<Esc>"
+ let expected = [#{type_bufnr: 0, col: 5, length: 2, type: 'included', id: 0, start: 1, end: 1}]
+ call assert_equal(expected, prop_list(1))
+
+ call assert_fails("call prop_add(1, 5, {'type': 'two', 'bufnr': 234343})", 'E158:')
+
+ call DeletePropTypes()
+ call prop_type_delete('included')
+ bwipe!
+endfunc
+
+" Test for the prop_add_list() function
+func Test_prop_add_list()
+ new
+ call AddPropTypes()
+ call setline(1, ['one one one', 'two two two', 'six six six', 'ten ten ten'])
+ call prop_add_list(#{type: 'one', id: 2},
+ \ [[1, 1, 1, 3], [2, 5, 2, 7], [3, 6, 4, 6]])
+ call assert_equal([#{id: 2, col: 1, type_bufnr: 0, end: 1, type: 'one',
+ \ length: 2, start: 1}], prop_list(1))
+ call assert_equal([#{id: 2, col: 5, type_bufnr: 0, end: 1, type: 'one',
+ \ length: 2, start: 1}], prop_list(2))
+ call assert_equal([#{id: 2, col: 6, type_bufnr: 0, end: 0, type: 'one',
+ \ length: 7, start: 1}], prop_list(3))
+ call assert_equal([#{id: 2, col: 1, type_bufnr: 0, end: 1, type: 'one',
+ \ length: 5, start: 0}], prop_list(4))
+ call prop_remove(#{id: 2})
+ call assert_equal([], prop_list(1))
+
+ call prop_add_list(#{type: 'one', id: 3},
+ \ [[1, 1, 1, 3], [2, 5, 2, 7, 9]])
+ call assert_equal([#{id: 3, col: 1, type_bufnr: 0, end: 1, type: 'one',
+ \ length: 2, start: 1}], prop_list(1))
+ call assert_equal([#{id: 9, col: 5, type_bufnr: 0, end: 1, type: 'one',
+ \ length: 2, start: 1}], prop_list(2))
+
+ call assert_fails('call prop_add_list([1, 2], [[1, 1, 3]])', 'E1206:')
+ call assert_fails('call prop_add_list({}, {})', 'E1211:')
+ call assert_fails('call prop_add_list({}, [[1, 1, 3]])', 'E965:')
+ call assert_fails('call prop_add_list(#{type: "abc"}, [[1, 1, 1, 3]])', 'E971:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[]])', 'E474:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, 1, 1], {}])', 'E714:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, "a"]])', 'E474:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[2, 2]])', 'E474:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, 2], [2, 2]])', 'E474:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[1, 1, 1, 2], [4, 1, 5, 2]])', 'E966:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[3, 1, 1, 2]])', 'E966:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [[2, 2, 2, 2], [3, 20, 3, 22]])', 'E964:')
+ call assert_fails('eval #{type: "one"}->prop_add_list([[2, 2, 2, 2], [3, 20, 3, 22]])', 'E964:')
+ call assert_fails('call prop_add_list(test_null_dict(), [[2, 2, 2]])', 'E965:')
+ call assert_fails('call prop_add_list(#{type: "one"}, test_null_list())', 'E1298:')
+ call assert_fails('call prop_add_list(#{type: "one"}, [test_null_list()])', 'E714:')
+
+ " only one error for multiple wrong values
+ call assert_fails('call prop_add_list(#{type: "one"}, [[{}, [], 0z00, 0.3]])', ['E728:', 'E728:'])
+ call DeletePropTypes()
+ bw!
+endfunc
+
+func Test_prop_remove()
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let props = Get_expected_props()
+ call assert_equal(props, prop_list(1))
+
+ " remove by id
+ call assert_equal(1, {'id': 12}->prop_remove(1))
+ unlet props[2]
+ call assert_equal(props, prop_list(1))
+
+ " remove by type
+ call assert_equal(1, prop_remove({'type': 'one'}, 1))
+ unlet props[1]
+ call assert_equal(props, prop_list(1))
+
+ " remove from unknown buffer
+ call assert_fails("call prop_remove({'type': 'one', 'bufnr': 123456}, 1)", 'E158:')
+
+ call DeletePropTypes()
+ bwipe!
+
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ call prop_add(1, 6, {'length': 2, 'id': 11, 'type': 'three'})
+ let props = Get_expected_props()
+ call insert(props, #{type_bufnr: 0, col: 6, length: 2, id: 11, type: 'three', start: 1, end: 1}, 3)
+ call assert_equal(props, prop_list(1))
+ call assert_equal(1, prop_remove({'type': 'three', 'id': 11, 'both': 1, 'all': 1}, 1))
+ unlet props[3]
+ call assert_equal(props, prop_list(1))
+
+ call assert_fails("call prop_remove({'id': 11, 'both': 1})", 'E860:')
+ call assert_fails("call prop_remove({'type': 'three', 'both': 1})", 'E860:')
+
+ call DeletePropTypes()
+ bwipe!
+
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let props = Get_expected_props() " [whole, one, two, three]
+ call assert_equal(props, prop_list(1))
+
+ " remove one by types
+ call assert_equal(1, prop_remove({'types': ['one', 'two', 'three']}, 1))
+ unlet props[1] " [whole, two, three]
+ call assert_equal(props, prop_list(1))
+
+ " remove 'all' by types
+ call assert_equal(2, prop_remove({'types': ['three', 'whole'], 'all': 1}, 1))
+ unlet props[0] " [two, three]
+ unlet props[1] " [three]
+ call assert_equal(props, prop_list(1))
+
+ " remove none by types
+ call assert_equal(0, prop_remove({'types': ['three', 'whole'], 'all': 1}, 1))
+ call assert_equal(props, prop_list(1))
+
+ " no types
+ call assert_fails("call prop_remove({'types': []}, 1)", 'E968:')
+ call assert_fails("call prop_remove({'types': ['not_a_real_type']}, 1)", 'E971:')
+
+ " only one of types and type can be supplied
+ call assert_fails("call prop_remove({'type': 'one', 'types': ['three'], 'all': 1}, 1)", 'E1295:')
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+def Test_prop_add_vim9()
+ prop_type_add('comment', {
+ highlight: 'Directory',
+ priority: 123,
+ start_incl: true,
+ end_incl: true,
+ combine: false,
+ })
+ prop_type_delete('comment')
+enddef
+
+def Test_prop_remove_vim9()
+ new
+ g:AddPropTypes()
+ g:SetupPropsInFirstLine()
+ assert_equal(1, prop_remove({type: 'three', id: 13, both: true, all: true}))
+ g:DeletePropTypes()
+ bwipe!
+enddef
+
+func SetupOneLine()
+ call setline(1, 'xonex xtwoxx')
+ normal gg0
+ call AddPropTypes()
+ call prop_add(1, 2, {'length': 3, 'id': 11, 'type': 'one'})
+ call prop_add(1, 8, {'length': 3, 'id': 12, 'type': 'two'})
+ let expected = [
+ \ #{type_bufnr: 0, col: 2, length: 3, id: 11, type: 'one', start: 1, end: 1},
+ \ #{type_bufnr: 0, col: 8, length: 3, id: 12, type: 'two', start: 1, end: 1},
+ \]
+ call assert_equal(expected, prop_list(1))
+ return expected
+endfunc
+
+func Test_prop_add_remove_buf()
+ new
+ let bufnr = bufnr('')
+ call AddPropTypes()
+ for lnum in range(1, 4)
+ call setline(lnum, 'one two three')
+ endfor
+ wincmd w
+ for lnum in range(1, 4)
+ call prop_add(lnum, 1, {'length': 3, 'id': 11, 'type': 'one', 'bufnr': bufnr})
+ call prop_add(lnum, 5, {'length': 3, 'id': 12, 'type': 'two', 'bufnr': bufnr})
+ call prop_add(lnum, 11, {'length': 3, 'id': 13, 'type': 'three', 'bufnr': bufnr})
+ endfor
+
+ let props = [
+ \ #{type_bufnr: 0, col: 1, length: 3, id: 11, type: 'one', start: 1, end: 1},
+ \ #{type_bufnr: 0, col: 5, length: 3, id: 12, type: 'two', start: 1, end: 1},
+ \ #{type_bufnr: 0, col: 11, length: 3, id: 13, type: 'three', start: 1, end: 1},
+ \]
+ call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
+
+ " remove by id
+ let before_props = deepcopy(props)
+ unlet props[1]
+
+ call prop_remove({'id': 12, 'bufnr': bufnr}, 1)
+ call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(3, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(4, {'bufnr': bufnr}))
+
+ call prop_remove({'id': 12, 'bufnr': bufnr}, 3, 4)
+ call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
+ call assert_equal(props, prop_list(3, {'bufnr': bufnr}))
+ call assert_equal(props, prop_list(4, {'bufnr': bufnr}))
+
+ call prop_remove({'id': 12, 'bufnr': bufnr})
+ for lnum in range(1, 4)
+ call assert_equal(props, prop_list(lnum, {'bufnr': bufnr}))
+ endfor
+
+ " remove by type
+ let before_props = deepcopy(props)
+ unlet props[0]
+
+ call prop_remove({'type': 'one', 'bufnr': bufnr}, 1)
+ call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(3, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(4, {'bufnr': bufnr}))
+
+ call prop_remove({'type': 'one', 'bufnr': bufnr}, 3, 4)
+ call assert_equal(props, prop_list(1, {'bufnr': bufnr}))
+ call assert_equal(before_props, prop_list(2, {'bufnr': bufnr}))
+ call assert_equal(props, prop_list(3, {'bufnr': bufnr}))
+ call assert_equal(props, prop_list(4, {'bufnr': bufnr}))
+
+ call prop_remove({'type': 'one', 'bufnr': bufnr})
+ for lnum in range(1, 4)
+ call assert_equal(props, prop_list(lnum, {'bufnr': bufnr}))
+ endfor
+
+ call DeletePropTypes()
+ wincmd w
+ bwipe!
+endfunc
+
+func Test_prop_backspace()
+ new
+ set bs=2
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+
+ exe "normal 0li\<BS>\<Esc>fxli\<BS>\<Esc>"
+ call assert_equal('one xtwoxx', getline(1))
+ let expected[0].col = 1
+ let expected[1].col = 6
+ call assert_equal(expected, prop_list(1))
+
+ call DeletePropTypes()
+ bwipe!
+ set bs&
+endfunc
+
+func Test_prop_change()
+ new
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+
+ " Characterwise.
+ exe "normal 7|c$\<Esc>"
+ call assert_equal('xonex ', getline(1))
+ call assert_equal(expected[:0], prop_list(1))
+ " Linewise.
+ exe "normal cc\<Esc>"
+ call assert_equal('', getline(1))
+ call assert_equal([], prop_list(1))
+
+ call DeletePropTypes()
+ bwipe!
+ set bs&
+endfunc
+
+func Test_prop_replace()
+ new
+ set bs=2
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+
+ exe "normal 0Ryyy\<Esc>"
+ call assert_equal('yyyex xtwoxx', getline(1))
+ call assert_equal(expected, prop_list(1))
+
+ exe "normal ftRyy\<BS>"
+ call assert_equal('yyyex xywoxx', getline(1))
+ call assert_equal(expected, prop_list(1))
+
+ exe "normal 0fwRyy\<BS>"
+ call assert_equal('yyyex xyyoxx', getline(1))
+ call assert_equal(expected, prop_list(1))
+
+ exe "normal 0foRyy\<BS>\<BS>"
+ call assert_equal('yyyex xyyoxx', getline(1))
+ call assert_equal(expected, prop_list(1))
+
+ " Replace three 1-byte chars with three 2-byte ones.
+ exe "normal 0l3rø"
+ call assert_equal('yøøøx xyyoxx', getline(1))
+ let expected[0].length += 3
+ let expected[1].col += 3
+ call assert_equal(expected, prop_list(1))
+
+ call DeletePropTypes()
+ bwipe!
+ set bs&
+endfunc
+
+func Test_prop_open_line()
+ new
+
+ " open new line, props stay in top line
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+ exe "normal o\<Esc>"
+ call assert_equal('xonex xtwoxx', getline(1))
+ call assert_equal('', getline(2))
+ call assert_equal(expected, prop_list(1))
+ call DeletePropTypes()
+
+ " move all props to next line
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+ exe "normal 0i\<CR>\<Esc>"
+ call assert_equal('', getline(1))
+ call assert_equal('xonex xtwoxx', getline(2))
+ call assert_equal(expected, prop_list(2))
+ call DeletePropTypes()
+
+ " split just before prop, move all props to next line
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+ exe "normal 0li\<CR>\<Esc>"
+ call assert_equal('x', getline(1))
+ call assert_equal('onex xtwoxx', getline(2))
+ let expected[0].col -= 1
+ let expected[1].col -= 1
+ call assert_equal(expected, prop_list(2))
+ call DeletePropTypes()
+
+ " split inside prop, split first prop
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+ exe "normal 0lli\<CR>\<Esc>"
+ call assert_equal('xo', getline(1))
+ call assert_equal('nex xtwoxx', getline(2))
+ let exp_first = [deepcopy(expected[0])]
+ let exp_first[0].length = 1
+ let exp_first[0].end = 0
+ call assert_equal(exp_first, prop_list(1))
+ let expected[0].col = 1
+ let expected[0].length = 2
+ let expected[0].start = 0
+ let expected[1].col -= 2
+ call assert_equal(expected, prop_list(2))
+ call DeletePropTypes()
+
+ " split just after first prop, second prop move to next line
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+ exe "normal 0fea\<CR>\<Esc>"
+ call assert_equal('xone', getline(1))
+ call assert_equal('x xtwoxx', getline(2))
+ let exp_first = expected[0:0]
+ call assert_equal(exp_first, prop_list(1))
+ let expected = expected[1:1]
+ let expected[0].col -= 4
+ call assert_equal(expected, prop_list(2))
+ call DeletePropTypes()
+
+ " split at the space character with 'ai' active, the leading space is removed
+ " in the second line and the prop is shifted accordingly.
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+ set ai
+ exe "normal 6|i\<CR>\<Esc>"
+ call assert_equal('xonex', getline(1))
+ call assert_equal('xtwoxx', getline(2))
+ let expected[1].col -= 6
+ call assert_equal(expected, prop_list(1) + prop_list(2))
+ set ai&
+ call DeletePropTypes()
+
+ bwipe!
+ set bs&
+endfunc
+
+func Test_prop_put()
+ new
+ let expected = SetupOneLine() " 'xonex xtwoxx'
+
+ let @a = 'new'
+ " insert just after the prop
+ normal 03l"ap
+ " insert inside the prop
+ normal 02l"ap
+ " insert just before the prop
+ normal 0"ap
+
+ call assert_equal('xnewonnewenewx xtwoxx', getline(1))
+ let expected[0].col += 3
+ let expected[0].length += 3
+ let expected[1].col += 9
+ call assert_equal(expected, prop_list(1))
+
+ " Visually select 4 chars in the prop and put "AB" to replace them
+ let @a = 'AB'
+ normal 05lv3l"ap
+ call assert_equal('xnewoABenewx xtwoxx', getline(1))
+ let expected[0].length -= 2
+ let expected[1].col -= 2
+ call assert_equal(expected, prop_list(1))
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_clear()
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ call assert_equal(Get_expected_props(), prop_list(1))
+
+ eval 1->prop_clear()
+ call assert_equal([], 1->prop_list())
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_clear_buf()
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let bufnr = bufnr('')
+ wincmd w
+ call assert_equal(Get_expected_props(), prop_list(1, {'bufnr': bufnr}))
+
+ call prop_clear(1, 1, {'bufnr': bufnr})
+ call assert_equal([], prop_list(1, {'bufnr': bufnr}))
+
+ wincmd w
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_setline()
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ call assert_equal(Get_expected_props(), prop_list(1))
+
+ call setline(1, 'foobar')
+ call assert_equal([], prop_list(1))
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_setbufline()
+ new
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let bufnr = bufnr('')
+ wincmd w
+ call assert_equal(Get_expected_props(), prop_list(1, {'bufnr': bufnr}))
+
+ call setbufline(bufnr, 1, 'foobar')
+ call assert_equal([], prop_list(1, {'bufnr': bufnr}))
+
+ wincmd w
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_substitute()
+ new
+ " Set first line to 'one two three'
+ call AddPropTypes()
+ call SetupPropsInFirstLine()
+ let expected_props = Get_expected_props()
+ call assert_equal(expected_props, prop_list(1))
+
+ " Change "n" in "one" to XX: 'oXXe two three'
+ s/n/XX/
+ let expected_props[0].length += 1
+ let expected_props[1].length += 1
+ let expected_props[2].col += 1
+ let expected_props[3].col += 1
+ call assert_equal(expected_props, prop_list(1))
+
+ " Delete "t" in "two" and "three" to XX: 'oXXe wo hree'
+ s/t//g
+ let expected_props[0].length -= 2
+ let expected_props[2].length -= 1
+ let expected_props[3].length -= 1
+ let expected_props[3].col -= 1
+ call assert_equal(expected_props, prop_list(1))
+
+ " Split the line by changing w to line break: 'oXXe ', 'o hree'
+ " The long prop is split and spans both lines.
+ " The props on "two" and "three" move to the next line.
+ s/w/\r/
+ let new_props = [
+ \ copy(expected_props[0]),
+ \ copy(expected_props[2]),
+ \ copy(expected_props[3]),
+ \ ]
+ let expected_props[0].length = 5
+ let expected_props[0].end = 0
+ unlet expected_props[3]
+ unlet expected_props[2]
+ call assert_equal(expected_props, prop_list(1))
+
+ let new_props[0].length = 6
+ let new_props[0].start = 0
+ let new_props[1].col = 1
+ let new_props[1].length = 1
+ let new_props[2].col = 3
+ call assert_equal(new_props, prop_list(2))
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_change_indent()
+ call prop_type_add('comment', {'highlight': 'Directory'})
+ new
+ call setline(1, [' xxx', 'yyyyy'])
+ call prop_add(2, 2, {'length': 2, 'type': 'comment'})
+ let expect = #{type_bufnr: 0, col: 2, length: 2, type: 'comment', start: 1, end: 1, id: 0}
+ call assert_equal([expect], prop_list(2))
+
+ set shiftwidth=3
+ normal 2G>>
+ call assert_equal(' yyyyy', getline(2))
+ let expect.col += 3
+ call assert_equal([expect], prop_list(2))
+
+ normal 2G==
+ call assert_equal(' yyyyy', getline(2))
+ let expect.col = 6
+ call assert_equal([expect], prop_list(2))
+
+ call prop_clear(2)
+ call prop_add(2, 2, {'length': 5, 'type': 'comment'})
+ let expect.col = 2
+ let expect.length = 5
+ call assert_equal([expect], prop_list(2))
+
+ normal 2G<<
+ call assert_equal(' yyyyy', getline(2))
+ let expect.length = 2
+ call assert_equal([expect], prop_list(2))
+
+ set shiftwidth&
+ call prop_type_delete('comment')
+endfunc
+
+" Setup a three line prop in lines 2 - 4.
+" Add short props in line 1 and 5.
+func Setup_three_line_prop()
+ new
+ call setline(1, ['one', 'twotwo', 'three', 'fourfour', 'five'])
+ call prop_add(1, 2, {'length': 1, 'type': 'comment'})
+ call prop_add(2, 4, {'end_lnum': 4, 'end_col': 5, 'type': 'comment'})
+ call prop_add(5, 2, {'length': 1, 'type': 'comment'})
+endfunc
+
+func Test_prop_multiline()
+ eval 'comment'->prop_type_add({'highlight': 'Directory'})
+ new
+ call setline(1, ['xxxxxxx', 'yyyyyyyyy', 'zzzzzzzz'])
+
+ " start halfway line 1, end halfway line 3
+ call prop_add(1, 3, {'end_lnum': 3, 'end_col': 5, 'type': 'comment'})
+ let expect1 = #{type_bufnr: 0, col: 3, length: 6, type: 'comment', start: 1, end: 0, id: 0}
+ call assert_equal([expect1], prop_list(1))
+ let expect2 = #{type_bufnr: 0, col: 1, length: 10, type: 'comment', start: 0, end: 0, id: 0}
+ call assert_equal([expect2], prop_list(2))
+ let expect3 = #{type_bufnr: 0, col: 1, length: 4, type: 'comment', start: 0, end: 1, id: 0}
+ call assert_equal([expect3], prop_list(3))
+ call prop_clear(1, 3)
+
+ " include all three lines
+ call prop_add(1, 1, {'end_lnum': 3, 'end_col': 999, 'type': 'comment'})
+ let expect1.col = 1
+ let expect1.length = 8
+ call assert_equal([expect1], prop_list(1))
+ call assert_equal([expect2], prop_list(2))
+ let expect3.length = 9
+ call assert_equal([expect3], prop_list(3))
+ call prop_clear(1, 3)
+
+ bwipe!
+
+ " Test deleting the first line of a multi-line prop.
+ call Setup_three_line_prop()
+ let expect_short = #{type_bufnr: 0, col: 2, length: 1, type: 'comment', start: 1, end: 1, id: 0}
+ call assert_equal([expect_short], prop_list(1))
+ let expect2 = #{type_bufnr: 0, col: 4, length: 4, type: 'comment', start: 1, end: 0, id: 0}
+ call assert_equal([expect2], prop_list(2))
+ 2del
+ call assert_equal([expect_short], prop_list(1))
+ let expect2 = #{type_bufnr: 0, col: 1, length: 6, type: 'comment', start: 1, end: 0, id: 0}
+ call assert_equal([expect2], prop_list(2))
+ bwipe!
+
+ " Test deleting the last line of a multi-line prop.
+ call Setup_three_line_prop()
+ let expect3 = #{type_bufnr: 0, col: 1, length: 6, type: 'comment', start: 0, end: 0, id: 0}
+ call assert_equal([expect3], prop_list(3))
+ let expect4 = #{type_bufnr: 0, col: 1, length: 4, type: 'comment', start: 0, end: 1, id: 0}
+ call assert_equal([expect4], prop_list(4))
+ 4del
+ let expect3.end = 1
+ call assert_equal([expect3], prop_list(3))
+ call assert_equal([expect_short], prop_list(4))
+ bwipe!
+
+ " Test appending a line below the multi-line text prop start.
+ call Setup_three_line_prop()
+ let expect2 = #{type_bufnr: 0, col: 4, length: 4, type: 'comment', start: 1, end: 0, id: 0}
+ call assert_equal([expect2], prop_list(2))
+ call append(2, "new line")
+ call assert_equal([expect2], prop_list(2))
+ let expect3 = #{type_bufnr: 0, col: 1, length: 9, type: 'comment', start: 0, end: 0, id: 0}
+ call assert_equal([expect3], prop_list(3))
+ bwipe!
+
+ call prop_type_delete('comment')
+endfunc
+
+func Run_test_with_line2byte(add_props)
+ new
+ setlocal ff=unix
+ if a:add_props
+ call prop_type_add('textprop', #{highlight: 'Search'})
+ endif
+ " Add a text prop to every fourth line and then change every fifth line so
+ " that it causes a data block split a few times.
+ for nr in range(1, 1000)
+ call setline(nr, 'some longer text here')
+ if a:add_props && nr % 4 == 0
+ call prop_add(nr, 13, #{type: 'textprop', length: 4})
+ endif
+ endfor
+ let expected = 22 * 997 + 1
+ call assert_equal(expected, line2byte(998))
+
+ for nr in range(1, 1000, 5)
+ exe nr .. "s/longer/much more/"
+ let expected += 3
+ call assert_equal(expected, line2byte(998), 'line ' .. nr)
+ endfor
+
+ if a:add_props
+ call prop_type_delete('textprop')
+ endif
+ bwipe!
+endfunc
+
+func Test_prop_line2byte()
+ call prop_type_add('comment', {'highlight': 'Directory'})
+ new
+ call setline(1, ['line1', 'second line', ''])
+ set ff=unix
+ call assert_equal(19, line2byte(3))
+ call prop_add(1, 1, {'end_col': 3, 'type': 'comment'})
+ call assert_equal(19, line2byte(3))
+ bwipe!
+
+ new
+ setlocal ff=unix
+ call setline(1, range(500))
+ call assert_equal(1491, line2byte(401))
+ call prop_add(2, 1, {'type': 'comment'})
+ call prop_add(222, 1, {'type': 'comment'})
+ call assert_equal(1491, line2byte(401))
+ call prop_remove({'type': 'comment'})
+ call assert_equal(1491, line2byte(401))
+ bwipe!
+
+ new
+ setlocal ff=unix
+ call setline(1, range(520))
+ call assert_equal(1491, line2byte(401))
+ call prop_add(2, 1, {'type': 'comment'})
+ call assert_equal(1491, line2byte(401))
+ 2delete
+ call assert_equal(1489, line2byte(400))
+ bwipe!
+
+ " Add many lines so that the data block is split.
+ " With and without props should give the same result.
+ call Run_test_with_line2byte(0)
+ call Run_test_with_line2byte(1)
+
+ call prop_type_delete('comment')
+endfunc
+
+func Test_prop_byte2line()
+ new
+ set ff=unix
+ call setline(1, ['one one', 'two two', 'three three', 'four four', 'five'])
+ call assert_equal(4, byte2line(line2byte(4)))
+ call assert_equal(5, byte2line(line2byte(5)))
+
+ call prop_type_add('prop', {'highlight': 'Directory'})
+ call prop_add(3, 1, {'length': 5, 'type': 'prop'})
+ call assert_equal(4, byte2line(line2byte(4)))
+ call assert_equal(5, byte2line(line2byte(5)))
+
+ bwipe!
+ call prop_type_delete('prop')
+endfunc
+
+func Test_prop_goto_byte()
+ new
+ call setline(1, '')
+ call setline(2, 'two three')
+ call setline(3, '')
+ call setline(4, 'four five')
+
+ call prop_type_add('testprop', {'highlight': 'Directory'})
+ call search('^two')
+ call prop_add(line('.'), col('.'), {
+ \ 'length': len('two'),
+ \ 'type': 'testprop'
+ \ })
+
+ call search('two \zsthree')
+ let expected_pos = line2byte(line('.')) + col('.') - 1
+ exe expected_pos .. 'goto'
+ let actual_pos = line2byte(line('.')) + col('.') - 1
+ eval actual_pos->assert_equal(expected_pos)
+
+ call search('four \zsfive')
+ let expected_pos = line2byte(line('.')) + col('.') - 1
+ exe expected_pos .. 'goto'
+ let actual_pos = line2byte(line('.')) + col('.') - 1
+ eval actual_pos->assert_equal(expected_pos)
+
+ call prop_type_delete('testprop')
+ bwipe!
+endfunc
+
+func Test_prop_undo()
+ new
+ call prop_type_add('comment', {'highlight': 'Directory'})
+ call setline(1, ['oneone', 'twotwo', 'three'])
+ " Set 'undolevels' to break changes into undo-able pieces.
+ set ul&
+
+ call prop_add(1, 3, {'end_col': 5, 'type': 'comment'})
+ let expected = [#{type_bufnr: 0, col: 3, length: 2, id: 0, type: 'comment', start: 1, end: 1}]
+ call assert_equal(expected, prop_list(1))
+
+ " Insert a character, then undo.
+ exe "normal 0lllix\<Esc>"
+ set ul&
+ let expected[0].length = 3
+ call assert_equal(expected, prop_list(1))
+ undo
+ let expected[0].length = 2
+ call assert_equal(expected, prop_list(1))
+
+ " Delete a character, then undo
+ exe "normal 0lllx"
+ set ul&
+ let expected[0].length = 1
+ call assert_equal(expected, prop_list(1))
+ undo
+ let expected[0].length = 2
+ call assert_equal(expected, prop_list(1))
+
+ " Delete the line, then undo
+ 1d
+ set ul&
+ call assert_equal([], prop_list(1))
+ undo
+ call assert_equal(expected, prop_list(1))
+
+ " Insert a character, delete two characters, then undo with "U"
+ exe "normal 0lllix\<Esc>"
+ set ul&
+ let expected[0].length = 3
+ call assert_equal(expected, prop_list(1))
+ exe "normal 0lllxx"
+ set ul&
+ let expected[0].length = 1
+ call assert_equal(expected, prop_list(1))
+ normal U
+ let expected[0].length = 2
+ call assert_equal(expected, prop_list(1))
+
+ " substitute a word, then undo
+ call setline(1, 'the number 123 is highlighted.')
+ call prop_add(1, 12, {'length': 3, 'type': 'comment'})
+ let expected = [#{type_bufnr: 0, col: 12, length: 3, id: 0, type: 'comment', start: 1, end: 1} ]
+ call assert_equal(expected, prop_list(1))
+ set ul&
+ 1s/number/foo
+ let expected[0].col = 9
+ call assert_equal(expected, prop_list(1))
+ undo
+ let expected[0].col = 12
+ call assert_equal(expected, prop_list(1))
+ call prop_clear(1)
+
+ " substitute with backslash
+ call setline(1, 'the number 123 is highlighted.')
+ call prop_add(1, 12, {'length': 3, 'type': 'comment'})
+ let expected = [#{type_bufnr: 0, col: 12, length: 3, id: 0, type: 'comment', start: 1, end: 1} ]
+ call assert_equal(expected, prop_list(1))
+ 1s/the/\The
+ call assert_equal(expected, prop_list(1))
+ 1s/^/\\
+ let expected[0].col += 1
+ call assert_equal(expected, prop_list(1))
+ 1s/^/\~
+ let expected[0].col += 1
+ call assert_equal(expected, prop_list(1))
+ 1s/123/12\\3
+ let expected[0].length += 1
+ call assert_equal(expected, prop_list(1))
+ call prop_clear(1)
+
+ bwipe!
+ call prop_type_delete('comment')
+endfunc
+
+func Test_prop_delete_text()
+ new
+ call prop_type_add('comment', {'highlight': 'Directory'})
+ call setline(1, ['oneone', 'twotwo', 'three'])
+
+ " zero length property
+ call prop_add(1, 3, {'type': 'comment'})
+ let expected = [#{type_bufnr: 0, col: 3, length: 0, id: 0, type: 'comment', start: 1, end: 1} ]
+ call assert_equal(expected, prop_list(1))
+
+ " delete one char moves the property
+ normal! x
+ let expected = [#{type_bufnr: 0, col: 2, length: 0, id: 0, type: 'comment', start: 1, end: 1} ]
+ call assert_equal(expected, prop_list(1))
+
+ " delete char of the property has no effect
+ normal! lx
+ let expected = [#{type_bufnr: 0, col: 2, length: 0, id: 0, type: 'comment', start: 1, end: 1} ]
+ call assert_equal(expected, prop_list(1))
+
+ " delete more chars moves property to first column, is not deleted
+ normal! 0xxxx
+ let expected = [#{type_bufnr: 0, col: 1, length: 0, id: 0, type: 'comment', start: 1, end: 1} ]
+ call assert_equal(expected, prop_list(1))
+
+ bwipe!
+ call prop_type_delete('comment')
+endfunc
+
+" screenshot test with textprop highlighting
+func Test_textprop_screenshot_various()
+ CheckScreendump
+ " The Vim running in the terminal needs to use utf-8.
+ if g:orig_encoding != 'utf-8'
+ throw 'Skipped: not using utf-8'
+ endif
+ call writefile([
+ \ "call setline(1, ["
+ \ .. "'One two',"
+ \ .. "'Numbér 123 änd thœn 4¾7.',"
+ \ .. "'--aa--bb--cc--dd--',"
+ \ .. "'// comment with error in it',"
+ \ .. "'first line',"
+ \ .. "' second line ',"
+ \ .. "'third line',"
+ \ .. "' fourth line',"
+ \ .. "])",
+ \ "hi NumberProp ctermfg=blue",
+ \ "hi LongProp ctermbg=yellow",
+ \ "hi BackgroundProp ctermbg=lightgrey",
+ \ "hi UnderlineProp cterm=underline",
+ \ "call prop_type_add('number', {'highlight': 'NumberProp'})",
+ \ "call prop_type_add('long', {'highlight': 'NumberProp'})",
+ \ "call prop_type_change('long', {'highlight': 'LongProp'})",
+ \ "call prop_type_add('start', {'highlight': 'NumberProp', 'start_incl': 1})",
+ \ "call prop_type_add('end', {'highlight': 'NumberProp', 'end_incl': 1})",
+ \ "call prop_type_add('both', {'highlight': 'NumberProp', 'start_incl': 1, 'end_incl': 1})",
+ \ "call prop_type_add('background', {'highlight': 'BackgroundProp', 'combine': 0})",
+ \ "call prop_type_add('backgroundcomb', {'highlight': 'NumberProp', 'combine': 1})",
+ \ "eval 'backgroundcomb'->prop_type_change({'highlight': 'BackgroundProp'})",
+ \ "call prop_type_add('error', {'highlight': 'UnderlineProp'})",
+ \ "call prop_add(1, 4, {'end_lnum': 3, 'end_col': 3, 'type': 'long'})",
+ \ "call prop_add(2, 9, {'length': 3, 'type': 'number'})",
+ \ "call prop_add(2, 24, {'length': 4, 'type': 'number'})",
+ \ "call prop_add(3, 3, {'length': 2, 'type': 'number'})",
+ \ "call prop_add(3, 7, {'length': 2, 'type': 'start'})",
+ \ "call prop_add(3, 11, {'length': 2, 'type': 'end'})",
+ \ "call prop_add(3, 15, {'length': 2, 'type': 'both'})",
+ \ "call prop_add(4, 6, {'length': 3, 'type': 'background'})",
+ \ "call prop_add(4, 12, {'length': 10, 'type': 'backgroundcomb'})",
+ \ "call prop_add(4, 17, {'length': 5, 'type': 'error'})",
+ \ "call prop_add(5, 7, {'length': 4, 'type': 'long'})",
+ \ "call prop_add(6, 1, {'length': 8, 'type': 'long'})",
+ \ "call prop_add(8, 1, {'length': 1, 'type': 'long'})",
+ \ "call prop_add(8, 11, {'length': 4, 'type': 'long'})",
+ \ "set number cursorline",
+ \ "hi clear SpellBad",
+ \ "set spell",
+ \ "syn match Comment '//.*'",
+ \ "hi Comment ctermfg=green",
+ \ "normal 3G0llix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>lllix\<Esc>",
+ \ "normal 3G0lli\<BS>\<Esc>",
+ \ "normal 6G0i\<BS>\<Esc>",
+ \ "normal 3J",
+ \ "normal 3G",
+ \], 'XtestProp', 'D')
+ let buf = RunVimInTerminal('-S XtestProp', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_textprop_01', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_textprop_hl_override()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['One one one one one', 'Two two two two two', 'Three three three three'])
+ hi OverProp ctermfg=blue ctermbg=yellow
+ hi CursorLine cterm=bold,underline ctermfg=red ctermbg=green
+ hi Vsual ctermfg=cyan ctermbg=grey
+ call prop_type_add('under', #{highlight: 'OverProp'})
+ call prop_type_add('over', #{highlight: 'OverProp', override: 1})
+ call prop_add(1, 5, #{type: 'under', length: 4})
+ call prop_add(1, 13, #{type: 'over', length: 4})
+ call prop_add(2, 5, #{type: 'under', length: 4})
+ call prop_add(2, 13, #{type: 'over', length: 4})
+ call prop_add(3, 5, #{type: 'under', length: 4})
+ call prop_add(3, 13, #{type: 'over', length: 4})
+ set cursorline
+ 2
+ END
+ call writefile(lines, 'XtestOverProp', 'D')
+ let buf = RunVimInTerminal('-S XtestOverProp', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_textprop_hl_override_1', {})
+
+ call term_sendkeys(buf, "3Gllv$hh")
+ call VerifyScreenDump(buf, 'Test_textprop_hl_override_2', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func RunTestVisualBlock(width, dump)
+ call writefile([
+ \ "call setline(1, ["
+ \ .. "'xxxxxxxxx 123 x',"
+ \ .. "'xxxxxxxx 123 x',"
+ \ .. "'xxxxxxx 123 x',"
+ \ .. "'xxxxxx 123 x',"
+ \ .. "'xxxxx 123 x',"
+ \ .. "'xxxx 123 xx',"
+ \ .. "'xxx 123 xxx',"
+ \ .. "'xx 123 xxxx',"
+ \ .. "'x 123 xxxxx',"
+ \ .. "' 123 xxxxxx',"
+ \ .. "])",
+ \ "hi SearchProp ctermbg=yellow",
+ \ "call prop_type_add('search', {'highlight': 'SearchProp'})",
+ \ "call prop_add(1, 11, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(2, 10, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(3, 9, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(4, 8, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(5, 7, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(6, 6, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(7, 5, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(8, 4, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(9, 3, {'length': 3, 'type': 'search'})",
+ \ "call prop_add(10, 2, {'length': 3, 'type': 'search'})",
+ \ "normal 1G6|\<C-V>" .. repeat('l', a:width - 1) .. "10jx",
+ \], 'XtestPropVis', 'D')
+ let buf = RunVimInTerminal('-S XtestPropVis', {'rows': 12})
+ call VerifyScreenDump(buf, 'Test_textprop_vis_' .. a:dump, {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" screenshot test with Visual block mode operations
+func Test_textprop_screenshot_visual()
+ CheckScreendump
+
+ " Delete two columns while text props are three chars wide.
+ call RunTestVisualBlock(2, '01')
+
+ " Same, but delete four columns
+ call RunTestVisualBlock(4, '02')
+endfunc
+
+func Test_textprop_after_tab()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, [
+ \ "\txxx",
+ \ "x\txxx",
+ \ ])
+ hi SearchProp ctermbg=yellow
+ call prop_type_add('search', {'highlight': 'SearchProp'})
+ call prop_add(1, 2, {'length': 3, 'type': 'search'})
+ call prop_add(2, 3, {'length': 3, 'type': 'search'})
+ END
+ call writefile(lines, 'XtextPropTab', 'D')
+ let buf = RunVimInTerminal('-S XtextPropTab', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_textprop_tab', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_textprop_nesting()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ var lines =<< trim LINESEND
+
+ const func: func.IFunction = ({
+ setLoading
+ }) => {
+ LINESEND
+ setline(1, lines)
+ prop_type_add('prop_add_test', {highlight: "ErrorMsg"})
+ prop_add(2, 31, {type: 'prop_add_test', end_lnum: 4, end_col: 2})
+ var text = 'text long enough to wrap line, text long enough to wrap line, text long enough to wrap line...'
+ prop_add(2, 0, {type: 'prop_add_test', text_wrap: 'truncate', text_align: 'after', text: text})
+ END
+ call writefile(lines, 'XtextpropNesting', 'D')
+ let buf = RunVimInTerminal('-S XtextpropNesting', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_textprop_nesting', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_textprop_nowrap_scrolled()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ set nowrap
+ setline(1, 'The number 123 is smaller than 4567.' .. repeat('X', &columns))
+ prop_type_add('number', {'highlight': 'ErrorMsg'})
+ prop_add(1, 12, {'length': 3, 'type': 'number'})
+ prop_add(1, 32, {'length': 4, 'type': 'number'})
+ feedkeys('gg20zl', 'nxt')
+ END
+ call writefile(lines, 'XtestNowrap', 'D')
+ let buf = RunVimInTerminal('-S XtestNowrap', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_textprop_nowrap_01', {})
+
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_textprop_nowrap_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_textprop_text_priority()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, "function( call, argument, here )")
+
+ call prop_type_add('one', #{highlight: 'Error'})
+ call prop_type_add('two', #{highlight: 'Function'})
+ call prop_type_add('three', #{highlight: 'DiffChange'})
+ call prop_type_add('arg', #{highlight: 'Search'})
+
+ call prop_add(1, 27, #{type: 'arg', length: len('here')})
+ call prop_add(1, 27, #{type: 'three', text: 'three: '})
+ call prop_add(1, 11, #{type: 'one', text: 'one: '})
+ call prop_add(1, 11, #{type: 'arg', length: len('call')})
+ call prop_add(1, 17, #{type: 'two', text: 'two: '})
+ call prop_add(1, 17, #{type: 'arg', length: len('argument')})
+ END
+ call writefile(lines, 'XtestPropPrio', 'D')
+ let buf = RunVimInTerminal('-S XtestPropPrio', {'rows': 5})
+ call VerifyScreenDump(buf, 'Test_prop_at_same_pos', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_textprop_with_syntax()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, [
+ \ "(abc)",
+ \ ])
+ syn match csParens "[()]" display
+ hi! link csParens MatchParen
+
+ call prop_type_add('TPTitle', #{ highlight: 'Title' })
+ call prop_add(1, 2, #{type: 'TPTitle', end_col: 5})
+ END
+ call writefile(lines, 'XtestPropSyn', 'D')
+ let buf = RunVimInTerminal('-S XtestPropSyn', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_textprop_syn_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Adding a text property to a new buffer should not fail
+func Test_textprop_empty_buffer()
+ call prop_type_add('comment', {'highlight': 'Search'})
+ new
+ call prop_add(1, 1, {'type': 'comment'})
+ close
+ call prop_type_delete('comment')
+endfunc
+
+" Adding a text property with invalid highlight should be ignored.
+func Test_textprop_invalid_highlight()
+ call assert_fails("call prop_type_add('dni', {'highlight': 'DoesNotExist'})", 'E970:')
+ new
+ call setline(1, ['asdf', 'asdf'])
+ call prop_add(1, 1, {'length': 4, 'type': 'dni'})
+ redraw
+ bwipe!
+ call prop_type_delete('dni')
+endfunc
+
+" Adding a text property to an empty buffer and then editing another
+func Test_textprop_empty_buffer_next()
+ call prop_type_add("xxx", {})
+ call prop_add(1, 1, {"type": "xxx"})
+ next X
+ call prop_type_delete('xxx')
+endfunc
+
+func Test_textprop_remove_from_buf()
+ new
+ let buf = bufnr('')
+ call prop_type_add('one', {'bufnr': buf})
+ call prop_add(1, 1, {'type': 'one', 'id': 234})
+ file x
+ edit y
+ call prop_remove({'id': 234, 'bufnr': buf}, 1)
+ call prop_type_delete('one', {'bufnr': buf})
+ bwipe! x
+ close
+endfunc
+
+func Test_textprop_in_unloaded_buf()
+ edit Xaaa
+ call setline(1, 'aaa')
+ write
+ edit Xbbb
+ call setline(1, 'bbb')
+ write
+ let bnr = bufnr('')
+ edit Xaaa
+
+ call prop_type_add('ErrorMsg', #{highlight:'ErrorMsg'})
+ call assert_fails("call prop_add(1, 1, #{end_lnum: 1, endcol: 2, type: 'ErrorMsg', bufnr: bnr})", 'E275:')
+ exe 'buf ' .. bnr
+ call assert_equal('bbb', getline(1))
+ call assert_equal(0, prop_list(1)->len())
+
+ bwipe! Xaaa
+ bwipe! Xbbb
+ cal delete('Xaaa')
+ cal delete('Xbbb')
+endfunc
+
+func Test_proptype_substitute2()
+ new
+ " text_prop.vim
+ call setline(1, [
+ \ 'The num 123 is smaller than 4567.',
+ \ '123 The number 123 is smaller than 4567.',
+ \ '123 The number 123 is smaller than 4567.'])
+
+ call prop_type_add('number', {'highlight': 'ErrorMsg'})
+
+ call prop_add(1, 12, {'length': 3, 'type': 'number'})
+ call prop_add(2, 1, {'length': 3, 'type': 'number'})
+ call prop_add(3, 36, {'length': 4, 'type': 'number'})
+ set ul&
+ let expected = [
+ \ #{type_bufnr: 0, id: 0, col: 13, end: 1, type: 'number', length: 3, start: 1},
+ \ #{type_bufnr: 0, id: 0, col: 1, end: 1, type: 'number', length: 3, start: 1},
+ \ #{type_bufnr: 0, id: 0, col: 50, end: 1, type: 'number', length: 4, start: 1}]
+
+ " TODO
+ if 0
+ " Add some text in between
+ %s/\s\+/ /g
+ call assert_equal(expected, prop_list(1) + prop_list(2) + prop_list(3))
+
+ " remove some text
+ :1s/[a-z]\{3\}//g
+ let expected = [{'id': 0, 'col': 10, 'end': 1, 'type': 'number', 'length': 3, 'start': 1}]
+ call assert_equal(expected, prop_list(1))
+ endif
+
+ call prop_type_delete('number')
+ bwipe!
+endfunc
+
+" This was causing property corruption.
+func Test_proptype_substitute3()
+ new
+ call setline(1, ['abcxxx', 'def'])
+ call prop_type_add("test", {"highlight": "Search"})
+ call prop_add(1, 2, {"end_lnum": 2, "end_col": 2, "type": "test"})
+ %s/x\+$//
+ redraw
+
+ call prop_type_delete('test')
+ bwipe!
+endfunc
+
+func Test_proptype_substitute_join()
+ new
+ call setline(1, [
+ \ 'This is some end',
+ \ 'start is highlighted end',
+ \ 'some is highlighted',
+ \ 'start is also highlighted'])
+
+ call prop_type_add('number', {'highlight': 'ErrorMsg'})
+
+ call prop_add(1, 6, {'length': 2, 'type': 'number'})
+ call prop_add(2, 7, {'length': 2, 'type': 'number'})
+ call prop_add(3, 6, {'length': 2, 'type': 'number'})
+ call prop_add(4, 7, {'length': 2, 'type': 'number'})
+ " The highlighted "is" in line 1, 2 and 4 is kept and ajudsted.
+ " The highlighted "is" in line 3 is deleted.
+ let expected = [
+ \ #{type_bufnr: 0, id: 0, col: 6, end: 1, type: 'number', length: 2, start: 1},
+ \ #{type_bufnr: 0, id: 0, col: 21, end: 1, type: 'number', length: 2, start: 1},
+ \ #{type_bufnr: 0, id: 0, col: 43, end: 1, type: 'number', length: 2, start: 1}]
+
+ s/end\nstart/joined/
+ s/end\n.*\nstart/joined/
+ call assert_equal('This is some joined is highlighted joined is also highlighted', getline(1))
+ call assert_equal(expected, prop_list(1))
+
+ call prop_type_delete('number')
+ bwipe!
+endfunc
+
+func SaveOptions()
+ let d = #{tabstop: &tabstop,
+ \ softtabstop: &softtabstop,
+ \ shiftwidth: &shiftwidth,
+ \ expandtab: &expandtab,
+ \ foldmethod: '"' .. &foldmethod .. '"',
+ \ }
+ return d
+endfunc
+
+func RestoreOptions(dict)
+ for name in keys(a:dict)
+ exe 'let &' .. name .. ' = ' .. a:dict[name]
+ endfor
+endfunc
+
+func Test_textprop_noexpandtab()
+ new
+ let save_dict = SaveOptions()
+
+ set tabstop=8
+ set softtabstop=4
+ set shiftwidth=4
+ set noexpandtab
+ set foldmethod=marker
+
+ call feedkeys("\<esc>\<esc>0Ca\<cr>\<esc>\<up>", "tx")
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
+ call feedkeys("0i\<tab>", "tx")
+ call prop_remove({'type': 'test'})
+ call prop_add(1, 2, {'end_col': 3, 'type': 'test'})
+ call feedkeys("A\<left>\<tab>", "tx")
+ call prop_remove({'type': 'test'})
+ try
+ " It is correct that this does not pass
+ call prop_add(1, 6, {'end_col': 7, 'type': 'test'})
+ " Has already collapsed here, start_col:6 does not result in an error
+ call feedkeys("A\<left>\<tab>", "tx")
+ catch /^Vim\%((\a\+)\)\=:E964/
+ endtry
+ call prop_remove({'type': 'test'})
+ call prop_type_delete('test')
+
+ call RestoreOptions(save_dict)
+ bwipe!
+endfunc
+
+func Test_textprop_noexpandtab_redraw()
+ new
+ let save_dict = SaveOptions()
+
+ set tabstop=8
+ set softtabstop=4
+ set shiftwidth=4
+ set noexpandtab
+ set foldmethod=marker
+
+ call feedkeys("\<esc>\<esc>0Ca\<cr>\<space>\<esc>\<up>", "tx")
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
+ call feedkeys("0i\<tab>", "tx")
+ " Internally broken at the next line
+ call feedkeys("A\<left>\<tab>", "tx")
+ redraw
+ " Index calculation failed internally on next line
+ call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
+ call prop_remove({'type': 'test', 'all': v:true})
+ call prop_type_delete('test')
+ call prop_type_delete('test')
+
+ call RestoreOptions(save_dict)
+ bwipe!
+endfunc
+
+func Test_textprop_ins_str()
+ new
+ call setline(1, 'just some text')
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call prop_add(1, 1, {'end_col': 2, 'type': 'test'})
+ call assert_equal([#{type_bufnr: 0, id: 0, col: 1, end: 1, type: 'test', length: 1, start: 1}], prop_list(1))
+
+ call feedkeys("foi\<F8>\<Esc>", "tx")
+ call assert_equal('just s<F8>ome text', getline(1))
+ call assert_equal([#{type_bufnr: 0, id: 0, col: 1, end: 1, type: 'test', length: 1, start: 1}], prop_list(1))
+
+ bwipe!
+ call prop_remove({'type': 'test'})
+ call prop_type_delete('test')
+endfunc
+
+func Test_find_prop_later_in_line()
+ new
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call setline(1, 'just some text')
+ call prop_add(1, 1, {'length': 4, 'type': 'test'})
+ call prop_add(1, 10, {'length': 3, 'type': 'test'})
+
+ call assert_equal(
+ \ #{type_bufnr: 0, id: 0, lnum: 1, col: 10, end: 1, type: 'test', length: 3, start: 1},
+ \ prop_find(#{type: 'test', lnum: 1, col: 6}))
+
+ bwipe!
+ call prop_type_delete('test')
+endfunc
+
+func Test_find_zerowidth_prop_sol()
+ new
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call setline(1, 'just some text')
+ call prop_add(1, 1, {'length': 0, 'type': 'test'})
+
+ call assert_equal(
+ \ #{type_bufnr: 0, id: 0, lnum: 1, col: 1, end: 1, type: 'test', length: 0, start: 1},
+ \ prop_find(#{type: 'test', lnum: 1}))
+
+ bwipe!
+ call prop_type_delete('test')
+endfunc
+
+" Test for passing invalid arguments to prop_xxx() functions
+func Test_prop_func_invalid_args()
+ call assert_fails('call prop_clear(1, 2, [])', 'E715:')
+ call assert_fails('call prop_clear(-1, 2)', 'E16:')
+ call assert_fails('call prop_find(test_null_dict())', 'E1297:')
+ call assert_fails('call prop_find({"bufnr" : []})', 'E730:')
+ call assert_fails('call prop_find({})', 'E968:')
+ call assert_fails('call prop_find({}, "x")', 'E474:')
+ call assert_fails('call prop_find({"lnum" : -2})', 'E16:')
+ call assert_fails('call prop_list(1, [])', 'E1206:')
+ call assert_fails('call prop_list(-1, {})', 'E16:')
+ call assert_fails('call prop_remove([])', 'E1206:')
+ call assert_fails('call prop_remove({}, -2)', 'E16:')
+ call assert_fails('call prop_remove({})', 'E968:')
+ call assert_fails('call prop_type_add([], {})', 'E730:')
+ call assert_fails("call prop_type_change('long', {'xyz' : 10})", 'E971:')
+ call assert_fails("call prop_type_delete([])", 'E730:')
+ call assert_fails("call prop_type_delete('xyz', [])", 'E715:')
+ call assert_fails("call prop_type_get([])", 'E730:')
+ call assert_fails("call prop_type_get('', [])", 'E475:')
+ call assert_fails("call prop_type_list([])", 'E715:')
+ call assert_fails("call prop_type_add('yyy', 'not_a_dict')", 'E715:')
+ call assert_fails("call prop_add(1, 5, {'type':'missing_type', 'length':1})", 'E971:')
+ call assert_fails("call prop_add(1, 5, {'type': ''})", 'E971:')
+ call assert_fails('call prop_add(1, 1, 0)', 'E1206:')
+
+ new
+ call setline(1, ['first', 'second'])
+ call prop_type_add('xxx', {})
+
+ call assert_fails("call prop_type_add('xxx', {})", 'E969:')
+ call assert_fails("call prop_add(2, 0, {'type': 'xxx'})", 'E964:')
+ call assert_fails("call prop_add(2, 3, {'type': 'xxx', 'end_lnum':1})", 'E475:')
+ call assert_fails("call prop_add(2, 3, {'type': 'xxx', 'end_lnum':3})", 'E966:')
+ call assert_fails("call prop_add(2, 3, {'type': 'xxx', 'length':-1})", 'E475:')
+ call assert_fails("call prop_add(2, 3, {'type': 'xxx', 'end_col':0})", 'E475:')
+ call assert_fails("call prop_add(2, 3, {'length':1})", 'E965:')
+
+ call prop_type_delete('xxx')
+ bwipe!
+endfunc
+
+func Test_prop_split_join()
+ new
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call setline(1, 'just some text')
+ call prop_add(1, 6, {'length': 4, 'type': 'test'})
+
+ " Split in middle of "some"
+ execute "normal! 8|i\<CR>"
+ call assert_equal(
+ \ [#{type_bufnr: 0, id: 0, col: 6, end: 0, type: 'test', length: 2, start: 1}],
+ \ prop_list(1))
+ call assert_equal(
+ \ [#{type_bufnr: 0, id: 0, col: 1, end: 1, type: 'test', length: 2, start: 0}],
+ \ prop_list(2))
+
+ " Join the two lines back together
+ normal! 1GJ
+ call assert_equal([#{type_bufnr: 0, id: 0, col: 6, end: 1, type: 'test', length: 5, start: 1}], prop_list(1))
+
+ bwipe!
+ call prop_type_delete('test')
+endfunc
+
+func Test_prop_increment_decrement()
+ new
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call setline(1, 'its 998 times')
+ call prop_add(1, 5, {'length': 3, 'type': 'test'})
+
+ exe "normal! 0f9\<C-A>"
+ eval getline(1)->assert_equal('its 999 times')
+ eval prop_list(1)->assert_equal([
+ \ #{type_bufnr: 0, id: 0, col: 5, end: 1, type: 'test', length: 3, start: 1}])
+
+ exe "normal! 0f9\<C-A>"
+ eval getline(1)->assert_equal('its 1000 times')
+ eval prop_list(1)->assert_equal([
+ \ #{type_bufnr: 0, id: 0, col: 5, end: 1, type: 'test', length: 4, start: 1}])
+
+ bwipe!
+ call prop_type_delete('test')
+endfunc
+
+func Test_prop_block_insert()
+ new
+ call prop_type_add('test', {'highlight': 'ErrorMsg'})
+ call setline(1, ['one ', 'two '])
+ call prop_add(1, 1, {'length': 3, 'type': 'test'})
+ call prop_add(2, 1, {'length': 3, 'type': 'test'})
+
+ " insert "xx" in the first column of both lines
+ exe "normal! gg0\<C-V>jIxx\<Esc>"
+ eval getline(1, 2)->assert_equal(['xxone ', 'xxtwo '])
+ let expected = [#{type_bufnr: 0, id: 0, col: 3, end: 1, type: 'test', length: 3, start: 1}]
+ eval prop_list(1)->assert_equal(expected)
+ eval prop_list(2)->assert_equal(expected)
+
+ " insert "yy" inside the text props to make them longer
+ exe "normal! gg03l\<C-V>jIyy\<Esc>"
+ eval getline(1, 2)->assert_equal(['xxoyyne ', 'xxtyywo '])
+ let expected[0].length = 5
+ eval prop_list(1)->assert_equal(expected)
+ eval prop_list(2)->assert_equal(expected)
+
+ " insert "zz" after the text props, text props don't change
+ exe "normal! gg07l\<C-V>jIzz\<Esc>"
+ eval getline(1, 2)->assert_equal(['xxoyynezz ', 'xxtyywozz '])
+ eval prop_list(1)->assert_equal(expected)
+ eval prop_list(2)->assert_equal(expected)
+
+ bwipe!
+ call prop_type_delete('test')
+endfunc
+
+" this was causing an ml_get error because w_botline was wrong
+func Test_prop_one_line_window()
+ enew
+ call range(2)->setline(1)
+ call prop_type_add('testprop', {})
+ call prop_add(1, 1, {'type': 'testprop'})
+ call popup_create('popup', {'textprop': 'testprop'})
+ $
+ new
+ wincmd _
+ call feedkeys("\r", 'xt')
+ redraw
+
+ call popup_clear()
+ call prop_type_delete('testprop')
+ close
+ bwipe!
+endfunc
+
+def Test_prop_column_zero_error()
+ prop_type_add('proptype', {highlight: 'Search'})
+ var caught = false
+ try
+ popup_create([{
+ text: 'a',
+ props: [{col: 0, length: 1, type: 'type'}],
+ }], {})
+ catch /E964:/
+ caught = true
+ endtry
+ assert_true(caught)
+
+ popup_clear()
+ prop_type_delete('proptype')
+enddef
+
+" This was calling ml_append_int() and copy a text property from a previous
+" line at the wrong moment. Exact text length matters.
+def Test_prop_splits_data_block()
+ new
+ var lines: list<string> = [repeat('x', 35)]->repeat(41)
+ + [repeat('!', 35)]
+ + [repeat('x', 35)]->repeat(56)
+ lines->setline(1)
+ prop_type_add('someprop', {highlight: 'ErrorMsg'})
+ prop_add(1, 27, {end_lnum: 1, end_col: 70, type: 'someprop'})
+ prop_remove({type: 'someprop'}, 1)
+ prop_add(35, 22, {end_lnum: 43, end_col: 43, type: 'someprop'})
+ prop_remove({type: 'someprop'}, 35, 43)
+ assert_equal([], prop_list(42))
+
+ bwipe!
+ prop_type_delete('someprop')
+enddef
+
+" This was calling ml_delete_int() and try to change text properties.
+def Test_prop_add_delete_line()
+ new
+ var a = 10
+ var b = 20
+ repeat([''], a)->append('$')
+ prop_type_add('Test', {highlight: 'ErrorMsg'})
+ for lnum in range(1, a)
+ for col in range(1, b)
+ prop_add(1, 1, {end_lnum: lnum, end_col: col, type: 'Test'})
+ endfor
+ endfor
+
+ # check deleting lines is OK
+ :5del
+ :1del
+ :$del
+
+ prop_type_delete('Test')
+ bwipe!
+enddef
+
+" This test is to detect a regression related to #10430. It is not an attempt
+" fully cover deleting lines in the presence of multi-line properties.
+def Test_delete_line_within_multiline_prop()
+ new
+ setline(1, '# Top.')
+ append(1, ['some_text = """', 'A string.', '"""', '# Bottom.'])
+ prop_type_add('Identifier', {'highlight': 'ModeMsg', 'priority': 0, 'combine': 0, 'start_incl': 0, 'end_incl': 0})
+ prop_type_add('String', {'highlight': 'MoreMsg', 'priority': 0, 'combine': 0, 'start_incl': 0, 'end_incl': 0})
+ prop_add(2, 1, {'type': 'Identifier', 'end_lnum': 2, 'end_col': 9})
+ prop_add(2, 13, {'type': 'String', 'end_lnum': 4, 'end_col': 4})
+
+ # The property for line 3 should extend into the previous and next lines.
+ var props = prop_list(3)
+ var prop = props[0]
+ assert_equal(1, len(props))
+ assert_equal(0, prop['start'])
+ assert_equal(0, prop['end'])
+
+ # This deletion should run without raising an exception.
+ try
+ :2 del
+ catch
+ assert_report('Line delete should have worked, but it raised an error.')
+ endtry
+
+ # The property for line 2 (was 3) should no longer extend into the previous
+ # line.
+ props = prop_list(2)
+ prop = props[0]
+ assert_equal(1, len(props))
+ assert_equal(1, prop['start'], 'Property was not changed to start within the line.')
+
+ # This deletion should run without raising an exception.
+ try
+ :3 del
+ catch
+ assert_report('Line delete should have worked, but it raised an error.')
+ endtry
+
+ # The property for line 2 (originally 3) should no longer extend into the next
+ # line.
+ props = prop_list(2)
+ prop = props[0]
+ assert_equal(1, len(props))
+ assert_equal(1, prop['end'], 'Property was not changed to end within the line.')
+
+ prop_type_delete('Identifier')
+ prop_type_delete('String')
+ bwip!
+enddef
+
+func Test_prop_in_linebreak()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set breakindent linebreak breakat+=]
+ call printf('%s]%s', repeat('x', 50), repeat('x', 70))->setline(1)
+ call prop_type_add('test', #{highlight: 'MatchParen'})
+ call prop_add(1, 51, #{length: 1, type: 'test'})
+ func AddMatch()
+ syntax on
+ syntax match xTest /.*/
+ hi link xTest Comment
+ set signcolumn=yes
+ endfunc
+ END
+ call writefile(lines, 'XscriptPropLinebreak', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropLinebreak', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_prop_linebreak_1', {})
+
+ call term_sendkeys(buf, ":call AddMatch()\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_linebreak_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_linebreak()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ set linebreak
+ setline(1, 'one twoword')
+ prop_type_add('test', {highlight: 'Special'})
+ prop_add(1, 4, {text: ': virtual text', type: 'test', text_wrap: 'wrap'})
+ END
+ call writefile(lines, 'XscriptPropWithLinebreak', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropWithLinebreak', #{rows: 6, cols: 50})
+ call VerifyScreenDump(buf, 'Test_prop_with_linebreak_1', {})
+ call term_sendkeys(buf, "iasdf asdf asdf asdf asdf as\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_with_linebreak_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_wrap()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ set linebreak
+ setline(1, 'asdf '->repeat(15))
+ prop_type_add('test', {highlight: 'Special'})
+ prop_add(1, 43, {text: 'some virtual text', type: 'test'})
+ normal G$
+ END
+ call writefile(lines, 'XscriptPropWithWrap', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropWithWrap', #{rows: 6, cols: 50})
+ call VerifyScreenDump(buf, 'Test_prop_with_wrap_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_after_tab()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set breakindent linebreak breakat+=]
+ call setline(1, "\t[xxx]")
+ call prop_type_add('test', #{highlight: 'ErrorMsg'})
+ call prop_add(1, 2, #{length: 1, type: 'test'})
+ END
+ call writefile(lines, 'XscriptPropAfterTab', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropAfterTab', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_prop_after_tab', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_before_tab()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ["\tx"]->repeat(6))
+ call prop_type_add('test', #{highlight: 'Search'})
+ call prop_add(1, 1, #{type: 'test', text: '123'})
+ call prop_add(2, 1, #{type: 'test', text: '1234567'})
+ call prop_add(3, 1, #{type: 'test', text: '12345678'})
+ call prop_add(4, 1, #{type: 'test', text: '123456789'})
+ call prop_add(5, 2, #{type: 'test', text: 'ABC'})
+ call prop_add(6, 3, #{type: 'test', text: 'ABC'})
+ normal gg0
+ END
+ call writefile(lines, 'XscriptPropBeforeTab', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropBeforeTab', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_01', {})
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_02', {})
+ call term_sendkeys(buf, "j0")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_03', {})
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_04', {})
+ call term_sendkeys(buf, "j0")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_05', {})
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_06', {})
+ call term_sendkeys(buf, "j0")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_07', {})
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_08', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_09', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_prop_before_tab_10', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_after_linebreak()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set linebreak wrap
+ call printf('%s+(%s)', 'x'->repeat(&columns / 2), 'x'->repeat(&columns / 2))->setline(1)
+ call prop_type_add('test', #{highlight: 'ErrorMsg'})
+ call prop_add(1, (&columns / 2) + 2, #{length: 1, type: 'test'})
+ END
+ call writefile(lines, 'XscriptPropAfterLinebreak', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropAfterLinebreak', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_prop_after_linebreak', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Buffer number of 0 should be ignored, as if the parameter wasn't passed.
+def Test_prop_bufnr_zero()
+ new
+ try
+ var bufnr = bufnr('')
+ setline(1, 'hello')
+ prop_type_add('bufnr-global', {highlight: 'ErrorMsg'})
+ prop_type_add('bufnr-buffer', {highlight: 'StatusLine', bufnr: bufnr})
+
+ prop_add(1, 1, {type: 'bufnr-global', length: 1})
+ prop_add(1, 2, {type: 'bufnr-buffer', length: 1})
+
+ var list = prop_list(1)
+ assert_equal([
+ {id: 0, col: 1, type_bufnr: 0, end: 1, type: 'bufnr-global', length: 1, start: 1},
+ {id: 0, col: 2, type_bufnr: bufnr, end: 1, type: 'bufnr-buffer', length: 1, start: 1},
+ ], list)
+
+ assert_equal(
+ {highlight: 'ErrorMsg', end_incl: 0, start_incl: 0, priority: 0, combine: 1},
+ prop_type_get('bufnr-global', {bufnr: list[0].type_bufnr}))
+
+ assert_equal(
+ {highlight: 'StatusLine', end_incl: 0, start_incl: 0, priority: 0, bufnr: bufnr, combine: 1},
+ prop_type_get('bufnr-buffer', {bufnr: list[1].type_bufnr}))
+ finally
+ bwipe!
+ prop_type_delete('bufnr-global')
+ endtry
+enddef
+
+" Tests for the prop_list() function
+func Test_prop_list()
+ let lines =<< trim END
+ new
+ call g:AddPropTypes()
+ call setline(1, repeat([repeat('a', 60)], 10))
+ call prop_add(1, 4, {'type': 'one', 'id': 5, 'end_col': 6})
+ call prop_add(1, 5, {'type': 'two', 'id': 10, 'end_col': 7})
+ call prop_add(3, 12, {'type': 'one', 'id': 20, 'end_col': 14})
+ call prop_add(3, 13, {'type': 'two', 'id': 10, 'end_col': 15})
+ call prop_add(5, 20, {'type': 'one', 'id': 10, 'end_col': 22})
+ call prop_add(5, 21, {'type': 'two', 'id': 20, 'end_col': 23})
+ call assert_equal([
+ \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1}], prop_list(1))
+ #" text properties between a few lines
+ call assert_equal([
+ \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1},
+ \ {'lnum': 5, 'id': 10, 'col': 20, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 5, 'id': 20, 'col': 21, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1}],
+ \ prop_list(2, {'end_lnum': 5}))
+ #" text properties across all the lines
+ call assert_equal([
+ \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 5, 'id': 10, 'col': 20, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1}],
+ \ prop_list(1, {'types': ['one'], 'end_lnum': -1}))
+ #" text properties with the specified identifier
+ call assert_equal([
+ \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 5, 'id': 20, 'col': 21, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1}],
+ \ prop_list(1, {'ids': [20], 'end_lnum': 10}))
+ #" text properties of the specified type and id
+ call assert_equal([
+ \ {'lnum': 1, 'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1},
+ \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1}],
+ \ prop_list(1, {'types': ['two'], 'ids': [10], 'end_lnum': 20}))
+ call assert_equal([], prop_list(1, {'ids': [40, 50], 'end_lnum': 10}))
+ call assert_equal([], prop_list(6, {'end_lnum': 10}))
+ call assert_equal([], prop_list(2, {'end_lnum': 2}))
+ #" error cases
+ call assert_fails("echo prop_list(1, {'end_lnum': -20})", 'E16:')
+ call assert_fails("echo prop_list(4, {'end_lnum': 2})", 'E16:')
+ call assert_fails("echo prop_list(1, {'end_lnum': '$'})", 'E889:')
+ call assert_fails("echo prop_list(1, {'types': ['blue'], 'end_lnum': 10})",
+ \ 'E971:')
+ call assert_fails("echo prop_list(1, {'types': ['one', 'blue'],
+ \ 'end_lnum': 10})", 'E971:')
+ call assert_fails("echo prop_list(1, {'types': ['one', 10],
+ \ 'end_lnum': 10})", 'E928:')
+ call assert_fails("echo prop_list(1, {'types': ['']})", 'E971:')
+ call assert_equal([], prop_list(2, {'types': []}))
+ call assert_equal([], prop_list(2, {'types': test_null_list()}))
+ call assert_fails("call prop_list(1, {'types': {}})", 'E714:')
+ call assert_fails("call prop_list(1, {'types': 'one'})", 'E714:')
+ call assert_equal([], prop_list(2, {'types': ['one'],
+ \ 'ids': test_null_list()}))
+ call assert_equal([], prop_list(2, {'types': ['one'], 'ids': []}))
+ call assert_fails("call prop_list(1, {'types': ['one'], 'ids': {}})",
+ \ 'E714:')
+ call assert_fails("call prop_list(1, {'types': ['one'], 'ids': 10})",
+ \ 'E714:')
+ call assert_fails("call prop_list(1, {'types': ['one'], 'ids': [[]]})",
+ \ 'E745:')
+ call assert_fails("call prop_list(1, {'types': ['one'], 'ids': [10, []]})",
+ \ 'E745:')
+
+ #" get text properties from a non-current buffer
+ wincmd w
+ call assert_equal([
+ \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 1, 'id': 10, 'col': 5, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1},
+ \ {'lnum': 3, 'id': 20, 'col': 12, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 3, 'id': 10, 'col': 13, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1}],
+ \ prop_list(1, {'bufnr': winbufnr(1), 'end_lnum': 4}))
+ wincmd w
+
+ #" get text properties after clearing all the properties
+ call prop_clear(1, line('$'))
+ call assert_equal([], prop_list(1, {'end_lnum': 10}))
+
+ call prop_add(2, 4, {'type': 'one', 'id': 5, 'end_col': 6})
+ call prop_add(2, 4, {'type': 'two', 'id': 10, 'end_col': 6})
+ call prop_add(2, 4, {'type': 'three', 'id': 15, 'end_col': 6})
+ #" get text properties with a list of types
+ call assert_equal([
+ \ {'id': 10, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1},
+ \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1}],
+ \ prop_list(2, {'types': ['one', 'two']}))
+ call assert_equal([
+ \ {'id': 15, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'three', 'length': 2, 'start': 1},
+ \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1}],
+ \ prop_list(2, {'types': ['one', 'three']}))
+ #" get text properties with a list of identifiers
+ call assert_equal([
+ \ {'id': 10, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1},
+ \ {'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1}],
+ \ prop_list(2, {'ids': [5, 10, 20]}))
+ call prop_clear(1, line('$'))
+ call assert_equal([], prop_list(2, {'types': ['one', 'two']}))
+ call assert_equal([], prop_list(2, {'ids': [5, 10, 20]}))
+
+ #" get text properties from a hidden buffer
+ edit! Xaaa
+ call setline(1, repeat([repeat('b', 60)], 10))
+ call prop_add(1, 4, {'type': 'one', 'id': 5, 'end_col': 6})
+ call prop_add(4, 8, {'type': 'two', 'id': 10, 'end_col': 10})
+ VAR bnr = bufnr()
+ hide edit Xbbb
+ call assert_equal([
+ \ {'lnum': 1, 'id': 5, 'col': 4, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'one', 'length': 2, 'start': 1},
+ \ {'lnum': 4, 'id': 10, 'col': 8, 'type_bufnr': 0, 'end': 1,
+ \ 'type': 'two', 'length': 2, 'start': 1}],
+ \ prop_list(1, {'bufnr': bnr,
+ \ 'types': ['one', 'two'], 'ids': [5, 10], 'end_lnum': -1}))
+ #" get text properties from an unloaded buffer
+ bunload! Xaaa
+ call assert_equal([], prop_list(1, {'bufnr': bnr, 'end_lnum': -1}))
+
+ call g:DeletePropTypes()
+ :%bw!
+ END
+ call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_prop_find_prev_on_same_line()
+ new
+
+ call setline(1, 'the quikc bronw fox jumsp over the layz dog')
+ call prop_type_add('misspell', #{highlight: 'ErrorMsg'})
+ for col in [8, 14, 24, 38]
+ call prop_add(1, col, #{type: 'misspell', length: 2})
+ endfor
+
+ call cursor(1, 18)
+ let expected = [
+ \ #{lnum: 1, id: 0, col: 14, end: 1, type: 'misspell', type_bufnr: 0, length: 2, start: 1},
+ \ #{lnum: 1, id: 0, col: 24, end: 1, type: 'misspell', type_bufnr: 0, length: 2, start: 1}
+ \ ]
+
+ let result = prop_find(#{type: 'misspell'}, 'b')
+ call assert_equal(expected[0], result)
+ let result = prop_find(#{type: 'misspell'}, 'f')
+ call assert_equal(expected[1], result)
+
+ call prop_type_delete('misspell')
+ bwipe!
+endfunc
+
+func Test_prop_spell()
+ new
+ set spell
+ call AddPropTypes()
+
+ call setline(1, ["helo world", "helo helo helo"])
+ call prop_add(1, 1, #{type: 'one', length: 4})
+ call prop_add(1, 6, #{type: 'two', length: 5})
+ call prop_add(2, 1, #{type: 'three', length: 4})
+ call prop_add(2, 6, #{type: 'three', length: 4})
+ call prop_add(2, 11, #{type: 'three', length: 4})
+
+ " The first prop over 'helo' increases its length after the word is corrected
+ " to 'Hello', the second one is shifted to the right.
+ let expected = [
+ \ {'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 1, 'type': 'one',
+ \ 'length': 5, 'start': 1},
+ \ {'id': 0, 'col': 7, 'type_bufnr': 0, 'end': 1, 'type': 'two',
+ \ 'length': 5, 'start': 1}
+ \ ]
+ call feedkeys("z=1\<CR>", 'xt')
+
+ call assert_equal('Hello world', getline(1))
+ call assert_equal(expected, prop_list(1))
+
+ " Repeat the replacement done by z=
+ spellrepall
+
+ let expected = [
+ \ {'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 1, 'type': 'three',
+ \ 'length': 5, 'start': 1},
+ \ {'id': 0, 'col': 7, 'type_bufnr': 0, 'end': 1, 'type': 'three',
+ \ 'length': 5, 'start': 1},
+ \ {'id': 0, 'col': 13, 'type_bufnr': 0, 'end': 1, 'type': 'three',
+ \ 'length': 5, 'start': 1}
+ \ ]
+ call assert_equal('Hello Hello Hello', getline(2))
+ call assert_equal(expected, prop_list(2))
+
+ call DeletePropTypes()
+ set spell&
+ bwipe!
+endfunc
+
+func Test_prop_shift_block()
+ new
+ call AddPropTypes()
+
+ call setline(1, ['some highlighted text']->repeat(2))
+ call prop_add(1, 10, #{type: 'one', length: 11})
+ call prop_add(2, 10, #{type: 'two', length: 11})
+
+ call cursor(1, 1)
+ call feedkeys("5l\<c-v>>", 'nxt')
+ call cursor(2, 1)
+ call feedkeys("5l\<c-v><", 'nxt')
+
+ let expected = [
+ \ {'lnum': 1, 'id': 0, 'col': 8, 'type_bufnr': 0, 'end': 1, 'type': 'one',
+ \ 'length': 11, 'start' : 1},
+ \ {'lnum': 2, 'id': 0, 'col': 6, 'type_bufnr': 0, 'end': 1, 'type': 'two',
+ \ 'length': 11, 'start' : 1}
+ \ ]
+ call assert_equal(expected, prop_list(1, #{end_lnum: 2}))
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_insert_multiline()
+ new
+ call AddPropTypes()
+
+ call setline(1, ['foobar', 'barbaz'])
+ call prop_add(1, 4, #{end_lnum: 2, end_col: 4, type: 'one'})
+
+ call feedkeys("1Goquxqux\<Esc>", 'nxt')
+ call feedkeys("2GOquxqux\<Esc>", 'nxt')
+
+ let lines =<< trim END
+ foobar
+ quxqux
+ quxqux
+ barbaz
+ END
+ call assert_equal(lines, getline(1, '$'))
+ let expected = [
+ \ {'lnum': 1, 'id': 0, 'col': 4, 'type_bufnr': 0, 'end': 0, 'type': 'one',
+ \ 'length': 4 , 'start': 1},
+ \ {'lnum': 2, 'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 0, 'type': 'one',
+ \ 'length': 7, 'start': 0},
+ \ {'lnum': 3, 'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 0, 'type': 'one',
+ \ 'length': 7, 'start': 0},
+ \ {'lnum': 4, 'id': 0, 'col': 1, 'type_bufnr': 0, 'end': 1, 'type': 'one',
+ \ 'length': 3, 'start': 0}
+ \ ]
+ call assert_equal(expected, prop_list(1, #{end_lnum: 10}))
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Test_prop_blockwise_change()
+ new
+ call AddPropTypes()
+
+ call setline(1, ['foooooo', 'bar', 'baaaaz'])
+ call prop_add(1, 1, #{end_col: 3, type: 'one'})
+ call prop_add(2, 1, #{end_col: 3, type: 'two'})
+ call prop_add(3, 1, #{end_col: 3, type: 'three'})
+
+ " Replace the first two columns with '123', since 'start_incl' is false the
+ " prop is not extended.
+ call feedkeys("gg\<c-v>2jc123\<Esc>", 'nxt')
+
+ let lines =<< trim END
+ 123oooooo
+ 123ar
+ 123aaaaz
+ END
+ call assert_equal(lines, getline(1, '$'))
+ let expected = [
+ \ {'lnum': 1, 'id': 0, 'col': 4, 'type_bufnr': 0, 'end': 1, 'type': 'one',
+ \ 'length': 1, 'start': 1},
+ \ {'lnum': 2, 'id': 0, 'col': 4, 'type_bufnr': 0, 'end': 1, 'type': 'two',
+ \ 'length': 1, 'start': 1},
+ \ {'lnum': 3, 'id': 0, 'col': 4, 'type_bufnr': 0, 'end': 1 ,
+ \ 'type': 'three', 'length': 1, 'start': 1}
+ \ ]
+ call assert_equal(expected, prop_list(1, #{end_lnum: 10}))
+
+ call DeletePropTypes()
+ bwipe!
+endfunc
+
+func Do_test_props_do_not_affect_byte_offsets(ff, increment)
+ new
+ let lcount = 410
+
+ " File format affects byte-offset calculations, so make sure it is known.
+ exec 'setlocal fileformat=' . a:ff
+
+ " Fill the buffer with varying length lines. We need a suitably large number
+ " to force Vim code through paths where previous error have occurred. This
+ " is more 'art' than 'science'.
+ let text = 'a'
+ call setline(1, text)
+ let offsets = [1]
+ for idx in range(lcount)
+ call add(offsets, offsets[idx] + len(text) + a:increment)
+ if (idx % 6) == 0
+ let text = text . 'a'
+ endif
+ call append(line('$'), text)
+ endfor
+
+ " Set a property that spans a few lines to cause Vim's internal buffer code
+ " to perform a reasonable amount of rearrangement.
+ call prop_type_add('one', {'highlight': 'ErrorMsg'})
+ call prop_add(1, 1, {'type': 'one', 'end_lnum': 6, 'end_col': 2})
+
+ for idx in range(lcount)
+ let boff = line2byte(idx + 1)
+ call assert_equal(offsets[idx], boff, 'Bad byte offset at line ' . (idx + 1))
+ endfor
+
+ call prop_type_delete('one')
+ bwipe!
+endfunc
+
+func Test_props_do_not_affect_byte_offsets()
+ call Do_test_props_do_not_affect_byte_offsets('unix', 1)
+endfunc
+
+func Test_props_do_not_affect_byte_offsets_dos()
+ call Do_test_props_do_not_affect_byte_offsets('dos', 2)
+endfunc
+
+func Test_props_do_not_affect_byte_offsets_editline()
+ new
+ let lcount = 410
+
+ " File format affects byte-offset calculations, so make sure it is known.
+ setlocal fileformat=unix
+
+ " Fill the buffer with varying length lines. We need a suitably large number
+ " to force Vim code through paths where previous error have occurred. This
+ " is more 'art' than 'science'.
+ let text = 'aa'
+ call setline(1, text)
+ let offsets = [1]
+ for idx in range(lcount)
+ call add(offsets, offsets[idx] + len(text) + 1)
+ if (idx % 6) == 0
+ let text = text . 'a'
+ endif
+ call append(line('$'), text)
+ endfor
+
+ " Set a property that just covers the first line. When this test was
+ " developed, this did not trigger a byte-offset error.
+ call prop_type_add('one', {'highlight': 'ErrorMsg'})
+ call prop_add(1, 1, {'type': 'one', 'end_lnum': 1, 'end_col': 3})
+
+ for idx in range(lcount)
+ let boff = line2byte(idx + 1)
+ call assert_equal(offsets[idx], boff,
+ \ 'Confounding bad byte offset at line ' . (idx + 1))
+ endfor
+
+ " Insert text in the middle of the first line, keeping the property
+ " unchanged.
+ :1
+ normal aHello
+ for idx in range(1, lcount)
+ let offsets[idx] = offsets[idx] + 5
+ endfor
+
+ for idx in range(lcount)
+ let boff = line2byte(idx + 1)
+ call assert_equal(offsets[idx], boff,
+ \ 'Bad byte offset at line ' . (idx + 1))
+ endfor
+
+ call prop_type_delete('one')
+ bwipe!
+endfunc
+
+func Test_prop_inserts_text()
+ CheckRunVimInTerminal
+
+ " Just a basic check for now
+ let lines =<< trim END
+ call setline(1, 'insert some text here and other text there and some more text after wrapping')
+ call prop_type_add('someprop', #{highlight: 'ErrorMsg'})
+ call prop_type_add('otherprop', #{highlight: 'Search'})
+ call prop_type_add('moreprop', #{highlight: 'DiffAdd'})
+ call prop_add(1, 18, #{type: 'someprop', text: 'SOME '})
+ call prop_add(1, 38, #{type: 'otherprop', text: "OTHER\t"})
+ call prop_add(1, 69, #{type: 'moreprop', text: 'MORE '})
+ normal $
+
+ call setline(2, 'prepost')
+ call prop_type_add('multibyte', #{highlight: 'Visual'})
+ call prop_add(2, 4, #{type: 'multibyte', text: 'söme和平téxt'})
+
+ call setline(3, 'Foo foo = { 1, 2 };')
+ call prop_type_add('testprop', #{highlight: 'Comment'})
+ call prop_add(3, 13, #{type: 'testprop', text: '.x='})
+ call prop_add(3, 16, #{type: 'testprop', text: '.y='})
+
+ call setline(4, '')
+ call prop_add(4, 1, #{type: 'someprop', text: 'empty line'})
+
+ call setline(5, 'look highlight')
+ call prop_type_add('nohi', #{})
+ call prop_add(5, 6, #{type: 'nohi', text: 'no '})
+ END
+ call writefile(lines, 'XscriptPropsWithText', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithText', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_1', {})
+
+ call term_sendkeys(buf, ":set signcolumn=yes\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_2', {})
+
+ call term_sendkeys(buf, "2G$")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_3', {})
+
+ call term_sendkeys(buf, "3Gf1")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_4', {})
+ call term_sendkeys(buf, "f2")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_5', {})
+
+ call term_sendkeys(buf, "4G")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_6', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_inserts_text_highlight()
+ CheckRunVimInTerminal
+
+ " Just a basic check for now
+ let lines =<< trim END
+ call setline(1, 'insert some text (here) and there')
+ call prop_type_add('someprop', #{highlight: 'ErrorMsg'})
+ let bef_prop = prop_add(1, 18, #{type: 'someprop', text: 'BEFORE'})
+ set hlsearch
+ let thematch = matchaddpos("DiffAdd", [[1, 18]])
+ func DoAfter()
+ call prop_remove(#{id: g:bef_prop})
+ call prop_add(1, 19, #{type: 'someprop', text: 'AFTER'})
+ let g:thematch = matchaddpos("DiffAdd", [[1, 18]])
+ let @/ = ''
+ endfunc
+ END
+ call writefile(lines, 'XscriptPropsWithHighlight', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithHighlight', #{rows: 6, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_hi_1', {})
+ call term_sendkeys(buf, "/text (he\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_hi_2', {})
+ call term_sendkeys(buf, ":call matchdelete(thematch)\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_hi_3', {})
+
+ call term_sendkeys(buf, ":call DoAfter()\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_hi_4', {})
+ call term_sendkeys(buf, "/text (he\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_hi_5', {})
+ call term_sendkeys(buf, ":call matchdelete(thematch)\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_inserts_text_hi_6', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_add_with_text_fails()
+ call prop_type_add('failing', #{highlight: 'ErrorMsg'})
+ call assert_fails("call prop_add(1, 0, #{type: 'failing', text: 'X', end_lnum: 1})", 'E1305:')
+ call assert_fails("call prop_add(1, 0, #{type: 'failing', text: 'X', end_col: 1})", 'E1305:')
+ call assert_fails("call prop_add(1, 0, #{type: 'failing', text: 'X', length: 1})", 'E1305:')
+
+ call prop_type_delete('failing')
+endfunc
+
+func Test_props_with_text_right_align_twice()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ["some text some text some text some text", 'line two'])
+ call prop_type_add('MyErrorText', #{highlight: 'ErrorMsg'})
+ call prop_type_add('MyPadding', #{highlight: 'DiffChange'})
+ call prop_add(1, 0, #{type: 'MyPadding', text: ' nothing here', text_wrap: 'wrap'})
+ call prop_add(1, 0, #{type: 'MyErrorText', text: 'Some error', text_wrap: 'wrap', text_align: 'right'})
+ call prop_add(1, 0, #{type: 'MyErrorText', text: 'Another error', text_wrap: 'wrap', text_align: 'right'})
+ normal G$
+ END
+ call writefile(lines, 'XscriptPropsRightAlign', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsRightAlign', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_prop_right_align_twice_1', {})
+
+ call term_sendkeys(buf, "ggisome more text\<Esc>G$")
+ call VerifyScreenDump(buf, 'Test_prop_right_align_twice_2', {})
+
+ call term_sendkeys(buf, ":set signcolumn=yes\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_right_align_twice_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set showbreak=+++
+ set breakindent
+ call setline(1, ' some text here and other text there')
+ call prop_type_add('rightprop', #{highlight: 'ErrorMsg'})
+ call prop_type_add('afterprop', #{highlight: 'Search'})
+ call prop_type_add('belowprop', #{highlight: 'DiffAdd'})
+ call prop_add(1, 0, #{type: 'rightprop', text: ' RIGHT ', text_align: 'right'})
+ call prop_add(1, 0, #{type: 'afterprop', text: "\tAFTER\t", text_align: 'after'})
+ call prop_add(1, 0, #{type: 'belowprop', text: ' BELOW ', text_align: 'below'})
+ call prop_add(1, 0, #{type: 'belowprop', text: ' ALSO BELOW ', text_align: 'below'})
+
+ call setline(2, 'Last line.')
+ call prop_add(2, 0, #{type: 'afterprop', text: ' After Last ', text_align: 'after'})
+ normal G$
+
+ call setline(3, 'right here')
+ call prop_add(3, 0, #{type: 'rightprop', text: 'söme和平téxt', text_align: 'right'})
+ END
+ call writefile(lines, 'XscriptPropsWithTextAfter', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithTextAfter', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_1', {})
+
+ call StopVimInTerminal(buf)
+
+ call assert_fails('call prop_add(1, 2, #{text: "yes", text_align: "right", type: "some"})', 'E1294:')
+endfunc
+
+func Test_props_with_text_after_and_list()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['one', 'two'])
+ prop_type_add('test', {highlight: 'Special'})
+ prop_add(1, 0, {
+ type: 'test',
+ text: range(50)->join(' '),
+ text_align: 'after',
+ text_padding_left: 3
+ })
+ prop_add(1, 0, {
+ type: 'test',
+ text: range(50)->join('-'),
+ text_align: 'after',
+ text_padding_left: 5
+ })
+ prop_add(1, 0, {
+ type: 'test',
+ text: range(50)->join('.'),
+ text_align: 'after',
+ text_padding_left: 1
+ })
+ normal G$
+ END
+ call writefile(lines, 'XscriptPropsAfter', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsAfter', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_props_after_1', {})
+
+ call term_sendkeys(buf, ":set list\<CR>")
+ call VerifyScreenDump(buf, 'Test_props_after_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after_below_trunc()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ edit foobar
+ set showbreak=+++
+ setline(1, ['onasdf asdf asdf asdf asd fas df', 'two'])
+ prop_type_add('test', {highlight: 'Special'})
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'the quick brown fox jumps over the lazy dog',
+ text_align: 'after',
+ })
+ prop_type_add('another', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'another',
+ text: 'the quick brown fox jumps over the lazy dog',
+ text_align: 'below',
+ text_padding_left: 4,
+ })
+ normal G$
+ END
+ call writefile(lines, 'XscriptPropsAfterTrunc', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsAfterTrunc', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_below_trunc_1', {})
+
+ call term_sendkeys(buf, ":set number\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_below_trunc_2', {})
+
+ call term_sendkeys(buf, ":set cursorline\<CR>gg")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_below_trunc_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_text_below_after_empty()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+
+ setline(1, ['vim9script', '', 'three', ''])
+
+ # Add text prop below empty line 2 with padding.
+ prop_type_add('test', {highlight: 'ErrorMsg'})
+ prop_add(2, 0, {
+ type: 'test',
+ text: 'The quick brown fox jumps over the lazy dog',
+ text_align: 'below',
+ text_padding_left: 1,
+ })
+
+ # Add text prop below empty line 4 without padding.
+ prop_type_add('other', {highlight: 'DiffChange'})
+ prop_add(4, 0, {
+ type: 'other',
+ text: 'The slow fox bumps into the lazy dog',
+ text_align: 'below',
+ text_padding_left: 0,
+ })
+ END
+ call writefile(lines, 'XscriptPropBelowAfterEmpty', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropBelowAfterEmpty', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_below_after_empty_1', {})
+
+ call term_sendkeys(buf, ":set number\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_below_after_empty_2', {})
+
+ call term_sendkeys(buf, ":set nowrap\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_below_after_empty_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_text_above_below_empty()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ setlocal number
+ call setline(1, ['11111111', '', '333333333', '', '55555555555'])
+
+ let vt = 'test'
+ call prop_type_add(vt, {'highlight': 'ToDo'})
+ for ln in range(1, line('$'))
+ call prop_add(ln, 0, {'type': vt, 'text': '---', 'text_align': 'above'})
+ call prop_add(ln, 0, {'type': vt, 'text': '+++', 'text_align': 'below'})
+ endfor
+ normal G
+ END
+ call writefile(lines, 'XscriptPropAboveBelowEmpty', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropAboveBelowEmpty', #{rows: 16, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_above_below_empty_1', {})
+
+ call term_sendkeys(buf, ":set list\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_above_below_empty_2', {})
+
+ call term_sendkeys(buf, ":set nolist\<CR>")
+ call term_sendkeys(buf, ":set colorcolumn=10\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_above_below_empty_3', {})
+
+ call term_sendkeys(buf, ":set colorcolumn=\<CR>")
+ call term_sendkeys(buf, ":set relativenumber\<CR>")
+ call term_sendkeys(buf, ":\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_above_below_empty_4', {})
+
+ call term_sendkeys(buf, "kk")
+ call VerifyScreenDump(buf, 'Test_prop_above_below_empty_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_multibyte_below()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ setlocal number
+ call setline(1, ['©', '©', '©'])
+
+ let vt = 'test'
+ call prop_type_add(vt, {'highlight': 'ToDo'})
+ for ln in range(1, line('$'))
+ call prop_add(ln, 0, {'type': vt, 'text': '+++', 'text_align': 'below'})
+ endfor
+ normal G
+ END
+ call writefile(lines, 'XscriptPropMultibyteBelow', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropMultibyteBelow', #{rows: 10, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_multibyte_below_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_text_above_empty()
+ CheckRunVimInTerminal
+
+ " check the cursor is in the correct line
+ let lines =<< trim END
+ setlocal number
+ call setline(1, ['11111111', '', '333333333', '', '55555555555'])
+
+ let vt = 'test'
+ call prop_type_add(vt, {'highlight': 'ToDo'})
+ for ln in range(1, line('$'))
+ call prop_add(ln, 0, {'type': vt, 'text': '---', 'text_align': 'above'})
+ endfor
+ normal G
+ END
+ call writefile(lines, 'XscriptPropAboveEmpty', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropAboveEmpty', #{rows: 16, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_above_empty_1', {})
+
+ call term_sendkeys(buf, ":set list\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_above_empty_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_text_below_after_match()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+
+ setline(1, ['vim9script', 'some text'])
+ set signcolumn=yes
+ matchaddpos('Search', [[1, 10]])
+ prop_type_add('test', {highlight: 'Error'})
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'The quick brown fox',
+ text_align: 'below'
+ })
+ END
+ call writefile(lines, 'XscriptPropsBelow', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsBelow', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_below_after_match_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after_joined()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three', 'four'])
+ call prop_type_add('afterprop', #{highlight: 'Search'})
+ call prop_add(1, 0, #{type: 'afterprop', text: ' ONE', text_align: 'after'})
+ call prop_add(4, 0, #{type: 'afterprop', text: ' FOUR', text_align: 'after'})
+ normal ggJ
+ normal GkJ
+
+ call setline(3, ['a', 'b', 'c', 'd', 'e', 'f'])
+ call prop_add(3, 0, #{type: 'afterprop', text: ' AAA', text_align: 'after'})
+ call prop_add(5, 0, #{type: 'afterprop', text: ' CCC', text_align: 'after'})
+ call prop_add(7, 0, #{type: 'afterprop', text: ' EEE', text_align: 'after'})
+ call prop_add(8, 0, #{type: 'afterprop', text: ' FFF', text_align: 'after'})
+ normal 3G6J
+ END
+ call writefile(lines, 'XscriptPropsWithTextAfterJoined', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithTextAfterJoined', #{rows: 6, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_joined_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after_truncated()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['one two three four five six seven'])
+ call prop_type_add('afterprop', #{highlight: 'Search'})
+ call prop_add(1, 0, #{type: 'afterprop', text: ' ONE and TWO and THREE and FOUR and FIVE'})
+
+ call setline(2, ['one two three four five six seven'])
+ call prop_add(2, 0, #{type: 'afterprop', text: ' one AND two AND three AND four AND five', text_align: 'right'})
+
+ call setline(3, ['one two three four five six seven'])
+ call prop_add(3, 0, #{type: 'afterprop', text: ' one AND two AND three AND four AND five lets wrap after some more text', text_align: 'below'})
+
+ call setline(4, ['cursor here'])
+ normal 4Gfh
+ END
+ call writefile(lines, 'XscriptPropsWithTextAfterTrunc', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithTextAfterTrunc', #{rows: 9, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_trunc_1', {})
+
+ call term_sendkeys(buf, ":37vsp\<CR>gg")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_trunc_2', {})
+
+ call term_sendkeys(buf, ":36wincmd |\<CR>")
+ call term_sendkeys(buf, "2G$")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_trunc_3', {})
+
+ call term_sendkeys(buf, ":33wincmd |\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_trunc_4', {})
+
+ call term_sendkeys(buf, ":18wincmd |\<CR>")
+ call term_sendkeys(buf, "0fx")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_trunc_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_empty_line()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['', 'aaa', '', 'bbbbbb'])
+ call prop_type_add('prop1', #{highlight: 'Search'})
+ call prop_add(1, 1, #{type: 'prop1', text_wrap: 'wrap', text: repeat('X', &columns)})
+ call prop_add(3, 1, #{type: 'prop1', text_wrap: 'wrap', text: repeat('X', &columns + 1)})
+ normal gg0
+ END
+ call writefile(lines, 'XscriptPropsWithTextEmptyLine', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithTextEmptyLine', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_empty_line_1', {})
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_empty_line_2', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_empty_line_3', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_empty_line_4', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_empty_line_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after_wraps()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['one two three four five six seven'])
+ call prop_type_add('afterprop', #{highlight: 'Search'})
+ call prop_add(1, 0, #{type: 'afterprop', text: ' ONE and TWO and THREE and FOUR and FIVE', text_wrap: 'wrap'})
+
+ call setline(2, ['one two three four five six seven'])
+ call prop_add(2, 0, #{type: 'afterprop', text: ' one AND two AND three AND four AND five', text_align: 'right', text_wrap: 'wrap'})
+
+ call setline(3, ['one two three four five six seven'])
+ call prop_add(3, 0, #{type: 'afterprop', text: ' one AND two AND three AND four AND five lets wrap after some more text', text_align: 'below', text_wrap: 'wrap'})
+
+ call setline(4, ['cursor here'])
+ normal 4Gfh
+ END
+ call writefile(lines, 'XscriptPropsWithTextAfterWraps', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithTextAfterWraps', #{rows: 9, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_wraps_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after_nowrap()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nowrap
+ call setline(1, ['one', 'two', 'three', 'four'])
+ call prop_type_add('belowprop', #{highlight: 'ErrorMsg'})
+ call prop_type_add('anotherprop', #{highlight: 'Search'})
+ call prop_type_add('someprop', #{highlight: 'DiffChange'})
+ call prop_add(1, 0, #{type: 'belowprop', text: ' Below the line ', text_align: 'below'})
+ call prop_add(2, 0, #{type: 'anotherprop', text: 'another', text_align: 'below'})
+ call prop_add(2, 0, #{type: 'belowprop', text: 'One More Here', text_align: 'below'})
+ call prop_add(1, 0, #{type: 'someprop', text: 'right here', text_align: 'right'})
+ call prop_add(1, 0, #{type: 'someprop', text: ' After the text', text_align: 'after'})
+ normal 3G$
+
+ call prop_add(3, 0, #{type: 'anotherprop', text: 'right aligned', text_align: 'right'})
+ call prop_add(3, 0, #{type: 'anotherprop', text: 'also right aligned', text_align: 'right'})
+ hi CursorLine ctermbg=lightgrey
+ END
+ call writefile(lines, 'XscriptPropsAfterNowrap', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsAfterNowrap', #{rows: 12, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_nowrap_1', {})
+
+ call term_sendkeys(buf, ":set signcolumn=yes foldcolumn=3 cursorline\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_nowrap_2', {})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_nowrap_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_with_text_below_cul()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+
+ setline(1, ['some text', 'last line'])
+ set cursorline nowrap
+ prop_type_add('test', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'The quick brown fox jumps over the lazy dog',
+ text_align: 'below',
+ text_padding_left: 4,
+ })
+ END
+ call writefile(lines, 'XscriptPropsBelowCurline', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsBelowCurline', #{rows: 6, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_below_cul_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_below_nowrap()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ edit foobar
+ set nowrap
+ set showbreak=+++\
+ setline(1, ['onasdf asdf asdf sdf df asdf asdf e asdf asdf asdf asdf asd fas df', 'two'])
+ prop_type_add('test', {highlight: 'Special'})
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'the quick brown fox jumps over the lazy dog',
+ text_align: 'after'
+ })
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'the quick brown fox jumps over the lazy dog',
+ text_align: 'below'
+ })
+ normal G$
+ END
+ call writefile(lines, 'XscriptPropsBelowNowrap', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsBelowNowrap', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_below_nowrap_1', {})
+
+ call term_sendkeys(buf, "gg$")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_below_nowrap_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_above()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['one two', 'three four', 'five six'])
+ call prop_type_add('above1', #{highlight: 'Search'})
+ call prop_type_add('above2', #{highlight: 'DiffChange'})
+ call prop_type_add('below', #{highlight: 'DiffAdd'})
+ call prop_add(1, 0, #{type: 'above1', text: 'first thing above', text_align: 'above'})
+ call prop_add(1, 0, #{type: 'above2', text: 'second thing above', text_align: 'above'})
+ call prop_add(3, 0, #{type: 'above1', text: 'another thing', text_align: 'above', text_padding_left: 3})
+
+ normal gglllj
+ func AddPropBelow()
+ call prop_add(1, 0, #{type: 'below', text: 'below', text_align: 'below'})
+ endfunc
+ func AddLongPropAbove()
+ 3,4delete
+ set wrap
+ call prop_add(1, 0, #{type: 'above1', text: range(50)->join(' '), text_align: 'above', text_padding_left: 2})
+ endfunc
+ END
+ call writefile(lines, 'XscriptPropsWithTextAbove', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsWithTextAbove', #{rows: 9, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_1', {})
+
+ call term_sendkeys(buf, "ggg$")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_1a', {})
+ call term_sendkeys(buf, "g0")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_1b', {})
+
+ call term_sendkeys(buf, ":set showbreak=>>\<CR>")
+ call term_sendkeys(buf, "ggll")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_1c', {})
+ call term_sendkeys(buf, ":set showbreak=\<CR>")
+
+ call term_sendkeys(buf, "ggI")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_2', {})
+ call term_sendkeys(buf, "inserted \<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_3', {})
+
+ call term_sendkeys(buf, ":set number signcolumn=yes\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_4', {})
+
+ call term_sendkeys(buf, ":set nowrap\<CR>gg$j")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_5', {})
+
+ call term_sendkeys(buf, ":call AddPropBelow()\<CR>")
+ call term_sendkeys(buf, "ggve")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_6', {})
+ call term_sendkeys(buf, "V")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_7', {})
+
+ call term_sendkeys(buf, "\<Esc>ls\<CR>\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_8', {})
+
+ call term_sendkeys(buf, ":call AddLongPropAbove()\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_above_9', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_above_with_indent()
+ new
+ call setline(1, ['first line', ' second line', ' line below'])
+ setlocal cindent
+ call prop_type_add('indented', #{highlight: 'Search'})
+ call prop_add(3, 0, #{type: 'indented', text: 'here', text_align: 'above', text_padding_left: 4})
+ call assert_equal(' line below', getline(3))
+
+ exe "normal 3G2|a\<CR>"
+ call assert_equal(' ', getline(3))
+ call assert_equal(' line below', getline(4))
+
+ bwipe!
+ call prop_type_delete('indented')
+endfunc
+
+func Test_prop_above_with_number()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['one one one', 'two two two', 'three three three'])
+ set number cpo+=n
+ prop_type_add('test', {highlight: 'DiffChange'})
+ prop_add(2, 0, {
+ text: 'above the text',
+ type: 'test',
+ text_align: 'above',
+ })
+ def g:OneMore()
+ prop_add(2, 0, {
+ text: 'also above the text',
+ type: 'test',
+ text_align: 'above',
+ })
+ enddef
+ END
+ call writefile(lines, 'XscriptPropAboveNr', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropAboveNr', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_prop_above_number_1', {})
+
+ call term_sendkeys(buf, ":call OneMore()\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_above_number_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_below_split_line()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['one one one', 'two two two', 'three three three'])
+ prop_type_add('test', {highlight: 'Search'})
+ prop_add(2, 0, {
+ text: '└─ Virtual text below the 2nd line',
+ type: 'test',
+ text_align: 'below',
+ text_padding_left: 3
+ })
+ END
+ call writefile(lines, 'XscriptPropBelowSpitLine', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropBelowSpitLine', #{rows: 8})
+ call term_sendkeys(buf, "2GA\<CR>xx")
+ call VerifyScreenDump(buf, 'Test_prop_below_split_line_1', {})
+
+ call term_sendkeys(buf, "\<Esc>:set number\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_below_split_line_2', {})
+
+ call term_sendkeys(buf, ":set nowrap\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_below_split_line_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_prop_above_below_smoothscroll()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, range(1, 10)->mapnew((_, v) => '" line ' .. v))
+
+ set smoothscroll wrap
+ call prop_type_add('mytype', {highlight: 'DiffChange'})
+ call prop_add(3, 0, {text: "insert above", type: "mytype", text_align: 'above'})
+ call prop_add(5, 0, {text: "insert above 1", type: "mytype", text_align: 'above'})
+ call prop_add(5, 0, {text: "insert above 2", type: "mytype", text_align: 'above'})
+ call prop_add(7, 0, {text: "insert below", type: "mytype", text_align: 'below'})
+ call prop_add(9, 0, {text: "insert below 1", type: "mytype", text_align: 'below'})
+ call prop_add(9, 0, {text: "insert below 2", type: "mytype", text_align: 'below'})
+ END
+ call writefile(lines, 'XscriptPropsSmoothscroll', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsSmoothscroll', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_above_below_smoothscroll_1', {})
+
+ for nr in range(2, 16)
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_prop_above_below_smoothscroll_' .. nr, {})
+ endfor
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_override()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, 'some text here')
+ hi Likethis ctermfg=blue ctermbg=cyan
+ prop_type_add('prop', {highlight: 'Likethis', override: true})
+ prop_add(1, 6, {type: 'prop', text: ' inserted '})
+ hi CursorLine cterm=underline ctermbg=lightgrey
+ set cursorline
+ END
+ call writefile(lines, 'XscriptPropsOverride', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsOverride', #{rows: 6, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_override_1', {})
+
+ call term_sendkeys(buf, ":set nocursorline\<CR>")
+ call term_sendkeys(buf, "0llvfr")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_override_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_CursorMoved()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['this is line one', 'this is line two', 'three', 'four', 'five'])
+
+ call prop_type_add('prop', #{highlight: 'Error'})
+ let g:long_text = repeat('x', &columns * 2)
+
+ let g:prop_id = v:null
+ func! Update()
+ if line('.') == 1
+ if g:prop_id == v:null
+ let g:prop_id = prop_add(1, 0, #{type: 'prop', text_wrap: 'wrap', text: g:long_text})
+ endif
+ elseif g:prop_id != v:null
+ call prop_remove(#{id: g:prop_id})
+ let g:prop_id = v:null
+ endif
+ endfunc
+
+ autocmd CursorMoved * call Update()
+ END
+ call writefile(lines, 'XscriptPropsCursorMovec', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsCursorMovec', #{rows: 8, cols: 60})
+ call term_sendkeys(buf, "gg0w")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_cursormoved_1', {})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_cursormoved_2', {})
+
+ " back to the first state
+ call term_sendkeys(buf, "k")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_cursormoved_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_props_with_text_after_split_join()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['1122'])
+ call prop_type_add('belowprop', #{highlight: 'ErrorMsg'})
+ call prop_add(1, 0, #{type: 'belowprop', text: ' Below the line ', text_align: 'below'})
+ exe "normal f2i\<CR>\<Esc>"
+
+ func AddMore()
+ call prop_type_add('another', #{highlight: 'Search'})
+ call prop_add(1, 0, #{type: 'another', text: ' after the text ', text_align: 'after'})
+ call prop_add(1, 0, #{type: 'another', text: ' right here', text_align: 'right'})
+ endfunc
+ END
+ call writefile(lines, 'XscriptPropsAfterSplitJoin', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsAfterSplitJoin', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_join_split_1', {})
+
+ call term_sendkeys(buf, "ggJ")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_join_split_2', {})
+
+ call term_sendkeys(buf, ":call AddMore()\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_join_split_3', {})
+
+ call term_sendkeys(buf, "ggf s\<CR>\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_join_split_4', {})
+
+ call term_sendkeys(buf, "ggJ")
+ call VerifyScreenDump(buf, 'Test_prop_with_text_after_join_split_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_removed_prop_with_text_cleans_up_array()
+ new
+ call setline(1, 'some text here')
+ call prop_type_add('some', #{highlight: 'ErrorMsg'})
+ let id1 = prop_add(1, 5, #{type: 'some', text: "SOME"})
+ call assert_equal(-1, id1)
+ let id2 = prop_add(1, 10, #{type: 'some', text: "HERE"})
+ call assert_equal(-2, id2)
+
+ " removing the props resets the index
+ call prop_remove(#{id: id1})
+ call prop_remove(#{id: id2})
+ let id1 = prop_add(1, 5, #{type: 'some', text: "SOME"})
+ call assert_equal(-1, id1)
+
+ call prop_type_delete('some')
+ bwipe!
+endfunc
+
+def Test_insert_text_before_virtual_text()
+ new foobar
+ setline(1, '12345678')
+ prop_type_add('test', {highlight: 'Search'})
+ prop_add(1, 5, {
+ type: 'test',
+ text: ' virtual text '
+ })
+ normal! f4axyz
+ normal! f5iXYZ
+ assert_equal('1234xyzXYZ5678', getline(1))
+
+ prop_type_delete('test')
+ bwipe!
+enddef
+
+func Test_insert_text_start_incl()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['text one text two', '', 'function(arg)'])
+
+ prop_type_add('propincl', {highlight: 'NonText', start_incl: true})
+ prop_add(1, 6, {type: 'propincl', text: 'after '})
+ cursor(1, 6)
+ prop_type_add('propnotincl', {highlight: 'NonText', start_incl: false})
+ prop_add(1, 15, {type: 'propnotincl', text: 'before '})
+
+ set cindent sw=4
+ prop_type_add('argname', {highlight: 'DiffChange', start_incl: true})
+ prop_add(3, 10, {type: 'argname', text: 'arg: '})
+ END
+ call writefile(lines, 'XscriptPropsStartIncl', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsStartIncl', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_1', {})
+
+ call term_sendkeys(buf, "i")
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_2', {})
+ call term_sendkeys(buf, "xx\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_3', {})
+
+ call term_sendkeys(buf, "2wi")
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_4', {})
+ call term_sendkeys(buf, "yy\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_5', {})
+
+ call term_sendkeys(buf, "3Gfai\<CR>\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_6', {})
+ call term_sendkeys(buf, ">>")
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_7', {})
+ call term_sendkeys(buf, "<<<<")
+ call VerifyScreenDump(buf, 'Test_prop_insert_start_incl_8', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_insert_text_list_mode()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['This is a line with quite a bit of text here.',
+ 'second line', 'third line'])
+ set list listchars+=extends:»
+ prop_type_add('Prop1', {highlight: 'Error'})
+ prop_add(1, 0, {
+ type: 'Prop1',
+ text: 'The quick brown fox jumps over the lazy dog',
+ text_align: 'right'
+ })
+ END
+ call writefile(lines, 'XscriptPropsListMode', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsListMode', #{rows: 8, cols: 60})
+ call term_sendkeys(buf, "ggj")
+ call VerifyScreenDump(buf, 'Test_prop_insert_list_mode_1', {})
+
+ call term_sendkeys(buf, ":set nowrap\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_insert_list_mode_2', {})
+
+ call term_sendkeys(buf, "ggd32l")
+ call VerifyScreenDump(buf, 'Test_prop_insert_list_mode_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_insert_text_with_padding()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['Some text to add virtual text to.',
+ 'second line',
+ 'Another line with some text to make the wrap.'])
+ prop_type_add('theprop', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'after',
+ text_align: 'after',
+ text_padding_left: 3,
+ })
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'right aligned',
+ text_align: 'right',
+ text_padding_left: 5,
+ })
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'below the line',
+ text_align: 'below',
+ text_padding_left: 4,
+ })
+ prop_add(3, 0, {
+ type: 'theprop',
+ text: 'rightmost',
+ text_align: 'right',
+ text_padding_left: 6,
+ text_wrap: 'wrap',
+ })
+ END
+ call writefile(lines, 'XscriptPropsPadded', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsPadded', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_text_with_padding_1', {})
+
+ call term_sendkeys(buf, "ggixxxxxxxxxx\<Esc>")
+ call term_sendkeys(buf, "3Gix\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_text_with_padding_2', {})
+
+ call term_sendkeys(buf, "ggix\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_text_with_padding_3', {})
+
+ call term_sendkeys(buf, ":set list\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_text_with_padding_4', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_long_text_below_with_padding()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['first line', 'second line'])
+ prop_type_add('theprop', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'after '->repeat(20),
+ text_align: 'below',
+ text_padding_left: 3,
+ })
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'more '->repeat(20),
+ text_align: 'below',
+ text_padding_left: 30,
+ })
+ normal 2Gw
+ END
+ call writefile(lines, 'XlongTextBelowWithPadding', 'D')
+ let buf = RunVimInTerminal('-S XlongTextBelowWithPadding', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_long_text_with_padding_1', {})
+
+ call term_sendkeys(buf, ":set list\<CR>")
+ call VerifyScreenDump(buf, 'Test_long_text_with_padding_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_text_after_nowrap()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['first line', range(80)->join(' '), 'third', 'fourth'])
+ set nowrap
+ prop_type_add('theprop', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'right after the text '->repeat(3),
+ text_align: 'after',
+ text_padding_left: 2,
+ })
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'in the middle '->repeat(4),
+ text_align: 'after',
+ text_padding_left: 3,
+ })
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'the last one '->repeat(3),
+ text_align: 'after',
+ text_padding_left: 1,
+ })
+ normal 2Gw
+ def g:ChangeText()
+ prop_clear(1)
+ set list
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'just after txt '->repeat(3),
+ text_align: 'after',
+ text_padding_left: 2,
+ })
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'in the middle '->repeat(4),
+ text_align: 'after',
+ text_padding_left: 1,
+ })
+ enddef
+ END
+ call writefile(lines, 'XTextAfterNowrap', 'D')
+ let buf = RunVimInTerminal('-S XTextAfterNowrap', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_text_after_nowrap_1', {})
+
+ call term_sendkeys(buf, "30w")
+ call VerifyScreenDump(buf, 'Test_text_after_nowrap_2', {})
+
+ call term_sendkeys(buf, "22w")
+ call VerifyScreenDump(buf, 'Test_text_after_nowrap_3', {})
+
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_text_after_nowrap_4', {})
+
+ call term_sendkeys(buf, "0")
+ call term_sendkeys(buf, ":call ChangeText()\<CR>")
+ call VerifyScreenDump(buf, 'Test_text_after_nowrap_5', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_text_after_nowrap_list()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+
+ set nowrap
+ set listchars+=extends:>
+ set list
+ setline(1, ['some text here', '', 'last line'])
+
+ prop_type_add('test', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'The quick brown fox jumps.',
+ text_padding_left: 2,
+ })
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'â–  The fox jumps over the lazy dog.',
+ text_padding_left: 2,
+ })
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'â–  The lazy dog.',
+ text_padding_left: 2,
+ })
+ normal 3G$
+ END
+ call writefile(lines, 'XTextAfterNowrapList', 'D')
+ let buf = RunVimInTerminal('-S XTextAfterNowrapList', #{rows: 6, cols: 60})
+ call VerifyScreenDump(buf, 'Test_text_after_nowrap_list_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_text_below_nowrap()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['first line', 'second line '->repeat(50), 'third', 'fourth'])
+ set nowrap number
+ prop_type_add('theprop', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'one below the text '->repeat(5),
+ text_align: 'below',
+ text_padding_left: 2,
+ })
+ prop_add(1, 0, {
+ type: 'theprop',
+ text: 'two below the text '->repeat(5),
+ text_align: 'below',
+ text_padding_left: 2,
+ })
+ normal 2Gw
+ END
+ call writefile(lines, 'XTextBelowNowrap', 'D')
+ let buf = RunVimInTerminal('-S XTextBelowNowrap', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_text_below_nowrap_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_virtual_text_in_popup_highlight()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+
+ # foreground highlight only, popup background is used
+ prop_type_add('Prop1', {'highlight': 'SpecialKey'})
+ # foreground and background highlight, popup background is not used
+ prop_type_add('Prop2', {'highlight': 'DiffDelete'})
+
+ var popupText = [{
+ text: 'Some text',
+ props: [
+ {
+ col: 1,
+ type: 'Prop1',
+ text: ' + '
+ },
+ {
+ col: 6,
+ type: 'Prop2',
+ text: ' x '
+ },
+ ]
+ }]
+ var popupArgs = {
+ line: 3,
+ col: 20,
+ maxwidth: 80,
+ highlight: 'PMenu',
+ border: [],
+ borderchars: [' '],
+ }
+
+ popup_create(popupText, popupArgs)
+ END
+ call writefile(lines, 'XscriptVirtualHighlight', 'D')
+ let buf = RunVimInTerminal('-S XscriptVirtualHighlight', #{rows: 8})
+ call VerifyScreenDump(buf, 'Test_virtual_text_in_popup_highlight_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_insert_text_change_arg()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['SetErrorCode( 10, 20 )', 'second line'])
+ prop_type_add('param', {highlight: 'DiffChange', start_incl: 1})
+ prop_type_add('padd', {highlight: 'NonText', start_incl: 1})
+ prop_add(1, 15, {
+ type: 'param',
+ text: 'id:',
+ })
+ prop_add(1, 15, {
+ type: 'padd',
+ text: '-',
+ })
+ prop_add(1, 19, {
+ type: 'param',
+ text: 'id:',
+ })
+ prop_add(1, 19, {
+ type: 'padd',
+ text: '-',
+ })
+ END
+ call writefile(lines, 'XscriptPropsChange', 'D')
+ let buf = RunVimInTerminal('-S XscriptPropsChange', #{rows: 5, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_text_change_arg_1', {})
+
+ call term_sendkeys(buf, "ggf1cw1234\<Esc>")
+ call VerifyScreenDump(buf, 'Test_prop_text_change_arg_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+def Test_textprop_in_quickfix_window()
+ enew!
+ var prop_type = 'my_prop'
+ prop_type_add(prop_type, {})
+
+ for lnum in range(1, 10)
+ setline(lnum, 'hello world')
+ endfor
+
+ cgetbuffer
+ copen
+
+ var bufnr = bufnr()
+ for lnum in range(1, line('$', bufnr->bufwinid()))
+ prop_add(lnum, 1, {
+ id: 1000 + lnum,
+ type: prop_type,
+ bufnr: bufnr,
+ })
+ endfor
+
+ prop_type_delete(prop_type)
+ cclose
+ bwipe!
+enddef
+
+func Test_text_prop_delete_updates()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+
+ setline(1, ['some text', 'more text', 'the end'])
+ prop_type_add('test', {highlight: 'DiffChange'})
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'The quick brown fox jumps over the lazy dog',
+ text_align: 'below',
+ text_padding_left: 3,
+ })
+ prop_add(1, 0, {
+ type: 'test',
+ text: 'The quick brown fox jumps over the lazy dog',
+ text_align: 'below',
+ text_padding_left: 5,
+ })
+
+ normal! G
+ END
+ call writefile(lines, 'XtextPropDelete', 'D')
+ let buf = RunVimInTerminal('-S XtextPropDelete', #{rows: 10, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_delete_updates_1', {})
+
+ " Check that after deleting the text prop type the text properties using
+ " this type no longer show and are not counted for cursor positioning.
+ call term_sendkeys(buf, ":call prop_type_delete('test')\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_delete_updates_2', {})
+
+ call term_sendkeys(buf, "ggj")
+ call VerifyScreenDump(buf, 'Test_prop_delete_updates_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_text_prop_diff_mode()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['9000', '0009', '0009', '9000', '0009'])
+
+ let type = 'test'
+ call prop_type_add(type, {})
+ let text = '<text>'
+ call prop_add(1, 1, {'type': type, 'text': text})
+ call prop_add(2, 0, {'type': type, 'text': text, 'text_align': 'after'})
+ call prop_add(3, 0, {'type': type, 'text': text, 'text_align': 'right'})
+ call prop_add(4, 0, {'type': type, 'text': text, 'text_align': 'above'})
+ call prop_add(5, 0, {'type': type, 'text': text, 'text_align': 'below'})
+ set diff
+
+ vnew
+ call setline(1, ['000', '000', '000', '000', '000'])
+ set diff
+ END
+ call writefile(lines, 'XtextPropDiff', 'D')
+ let buf = RunVimInTerminal('-S XtextPropDiff', #{rows: 10, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_diff_mode_1', {})
+
+ call term_sendkeys(buf, ":windo set number\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_diff_mode_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_error_when_using_negative_id()
+ call prop_type_add('test1', #{highlight: 'ErrorMsg'})
+ call prop_add(1, 1, #{type: 'test1', text: 'virtual'})
+ call assert_fails("call prop_add(1, 1, #{type: 'test1', length: 1, id: -1})", 'E1293:')
+
+ call prop_type_delete('test1')
+endfunc
+
+func Test_error_after_using_negative_id()
+ " This needs to run a separate Vim instance because the
+ " "did_use_negative_pop_id" will be set.
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+
+ setline(1, ['one', 'two', 'three'])
+ prop_type_add('test_1', {highlight: 'Error'})
+ prop_type_add('test_2', {highlight: 'WildMenu'})
+
+ prop_add(3, 1, {
+ type: 'test_1',
+ length: 5,
+ id: -1
+ })
+
+ def g:AddTextprop()
+ prop_add(1, 0, {
+ type: 'test_2',
+ text: 'The quick fox',
+ text_padding_left: 2
+ })
+ enddef
+ END
+ call writefile(lines, 'XtextPropError', 'D')
+ let buf = RunVimInTerminal('-S XtextPropError', #{rows: 8, cols: 60})
+ call VerifyScreenDump(buf, 'Test_prop_negative_error_1', {})
+
+ call term_sendkeys(buf, ":call AddTextprop()\<CR>")
+ call VerifyScreenDump(buf, 'Test_prop_negative_error_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_timers.vim b/src/testdir/test_timers.vim
new file mode 100644
index 0000000..fe80bb9
--- /dev/null
+++ b/src/testdir/test_timers.vim
@@ -0,0 +1,544 @@
+" Test for timers
+
+source check.vim
+CheckFeature timers
+
+source screendump.vim
+source shared.vim
+source term_util.vim
+
+func MyHandler(timer)
+ let g:val += 1
+endfunc
+
+func MyHandlerWithLists(lists, timer)
+ let x = string(a:lists)
+endfunc
+
+func Test_timer_oneshot()
+ let g:test_is_flaky = 1
+ let g:val = 0
+ let timer = timer_start(50, 'MyHandler')
+ let slept = WaitFor('g:val == 1')
+ call assert_equal(1, g:val)
+ if has('mac')
+ " Mac on Travis can be very slow.
+ let limit = 180
+ else
+ let limit = 100
+ endif
+ if has('reltime')
+ call assert_inrange(49, limit, slept)
+ else
+ call assert_inrange(20, limit, slept)
+ endif
+endfunc
+
+func Test_timer_repeat_three()
+ let g:test_is_flaky = 1
+ let g:val = 0
+ let timer = timer_start(50, 'MyHandler', {'repeat': 3})
+ let slept = WaitFor('g:val == 3')
+ call assert_equal(3, g:val)
+ if has('reltime')
+ if has('mac')
+ " Mac on Travis can be slow.
+ call assert_inrange(149, 400, slept)
+ else
+ call assert_inrange(149, 250, slept)
+ endif
+ else
+ call assert_inrange(80, 200, slept)
+ endif
+endfunc
+
+func Test_timer_repeat_many()
+ let g:test_is_flaky = 1
+ let g:val = 0
+ let timer = timer_start(50, 'MyHandler', {'repeat': -1})
+ sleep 200m
+ call timer_stop(timer)
+ " Mac on Travis can be slow.
+ if has('mac')
+ call assert_inrange(1, 5, g:val)
+ else
+ call assert_inrange(2, 5, g:val)
+ endif
+endfunc
+
+func Test_timer_with_partial_callback()
+ let g:test_is_flaky = 1
+ let g:val = 0
+ let meow = {'one': 1}
+ function meow.bite(...)
+ let g:val += self.one
+ endfunction
+
+ call timer_start(50, meow.bite)
+ let slept = WaitFor('g:val == 1')
+ call assert_equal(1, g:val)
+ if has('reltime')
+ " Mac on Travis can be slow.
+ if has('mac')
+ call assert_inrange(49, 180, slept)
+ else
+ call assert_inrange(49, 130, slept)
+ endif
+ else
+ call assert_inrange(20, 100, slept)
+ endif
+endfunc
+
+func Test_timer_retain_partial()
+ call timer_start(50, function('MyHandlerWithLists', [['a']]))
+ call test_garbagecollect_now()
+ sleep 100m
+endfunc
+
+func Test_timer_info()
+ let id = timer_start(1000, 'MyHandler')
+ let info = id->timer_info()
+ call assert_equal(id, info[0]['id'])
+ call assert_equal(1000, info[0]['time'])
+ call assert_true(info[0]['remaining'] > 500)
+ call assert_true(info[0]['remaining'] <= 1000)
+ call assert_equal(1, info[0]['repeat'])
+ call assert_equal("function('MyHandler')", string(info[0]['callback']))
+
+ let found = 0
+ for info in timer_info()
+ if info['id'] == id
+ let found += 1
+ endif
+ endfor
+ call assert_equal(1, found)
+
+ call timer_stop(id)
+ call assert_equal([], timer_info(id))
+
+ call assert_fails('call timer_info("abc")', 'E1210:')
+
+ " check repeat count inside the callback
+ let g:timer_repeat = []
+ let tid = timer_start(10, {tid -> execute("call add(g:timer_repeat, timer_info(tid)[0].repeat)")}, #{repeat: 3})
+ call WaitForAssert({-> assert_equal([2, 1, 0], g:timer_repeat)})
+ unlet g:timer_repeat
+endfunc
+
+func Test_timer_stopall()
+ let id1 = timer_start(1000, 'MyHandler')
+ let id2 = timer_start(2000, 'MyHandler')
+ let info = timer_info()
+ " count one for the TestTimeout() timer
+ call assert_equal(3, len(info))
+
+ call timer_stopall()
+ let info = timer_info()
+ call assert_equal(0, len(info))
+endfunc
+
+def Test_timer_stopall_with_popup()
+ # Create a popup that times out after ten seconds.
+ # Another timer will fire in half a second and close it early after stopping
+ # all timers.
+ var pop = popup_create('Popup', {time: 10000})
+ var tmr = timer_start(500, (_) => {
+ timer_stopall()
+ popup_clear()
+ })
+ sleep 1
+ assert_equal([], timer_info(tmr))
+ assert_equal([], popup_list())
+enddef
+
+func Test_timer_paused()
+ let g:test_is_flaky = 1
+ let g:val = 0
+
+ let id = timer_start(50, 'MyHandler')
+ let info = timer_info(id)
+ call assert_equal(0, info[0]['paused'])
+
+ eval id->timer_pause(1)
+ let info = timer_info(id)
+ call assert_equal(1, info[0]['paused'])
+ sleep 100m
+ call assert_equal(0, g:val)
+
+ call timer_pause(id, 0)
+ let info = timer_info(id)
+ call assert_equal(0, info[0]['paused'])
+
+ let slept = WaitFor('g:val == 1')
+ call assert_equal(1, g:val)
+ if has('reltime')
+ if has('mac')
+ " The travis Mac machines appear to be very busy.
+ call assert_inrange(0, 90, slept)
+ else
+ call assert_inrange(0, 30, slept)
+ endif
+ else
+ call assert_inrange(0, 10, slept)
+ endif
+
+ call assert_fails('call timer_pause("abc", 1)', 'E39:')
+endfunc
+
+func StopMyself(timer)
+ let g:called += 1
+ if g:called == 2
+ call timer_stop(a:timer)
+ endif
+endfunc
+
+func Test_timer_delete_myself()
+ let g:called = 0
+ let t = timer_start(10, 'StopMyself', {'repeat': -1})
+ call WaitForAssert({-> assert_equal(2, g:called)})
+ call assert_equal(2, g:called)
+ call assert_equal([], timer_info(t))
+endfunc
+
+func StopTimer1(timer)
+ let g:timer2 = 10->timer_start('StopTimer2')
+ " avoid maxfuncdepth error
+ call timer_pause(g:timer1, 1)
+ sleep 20m
+endfunc
+
+func StopTimer2(timer)
+ call timer_stop(g:timer1)
+endfunc
+
+func Test_timer_stop_in_callback()
+ let g:test_is_flaky = 1
+ call assert_equal(1, len(timer_info()))
+ let g:timer1 = timer_start(10, 'StopTimer1')
+ let slept = 0
+ for i in range(10)
+ if len(timer_info()) == 1
+ break
+ endif
+ sleep 10m
+ let slept += 10
+ endfor
+ if slept == 100
+ call assert_equal(1, len(timer_info()))
+ else
+ " This should take only 30 msec, but on Mac it's often longer
+ call assert_inrange(0, 50, slept)
+ endif
+endfunc
+
+func StopTimerAll(timer)
+ call timer_stopall()
+endfunc
+
+func Test_timer_stop_all_in_callback()
+ let g:test_is_flaky = 1
+ " One timer is for TestTimeout()
+ call assert_equal(1, len(timer_info()))
+ call timer_start(10, 'StopTimerAll')
+ call assert_equal(2, len(timer_info()))
+ let slept = 0
+ for i in range(10)
+ if len(timer_info()) == 0
+ break
+ endif
+ sleep 10m
+ let slept += 10
+ endfor
+ if slept == 100
+ call assert_equal(0, len(timer_info()))
+ else
+ call assert_inrange(0, 30, slept)
+ endif
+endfunc
+
+func FeedkeysCb(timer)
+ call feedkeys("hello\<CR>", 'nt')
+endfunc
+
+func InputCb(timer)
+ call timer_start(10, 'FeedkeysCb')
+ let g:val = input('?')
+ call Resume()
+endfunc
+
+func Test_timer_input_in_timer()
+ let g:val = ''
+ call timer_start(10, 'InputCb')
+ call Standby(1000)
+ call assert_equal('hello', g:val)
+endfunc
+
+func FuncWithError(timer)
+ let g:call_count += 1
+ if g:call_count == 4
+ return
+ endif
+ doesnotexist
+endfunc
+
+func Test_timer_errors()
+ let g:call_count = 0
+ let timer = timer_start(10, 'FuncWithError', {'repeat': -1})
+ " Timer will be stopped after failing 3 out of 3 times.
+ call WaitForAssert({-> assert_equal(3, g:call_count)})
+ sleep 50m
+ call assert_equal(3, g:call_count)
+
+ call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E1206:')
+ call assert_fails('call timer_start(100, [])', 'E921:')
+ call assert_fails('call timer_stop("abc")', 'E1210:')
+endfunc
+
+func FuncWithCaughtError(timer)
+ let g:call_count += 1
+ try
+ doesnotexist
+ catch
+ " nop
+ endtry
+endfunc
+
+func Test_timer_catch_error()
+ let g:call_count = 0
+ let timer = timer_start(10, 'FuncWithCaughtError', {'repeat': 4})
+ " Timer will not be stopped.
+ call WaitForAssert({-> assert_equal(4, g:call_count)})
+ sleep 50m
+ call assert_equal(4, g:call_count)
+endfunc
+
+func FeedAndPeek(timer)
+ call test_feedinput('a')
+ call getchar(1)
+endfunc
+
+func Interrupt(timer)
+ eval "\<C-C>"->test_feedinput()
+endfunc
+
+func Test_timer_peek_and_get_char()
+ if !has('unix') && !has('gui_running')
+ throw 'Skipped: cannot feed low-level input'
+ endif
+
+ call timer_start(0, 'FeedAndPeek')
+ let intr = timer_start(100, 'Interrupt')
+ let c = getchar()
+ call assert_equal(char2nr('a'), c)
+ eval intr->timer_stop()
+endfunc
+
+func Test_timer_getchar_zero()
+ if has('win32') && !has('gui_running')
+ throw 'Skipped: cannot feed low-level input'
+ endif
+ CheckFunction reltimefloat
+
+ " Measure the elapsed time to avoid a hang when it fails.
+ let start = reltime()
+ let id = timer_start(20, {-> feedkeys('x', 'L')})
+ let c = 0
+ while c == 0 && reltimefloat(reltime(start)) < 0.2
+ let c = getchar(0)
+ sleep 10m
+ endwhile
+ call assert_equal('x', nr2char(c))
+ call timer_stop(id)
+endfunc
+
+func Test_timer_ex_mode()
+ " Function with an empty line.
+ func Foo(...)
+
+ endfunc
+ let timer = timer_start(40, function('g:Foo'), {'repeat':-1})
+ " This used to throw error E749.
+ exe "normal Qsleep 100m\rvi\r"
+ call timer_stop(timer)
+endfunc
+
+func Test_timer_restore_count()
+ CheckRunVimInTerminal
+ " Check that v:count is saved and restored, not changed by a timer.
+ call writefile([
+ \ 'nnoremap <expr><silent> L v:count ? v:count . "l" : "l"',
+ \ 'func Doit(id)',
+ \ ' normal 3j',
+ \ 'endfunc',
+ \ 'call timer_start(100, "Doit")',
+ \ ], 'Xtrcscript', 'D')
+ call writefile([
+ \ '1-1234',
+ \ '2-1234',
+ \ '3-1234',
+ \ ], 'Xtrctext', 'D')
+ let buf = RunVimInTerminal('-S Xtrcscript Xtrctext', {})
+
+ " Wait for the timer to move the cursor to the third line.
+ call WaitForAssert({-> assert_equal(3, term_getcursor(buf)[0])})
+ call assert_equal(1, term_getcursor(buf)[1])
+ " Now check that v:count has not been set to 3
+ call term_sendkeys(buf, 'L')
+ call WaitForAssert({-> assert_equal(2, term_getcursor(buf)[1])})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test that the garbage collector isn't triggered if a timer callback invokes
+" vgetc().
+func Test_nocatch_timer_garbage_collect()
+ " FIXME: why does this fail only on MacOS M1?
+ try
+ CheckNotMacM1
+ catch /Skipped/
+ let g:skipped_reason = v:exception
+ return
+ endtry
+
+ " 'uptimetime. must be bigger than the timer timeout
+ set ut=200
+ call test_garbagecollect_soon()
+ call test_override('no_wait_return', 0)
+ func CauseAnError(id)
+ " This will show an error and wait for Enter.
+ let a = {'foo', 'bar'}
+ endfunc
+ func FeedChar(id)
+ call feedkeys(":\<CR>", 't')
+ endfunc
+ call timer_start(300, 'FeedChar')
+ call timer_start(100, 'CauseAnError')
+ let x = getchar() " wait for error in timer
+ let x = getchar(0) " read any remaining chars
+ let x = getchar(0)
+
+ set ut&
+ call test_override('no_wait_return', 1)
+ delfunc CauseAnError
+ delfunc FeedChar
+endfunc
+
+func Test_timer_error_in_timer_callback()
+ if !has('terminal') || (has('win32') && has('gui_running'))
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+
+ let lines =<< trim [CODE]
+ func Func(timer)
+ " fail to create list
+ let x = [
+ endfunc
+ set updatetime=50
+ call timer_start(1, 'Func')
+ [CODE]
+ call writefile(lines, 'Xtest.vim', 'D')
+
+ let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
+
+ " GC must not run during timer callback, which can make Vim crash.
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>")
+ call TermWait(buf, 50)
+ call assert_equal('run', job_status(job))
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitFor({-> job_status(job) ==# 'dead'})
+ if has('unix')
+ call assert_equal('', job_info(job).termsig)
+ endif
+
+ exe buf .. 'bwipe!'
+endfunc
+
+" Test for garbage collection when a timer is still running
+func Test_timer_garbage_collect()
+ let timer = timer_start(1000, function('MyHandler'), {'repeat' : 10})
+ call test_garbagecollect_now()
+ let l = timer_info(timer)
+ call assert_equal(function('MyHandler'), l[0].callback)
+ call timer_stop(timer)
+endfunc
+
+func Test_timer_invalid_callback()
+ call assert_fails('call timer_start(0, "0")', 'E921:')
+endfunc
+
+func Test_timer_changing_function_list()
+ CheckRunVimInTerminal
+
+ " Create a large number of functions. Should get the "more" prompt.
+ " The typing "G" triggers the timer, which changes the function table.
+ let lines =<< trim END
+ for func in map(range(1,99), "'Func' .. v:val")
+ exe "func " .. func .. "()"
+ endfunc
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> 0})
+ END
+ call writefile(lines, 'XTest_timerchange', 'D')
+ let buf = RunVimInTerminal('-S XTest_timerchange', #{rows: 10})
+ call term_sendkeys(buf, ":fu\<CR>")
+ call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 10))})
+ call term_sendkeys(buf, "G")
+ call WaitForAssert({-> assert_match('E454:', term_getline(buf, 9))})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_timer_outputting_message()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ setline(1, 'some text')
+ set showcmd ut=2000 cmdheight=1
+ timer_start(0, (_) => {
+ echon repeat('x', &columns - 11)
+ })
+ END
+ call writefile(lines, 'XTest_timermessage', 'D')
+ let buf = RunVimInTerminal('-S XTest_timermessage', #{rows: 6})
+ call term_sendkeys(buf, "l")
+ call term_wait(buf)
+ " should not get a hit-enter prompt
+ call WaitForAssert({-> assert_match('xxxxxxxxxxx', term_getline(buf, 6))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_timer_using_win_execute_undo_sync()
+ " FIXME: why does this fail only on MacOS M1?
+ CheckNotMacM1
+
+ let bufnr1 = bufnr()
+ new
+ let g:bufnr2 = bufnr()
+ let g:winid = win_getid()
+ exe "buffer " .. bufnr1
+ wincmd w
+ call setline(1, ['test'])
+ autocmd InsertEnter * call timer_start(100, { -> win_execute(g:winid, 'buffer ' .. g:bufnr2) })
+ call timer_start(200, { -> feedkeys("\<CR>bbbb\<Esc>") })
+ call feedkeys("Oaaaa", 'x!t')
+ " will hang here until the second timer fires
+ call assert_equal(['aaaa', 'bbbb', 'test'], getline(1, '$'))
+ undo
+ call assert_equal(['test'], getline(1, '$'))
+
+ bwipe!
+ bwipe!
+ unlet g:winid
+ unlet g:bufnr2
+ au! InsertEnter
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_true_false.vim b/src/testdir/test_true_false.vim
new file mode 100644
index 0000000..2b27ba7
--- /dev/null
+++ b/src/testdir/test_true_false.vim
@@ -0,0 +1,154 @@
+" Test behavior of boolean-like values.
+
+source check.vim
+
+" Test what is explained at ":help TRUE" and ":help FALSE".
+func Test_if()
+ if v:false
+ call assert_true(false, 'v:false is false')
+ endif
+ if 0
+ call assert_true(false, 'zero is false')
+ endif
+ if "0"
+ call assert_true(false, 'zero string is false')
+ endif
+ if "foo"
+ call assert_true(false, 'foo is false')
+ endif
+ if " "
+ call assert_true(false, 'space is false')
+ endif
+ if empty("foo")
+ call assert_true(false, 'foo is not empty')
+ endif
+
+ if v:true
+ else
+ call assert_true(false, 'v:true is true')
+ endif
+ if 1
+ else
+ call assert_true(false, 'one is true')
+ endif
+ if "1"
+ else
+ call assert_true(false, 'one string is true')
+ endif
+ if "1foo"
+ else
+ call assert_true(false, 'one in string is true')
+ endif
+
+ call assert_fails('if [1]', 'E745:')
+ call assert_fails('if {1: 1}', 'E728:')
+ call assert_fails('if function("string")', 'E703:')
+ call assert_fails('if 1.3")', 'E805:')
+endfunc
+
+function Try_arg_true_false(expr, false_val, true_val)
+ for v in ['v:false', '0', '"0"', '"foo"', '" "']
+ let r = eval(substitute(a:expr, '%v%', v, ''))
+ call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . string(a:false_val) . ' but ' . string(r))
+ endfor
+ for v in ['v:true', '1', '"1"', '"1foo"']
+ let r = eval(substitute(a:expr, '%v%', v, ''))
+ call assert_equal(a:true_val, r, 'result for ' . v . ' is not ' . string(a:true_val) . ' but ' . string(r))
+ endfor
+endfunc
+
+" Test using TRUE or FALSE values for an argument.
+func Test_true_false_arg()
+ call Try_arg_true_false('count(["a", "A"], "a", %v%)', 1, 2)
+
+ set wildignore=*.swp
+ call Try_arg_true_false('expand("foo.swp", %v%)', "", "foo.swp")
+ call Try_arg_true_false('expand("foo.vim", 0, %v%)', "foo.vim", ["foo.vim"])
+
+ call setreg('a', ['x', 'y'])
+ call Try_arg_true_false('getreg("a", 1, %v%)', "x\ny\n", ['x', 'y'])
+
+ set wildignore=*.vim
+ call Try_arg_true_false('glob("runtest.vim", %v%)', "", "runtest.vim")
+ set wildignore=*.swp
+ call Try_arg_true_false('glob("runtest.vim", 0, %v%)', "runtest.vim", ["runtest.vim"])
+ if has('unix')
+ silent !ln -s doesntexit Xlink
+ call Try_arg_true_false('glob("Xlink", 0, 0, %v%)', "", "Xlink")
+ silent !rm Xlink
+ endif
+
+ set wildignore=*.vim
+ call Try_arg_true_false('globpath(".", "runtest.vim", %v%)', "", "./runtest.vim")
+ set wildignore=*.swp
+ call Try_arg_true_false('globpath(".", "runtest.vim", 0, %v%)', "./runtest.vim", ["./runtest.vim"])
+ if has('unix')
+ silent !ln -s doesntexit Xlink
+ call Try_arg_true_false('globpath(".", "Xlink", 0, 0, %v%)', "", "./Xlink")
+ silent !rm Xlink
+ endif
+
+ abbr asdf asdff
+ call Try_arg_true_false('hasmapto("asdff", "i", %v%)', 0, 1)
+
+ call Try_arg_true_false('index(["a", "A"], "A", 0, %v%)', 1, 0)
+
+ function FilterMapArg(d)
+ if type(a:d) == type({})
+ return filter(a:d, 'v:key == "rhs"')
+ endif
+ return a:d
+ endfunction
+ call Try_arg_true_false('maparg("asdf", "i", %v%)', "", "asdff")
+ call Try_arg_true_false('FilterMapArg(maparg("asdf", "i", 1, %v%))', "asdff", {'rhs': 'asdff'})
+
+ call Try_arg_true_false('"asdf"->hasmapto("i", %v%)', 0, 1)
+
+ new colored
+ call setline(1, '<here>')
+ syn match brackets "<.*>"
+ syn match here "here" transparent
+ let brackets_id = synID(1, 1, 0)
+ let here_id = synID(1, 3, 0)
+ call Try_arg_true_false('synID(1, 3, %v%)', here_id, brackets_id)
+ bwipe!
+endfunc
+
+function Try_arg_non_zero(expr, false_val, true_val)
+ for v in ['v:false', '0', '[1]', '{2:3}', '3.4']
+ let r = eval(substitute(a:expr, '%v%', v, ''))
+ call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . a:false_val . ' but ' . r)
+ endfor
+ for v in ['v:true', '1', '" "', '"0"']
+ let r = eval(substitute(a:expr, '%v%', v, ''))
+ call assert_equal(a:true_val, r, 'result for ' . v . ' is not ' . a:true_val . ' but ' . r)
+ endfor
+endfunc
+
+
+" Test using non-zero-arg for an argument.
+func Test_non_zero_arg()
+ call test_settime(93784)
+ call Try_arg_non_zero("mode(%v%)", 'x', 'x!')
+ call test_settime(0)
+
+ call Try_arg_non_zero("shellescape('foo%', %v%)", "'foo%'", "'foo\\%'")
+
+ " visualmode() needs to be called twice to check
+ for v in [v:false, 0, [1], {2:3}, 3.4]
+ normal vv
+ let r = visualmode(v)
+ call assert_equal('v', r, 'result for ' . string(v) . ' is not "v" but ' . r)
+ let r = visualmode(v)
+ call assert_equal('v', r, 'result for ' . string(v) . ' is not "v" but ' . r)
+ endfor
+ for v in [v:true, 1, " ", "0"]
+ normal vv
+ let r = visualmode(v)
+ call assert_equal('v', r, 'result for ' . v . ' is not "v" but ' . r)
+ let r = visualmode(v)
+ call assert_equal('', r, 'result for ' . v . ' is not "" but ' . r)
+ endfor
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_trycatch.vim b/src/testdir/test_trycatch.vim
new file mode 100644
index 0000000..9d62084
--- /dev/null
+++ b/src/testdir/test_trycatch.vim
@@ -0,0 +1,2351 @@
+" Test try-catch-finally exception handling
+" Most of this was formerly in test49.
+
+source check.vim
+source shared.vim
+import './vim9.vim' as v9
+
+"-------------------------------------------------------------------------------
+" Test environment {{{1
+"-------------------------------------------------------------------------------
+
+com! XpathINIT let g:Xpath = ''
+com! -nargs=1 -bar Xpath let g:Xpath = g:Xpath . <args>
+
+" Test 25: Executing :finally clauses on normal control flow {{{1
+"
+" Control flow in a :try conditional should always fall through to its
+" :finally clause. A :finally clause of a :try conditional inside an
+" inactive conditional should never be executed.
+"-------------------------------------------------------------------------------
+
+func T25_F()
+ let loops = 3
+ while loops > 0
+ Xpath 'a' . loops
+ if loops >= 2
+ try
+ Xpath 'b' . loops
+ if loops == 2
+ try
+ Xpath 'c' . loops
+ finally
+ Xpath 'd' . loops
+ endtry
+ endif
+ finally
+ Xpath 'e' . loops
+ if loops == 2
+ try
+ Xpath 'f' . loops
+ final
+ Xpath 'g' . loops
+ endtry
+ endif
+ endtry
+ endif
+ Xpath 'h' . loops
+ let loops = loops - 1
+ endwhile
+ Xpath 'i'
+endfunc
+
+" Also try using "fina" and "final" and "finall" as abbreviations.
+func T25_G()
+ if 1
+ try
+ Xpath 'A'
+ call T25_F()
+ Xpath 'B'
+ fina
+ Xpath 'C'
+ endtry
+ else
+ try
+ Xpath 'D'
+ finall
+ Xpath 'E'
+ endtry
+ endif
+endfunc
+
+func Test_finally()
+ XpathINIT
+ call T25_G()
+ call assert_equal('Aa3b3e3h3a2b2c2d2e2f2g2h2a1h1iBC', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 26: Executing :finally clauses after :continue or :break {{{1
+"
+" For a :continue or :break dynamically enclosed in a :try/:endtry
+" region inside the next surrounding :while/:endwhile, if the
+" :continue/:break is before the :finally, the :finally clause is
+" executed first. If the :continue/:break is after the :finally, the
+" :finally clause is broken (like an :if/:endif region).
+"-------------------------------------------------------------------------------
+
+func T26_F()
+ try
+ let loops = 3
+ while loops > 0
+ try
+ try
+ if loops == 2
+ Xpath 'a' . loops
+ let loops = loops - 1
+ continue
+ elseif loops == 1
+ Xpath 'b' . loops
+ break
+ finish
+ endif
+ Xpath 'c' . loops
+ endtry
+ finally
+ Xpath 'd' . loops
+ endtry
+ Xpath 'e' . loops
+ let loops = loops - 1
+ endwhile
+ Xpath 'f'
+ finally
+ Xpath 'g'
+ let loops = 3
+ while loops > 0
+ try
+ finally
+ try
+ if loops == 2
+ Xpath 'h' . loops
+ let loops = loops - 1
+ continue
+ elseif loops == 1
+ Xpath 'i' . loops
+ break
+ finish
+ endif
+ endtry
+ Xpath 'j' . loops
+ endtry
+ Xpath 'k' . loops
+ let loops = loops - 1
+ endwhile
+ Xpath 'l'
+ endtry
+ Xpath 'm'
+endfunc
+
+func Test_finally_after_continue()
+ XpathINIT
+ call T26_F()
+ call assert_equal('c3d3e3a2d1b1d1fgj3k3h2i1lm', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 32: Remembering the :return value on :finally {{{1
+"
+" If a :finally clause is executed due to a :return specifying
+" a value, this is the value visible to the caller if not overwritten
+" by a new :return in the :finally clause. A :return without a value
+" in the :finally clause overwrites with value 0.
+"-------------------------------------------------------------------------------
+
+func T32_F()
+ try
+ Xpath 'a'
+ try
+ Xpath 'b'
+ return "ABCD"
+ Xpath 'c'
+ finally
+ Xpath 'd'
+ endtry
+ Xpath 'e'
+ finally
+ Xpath 'f'
+ endtry
+ Xpath 'g'
+endfunc
+
+func T32_G()
+ try
+ Xpath 'h'
+ return 8
+ Xpath 'i'
+ finally
+ Xpath 'j'
+ return 16 + strlen(T32_F())
+ Xpath 'k'
+ endtry
+ Xpath 'l'
+endfunc
+
+func T32_H()
+ try
+ Xpath 'm'
+ return 32
+ Xpath 'n'
+ finally
+ Xpath 'o'
+ return
+ Xpath 'p'
+ endtry
+ Xpath 'q'
+endfunc
+
+func T32_I()
+ try
+ Xpath 'r'
+ finally
+ Xpath 's'
+ return T32_G() + T32_H() + 64
+ Xpath 't'
+ endtry
+ Xpath 'u'
+endfunc
+
+func Test_finally_return()
+ XpathINIT
+ call assert_equal(84, T32_I())
+ call assert_equal('rshjabdfmo', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 33: :return under :execute or user command and :finally {{{1
+"
+" A :return command may be executed under an ":execute" or from
+" a user command. Executing of :finally clauses and passing through
+" the return code works also then.
+"-------------------------------------------------------------------------------
+
+func T33_F()
+ try
+ RETURN 10
+ Xpath 'a'
+ finally
+ Xpath 'b'
+ endtry
+ Xpath 'c'
+endfunc
+
+func T33_G()
+ try
+ RETURN 20
+ Xpath 'd'
+ finally
+ Xpath 'e'
+ RETURN 30
+ Xpath 'f'
+ endtry
+ Xpath 'g'
+endfunc
+
+func T33_H()
+ try
+ execute "try | return 40 | finally | return 50 | endtry"
+ Xpath 'h'
+ finally
+ Xpath 'i'
+ endtry
+ Xpath 'j'
+endfunc
+
+func T33_I()
+ try
+ execute "try | return 60 | finally | return 70 | endtry"
+ Xpath 'k'
+ finally
+ Xpath 'l'
+ execute "try | return 80 | finally | return 90 | endtry"
+ Xpath 'm'
+ endtry
+ Xpath 'n'
+endfunc
+
+func T33_J()
+ try
+ RETURN 100
+ Xpath 'o'
+ finally
+ Xpath 'p'
+ return
+ Xpath 'q'
+ endtry
+ Xpath 'r'
+endfunc
+
+func T33_K()
+ try
+ execute "try | return 110 | finally | return 120 | endtry"
+ Xpath 's'
+ finally
+ Xpath 't'
+ execute "try | return 130 | finally | return | endtry"
+ Xpath 'u'
+ endtry
+ Xpath 'v'
+endfunc
+
+func T33_L()
+ try
+ return
+ Xpath 'w'
+ finally
+ Xpath 'x'
+ RETURN 140
+ Xpath 'y'
+ endtry
+ Xpath 'z'
+endfunc
+
+func T33_M()
+ try
+ return
+ Xpath 'A'
+ finally
+ Xpath 'B'
+ execute "try | return 150 | finally | return 160 | endtry"
+ Xpath 'C'
+ endtry
+ Xpath 'D'
+endfunc
+
+func T33_N()
+ RETURN 170
+endfunc
+
+func T33_O()
+ execute "try | return 180 | finally | return 190 | endtry"
+endfunc
+
+func Test_finally_cmd_return()
+ command! -nargs=? RETURN
+ \ try | return <args> | finally | return <args> * 2 | endtry
+ XpathINIT
+ call assert_equal(20, T33_F())
+ call assert_equal(60, T33_G())
+ call assert_equal(50, T33_H())
+ call assert_equal(90, T33_I())
+ call assert_equal(0, T33_J())
+ call assert_equal(0, T33_K())
+ call assert_equal(280, T33_L())
+ call assert_equal(160, T33_M())
+ call assert_equal(340, T33_N())
+ call assert_equal(190, T33_O())
+ call assert_equal('beilptxB', g:Xpath)
+ delcommand RETURN
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 41: Skipped :throw finding next command {{{1
+"
+" A :throw in an inactive conditional must not hide a following
+" command.
+"-------------------------------------------------------------------------------
+
+func T41_F()
+ Xpath 'a'
+ if 0 | throw 'never' | endif | Xpath 'b'
+ Xpath 'c'
+endfunc
+
+func T41_G()
+ Xpath 'd'
+ while 0 | throw 'never' | endwhile | Xpath 'e'
+ Xpath 'f'
+endfunc
+
+func T41_H()
+ Xpath 'g'
+ if 0 | try | throw 'never' | endtry | endif | Xpath 'h'
+ Xpath 'i'
+endfunc
+
+func Test_throw_inactive_cond()
+ XpathINIT
+ try
+ Xpath 'j'
+ call T41_F()
+ Xpath 'k'
+ catch /.*/
+ Xpath 'l'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ Xpath 'm'
+ call T41_G()
+ Xpath 'n'
+ catch /.*/
+ Xpath 'o'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ Xpath 'p'
+ call T41_H()
+ Xpath 'q'
+ catch /.*/
+ Xpath 'r'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ call assert_equal('jabckmdefnpghiq', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 42: Catching number and string exceptions {{{1
+"
+" When a number is thrown, it is converted to a string exception.
+" Numbers and strings may be caught by specifying a regular exception
+" as argument to the :catch command.
+"-------------------------------------------------------------------------------
+
+
+func T42_F()
+ try
+
+ try
+ Xpath 'a'
+ throw 4711
+ Xpath 'b'
+ catch /4711/
+ Xpath 'c'
+ endtry
+
+ try
+ Xpath 'd'
+ throw 4711
+ Xpath 'e'
+ catch /^4711$/
+ Xpath 'f'
+ endtry
+
+ try
+ Xpath 'g'
+ throw 4711
+ Xpath 'h'
+ catch /\d/
+ Xpath 'i'
+ endtry
+
+ try
+ Xpath 'j'
+ throw 4711
+ Xpath 'k'
+ catch /^\d\+$/
+ Xpath 'l'
+ endtry
+
+ try
+ Xpath 'm'
+ throw "arrgh"
+ Xpath 'n'
+ catch /arrgh/
+ Xpath 'o'
+ endtry
+
+ try
+ Xpath 'p'
+ throw "arrgh"
+ Xpath 'q'
+ catch /^arrgh$/
+ Xpath 'r'
+ endtry
+
+ try
+ Xpath 's'
+ throw "arrgh"
+ Xpath 't'
+ catch /\l/
+ Xpath 'u'
+ endtry
+
+ try
+ Xpath 'v'
+ throw "arrgh"
+ Xpath 'w'
+ catch /^\l\+$/
+ Xpath 'x'
+ endtry
+
+ try
+ try
+ Xpath 'y'
+ throw "ARRGH"
+ Xpath 'z'
+ catch /^arrgh$/
+ Xpath 'A'
+ endtry
+ catch /^\carrgh$/
+ Xpath 'B'
+ endtry
+
+ try
+ Xpath 'C'
+ throw ""
+ Xpath 'D'
+ catch /^$/
+ Xpath 'E'
+ endtry
+
+ catch /.*/
+ Xpath 'F'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+endfunc
+
+func Test_catch_number_string()
+ XpathINIT
+ call T42_F()
+ call assert_equal('acdfgijlmoprsuvxyBCE', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 43: Selecting the correct :catch clause {{{1
+"
+" When an exception is thrown and there are multiple :catch clauses,
+" the first matching one is taken.
+"-------------------------------------------------------------------------------
+
+func T43_F()
+ let loops = 3
+ while loops > 0
+ try
+ if loops == 3
+ Xpath 'a' . loops
+ throw "a"
+ Xpath 'b' . loops
+ elseif loops == 2
+ Xpath 'c' . loops
+ throw "ab"
+ Xpath 'd' . loops
+ elseif loops == 1
+ Xpath 'e' . loops
+ throw "abc"
+ Xpath 'f' . loops
+ endif
+ catch /abc/
+ Xpath 'g' . loops
+ catch /ab/
+ Xpath 'h' . loops
+ catch /.*/
+ Xpath 'i' . loops
+ catch /a/
+ Xpath 'j' . loops
+ endtry
+
+ let loops = loops - 1
+ endwhile
+ Xpath 'k'
+endfunc
+
+func Test_multi_catch()
+ XpathINIT
+ call T43_F()
+ call assert_equal('a3i3c2h2e1g1k', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 44: Missing or empty :catch patterns {{{1
+"
+" A missing or empty :catch pattern means the same as /.*/, that is,
+" catches everything. To catch only empty exceptions, /^$/ must be
+" used. A :catch with missing, empty, or /.*/ argument also works
+" when followed by another command separated by a bar on the same
+" line. :catch patterns cannot be specified between ||. But other
+" pattern separators can be used instead of //.
+"-------------------------------------------------------------------------------
+
+func T44_F()
+ try
+ try
+ Xpath 'a'
+ throw ""
+ catch /^$/
+ Xpath 'b'
+ endtry
+
+ try
+ Xpath 'c'
+ throw ""
+ catch /.*/
+ Xpath 'd'
+ endtry
+
+ try
+ Xpath 'e'
+ throw ""
+ catch //
+ Xpath 'f'
+ endtry
+
+ try
+ Xpath 'g'
+ throw ""
+ catch
+ Xpath 'h'
+ endtry
+
+ try
+ Xpath 'i'
+ throw "oops"
+ catch /^$/
+ Xpath 'j'
+ catch /.*/
+ Xpath 'k'
+ endtry
+
+ try
+ Xpath 'l'
+ throw "arrgh"
+ catch /^$/
+ Xpath 'm'
+ catch //
+ Xpath 'n'
+ endtry
+
+ try
+ Xpath 'o'
+ throw "brrr"
+ catch /^$/
+ Xpath 'p'
+ catch
+ Xpath 'q'
+ endtry
+
+ try | Xpath 'r' | throw "x" | catch /.*/ | Xpath 's' | endtry
+
+ try | Xpath 't' | throw "y" | catch // | Xpath 'u' | endtry
+
+ while 1
+ try
+ let caught = 0
+ let v:errmsg = ""
+ " Extra try level: if ":catch" without arguments below raises
+ " a syntax error because it misinterprets the "Xpath" as a pattern,
+ " let it be caught by the ":catch /.*/" below.
+ try
+ try | Xpath 'v' | throw "z" | catch | Xpath 'w' | :
+ endtry
+ endtry
+ catch /.*/
+ let caught = 1
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ finally
+ if $VIMNOERRTHROW && v:errmsg != ""
+ call assert_report(v:errmsg)
+ endif
+ if caught || $VIMNOERRTHROW && v:errmsg != ""
+ Xpath 'x'
+ endif
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ endwhile
+
+ let cologne = 4711
+ try
+ try
+ Xpath 'y'
+ throw "throw cologne"
+ " Next lines catches all and throws 4711:
+ catch |throw cologne|
+ Xpath 'z'
+ endtry
+ catch /4711/
+ Xpath 'A'
+ endtry
+
+ try
+ Xpath 'B'
+ throw "plus"
+ catch +plus+
+ Xpath 'C'
+ endtry
+
+ Xpath 'D'
+ catch /.*/
+ Xpath 'E'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+endfunc
+
+func Test_empty_catch()
+ XpathINIT
+ call T44_F()
+ call assert_equal('abcdefghiklnoqrstuvwyABCD', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 45: Catching exceptions from nested :try blocks {{{1
+"
+" When :try blocks are nested, an exception is caught by the innermost
+" try conditional that has a matching :catch clause.
+"-------------------------------------------------------------------------------
+
+func T45_F()
+ let loops = 3
+ while loops > 0
+ try
+ try
+ try
+ try
+ if loops == 3
+ Xpath 'a' . loops
+ throw "a"
+ Xpath 'b' . loops
+ elseif loops == 2
+ Xpath 'c' . loops
+ throw "ab"
+ Xpath 'd' . loops
+ elseif loops == 1
+ Xpath 'e' . loops
+ throw "abc"
+ Xpath 'f' . loops
+ endif
+ catch /abc/
+ Xpath 'g' . loops
+ endtry
+ catch /ab/
+ Xpath 'h' . loops
+ endtry
+ catch /.*/
+ Xpath 'i' . loops
+ endtry
+ catch /a/
+ Xpath 'j' . loops
+ endtry
+
+ let loops = loops - 1
+ endwhile
+ Xpath 'k'
+endfunc
+
+func Test_catch_from_nested_try()
+ XpathINIT
+ call T45_F()
+ call assert_equal('a3i3c2h2e1g1k', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 46: Executing :finally after a :throw in nested :try {{{1
+"
+" When an exception is thrown from within nested :try blocks, the
+" :finally clauses of the non-catching try conditionals should be
+" executed before the matching :catch of the next surrounding :try
+" gets the control. If this also has a :finally clause, it is
+" executed afterwards.
+"-------------------------------------------------------------------------------
+
+func T46_F()
+ let sum = 0
+
+ try
+ Xpath 'a'
+ try
+ Xpath 'b'
+ try
+ Xpath 'c'
+ try
+ Xpath 'd'
+ throw "ABC"
+ Xpath 'e'
+ catch /xyz/
+ Xpath 'f'
+ finally
+ Xpath 'g'
+ if sum != 0
+ Xpath 'h'
+ endif
+ let sum = sum + 1
+ endtry
+ Xpath 'i'
+ catch /123/
+ Xpath 'j'
+ catch /321/
+ Xpath 'k'
+ finally
+ Xpath 'l'
+ if sum != 1
+ Xpath 'm'
+ endif
+ let sum = sum + 2
+ endtry
+ Xpath 'n'
+ finally
+ Xpath 'o'
+ if sum != 3
+ Xpath 'p'
+ endif
+ let sum = sum + 4
+ endtry
+ Xpath 'q'
+ catch /ABC/
+ Xpath 'r'
+ if sum != 7
+ Xpath 's'
+ endif
+ let sum = sum + 8
+ finally
+ Xpath 't'
+ if sum != 15
+ Xpath 'u'
+ endif
+ let sum = sum + 16
+ endtry
+ Xpath 'v'
+ if sum != 31
+ Xpath 'w'
+ endif
+endfunc
+
+func Test_finally_after_throw()
+ XpathINIT
+ call T46_F()
+ call assert_equal('abcdglortv', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 47: Throwing exceptions from a :catch clause {{{1
+"
+" When an exception is thrown from a :catch clause, it should not be
+" caught by a :catch of the same :try conditional. After executing
+" the :finally clause (if present), surrounding try conditionals
+" should be checked for a matching :catch.
+"-------------------------------------------------------------------------------
+
+func T47_F()
+ Xpath 'a'
+ try
+ Xpath 'b'
+ try
+ Xpath 'c'
+ try
+ Xpath 'd'
+ throw "x1"
+ Xpath 'e'
+ catch /x1/
+ Xpath 'f'
+ try
+ Xpath 'g'
+ throw "x2"
+ Xpath 'h'
+ catch /x1/
+ Xpath 'i'
+ catch /x2/
+ Xpath 'j'
+ try
+ Xpath 'k'
+ throw "x3"
+ Xpath 'l'
+ catch /x1/
+ Xpath 'm'
+ catch /x2/
+ Xpath 'n'
+ finally
+ Xpath 'o'
+ endtry
+ Xpath 'p'
+ catch /x3/
+ Xpath 'q'
+ endtry
+ Xpath 'r'
+ catch /x1/
+ Xpath 's'
+ catch /x2/
+ Xpath 't'
+ catch /x3/
+ Xpath 'u'
+ finally
+ Xpath 'v'
+ endtry
+ Xpath 'w'
+ catch /x1/
+ Xpath 'x'
+ catch /x2/
+ Xpath 'y'
+ catch /x3/
+ Xpath 'z'
+ endtry
+ Xpath 'A'
+ catch /.*/
+ Xpath 'B'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+ Xpath 'C'
+endfunc
+
+func Test_throw_from_catch()
+ XpathINIT
+ call T47_F()
+ call assert_equal('abcdfgjkovzAC', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 48: Throwing exceptions from a :finally clause {{{1
+"
+" When an exception is thrown from a :finally clause, it should not be
+" caught by a :catch of the same :try conditional. Surrounding try
+" conditionals should be checked for a matching :catch. A previously
+" thrown exception is discarded.
+"-------------------------------------------------------------------------------
+
+func T48_F()
+ try
+
+ try
+ try
+ Xpath 'a'
+ catch /x1/
+ Xpath 'b'
+ finally
+ Xpath 'c'
+ throw "x1"
+ Xpath 'd'
+ endtry
+ Xpath 'e'
+ catch /x1/
+ Xpath 'f'
+ endtry
+ Xpath 'g'
+
+ try
+ try
+ Xpath 'h'
+ throw "x2"
+ Xpath 'i'
+ catch /x2/
+ Xpath 'j'
+ catch /x3/
+ Xpath 'k'
+ finally
+ Xpath 'l'
+ throw "x3"
+ Xpath 'm'
+ endtry
+ Xpath 'n'
+ catch /x2/
+ Xpath 'o'
+ catch /x3/
+ Xpath 'p'
+ endtry
+ Xpath 'q'
+
+ try
+ try
+ try
+ Xpath 'r'
+ throw "x4"
+ Xpath 's'
+ catch /x5/
+ Xpath 't'
+ finally
+ Xpath 'u'
+ throw "x5" " discards 'x4'
+ Xpath 'v'
+ endtry
+ Xpath 'w'
+ catch /x4/
+ Xpath 'x'
+ finally
+ Xpath 'y'
+ endtry
+ Xpath 'z'
+ catch /x5/
+ Xpath 'A'
+ endtry
+ Xpath 'B'
+
+ catch /.*/
+ Xpath 'C'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+ Xpath 'D'
+endfunc
+
+func Test_throw_from_finally()
+ XpathINIT
+ call T48_F()
+ call assert_equal('acfghjlpqruyABD', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 51: Throwing exceptions across :execute and user commands {{{1
+"
+" A :throw command may be executed under an ":execute" or from
+" a user command.
+"-------------------------------------------------------------------------------
+
+func T51_F()
+ command! -nargs=? THROW1 throw <args> | throw 1
+ command! -nargs=? THROW2 try | throw <args> | endtry | throw 2
+ command! -nargs=? THROW3 try | throw 3 | catch /3/ | throw <args> | endtry
+ command! -nargs=? THROW4 try | throw 4 | finally | throw <args> | endtry
+
+ try
+
+ try
+ try
+ Xpath 'a'
+ THROW1 "A"
+ catch /A/
+ Xpath 'b'
+ endtry
+ catch /1/
+ Xpath 'c'
+ endtry
+
+ try
+ try
+ Xpath 'd'
+ THROW2 "B"
+ catch /B/
+ Xpath 'e'
+ endtry
+ catch /2/
+ Xpath 'f'
+ endtry
+
+ try
+ try
+ Xpath 'g'
+ THROW3 "C"
+ catch /C/
+ Xpath 'h'
+ endtry
+ catch /3/
+ Xpath 'i'
+ endtry
+
+ try
+ try
+ Xpath 'j'
+ THROW4 "D"
+ catch /D/
+ Xpath 'k'
+ endtry
+ catch /4/
+ Xpath 'l'
+ endtry
+
+ try
+ try
+ Xpath 'm'
+ execute 'throw "E" | throw 5'
+ catch /E/
+ Xpath 'n'
+ endtry
+ catch /5/
+ Xpath 'o'
+ endtry
+
+ try
+ try
+ Xpath 'p'
+ execute 'try | throw "F" | endtry | throw 6'
+ catch /F/
+ Xpath 'q'
+ endtry
+ catch /6/
+ Xpath 'r'
+ endtry
+
+ try
+ try
+ Xpath 's'
+ execute'try | throw 7 | catch /7/ | throw "G" | endtry'
+ catch /G/
+ Xpath 't'
+ endtry
+ catch /7/
+ Xpath 'u'
+ endtry
+
+ try
+ try
+ Xpath 'v'
+ execute 'try | throw 8 | finally | throw "H" | endtry'
+ catch /H/
+ Xpath 'w'
+ endtry
+ catch /8/
+ Xpath 'x'
+ endtry
+
+ catch /.*/
+ Xpath 'y'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ Xpath 'z'
+
+ delcommand THROW1
+ delcommand THROW2
+ delcommand THROW3
+ delcommand THROW4
+endfunc
+
+func Test_throw_across_commands()
+ XpathINIT
+ call T51_F()
+ call assert_equal('abdeghjkmnpqstvwz', g:Xpath)
+endfunc
+
+
+
+"-------------------------------------------------------------------------------
+" Test 69: :throw across :if, :elseif, :while {{{1
+"
+" On an :if, :elseif, or :while command, an exception might be thrown
+" during evaluation of the expression to test. The exception can be
+" caught by the script.
+"-------------------------------------------------------------------------------
+
+func T69_throw(x)
+ Xpath 'x'
+ throw a:x
+endfunc
+
+func Test_throw_ifelsewhile()
+ XpathINIT
+
+ try
+ try
+ Xpath 'a'
+ if 111 == T69_throw("if") + 111
+ Xpath 'b'
+ else
+ Xpath 'c'
+ endif
+ Xpath 'd'
+ catch /^if$/
+ Xpath 'e'
+ catch /.*/
+ Xpath 'f'
+ call assert_report("if: " . v:exception . " in " . v:throwpoint)
+ endtry
+
+ try
+ Xpath 'g'
+ if v:false
+ Xpath 'h'
+ elseif 222 == T69_throw("elseif") + 222
+ Xpath 'i'
+ else
+ Xpath 'j'
+ endif
+ Xpath 'k'
+ catch /^elseif$/
+ Xpath 'l'
+ catch /.*/
+ Xpath 'm'
+ call assert_report("elseif: " . v:exception . " in " . v:throwpoint)
+ endtry
+
+ try
+ Xpath 'n'
+ while 333 == T69_throw("while") + 333
+ Xpath 'o'
+ break
+ endwhile
+ Xpath 'p'
+ catch /^while$/
+ Xpath 'q'
+ catch /.*/
+ Xpath 'r'
+ call assert_report("while: " .. v:exception .. " in " .. v:throwpoint)
+ endtry
+ catch /^0$/ " default return value
+ Xpath 's'
+ call assert_report(v:throwpoint)
+ catch /.*/
+ call assert_report(v:exception .. " in " .. v:throwpoint)
+ Xpath 't'
+ endtry
+
+ call assert_equal('axegxlnxq', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 70: :throw across :return or :throw {{{1
+"
+" On a :return or :throw command, an exception might be thrown during
+" evaluation of the expression to return or throw, respectively. The
+" exception can be caught by the script.
+"-------------------------------------------------------------------------------
+
+let T70_taken = ""
+
+func T70_throw(x, n)
+ let g:T70_taken = g:T70_taken . "T" . a:n
+ throw a:x
+endfunc
+
+func T70_F(x, y, n)
+ let g:T70_taken = g:T70_taken . "F" . a:n
+ return a:x + T70_throw(a:y, a:n)
+endfunc
+
+func T70_G(x, y, n)
+ let g:T70_taken = g:T70_taken . "G" . a:n
+ throw a:x . T70_throw(a:y, a:n)
+ return a:x
+endfunc
+
+func Test_throwreturn()
+ XpathINIT
+
+ try
+ try
+ Xpath 'a'
+ call T70_F(4711, "return", 1)
+ Xpath 'b'
+ catch /^return$/
+ Xpath 'c'
+ catch /.*/
+ Xpath 'd'
+ call assert_report("return: " .. v:exception .. " in " .. v:throwpoint)
+ endtry
+
+ try
+ Xpath 'e'
+ let var = T70_F(4712, "return-var", 2)
+ Xpath 'f'
+ catch /^return-var$/
+ Xpath 'g'
+ catch /.*/
+ Xpath 'h'
+ call assert_report("return-var: " . v:exception . " in " . v:throwpoint)
+ finally
+ unlet! var
+ endtry
+
+ try
+ Xpath 'i'
+ throw "except1" . T70_throw("throw1", 3)
+ Xpath 'j'
+ catch /^except1/
+ Xpath 'k'
+ catch /^throw1$/
+ Xpath 'l'
+ catch /.*/
+ Xpath 'm'
+ call assert_report("throw1: " .. v:exception .. " in " .. v:throwpoint)
+ endtry
+
+ try
+ Xpath 'n'
+ call T70_G("except2", "throw2", 4)
+ Xpath 'o'
+ catch /^except2/
+ Xpath 'p'
+ catch /^throw2$/
+ Xpath 'q'
+ catch /.*/
+ Xpath 'r'
+ call assert_report("throw2: " .. v:exception .. " in " .. v:throwpoint)
+ endtry
+
+ try
+ Xpath 's'
+ let var = T70_G("except3", "throw3", 5)
+ Xpath 't'
+ catch /^except3/
+ Xpath 'u'
+ catch /^throw3$/
+ Xpath 'v'
+ catch /.*/
+ Xpath 'w'
+ call assert_report("throw3: " .. v:exception .. " in " .. v:throwpoint)
+ finally
+ unlet! var
+ endtry
+
+ call assert_equal('F1T1F2T2T3G4T4G5T5', g:T70_taken)
+ Xpath 'x'
+ catch /^0$/ " default return value
+ Xpath 'y'
+ call assert_report(v:throwpoint)
+ catch /.*/
+ Xpath 'z'
+ call assert_report('Caught' .. v:exception .. ' in ' .. v:throwpoint)
+ endtry
+
+ call assert_equal('acegilnqsvx', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 71: :throw across :echo variants and :execute {{{1
+"
+" On an :echo, :echon, :echomsg, :echoerr, or :execute command, an
+" exception might be thrown during evaluation of the arguments to
+" be displayed or executed as a command, respectively. Any following
+" arguments are not evaluated, then. The exception can be caught by
+" the script.
+"-------------------------------------------------------------------------------
+
+let T71_taken = ""
+
+func T71_throw(x, n)
+ let g:T71_taken = g:T71_taken . "T" . a:n
+ throw a:x
+endfunc
+
+func T71_F(n)
+ let g:T71_taken = g:T71_taken . "F" . a:n
+ return "F" . a:n
+endfunc
+
+func Test_throw_echo()
+ XpathINIT
+
+ try
+ try
+ Xpath 'a'
+ echo 'echo ' . T71_throw("echo-except", 1) . T71_F(1)
+ Xpath 'b'
+ catch /^echo-except$/
+ Xpath 'c'
+ catch /.*/
+ Xpath 'd'
+ call assert_report("echo: " .. v:exception .. " in " .. v:throwpoint)
+ endtry
+
+ try
+ Xpath 'e'
+ echon "echon " . T71_throw("echon-except", 2) . T71_F(2)
+ Xpath 'f'
+ catch /^echon-except$/
+ Xpath 'g'
+ catch /.*/
+ Xpath 'h'
+ call assert_report('echon: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ Xpath 'i'
+ echomsg "echomsg " . T71_throw("echomsg-except", 3) . T71_F(3)
+ Xpath 'j'
+ catch /^echomsg-except$/
+ Xpath 'k'
+ catch /.*/
+ Xpath 'l'
+ call assert_report('echomsg: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ Xpath 'm'
+ echoerr "echoerr " . T71_throw("echoerr-except", 4) . T71_F(4)
+ Xpath 'n'
+ catch /^echoerr-except$/
+ Xpath 'o'
+ catch /Vim/
+ Xpath 'p'
+ catch /echoerr/
+ Xpath 'q'
+ catch /.*/
+ Xpath 'r'
+ call assert_report('echoerr: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ Xpath 's'
+ execute "echo 'execute " . T71_throw("execute-except", 5) . T71_F(5) "'"
+ Xpath 't'
+ catch /^execute-except$/
+ Xpath 'u'
+ catch /.*/
+ Xpath 'v'
+ call assert_report('execute: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ call assert_equal('T1T2T3T4T5', g:T71_taken)
+ Xpath 'w'
+ catch /^0$/ " default return value
+ Xpath 'x'
+ call assert_report(v:throwpoint)
+ catch /.*/
+ Xpath 'y'
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ call assert_equal('acegikmosuw', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 72: :throw across :let or :unlet {{{1
+"
+" On a :let command, an exception might be thrown during evaluation
+" of the expression to assign. On an :let or :unlet command, the
+" evaluation of the name of the variable to be assigned or list or
+" deleted, respectively, may throw an exception. Any following
+" arguments are not evaluated, then. The exception can be caught by
+" the script.
+"-------------------------------------------------------------------------------
+
+let throwcount = 0
+
+func T72_throw(x)
+ let g:throwcount = g:throwcount + 1
+ throw a:x
+endfunc
+
+let T72_addpath = ''
+
+func T72_addpath(p)
+ let g:T72_addpath = g:T72_addpath . a:p
+endfunc
+
+func Test_throw_let()
+ XpathINIT
+
+ try
+ try
+ let $VAR = 'old_value'
+ Xpath 'a'
+ let $VAR = 'let(' . T72_throw('var') . ')'
+ Xpath 'b'
+ catch /^var$/
+ Xpath 'c'
+ finally
+ call assert_equal('old_value', $VAR)
+ endtry
+
+ try
+ let @a = 'old_value'
+ Xpath 'd'
+ let @a = 'let(' . T72_throw('reg') . ')'
+ Xpath 'e'
+ catch /^reg$/
+ try
+ Xpath 'f'
+ let @A = 'let(' . T72_throw('REG') . ')'
+ Xpath 'g'
+ catch /^REG$/
+ Xpath 'h'
+ endtry
+ finally
+ call assert_equal('old_value', @a)
+ call assert_equal('old_value', @A)
+ endtry
+
+ try
+ let saved_gpath = &g:path
+ let saved_lpath = &l:path
+ Xpath 'i'
+ let &path = 'let(' . T72_throw('opt') . ')'
+ Xpath 'j'
+ catch /^opt$/
+ try
+ Xpath 'k'
+ let &g:path = 'let(' . T72_throw('gopt') . ')'
+ Xpath 'l'
+ catch /^gopt$/
+ try
+ Xpath 'm'
+ let &l:path = 'let(' . T72_throw('lopt') . ')'
+ Xpath 'n'
+ catch /^lopt$/
+ Xpath 'o'
+ endtry
+ endtry
+ finally
+ call assert_equal(saved_gpath, &g:path)
+ call assert_equal(saved_lpath, &l:path)
+ let &g:path = saved_gpath
+ let &l:path = saved_lpath
+ endtry
+
+ unlet! var1 var2 var3
+
+ try
+ Xpath 'p'
+ let var1 = 'let(' . T72_throw('var1') . ')'
+ Xpath 'q'
+ catch /^var1$/
+ Xpath 'r'
+ finally
+ call assert_true(!exists('var1'))
+ endtry
+
+ try
+ let var2 = 'old_value'
+ Xpath 's'
+ let var2 = 'let(' . T72_throw('var2'). ')'
+ Xpath 't'
+ catch /^var2$/
+ Xpath 'u'
+ finally
+ call assert_equal('old_value', var2)
+ endtry
+
+ try
+ Xpath 'v'
+ let var{T72_throw('var3')} = 4711
+ Xpath 'w'
+ catch /^var3$/
+ Xpath 'x'
+ endtry
+
+ try
+ call T72_addpath('T1')
+ let var{T72_throw('var4')} var{T72_addpath('T2')} | call T72_addpath('T3')
+ call T72_addpath('T4')
+ catch /^var4$/
+ call T72_addpath('T5')
+ endtry
+
+ try
+ call T72_addpath('T6')
+ unlet var{T72_throw('var5')} var{T72_addpath('T7')}
+ \ | call T72_addpath('T8')
+ call T72_addpath('T9')
+ catch /^var5$/
+ call T72_addpath('T10')
+ endtry
+
+ call assert_equal('T1T5T6T10', g:T72_addpath)
+ call assert_equal(11, g:throwcount)
+ catch /.*/
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ call assert_equal('acdfhikmoprsuvx', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 73: :throw across :function, :delfunction {{{1
+"
+" The :function and :delfunction commands may cause an expression
+" specified in braces to be evaluated. During evaluation, an
+" exception might be thrown. The exception can be caught by the
+" script.
+"-------------------------------------------------------------------------------
+
+let T73_taken = ''
+
+func T73_throw(x, n)
+ let g:T73_taken = g:T73_taken . 'T' . a:n
+ throw a:x
+endfunc
+
+func T73_expr(x, n)
+ let g:T73_taken = g:T73_taken . 'E' . a:n
+ if a:n % 2 == 0
+ call T73_throw(a:x, a:n)
+ endif
+ return 2 - a:n % 2
+endfunc
+
+func Test_throw_func()
+ XpathINIT
+
+ try
+ try
+ " Define function.
+ Xpath 'a'
+ function! F0()
+ endfunction
+ Xpath 'b'
+ function! F{T73_expr('function-def-ok', 1)}()
+ endfunction
+ Xpath 'c'
+ function! F{T73_expr('function-def', 2)}()
+ endfunction
+ Xpath 'd'
+ catch /^function-def-ok$/
+ Xpath 'e'
+ catch /^function-def$/
+ Xpath 'f'
+ catch /.*/
+ call assert_report('def: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ " List function.
+ Xpath 'g'
+ function F0
+ Xpath 'h'
+ function F{T73_expr('function-lst-ok', 3)}
+ Xpath 'i'
+ function F{T73_expr('function-lst', 4)}
+ Xpath 'j'
+ catch /^function-lst-ok$/
+ Xpath 'k'
+ catch /^function-lst$/
+ Xpath 'l'
+ catch /.*/
+ call assert_report('lst: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ " Delete function
+ Xpath 'm'
+ delfunction F0
+ Xpath 'n'
+ delfunction F{T73_expr('function-del-ok', 5)}
+ Xpath 'o'
+ delfunction F{T73_expr('function-del', 6)}
+ Xpath 'p'
+ catch /^function-del-ok$/
+ Xpath 'q'
+ catch /^function-del$/
+ Xpath 'r'
+ catch /.*/
+ call assert_report('del: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+ call assert_equal('E1E2T2E3E4T4E5E6T6', g:T73_taken)
+ catch /.*/
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ call assert_equal('abcfghilmnor', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 74: :throw across builtin functions and commands {{{1
+"
+" Some functions like exists(), searchpair() take expression
+" arguments, other functions or commands like substitute() or
+" :substitute cause an expression (specified in the regular
+" expression) to be evaluated. During evaluation an exception
+" might be thrown. The exception can be caught by the script.
+"-------------------------------------------------------------------------------
+
+let T74_taken = ""
+
+func T74_throw(x, n)
+ let g:T74_taken = g:T74_taken . "T" . a:n
+ throw a:x
+endfunc
+
+func T74_expr(x, n)
+ let g:T74_taken = g:T74_taken . "E" . a:n
+ call T74_throw(a:x . a:n, a:n)
+ return "EXPR"
+endfunc
+
+func T74_skip(x, n)
+ let g:T74_taken = g:T74_taken . "S" . a:n . "(" . line(".")
+ let theline = getline(".")
+ if theline =~ "skip"
+ let g:T74_taken = g:T74_taken . "s)"
+ return 1
+ elseif theline =~ "throw"
+ let g:T74_taken = g:T74_taken . "t)"
+ call T74_throw(a:x . a:n, a:n)
+ else
+ let g:T74_taken = g:T74_taken . ")"
+ return 0
+ endif
+endfunc
+
+func T74_subst(x, n)
+ let g:T74_taken = g:T74_taken . "U" . a:n . "(" . line(".")
+ let theline = getline(".")
+ if theline =~ "not" " T74_subst() should not be called for this line
+ let g:T74_taken = g:T74_taken . "n)"
+ call T74_throw(a:x . a:n, a:n)
+ elseif theline =~ "throw"
+ let g:T74_taken = g:T74_taken . "t)"
+ call T74_throw(a:x . a:n, a:n)
+ else
+ let g:T74_taken = g:T74_taken . ")"
+ return "replaced"
+ endif
+endfunc
+
+func Test_throw_builtin_func()
+ XpathINIT
+
+ try
+ try
+ Xpath 'a'
+ let result = exists('*{T74_expr("exists", 1)}')
+ Xpath 'b'
+ catch /^exists1$/
+ Xpath 'c'
+ try
+ let result = exists('{T74_expr("exists", 2)}')
+ Xpath 'd'
+ catch /^exists2$/
+ Xpath 'e'
+ catch /.*/
+ call assert_report('exists2: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+ catch /.*/
+ call assert_report('exists1: ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ try
+ let file = tempname()
+ exec "edit" file
+ call append(0, [
+ \ 'begin',
+ \ 'xx',
+ \ 'middle 3',
+ \ 'xx',
+ \ 'middle 5 skip',
+ \ 'xx',
+ \ 'middle 7 throw',
+ \ 'xx',
+ \ 'end'])
+ normal! gg
+ Xpath 'f'
+ let result = searchpair("begin", "middle", "end", '',
+ \ 'T74_skip("searchpair", 3)')
+ Xpath 'g'
+ let result = searchpair("begin", "middle", "end", '',
+ \ 'T74_skip("searchpair", 4)')
+ Xpath 'h'
+ let result = searchpair("begin", "middle", "end", '',
+ \ 'T74_skip("searchpair", 5)')
+ Xpath 'i'
+ catch /^searchpair[35]$/
+ Xpath 'j'
+ catch /^searchpair4$/
+ Xpath 'k'
+ catch /.*/
+ call assert_report('searchpair: ' . v:exception . ' in ' . v:throwpoint)
+ finally
+ bwipeout!
+ call delete(file)
+ endtry
+
+ try
+ let file = tempname()
+ exec "edit" file
+ call append(0, [
+ \ 'subst 1',
+ \ 'subst 2',
+ \ 'not',
+ \ 'subst 4',
+ \ 'subst throw',
+ \ 'subst 6'])
+ normal! gg
+ Xpath 'l'
+ 1,2substitute/subst/\=T74_subst("substitute", 6)/
+ try
+ Xpath 'm'
+ try
+ let v:errmsg = ""
+ 3substitute/subst/\=T74_subst("substitute", 7)/
+ finally
+ if v:errmsg != ""
+ " If exceptions are not thrown on errors, fake the error
+ " exception in order to get the same execution path.
+ throw "faked Vim(substitute)"
+ endif
+ endtry
+ catch /Vim(substitute)/ " Pattern not found ('e' flag missing)
+ Xpath 'n'
+ 3substitute/subst/\=T74_subst("substitute", 8)/e
+ Xpath 'o'
+ endtry
+ Xpath 'p'
+ 4,6substitute/subst/\=T74_subst("substitute", 9)/
+ Xpath 'q'
+ catch /^substitute[678]/
+ Xpath 'r'
+ catch /^substitute9/
+ Xpath 's'
+ finally
+ bwipeout!
+ call delete(file)
+ endtry
+
+ try
+ Xpath 't'
+ let var = substitute("sub", "sub", '\=T74_throw("substitute()y", 10)', '')
+ Xpath 'u'
+ catch /substitute()y/
+ Xpath 'v'
+ catch /.*/
+ call assert_report('substitute()y: ' . v:exception . ' in '
+ \ . v:throwpoint)
+ endtry
+
+ try
+ Xpath 'w'
+ let var = substitute("not", "sub", '\=T74_throw("substitute()n", 11)', '')
+ Xpath 'x'
+ catch /substitute()n/
+ Xpath 'y'
+ catch /.*/
+ call assert_report('substitute()n: ' . v:exception . ' in '
+ \ . v:throwpoint)
+ endtry
+
+ call assert_equal('E1T1E2T2S3(3)S4(5s)S4(7t)T4U6(1)U6(2)U9(4)U9(5t)T9T10',
+ \ g:T74_taken)
+
+ catch /.*/
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ endtry
+
+ call assert_equal('acefgklmnopstvwx', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 75: Errors in builtin functions. {{{1
+"
+" On an error in a builtin function called inside a :try/:endtry
+" region, the evaluation of the expression calling that function and
+" the command containing that expression are abandoned. The error can
+" be caught as an exception.
+"
+" A simple :call of the builtin function is a trivial case. If the
+" builtin function is called in the argument list of another function,
+" no further arguments are evaluated, and the other function is not
+" executed. If the builtin function is called from the argument of
+" a :return command, the :return command is not executed. If the
+" builtin function is called from the argument of a :throw command,
+" the :throw command is not executed. The evaluation of the
+" expression calling the builtin function is abandoned.
+"-------------------------------------------------------------------------------
+
+func T75_F1(arg1)
+ Xpath 'a'
+endfunc
+
+func T75_F2(arg1, arg2)
+ Xpath 'b'
+endfunc
+
+func T75_G()
+ Xpath 'c'
+endfunc
+
+func T75_H()
+ Xpath 'd'
+endfunc
+
+func T75_R()
+ while 1
+ try
+ let caught = 0
+ let v:errmsg = ""
+ Xpath 'e'
+ return append(1, "s")
+ catch /E21/
+ let caught = 1
+ catch /.*/
+ Xpath 'f'
+ finally
+ Xpath 'g'
+ if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
+ Xpath 'h'
+ endif
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ endwhile
+ Xpath 'i'
+endfunc
+
+func Test_builtin_func_error()
+ XpathINIT
+
+ try
+ set noma " let append() fail with "E21"
+
+ while 1
+ try
+ let caught = 0
+ let v:errmsg = ""
+ Xpath 'j'
+ call append(1, "s")
+ catch /E21/
+ let caught = 1
+ catch /.*/
+ Xpath 'k'
+ finally
+ Xpath 'l'
+ if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
+ Xpath 'm'
+ endif
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ endwhile
+
+ while 1
+ try
+ let caught = 0
+ let v:errmsg = ""
+ Xpath 'n'
+ call T75_F1('x' . append(1, "s"))
+ catch /E21/
+ let caught = 1
+ catch /.*/
+ Xpath 'o'
+ finally
+ Xpath 'p'
+ if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
+ Xpath 'q'
+ endif
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ endwhile
+
+ while 1
+ try
+ let caught = 0
+ let v:errmsg = ""
+ Xpath 'r'
+ call T75_F2('x' . append(1, "s"), T75_G())
+ catch /E21/
+ let caught = 1
+ catch /.*/
+ Xpath 's'
+ finally
+ Xpath 't'
+ if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
+ Xpath 'u'
+ endif
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ endwhile
+
+ call T75_R()
+
+ while 1
+ try
+ let caught = 0
+ let v:errmsg = ""
+ Xpath 'v'
+ throw "T" . append(1, "s")
+ catch /E21/
+ let caught = 1
+ catch /^T.*/
+ Xpath 'w'
+ catch /.*/
+ Xpath 'x'
+ finally
+ Xpath 'y'
+ if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
+ Xpath 'z'
+ endif
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ endwhile
+
+ while 1
+ try
+ let caught = 0
+ let v:errmsg = ""
+ Xpath 'A'
+ let x = "a"
+ let x = x . "b" . append(1, "s") . T75_H()
+ catch /E21/
+ let caught = 1
+ catch /.*/
+ Xpath 'B'
+ finally
+ Xpath 'C'
+ if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21'
+ Xpath 'D'
+ endif
+ call assert_equal('a', x)
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ endwhile
+ catch /.*/
+ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint)
+ finally
+ set ma&
+ endtry
+
+ call assert_equal('jlmnpqrtueghivyzACD', g:Xpath)
+endfunc
+
+func Test_reload_in_try_catch()
+ call writefile(['x'], 'Xreload', 'D')
+ set autoread
+ edit Xreload
+ tabnew
+ call writefile(['xx'], 'Xreload')
+ augroup ReLoad
+ au FileReadPost Xreload let x = doesnotexist
+ au BufReadPost Xreload let x = doesnotexist
+ augroup END
+ try
+ edit Xreload
+ catch
+ endtry
+ tabnew
+
+ tabclose
+ tabclose
+ autocmd! ReLoad
+ set noautoread
+ bwipe! Xreload
+endfunc
+
+" Test for errors with :catch, :throw, :finally {{{1
+func Test_try_catch_errors()
+ call assert_fails('throw |', 'E471:')
+ call assert_fails("throw \n ", 'E471:')
+ call assert_fails('catch abc', 'E654:')
+ call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:')
+ call assert_fails('finally', 'E606:')
+ call assert_fails('try | finally | finally | endtry', 'E607:')
+ call assert_fails('try | for i in range(5) | endif | endtry', 'E580:')
+ call assert_fails('try | while v:true | endtry', 'E170:')
+ call assert_fails('try | if v:true | endtry', 'E171:')
+
+ " this was using a negative index in cstack[]
+ let lines =<< trim END
+ try
+ for
+ if
+ endwhile
+ if
+ finally
+ END
+ call v9.CheckScriptFailure(lines, 'E690:')
+
+ let lines =<< trim END
+ try
+ for
+ if
+ endwhile
+ if
+ endtry
+ END
+ call v9.CheckScriptFailure(lines, 'E690:')
+endfunc
+
+" Test for verbose messages with :try :catch, and :finally {{{1
+func Test_try_catch_verbose()
+ " This test works only when the language is English
+ CheckEnglish
+
+ set verbose=14
+
+ " Test for verbose messages displayed when an exception is caught
+ redir => msg
+ try
+ echo i
+ catch /E121:/
+ finally
+ endtry
+ redir END
+ let expected = [
+ \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', '',
+ \ 'Exception caught: Vim(echo):E121: Undefined variable: i', '',
+ \ 'Exception finished: Vim(echo):E121: Undefined variable: i']
+ call assert_equal(expected, split(msg, "\n"))
+
+ " Test for verbose messages displayed when an exception is discarded
+ redir => msg
+ try
+ try
+ throw 'abc'
+ finally
+ throw 'xyz'
+ endtry
+ catch
+ endtry
+ redir END
+ let expected = [
+ \ 'Exception thrown: abc', '',
+ \ 'Exception made pending: abc', '',
+ \ 'Exception thrown: xyz', '',
+ \ 'Exception discarded: abc', '',
+ \ 'Exception caught: xyz', '',
+ \ 'Exception finished: xyz']
+ call assert_equal(expected, split(msg, "\n"))
+
+ " Test for messages displayed when :throw is resumed after :finally
+ redir => msg
+ try
+ try
+ throw 'abc'
+ finally
+ endtry
+ catch
+ endtry
+ redir END
+ let expected = [
+ \ 'Exception thrown: abc', '',
+ \ 'Exception made pending: abc', '',
+ \ 'Exception resumed: abc', '',
+ \ 'Exception caught: abc', '',
+ \ 'Exception finished: abc']
+ call assert_equal(expected, split(msg, "\n"))
+
+ " Test for messages displayed when :break is resumed after :finally
+ redir => msg
+ for i in range(1)
+ try
+ break
+ finally
+ endtry
+ endfor
+ redir END
+ let expected = [':break made pending', '', ':break resumed']
+ call assert_equal(expected, split(msg, "\n"))
+
+ " Test for messages displayed when :continue is resumed after :finally
+ redir => msg
+ for i in range(1)
+ try
+ continue
+ finally
+ endtry
+ endfor
+ redir END
+ let expected = [':continue made pending', '', ':continue resumed']
+ call assert_equal(expected, split(msg, "\n"))
+
+ " Test for messages displayed when :return is resumed after :finally
+ func Xtest()
+ try
+ return 'vim'
+ finally
+ endtry
+ endfunc
+ redir => msg
+ call Xtest()
+ redir END
+ let expected = [
+ \ 'calling Xtest()', '',
+ \ ':return vim made pending', '',
+ \ ':return vim resumed', '',
+ \ 'Xtest returning ''vim''', '',
+ \ 'continuing in Test_try_catch_verbose']
+ call assert_equal(expected, split(msg, "\n"))
+ delfunc Xtest
+
+ " Test for messages displayed when :finish is resumed after :finally
+ call writefile(['try', 'finish', 'finally', 'endtry'], 'Xscript')
+ redir => msg
+ source Xscript
+ redir END
+ let expected = [
+ \ ':finish made pending', '',
+ \ ':finish resumed', '',
+ \ 'finished sourcing Xscript',
+ \ 'continuing in Test_try_catch_verbose']
+ call assert_equal(expected, split(msg, "\n")[1:])
+ call delete('Xscript')
+
+ " Test for messages displayed when a pending :continue is discarded by an
+ " exception in a finally handler
+ redir => msg
+ try
+ for i in range(1)
+ try
+ continue
+ finally
+ throw 'abc'
+ endtry
+ endfor
+ catch
+ endtry
+ redir END
+ let expected = [
+ \ ':continue made pending', '',
+ \ 'Exception thrown: abc', '',
+ \ ':continue discarded', '',
+ \ 'Exception caught: abc', '',
+ \ 'Exception finished: abc']
+ call assert_equal(expected, split(msg, "\n"))
+
+ set verbose&
+endfunc
+
+" Test for throwing an exception from a BufEnter autocmd {{{1
+func Test_BufEnter_exception()
+ augroup bufenter_exception
+ au!
+ autocmd BufEnter Xfile1 throw 'abc'
+ augroup END
+
+ let caught_abc = 0
+ try
+ sp Xfile1
+ catch /^abc/
+ let caught_abc = 1
+ endtry
+ call assert_equal(1, caught_abc)
+ call assert_equal(1, winnr('$'))
+
+ augroup bufenter_exception
+ au!
+ augroup END
+ augroup! bufenter_exception
+ %bwipe!
+
+ " Test for recursively throwing exceptions in autocmds
+ augroup bufenter_exception
+ au!
+ autocmd BufEnter Xfile1 throw 'bufenter'
+ autocmd BufLeave Xfile1 throw 'bufleave'
+ augroup END
+
+ let ex_count = 0
+ try
+ try
+ sp Xfile1
+ catch /^bufenter/
+ let ex_count += 1
+ endtry
+ catch /^bufleave/
+ let ex_count += 10
+ endtry
+ call assert_equal(10, ex_count)
+ call assert_equal(2, winnr('$'))
+
+ augroup bufenter_exception
+ au!
+ augroup END
+ augroup! bufenter_exception
+ %bwipe!
+endfunc
+
+" Test for using try/catch in a user command with a failing expression {{{1
+func Test_user_command_try_catch()
+ let lines =<< trim END
+ function s:throw() abort
+ throw 'error'
+ endfunction
+
+ command! Execute
+ \ try
+ \ | let s:x = s:throw()
+ \ | catch
+ \ | let g:caught = 'caught'
+ \ | endtry
+
+ let g:caught = 'no'
+ Execute
+ call assert_equal('caught', g:caught)
+ END
+ call writefile(lines, 'XtestTryCatch')
+ source XtestTryCatch
+
+ call delete('XtestTryCatch')
+ unlet g:caught
+endfunc
+
+" Test for using throw in a called function with following error {{{1
+func Test_user_command_throw_in_function_call()
+ let lines =<< trim END
+ function s:get_dict() abort
+ throw 'my_error'
+ endfunction
+
+ try
+ call s:get_dict().foo()
+ catch /my_error/
+ let caught = 'yes'
+ catch
+ let caught = v:exception
+ endtry
+ call assert_equal('yes', caught)
+ END
+ call writefile(lines, 'XtestThrow')
+ source XtestThrow
+
+ call delete('XtestThrow')
+ unlet g:caught
+endfunc
+
+" Test that after reporting an uncaught exception there is no error for a
+" missing :endif
+func Test_after_exception_no_endif_error()
+ function Throw()
+ throw "Failure"
+ endfunction
+
+ function Foo()
+ if 1
+ call Throw()
+ endif
+ endfunction
+ call assert_fails('call Foo()', ['E605:', 'E605:'])
+ delfunc Throw
+ delfunc Foo
+endfunc
+
+" Test for using throw in a called function with following endtry {{{1
+func Test_user_command_function_call_with_endtry()
+ let lines =<< trim END
+ funct s:throw(msg) abort
+ throw a:msg
+ endfunc
+ func s:main() abort
+ try
+ try
+ throw 'err1'
+ catch
+ call s:throw('err2') | endtry
+ catch
+ let s:caught = 'yes'
+ endtry
+ endfunc
+
+ call s:main()
+ call assert_equal('yes', s:caught)
+ END
+ call writefile(lines, 'XtestThrow', 'D')
+ source XtestThrow
+endfunc
+
+func ThisWillFail()
+
+endfunc
+
+" This was crashing prior to the fix in 8.2.3478.
+func Test_error_in_catch_and_finally()
+ let lines =<< trim END
+ try
+ echo x
+ catch
+ for l in []
+ finally
+ END
+ call writefile(lines, 'XtestCatchAndFinally', 'D')
+ try
+ source XtestCatchAndFinally
+ catch /E600:/
+ endtry
+endfunc
+
+" This was causing an illegal memory access
+func Test_leave_block_in_endtry_not_called()
+ let lines =<< trim END
+ vim9script
+ try #
+ for x in []
+ if
+ endwhile
+ if
+ endtry
+ END
+ call writefile(lines, 'XtestEndtry', 'D')
+ try
+ source XtestEndtry
+ catch /E171:/
+ endtry
+endfunc
+
+" Modeline {{{1
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_undo.vim b/src/testdir/test_undo.vim
new file mode 100644
index 0000000..2529e21
--- /dev/null
+++ b/src/testdir/test_undo.vim
@@ -0,0 +1,807 @@
+" Tests for the undo tree.
+" Since this script is sourced we need to explicitly break changes up in
+" undo-able pieces. Do that by setting 'undolevels'.
+" Also tests :earlier and :later.
+
+source check.vim
+source screendump.vim
+
+func Test_undotree()
+ new
+
+ normal! Aabc
+ set ul=100
+ let d = undotree()
+ call assert_equal(1, d.seq_last)
+ call assert_equal(1, d.seq_cur)
+ call assert_equal(0, d.save_last)
+ call assert_equal(0, d.save_cur)
+ call assert_equal(1, len(d.entries))
+ call assert_equal(1, d.entries[0].newhead)
+ call assert_equal(1, d.entries[0].seq)
+ call assert_true(d.entries[0].time <= d.time_cur)
+
+ normal! Adef
+ set ul=100
+ let d = undotree()
+ call assert_equal(2, d.seq_last)
+ call assert_equal(2, d.seq_cur)
+ call assert_equal(0, d.save_last)
+ call assert_equal(0, d.save_cur)
+ call assert_equal(2, len(d.entries))
+ call assert_equal(1, d.entries[0].seq)
+ call assert_equal(1, d.entries[1].newhead)
+ call assert_equal(2, d.entries[1].seq)
+ call assert_true(d.entries[1].time <= d.time_cur)
+
+ undo
+ set ul=100
+ let d = undotree()
+ call assert_equal(2, d.seq_last)
+ call assert_equal(1, d.seq_cur)
+ call assert_equal(0, d.save_last)
+ call assert_equal(0, d.save_cur)
+ call assert_equal(2, len(d.entries))
+ call assert_equal(1, d.entries[0].seq)
+ call assert_equal(1, d.entries[1].curhead)
+ call assert_equal(1, d.entries[1].newhead)
+ call assert_equal(2, d.entries[1].seq)
+ call assert_true(d.entries[1].time == d.time_cur)
+
+ normal! Aghi
+ set ul=100
+ let d = undotree()
+ call assert_equal(3, d.seq_last)
+ call assert_equal(3, d.seq_cur)
+ call assert_equal(0, d.save_last)
+ call assert_equal(0, d.save_cur)
+ call assert_equal(2, len(d.entries))
+ call assert_equal(1, d.entries[0].seq)
+ call assert_equal(2, d.entries[1].alt[0].seq)
+ call assert_equal(1, d.entries[1].newhead)
+ call assert_equal(3, d.entries[1].seq)
+ call assert_true(d.entries[1].time <= d.time_cur)
+
+ undo
+ set ul=100
+ let d = undotree()
+ call assert_equal(3, d.seq_last)
+ call assert_equal(1, d.seq_cur)
+ call assert_equal(0, d.save_last)
+ call assert_equal(0, d.save_cur)
+ call assert_equal(2, len(d.entries))
+ call assert_equal(1, d.entries[0].seq)
+ call assert_equal(2, d.entries[1].alt[0].seq)
+ call assert_equal(1, d.entries[1].curhead)
+ call assert_equal(1, d.entries[1].newhead)
+ call assert_equal(3, d.entries[1].seq)
+ call assert_true(d.entries[1].time == d.time_cur)
+
+ w! Xtest
+ let d = undotree()
+ call assert_equal(1, d.save_cur)
+ call assert_equal(1, d.save_last)
+ call delete('Xtest')
+ bwipe! Xtest
+endfunc
+
+func FillBuffer()
+ for i in range(1,13)
+ put=i
+ " Set 'undolevels' to split undo.
+ exe "setg ul=" . &g:ul
+ endfor
+endfunc
+
+func Test_global_local_undolevels()
+ new one
+ set undolevels=5
+ call FillBuffer()
+ " will only undo the last 5 changes, end up with 13 - (5 + 1) = 7 lines
+ earlier 10
+ call assert_equal(5, &g:undolevels)
+ call assert_equal(-123456, &l:undolevels)
+ call assert_equal('7', getline('$'))
+
+ new two
+ setlocal undolevels=2
+ call FillBuffer()
+ " will only undo the last 2 changes, end up with 13 - (2 + 1) = 10 lines
+ earlier 10
+ call assert_equal(5, &g:undolevels)
+ call assert_equal(2, &l:undolevels)
+ call assert_equal('10', getline('$'))
+
+ setlocal ul=10
+ call assert_equal(5, &g:undolevels)
+ call assert_equal(10, &l:undolevels)
+
+ " Setting local value in "two" must not change local value in "one"
+ wincmd p
+ call assert_equal(5, &g:undolevels)
+ call assert_equal(-123456, &l:undolevels)
+
+ new three
+ setglobal ul=50
+ call assert_equal(50, &g:undolevels)
+ call assert_equal(-123456, &l:undolevels)
+
+ " Resetting the local 'undolevels' value to use the global value
+ setlocal undolevels=5
+ setlocal undolevels<
+ call assert_equal(-123456, &l:undolevels)
+
+ " Drop created windows
+ set ul&
+ new
+ only!
+endfunc
+
+func BackOne(expected)
+ call feedkeys('g-', 'xt')
+ call assert_equal(a:expected, getline(1))
+endfunc
+
+func Test_undo_del_chars()
+ " Setup a buffer without creating undo entries
+ new
+ set ul=-1
+ call setline(1, ['123-456'])
+ set ul=100
+ 1
+ call test_settime(100)
+
+ " Delete three characters and undo with g-
+ call feedkeys('x', 'xt')
+ call feedkeys('x', 'xt')
+ call feedkeys('x', 'xt')
+ call assert_equal('-456', getline(1))
+ call BackOne('3-456')
+ call BackOne('23-456')
+ call BackOne('123-456')
+ call assert_fails("BackOne('123-456')")
+
+ :" Delete three other characters and go back in time with g-
+ call feedkeys('$x', 'xt')
+ call feedkeys('x', 'xt')
+ call feedkeys('x', 'xt')
+ call assert_equal('123-', getline(1))
+ call test_settime(101)
+
+ call BackOne('123-4')
+ call BackOne('123-45')
+ " skips '123-456' because it's older
+ call BackOne('-456')
+ call BackOne('3-456')
+ call BackOne('23-456')
+ call BackOne('123-456')
+ call assert_fails("BackOne('123-456')")
+ normal 10g+
+ call assert_equal('123-', getline(1))
+
+ :" Jump two seconds and go some seconds forward and backward
+ call test_settime(103)
+ call feedkeys("Aa\<Esc>", 'xt')
+ call feedkeys("Ab\<Esc>", 'xt')
+ call feedkeys("Ac\<Esc>", 'xt')
+ call assert_equal('123-abc', getline(1))
+ earlier 1s
+ call assert_equal('123-', getline(1))
+ earlier 3s
+ call assert_equal('123-456', getline(1))
+ later 1s
+ call assert_equal('123-', getline(1))
+ later 1h
+ call assert_equal('123-abc', getline(1))
+
+ close!
+endfunc
+
+func Test_undolist()
+ new
+ set ul=100
+
+ let a = execute('undolist')
+ call assert_equal("\nNothing to undo", a)
+
+ " 1 leaf (2 changes).
+ call feedkeys('achange1', 'xt')
+ call feedkeys('achange2', 'xt')
+ let a = execute('undolist')
+ call assert_match("^\nnumber changes when *saved\n *2 *2 .*$", a)
+
+ " 2 leaves.
+ call feedkeys('u', 'xt')
+ call feedkeys('achange3\<Esc>', 'xt')
+ let a = execute('undolist')
+ call assert_match("^\nnumber changes when *saved\n *2 *2 *.*\n *3 *2 .*$", a)
+ close!
+endfunc
+
+func Test_U_command()
+ new
+ set ul=100
+ call feedkeys("achange1\<Esc>", 'xt')
+ call feedkeys("achange2\<Esc>", 'xt')
+ norm! U
+ call assert_equal('', getline(1))
+ norm! U
+ call assert_equal('change1change2', getline(1))
+ close!
+endfunc
+
+func Test_undojoin()
+ new
+ call feedkeys("Goaaaa\<Esc>", 'xt')
+ call feedkeys("obbbb\<Esc>", 'xt')
+ call assert_equal(['aaaa', 'bbbb'], getline(2, '$'))
+ call feedkeys("u", 'xt')
+ call assert_equal(['aaaa'], getline(2, '$'))
+ call feedkeys("obbbb\<Esc>", 'xt')
+ undojoin
+ " Note: next change must not be as if typed
+ call feedkeys("occcc\<Esc>", 'x')
+ call assert_equal(['aaaa', 'bbbb', 'cccc'], getline(2, '$'))
+ call feedkeys("u", 'xt')
+ call assert_equal(['aaaa'], getline(2, '$'))
+ bwipe!
+endfunc
+
+func Test_undojoin_redo()
+ new
+ call setline(1, ['first line', 'second line'])
+ call feedkeys("ixx\<Esc>", 'xt')
+ call feedkeys(":undojoin | redo\<CR>", 'xt')
+ call assert_equal('xxfirst line', getline(1))
+ call assert_equal('second line', getline(2))
+ bwipe!
+endfunc
+
+" undojoin not allowed after undo
+func Test_undojoin_after_undo()
+ new
+ call feedkeys("ixx\<Esc>u", 'xt')
+ call assert_fails(':undojoin', 'E790:')
+ bwipe!
+endfunc
+
+" undojoin is a noop when no change yet, or when 'undolevels' is negative
+func Test_undojoin_noop()
+ new
+ call feedkeys(":undojoin\<CR>", 'xt')
+ call assert_equal([''], getline(1, '$'))
+ setlocal undolevels=-1
+ call feedkeys("ixx\<Esc>u", 'xt')
+ call feedkeys(":undojoin\<CR>", 'xt')
+ call assert_equal(['xx'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_undo_write()
+ call delete('Xtest')
+ split Xtest
+ call feedkeys("ione one one\<Esc>", 'xt')
+ w!
+ call feedkeys("otwo\<Esc>", 'xt')
+ call feedkeys("otwo\<Esc>", 'xt')
+ w
+ call feedkeys("othree\<Esc>", 'xt')
+ call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$'))
+ earlier 1f
+ call assert_equal(['one one one', 'two', 'two'], getline(1, '$'))
+ earlier 1f
+ call assert_equal(['one one one'], getline(1, '$'))
+ earlier 1f
+ call assert_equal([''], getline(1, '$'))
+ later 1f
+ call assert_equal(['one one one'], getline(1, '$'))
+ later 1f
+ call assert_equal(['one one one', 'two', 'two'], getline(1, '$'))
+ later 1f
+ call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$'))
+
+ close!
+ call delete('Xtest')
+ bwipe! Xtest
+
+ call assert_fails('earlier xyz', 'E475:')
+endfunc
+
+func Test_insert_expr()
+ new
+ " calling setline() triggers undo sync
+ call feedkeys("oa\<Esc>", 'xt')
+ call feedkeys("ob\<Esc>", 'xt')
+ set ul=100
+ call feedkeys("o1\<Esc>a2\<C-R>=setline('.','1234')\<CR>\<CR>\<Esc>", 'x')
+ call assert_equal(['a', 'b', '120', '34'], getline(2, '$'))
+ call feedkeys("u", 'x')
+ call assert_equal(['a', 'b', '12'], getline(2, '$'))
+ call feedkeys("u", 'x')
+ call assert_equal(['a', 'b'], getline(2, '$'))
+
+ call feedkeys("oc\<Esc>", 'xt')
+ set ul=100
+ call feedkeys("o1\<Esc>a2\<C-R>=setline('.','1234')\<CR>\<CR>\<Esc>", 'x')
+ call assert_equal(['a', 'b', 'c', '120', '34'], getline(2, '$'))
+ call feedkeys("u", 'x')
+ call assert_equal(['a', 'b', 'c', '12'], getline(2, '$'))
+
+ call feedkeys("od\<Esc>", 'xt')
+ set ul=100
+ call feedkeys("o1\<Esc>a2\<C-R>=string(123)\<CR>\<Esc>", 'x')
+ call assert_equal(['a', 'b', 'c', '12', 'd', '12123'], getline(2, '$'))
+ call feedkeys("u", 'x')
+ call assert_equal(['a', 'b', 'c', '12', 'd'], getline(2, '$'))
+
+ close!
+endfunc
+
+func Test_undofile_earlier()
+ if has('win32')
+ " FIXME: This test is flaky on MS-Windows.
+ let g:test_is_flaky = 1
+ endif
+
+ " Issue #1254
+ " create undofile with timestamps older than Vim startup time.
+ let t0 = localtime() - 43200
+ call test_settime(t0)
+ new XfileEarlier
+ call feedkeys("ione\<Esc>", 'xt')
+ set ul=100
+ call test_settime(t0 + 1)
+ call feedkeys("otwo\<Esc>", 'xt')
+ set ul=100
+ call test_settime(t0 + 2)
+ call feedkeys("othree\<Esc>", 'xt')
+ set ul=100
+ w
+ wundo Xundofile
+ bwipe!
+ " restore normal timestamps.
+ call test_settime(0)
+ new XfileEarlier
+ rundo Xundofile
+ earlier 1d
+ call assert_equal('', getline(1))
+ bwipe!
+ call delete('XfileEarlier')
+ call delete('Xundofile')
+endfunc
+
+func Test_wundo_errors()
+ new
+ call setline(1, 'hello')
+ call assert_fails('wundo! Xdoesnotexist/Xundofile', 'E828:')
+ bwipe!
+endfunc
+
+" Check that reading a truncated undo file doesn't hang.
+func Test_undofile_truncated()
+ new
+ call setline(1, 'hello')
+ set ul=100
+ wundo Xundofile
+ let contents = readfile('Xundofile', 'B')
+
+ " try several sizes
+ for size in range(20, 500, 33)
+ call writefile(contents[0:size], 'Xundofile', 'D')
+ call assert_fails('rundo Xundofile', 'E825:')
+ endfor
+
+ bwipe!
+endfunc
+
+func Test_rundo_errors()
+ call assert_fails('rundo XfileDoesNotExist', 'E822:')
+
+ call writefile(['abc'], 'Xundofile', 'D')
+ call assert_fails('rundo Xundofile', 'E823:')
+endfunc
+
+func Test_undofile_next()
+ set undofile
+ new Xfoo.txt
+ execute "norm ix\<c-g>uy\<c-g>uz\<Esc>"
+ write
+ bwipe
+
+ next Xfoo.txt
+ call assert_equal('xyz', getline(1))
+ silent undo
+ call assert_equal('xy', getline(1))
+ silent undo
+ call assert_equal('x', getline(1))
+ bwipe!
+
+ call delete('Xfoo.txt')
+ call delete('.Xfoo.txt.un~')
+ set undofile&
+endfunc
+
+" Test for undo working properly when executing commands from a register.
+" Also test this in an empty buffer.
+func Test_cmd_in_reg_undo()
+ enew!
+ let @a = "Ox\<Esc>jAy\<Esc>kdd"
+ edit +/^$ test_undo.vim
+ normal @au
+ call assert_equal(0, &modified)
+ return
+ new
+ normal @au
+ call assert_equal(0, &modified)
+ only!
+ let @a = ''
+endfunc
+
+" This used to cause an illegal memory access
+func Test_undo_append()
+ new
+ call feedkeys("axx\<Esc>v", 'xt')
+ undo
+ norm o
+ quit
+endfunc
+
+func Test_undo_0()
+ new
+ set ul=100
+ normal i1
+ undo
+ normal i2
+ undo
+ normal i3
+
+ undo 0
+ let d = undotree()
+ call assert_equal('', getline(1))
+ call assert_equal(0, d.seq_cur)
+
+ redo
+ let d = undotree()
+ call assert_equal('3', getline(1))
+ call assert_equal(3, d.seq_cur)
+
+ undo 2
+ undo 0
+ let d = undotree()
+ call assert_equal('', getline(1))
+ call assert_equal(0, d.seq_cur)
+
+ redo
+ let d = undotree()
+ call assert_equal('2', getline(1))
+ call assert_equal(2, d.seq_cur)
+
+ undo 1
+ undo 0
+ let d = undotree()
+ call assert_equal('', getline(1))
+ call assert_equal(0, d.seq_cur)
+
+ redo
+ let d = undotree()
+ call assert_equal('1', getline(1))
+ call assert_equal(1, d.seq_cur)
+
+ bwipe!
+endfunc
+
+" undo or redo are noop if there is nothing to undo or redo
+func Test_undo_redo_noop()
+ new
+ call assert_fails('undo 2', 'E830:')
+
+ message clear
+ undo
+ let messages = split(execute('message'), "\n")
+ call assert_equal('Already at oldest change', messages[-1])
+
+ message clear
+ redo
+ let messages = split(execute('message'), "\n")
+ call assert_equal('Already at newest change', messages[-1])
+
+ bwipe!
+endfunc
+
+func Test_redo_empty_line()
+ new
+ exe "norm\x16r\x160"
+ exe "norm."
+ bwipe!
+endfunc
+
+funct Test_undofile()
+ " Test undofile() without setting 'undodir'.
+ if has('persistent_undo')
+ call assert_equal(fnamemodify('.Xundofoo.un~', ':p'), undofile('Xundofoo'))
+ else
+ call assert_equal('', undofile('Xundofoo'))
+ endif
+ call assert_equal('', undofile(''))
+
+ " Test undofile() with 'undodir' set to to an existing directory.
+ call mkdir('Xundodir')
+ set undodir=Xundodir
+ let cwd = getcwd()
+ if has('win32')
+ " Replace windows drive such as C:... into C%...
+ let cwd = substitute(cwd, '^\([a-zA-Z]\):', '\1%', 'g')
+ endif
+ let cwd = substitute(cwd . '/Xundofoo', '/', '%', 'g')
+ if has('persistent_undo')
+ call assert_equal('Xundodir/' . cwd, undofile('Xundofoo'))
+ else
+ call assert_equal('', undofile('Xundofoo'))
+ endif
+ call assert_equal('', undofile(''))
+ call delete('Xundodir', 'd')
+
+ " Test undofile() with 'undodir' set to a non-existing directory.
+ call assert_equal('', 'Xundofoo'->undofile())
+
+ if !has('win32') && isdirectory('/tmp')
+ set undodir=/tmp
+ if has('osx')
+ call assert_equal('/tmp/%private%tmp%file', undofile('///tmp/file'))
+ else
+ call assert_equal('/tmp/%tmp%file', undofile('///tmp/file'))
+ endif
+ endif
+
+ set undodir&
+endfunc
+
+" Tests for the undo file
+" Explicitly break changes up in undo-able pieces by setting 'undolevels'.
+func Test_undofile_2()
+ set undolevels=100 undofile
+ edit Xtestfile
+ call append(0, 'this is one line')
+ call cursor(1, 1)
+
+ " first a simple one-line change.
+ set undolevels=100
+ s/one/ONE/
+ set undolevels=100
+ write
+ bwipe!
+ edit Xtestfile
+ undo
+ call assert_equal('this is one line', getline(1))
+
+ " change in original file fails check
+ set noundofile
+ edit! Xtestfile
+ s/line/Line/
+ write
+ set undofile
+ bwipe!
+ edit Xtestfile
+ undo
+ call assert_equal('this is ONE Line', getline(1))
+
+ " add 10 lines, delete 6 lines, undo 3
+ set undofile
+ call setbufline('%', 1, ['one', 'two', 'three', 'four', 'five', 'six',
+ \ 'seven', 'eight', 'nine', 'ten'])
+ set undolevels=100
+ normal 3Gdd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ write
+ bwipe!
+ edit Xtestfile
+ normal uuu
+ call assert_equal(['one', 'two', 'six', 'seven', 'eight', 'nine', 'ten'],
+ \ getline(1, '$'))
+
+ " Test that reading the undofiles when setting undofile works
+ set noundofile undolevels=0
+ exe "normal i\n"
+ undo
+ edit! Xtestfile
+ set undofile undolevels=100
+ normal uuuuuu
+ call assert_equal(['one', 'two', 'three', 'four', 'five', 'six', 'seven',
+ \ 'eight', 'nine', 'ten'], getline(1, '$'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'zip' crypt method
+func Test_undofile_cryptmethod_zip()
+ edit Xtestfile
+ set undofile cryptmethod=zip
+ call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ normal kkkdd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ " encrypt the file using key 'foobar'
+ call feedkeys("foobar\nfoobar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foobar\n")
+ edit Xtestfile
+ set key=
+ normal uu
+ call assert_equal(['monday', 'wednesday', 'thursday', 'friday', ''],
+ \ getline(1, '$'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'blowfish' crypt method
+func Test_undofile_cryptmethod_blowfish()
+ edit Xtestfile
+ set undofile cryptmethod=blowfish
+ call append(0, ['jan', 'feb', 'mar', 'apr', 'jun'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ exe 'normal kk0ifoo '
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ exe 'normal ibar '
+ set undolevels=100
+ " encrypt the file using key 'foobar'
+ call feedkeys("foobar\nfoobar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foobar\n")
+ edit Xtestfile
+ set key=
+ call search('bar')
+ call assert_equal('bar apr', getline('.'))
+ undo
+ call assert_equal('apr', getline('.'))
+ undo
+ call assert_equal('foo mar', getline('.'))
+ undo
+ call assert_equal('mar', getline('.'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'blowfish2' crypt method
+func Test_undofile_cryptmethod_blowfish2()
+ edit Xtestfile
+ set undofile cryptmethod=blowfish2
+ call append(0, ['jan', 'feb', 'mar', 'apr', 'jun'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ exe 'normal kk0ifoo '
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ exe 'normal ibar '
+ set undolevels=100
+ " encrypt the file using key 'foo2bar'
+ call feedkeys("foo2bar\nfoo2bar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foo2bar\n")
+ edit Xtestfile
+ set key=
+ call search('bar')
+ call assert_equal('bar apr', getline('.'))
+ normal u
+ call assert_equal('apr', getline('.'))
+ normal u
+ call assert_equal('foo mar', getline('.'))
+ normal u
+ call assert_equal('mar', getline('.'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" Test for redoing with incrementing numbered registers
+func Test_redo_repeat_numbered_register()
+ new
+ for [i, v] in [[1, 'one'], [2, 'two'], [3, 'three'],
+ \ [4, 'four'], [5, 'five'], [6, 'six'],
+ \ [7, 'seven'], [8, 'eight'], [9, 'nine']]
+ exe 'let @' .. i .. '="' .. v .. '\n"'
+ endfor
+ call feedkeys('"1p.........', 'xt')
+ call assert_equal(['', 'one', 'two', 'three', 'four', 'five', 'six',
+ \ 'seven', 'eight', 'nine', 'nine'], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Test for redo in insert mode using CTRL-O with multibyte characters
+func Test_redo_multibyte_in_insert_mode()
+ new
+ call feedkeys("a\<C-K>ft", 'xt')
+ call feedkeys("uiHe\<C-O>.llo", 'xt')
+ call assert_equal("He\ufb05llo", getline(1))
+ bwipe!
+endfunc
+
+func Test_undo_mark()
+ new
+ " The undo is applied to the only line.
+ call setline(1, 'hello')
+ call feedkeys("ggyiw$p", 'xt')
+ undo
+ call assert_equal([0, 1, 1, 0], getpos("'["))
+ call assert_equal([0, 1, 1, 0], getpos("']"))
+ " The undo removes the last line.
+ call feedkeys("Goaaaa\<Esc>", 'xt')
+ call feedkeys("obbbb\<Esc>", 'xt')
+ undo
+ call assert_equal([0, 2, 1, 0], getpos("'["))
+ call assert_equal([0, 2, 1, 0], getpos("']"))
+ bwipe!
+endfunc
+
+func Test_undo_after_write()
+ " use a terminal to make undo work like when text is typed
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ edit Xtestfile.txt
+ set undolevels=100 undofile
+ imap . <Cmd>write<CR>
+ write
+ END
+ call writefile(lines, 'Xtest_undo_after_write', 'D')
+ let buf = RunVimInTerminal('-S Xtest_undo_after_write', #{rows: 6})
+
+ call term_sendkeys(buf, "Otest.\<CR>boo!!!\<Esc>")
+ sleep 100m
+ call term_sendkeys(buf, "u")
+ call VerifyScreenDump(buf, 'Test_undo_after_write_1', {})
+
+ call term_sendkeys(buf, "u")
+ call VerifyScreenDump(buf, 'Test_undo_after_write_2', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xtestfile.txt')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_unlet.vim b/src/testdir/test_unlet.vim
new file mode 100644
index 0000000..6be9633
--- /dev/null
+++ b/src/testdir/test_unlet.vim
@@ -0,0 +1,67 @@
+" Tests for :unlet
+
+func Test_read_only()
+ " these caused a crash
+ call assert_fails('unlet count', 'E795:')
+ call assert_fails('unlet errmsg', 'E795:')
+endfunc
+
+func Test_existing()
+ let does_exist = 1
+ call assert_true(exists('does_exist'))
+ unlet does_exist
+ call assert_false(exists('does_exist'))
+endfunc
+
+func Test_not_existing()
+ unlet! does_not_exist
+ call assert_fails('unlet does_not_exist', 'E108:')
+endfunc
+
+func Test_unlet_fails()
+ call assert_fails('unlet v:["count"]', 'E46:')
+ call assert_fails('unlet $', 'E475:')
+ let v = {}
+ call assert_fails('unlet v[:]', 'E719:')
+ let l = []
+ call assert_fails("unlet l['k'", 'E111:')
+ let d = {'k' : 1}
+ call assert_fails("unlet d.k2", 'E716:')
+ call assert_fails("unlet {a};", 'E488:')
+endfunc
+
+func Test_unlet_env()
+ let envcmd = has('win32') ? 'set' : 'env'
+
+ let $FOOBAR = 'test'
+ let found = 0
+ for kv in split(system(envcmd), "\r*\n")
+ if kv == 'FOOBAR=test'
+ let found = 1
+ endif
+ endfor
+ call assert_equal(1, found)
+
+ unlet $FOOBAR
+ let found = 0
+ for kv in split(system(envcmd), "\r*\n")
+ if kv == 'FOOBAR=test'
+ let found = 1
+ endif
+ endfor
+ call assert_equal(0, found)
+
+ unlet $MUST_NOT_BE_AN_ERROR
+endfunc
+
+func Test_unlet_complete()
+ let g:FOOBAR = 1
+ call feedkeys(":unlet g:FOO\t\n", 'tx')
+ call assert_true(!exists('g:FOOBAR'))
+
+ let $FOOBAR = 1
+ call feedkeys(":unlet $FOO\t\n", 'tx')
+ call assert_true(!exists('$FOOBAR') || empty($FOOBAR))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim
new file mode 100644
index 0000000..ade259c
--- /dev/null
+++ b/src/testdir/test_user_func.vim
@@ -0,0 +1,802 @@
+" Test for user functions.
+" Also test an <expr> mapping calling a function.
+" Also test that a builtin function cannot be replaced.
+" Also test for regression when calling arbitrary expression.
+
+source check.vim
+source shared.vim
+import './vim9.vim' as v9
+
+func Table(title, ...)
+ let ret = a:title
+ let idx = 1
+ while idx <= a:0
+ exe "let ret = ret . a:" . idx
+ let idx = idx + 1
+ endwhile
+ return ret
+endfunc
+
+func Compute(n1, n2, divname)
+ if a:n2 == 0
+ return "fail"
+ endif
+ exe "let g:" . a:divname . " = ". a:n1 / a:n2
+ return "ok"
+endfunc
+
+func Expr1()
+ silent! normal! v
+ return "111"
+endfunc
+
+func Expr2()
+ call search('XX', 'b')
+ return "222"
+endfunc
+
+func ListItem()
+ let g:counter += 1
+ return g:counter . '. '
+endfunc
+
+func ListReset()
+ let g:counter = 0
+ return ''
+endfunc
+
+func FuncWithRef(a)
+ unlet g:FuncRef
+ return a:a
+endfunc
+
+func Test_user_func()
+ let g:FuncRef = function("FuncWithRef")
+ let g:counter = 0
+ inoremap <expr> ( ListItem()
+ inoremap <expr> [ ListReset()
+ imap <expr> + Expr1()
+ imap <expr> * Expr2()
+ let g:retval = "nop"
+
+ call assert_equal('xxx4asdf', Table("xxx", 4, "asdf"))
+ call assert_equal('fail', Compute(45, 0, "retval"))
+ call assert_equal('nop', g:retval)
+ call assert_equal('ok', Compute(45, 5, "retval"))
+ call assert_equal(9, g:retval)
+ call assert_equal(333, g:FuncRef(333))
+
+ let g:retval = "nop"
+ call assert_equal('xxx4asdf', "xxx"->Table(4, "asdf"))
+ call assert_equal('fail', 45->Compute(0, "retval"))
+ call assert_equal('nop', g:retval)
+ call assert_equal('ok', 45->Compute(5, "retval"))
+ call assert_equal(9, g:retval)
+ " call assert_equal(333, 333->g:FuncRef())
+
+ enew
+
+ normal oXX+-XX
+ call assert_equal('XX111-XX', getline('.'))
+ normal o---*---
+ call assert_equal('---222---', getline('.'))
+ normal o(one
+ call assert_equal('1. one', getline('.'))
+ normal o(two
+ call assert_equal('2. two', getline('.'))
+ normal o[(one again
+ call assert_equal('1. one again', getline('.'))
+
+ " Try to overwrite a function in the global (g:) scope
+ call assert_equal(3, max([1, 2, 3]))
+ call assert_fails("call extend(g:, {'max': function('min')})", 'E704:')
+ call assert_equal(3, max([1, 2, 3]))
+
+ " Try to overwrite an user defined function with a function reference
+ call assert_fails("let Expr1 = function('min')", 'E705:')
+
+ " Regression: the first line below used to throw ?E110: Missing ')'?
+ " Second is here just to prove that this line is correct when not skipping
+ " rhs of &&.
+ call assert_equal(0, (0 && (function('tr'))(1, 2, 3)))
+ call assert_equal(1, (1 && (function('tr'))(1, 2, 3)))
+
+ delfunc Table
+ delfunc Compute
+ delfunc Expr1
+ delfunc Expr2
+ delfunc ListItem
+ delfunc ListReset
+ unlet g:retval g:counter
+ enew!
+endfunc
+
+func Log(val, base = 10)
+ return log(a:val) / log(a:base)
+endfunc
+
+func Args(mandatory, optional = v:null, ...)
+ return deepcopy(a:)
+endfunc
+
+func Args2(a = 1, b = 2, c = 3)
+ return deepcopy(a:)
+endfunc
+
+func MakeBadFunc()
+ func s:fcn(a, b=1, c)
+ endfunc
+endfunc
+
+func Test_default_arg()
+ call assert_equal(1.0, Log(10))
+ call assert_equal(log(10), Log(10, exp(1)))
+ call assert_fails("call Log(1,2,3)", 'E118:')
+
+ let res = Args(1)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, v:null)
+ call assert_equal(res['0'], 0)
+
+ let res = Args(1,2)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, 2)
+ call assert_equal(res['0'], 0)
+
+ let res = Args(1,2,3)
+ call assert_equal(res.mandatory, 1)
+ call assert_equal(res.optional, 2)
+ call assert_equal(res['0'], 1)
+
+ call assert_fails("call MakeBadFunc()", 'E989:')
+ call assert_fails("fu F(a=1 ,) | endf", 'E1068:')
+
+ let d = Args2(7, v:none, 9)
+ call assert_equal([7, 2, 9], [d.a, d.b, d.c])
+
+ call assert_equal("\n"
+ \ .. " function Args2(a = 1, b = 2, c = 3)\n"
+ \ .. "1 return deepcopy(a:)\n"
+ \ .. " endfunction",
+ \ execute('func Args2'))
+
+ " Error in default argument expression
+ let l =<< trim END
+ func F1(x = y)
+ return a:x * 2
+ endfunc
+ echo F1()
+ END
+ let @a = l->join("\n")
+ call assert_fails("exe @a", 'E121:')
+endfunc
+
+func s:addFoo(lead)
+ return a:lead .. 'foo'
+endfunc
+
+func Test_user_method()
+ eval 'bar'->s:addFoo()->assert_equal('barfoo')
+endfunc
+
+func Test_method_with_linebreaks()
+ let lines =<< trim END
+ vim9script
+
+ export def Scan(ll: list<number>): func(func(number))
+ return (Emit: func(number)) => {
+ for v in ll
+ Emit(v)
+ endfor
+ }
+ enddef
+
+ export def Build(Cont: func(func(number))): list<number>
+ var result: list<number> = []
+ Cont((v) => {
+ add(result, v)
+ })
+ return result
+ enddef
+
+ export def Noop(Cont: func(func(number))): func(func(number))
+ return (Emit: func(number)) => {
+ Cont(Emit)
+ }
+ enddef
+ END
+ call writefile(lines, 'Xlib.vim', 'D')
+
+ let lines =<< trim END
+ vim9script
+
+ import "./Xlib.vim" as lib
+
+ const x = [1, 2, 3]
+
+ var result = lib.Scan(x)->lib.Noop()->lib.Build()
+ assert_equal([1, 2, 3], result)
+
+ result = lib.Scan(x)->lib.Noop()
+ ->lib.Build()
+ assert_equal([1, 2, 3], result)
+
+ result = lib.Scan(x)
+ ->lib.Noop()->lib.Build()
+ assert_equal([1, 2, 3], result)
+
+ result = lib.Scan(x)
+ ->lib.Noop()
+ ->lib.Build()
+ assert_equal([1, 2, 3], result)
+ END
+ call v9.CheckScriptSuccess(lines)
+endfunc
+
+func Test_failed_call_in_try()
+ try | call UnknownFunc() | catch | endtry
+endfunc
+
+" Test for listing user-defined functions
+func Test_function_list()
+ call assert_fails("function Xabc", 'E123:')
+endfunc
+
+" Test for <sfile>, <slnum> in a function
+func Test_sfile_in_function()
+ func Xfunc()
+ call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>'))
+ call assert_equal('2', expand('<slnum>'))
+ endfunc
+ call Xfunc()
+ delfunc Xfunc
+endfunc
+
+" Test trailing text after :endfunction {{{1
+func Test_endfunction_trailing()
+ call assert_false(exists('*Xtest'))
+
+ exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'"
+ call assert_true(exists('*Xtest'))
+ call assert_equal('yes', done)
+ delfunc Xtest
+ unlet done
+
+ exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'"
+ call assert_true(exists('*Xtest'))
+ call assert_equal('yes', done)
+ delfunc Xtest
+ unlet done
+
+ " trailing line break
+ exe "func Xtest()\necho 'hello'\nendfunc\n"
+ call assert_true(exists('*Xtest'))
+ delfunc Xtest
+
+ set verbose=1
+ exe "func Xtest()\necho 'hello'\nendfunc \" garbage"
+ call assert_notmatch('W22:', split(execute('1messages'), "\n")[0])
+ call assert_true(exists('*Xtest'))
+ delfunc Xtest
+
+ exe "func Xtest()\necho 'hello'\nendfunc garbage"
+ call assert_match('W22:', split(execute('1messages'), "\n")[0])
+ call assert_true(exists('*Xtest'))
+ delfunc Xtest
+ set verbose=0
+
+ func Xtest(a1, a2)
+ echo a:a1 .. a:a2
+ endfunc
+ set verbose=15
+ redir @a
+ call Xtest(123, repeat('x', 100))
+ redir END
+ call assert_match('calling Xtest(123, ''xxxxxxx.*x\.\.\.x.*xxxx'')', getreg('a'))
+ delfunc Xtest
+ set verbose=0
+
+ function Foo()
+ echo 'hello'
+ endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
+ delfunc Foo
+endfunc
+
+func Test_delfunction_force()
+ delfunc! Xtest
+ delfunc! Xtest
+ func Xtest()
+ echo 'nothing'
+ endfunc
+ delfunc! Xtest
+ delfunc! Xtest
+
+ " Try deleting the current function
+ call assert_fails('delfunc Test_delfunction_force', 'E131:')
+endfunc
+
+func Test_function_defined_line()
+ CheckNotGui
+
+ let lines =<< trim [CODE]
+ " F1
+ func F1()
+ " F2
+ func F2()
+ "
+ "
+ "
+ return
+ endfunc
+ " F3
+ execute "func F3()\n\n\n\nreturn\nendfunc"
+ " F4
+ execute "func F4()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ endfunc
+ " F5
+ execute "func F5()\n\n\n\nreturn\nendfunc"
+ " F6
+ execute "func F6()\n
+ \\n
+ \\n
+ \\n
+ \return\n
+ \endfunc"
+ call F1()
+ verbose func F1
+ verbose func F2
+ verbose func F3
+ verbose func F4
+ verbose func F5
+ verbose func F6
+ qall!
+ [CODE]
+
+ call writefile(lines, 'Xtest.vim', 'D')
+ let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim')
+ call assert_equal(0, v:shell_error)
+
+ let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 2$', m)
+
+ let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 4$', m)
+
+ let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 11$', m)
+
+ let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 13$', m)
+
+ let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 21$', m)
+
+ let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*')
+ call assert_match(' line 23$', m)
+endfunc
+
+" Test for defining a function reference in the global scope
+func Test_add_funcref_to_global_scope()
+ let x = g:
+ let caught_E862 = 0
+ try
+ func x.Xfunc()
+ return 1
+ endfunc
+ catch /E862:/
+ let caught_E862 = 1
+ endtry
+ call assert_equal(1, caught_E862)
+endfunc
+
+func Test_funccall_garbage_collect()
+ func Func(x, ...)
+ call add(a:x, a:000)
+ endfunc
+ call Func([], [])
+ " Must not crash cause by invalid freeing
+ call test_garbagecollect_now()
+ call assert_true(v:true)
+ delfunc Func
+endfunc
+
+" Test for script-local function
+func <SID>DoLast()
+ call append(line('$'), "last line")
+endfunc
+
+func s:DoNothing()
+ call append(line('$'), "nothing line")
+endfunc
+
+func Test_script_local_func()
+ set nocp nomore viminfo+=nviminfo
+ new
+ nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr>
+
+ normal _x
+ call assert_equal('nothing line', getline(2))
+ call assert_equal('last line', getline(3))
+ close!
+
+ " Try to call a script local function in global scope
+ let lines =<< trim [CODE]
+ :call assert_fails('call s:Xfunc()', 'E81:')
+ :call assert_fails('let x = call("<SID>Xfunc", [])', 'E120:')
+ :call writefile(v:errors, 'Xresult')
+ :qall
+
+ [CODE]
+ call writefile(lines, 'Xscript', 'D')
+ if RunVim([], [], '-s Xscript')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+" Test for errors in defining new functions
+func Test_func_def_error()
+ call assert_fails('func Xfunc abc ()', 'E124:')
+ call assert_fails('func Xfunc(', 'E125:')
+ call assert_fails('func xfunc()', 'E128:')
+
+ " Try to redefine a function that is in use
+ let caught_E127 = 0
+ try
+ func! Test_func_def_error()
+ endfunc
+ catch /E127:/
+ let caught_E127 = 1
+ endtry
+ call assert_equal(1, caught_E127)
+
+ " Try to define a function in a dict twice
+ let d = {}
+ let lines =<< trim END
+ func d.F1()
+ return 1
+ endfunc
+ END
+ let l = join(lines, "\n") . "\n"
+ exe l
+ call assert_fails('exe l', 'E717:')
+ call assert_fails('call feedkeys(":func d.F1()\<CR>", "xt")', 'E717:')
+
+ " Define an autoload function with an incorrect file name
+ call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript', 'D')
+ call assert_fails('source Xscript', 'E746:')
+
+ " Try to list functions using an invalid search pattern
+ call assert_fails('function /\%(/', 'E53:')
+endfunc
+
+" Test for deleting a function
+func Test_del_func()
+ call assert_fails('delfunction Xabc', 'E117:')
+ let d = {'a' : 10}
+ call assert_fails('delfunc d.a', 'E718:')
+ func d.fn()
+ return 1
+ endfunc
+
+ " cannot delete the dict function by number
+ let nr = substitute(execute('echo d'), '.*function(''\(\d\+\)'').*', '\1', '')
+ call assert_fails('delfunction g:' .. nr, 'E475: Invalid argument: g:')
+
+ delfunc d.fn
+ call assert_equal({'a' : 10}, d)
+endfunc
+
+" Test for calling return outside of a function
+func Test_return_outside_func()
+ call writefile(['return 10'], 'Xscript', 'D')
+ call assert_fails('source Xscript', 'E133:')
+endfunc
+
+" Test for errors in calling a function
+func Test_func_arg_error()
+ " Too many arguments
+ call assert_fails("call call('min', range(1,20))", 'E118:')
+ call assert_fails("call call('min', range(1,21))", 'E699:')
+ call assert_fails('echo min(0,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,0,1)',
+ \ 'E740:')
+
+ " Missing dict argument
+ func Xfunc() dict
+ return 1
+ endfunc
+ call assert_fails('call Xfunc()', 'E725:')
+ delfunc Xfunc
+endfunc
+
+func Test_func_dict()
+ let mydict = {'a': 'b'}
+ function mydict.somefunc() dict
+ return len(self)
+ endfunc
+
+ call assert_equal("{'a': 'b', 'somefunc': function('3')}", string(mydict))
+ call assert_equal(2, mydict.somefunc())
+ call assert_match("^\n function \\d\\\+() dict"
+ \ .. "\n1 return len(self)"
+ \ .. "\n endfunction$", execute('func mydict.somefunc'))
+ call assert_fails('call mydict.nonexist()', 'E716:')
+endfunc
+
+func Test_func_range()
+ new
+ call setline(1, range(1, 8))
+ func FuncRange() range
+ echo a:firstline
+ echo a:lastline
+ endfunc
+ 3
+ call assert_equal("\n3\n3", execute('call FuncRange()'))
+ call assert_equal("\n4\n6", execute('4,6 call FuncRange()'))
+ call assert_equal("\n function FuncRange() range"
+ \ .. "\n1 echo a:firstline"
+ \ .. "\n2 echo a:lastline"
+ \ .. "\n endfunction",
+ \ execute('function FuncRange'))
+
+ bwipe!
+endfunc
+
+" Test for memory allocation failure when defining a new function
+func Test_funcdef_alloc_failure()
+ new
+ let lines =<< trim END
+ func Xtestfunc()
+ return 321
+ endfunc
+ END
+ call setline(1, lines)
+ call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ call assert_fails('source', 'E342:')
+ call assert_false(exists('*Xtestfunc'))
+ call assert_fails('delfunc Xtestfunc', 'E117:')
+ %d _
+ let lines =<< trim END
+ def g:Xvim9func(): number
+ return 456
+ enddef
+ END
+ call setline(1, lines)
+ call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ call assert_fails('source', 'E342:')
+ call assert_false(exists('*Xvim9func'))
+ "call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ "call assert_fails('source', 'E342:')
+ "call assert_false(exists('*Xtestfunc'))
+ "call assert_fails('delfunc Xtestfunc', 'E117:')
+ bw!
+endfunc
+
+func AddDefer(arg1, ...)
+ call extend(g:deferred, [a:arg1])
+ if a:0 == 1
+ call extend(g:deferred, [a:1])
+ endif
+endfunc
+
+func WithDeferTwo()
+ call extend(g:deferred, ['in Two'])
+ for nr in range(3)
+ defer AddDefer('Two' .. nr)
+ endfor
+ call extend(g:deferred, ['end Two'])
+endfunc
+
+func WithDeferOne()
+ call extend(g:deferred, ['in One'])
+ call writefile(['text'], 'Xfuncdefer')
+ defer delete('Xfuncdefer')
+ defer AddDefer('One')
+ call WithDeferTwo()
+ call extend(g:deferred, ['end One'])
+endfunc
+
+func WithPartialDefer()
+ call extend(g:deferred, ['in Partial'])
+ let Part = funcref('AddDefer', ['arg1'])
+ defer Part("arg2")
+ call extend(g:deferred, ['end Partial'])
+endfunc
+
+func Test_defer()
+ let g:deferred = []
+ call WithDeferOne()
+
+ call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred)
+ unlet g:deferred
+
+ call assert_equal('', glob('Xfuncdefer'))
+
+ call assert_fails('defer delete("Xfuncdefer")->Another()', 'E488:')
+ call assert_fails('defer delete("Xfuncdefer").member', 'E488:')
+
+ let g:deferred = []
+ call WithPartialDefer()
+ call assert_equal(['in Partial', 'end Partial', 'arg1', 'arg2'], g:deferred)
+ unlet g:deferred
+
+ let Part = funcref('AddDefer', ['arg1'], {})
+ call assert_fails('defer Part("arg2")', 'E1300:')
+endfunc
+
+func DeferLevelTwo()
+ call writefile(['text'], 'XDeleteTwo', 'D')
+ throw 'someerror'
+endfunc
+
+def DeferLevelOne()
+ call writefile(['text'], 'XDeleteOne', 'D')
+ call g:DeferLevelTwo()
+enddef
+
+func Test_defer_throw()
+ let caught = 'no'
+ try
+ call DeferLevelOne()
+ catch /someerror/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+ call assert_false(filereadable('XDeleteOne'))
+ call assert_false(filereadable('XDeleteTwo'))
+endfunc
+
+func Test_defer_quitall()
+ let lines =<< trim END
+ vim9script
+ func DeferLevelTwo()
+ call writefile(['text'], 'XQuitallTwo', 'D')
+ qa!
+ endfunc
+
+ def DeferLevelOne()
+ call writefile(['text'], 'XQuitallOne', 'D')
+ call DeferLevelTwo()
+ enddef
+
+ DeferLevelOne()
+ END
+ call writefile(lines, 'XdeferQuitall', 'D')
+ let res = system(GetVimCommand() .. ' -X -S XdeferQuitall')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('XQuitallOne'))
+ call assert_false(filereadable('XQuitallTwo'))
+endfunc
+
+func Test_defer_quitall_in_expr_func()
+ let lines =<< trim END
+ def DefIndex(idx: number, val: string): bool
+ call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
+ if val == 'b'
+ qa!
+ endif
+ return val == 'c'
+ enddef
+
+ def Test_defer_in_funcref()
+ assert_equal(2, indexof(['a', 'b', 'c'], funcref('g:DefIndex')))
+ enddef
+ call Test_defer_in_funcref()
+ END
+ call writefile(lines, 'XdeferQuitallExpr', 'D')
+ let res = system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('Xentry0'))
+ call assert_false(filereadable('Xentry1'))
+ call assert_false(filereadable('Xentry2'))
+endfunc
+
+func FuncIndex(idx, val)
+ call writefile([a:idx .. ': ' .. a:val], 'Xentry' .. a:idx, 'D')
+ return a:val == 'c'
+endfunc
+
+def DefIndex(idx: number, val: string): bool
+ call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
+ return val == 'c'
+enddef
+
+def DefIndexXtra(xtra: string, idx: number, val: string): bool
+ call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
+ return val == 'c'
+enddef
+
+def Test_defer_in_funcref()
+ assert_equal(2, indexof(['a', 'b', 'c'], function('g:FuncIndex')))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], g:DefIndex))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], function('g:DefIndex')))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], funcref(g:DefIndex)))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], function(g:DefIndexXtra, ['xtra'])))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+
+ assert_equal(2, indexof(['a', 'b', 'c'], funcref(g:DefIndexXtra, ['xtra'])))
+ assert_false(filereadable('Xentry0'))
+ assert_false(filereadable('Xentry1'))
+ assert_false(filereadable('Xentry2'))
+enddef
+
+func Test_defer_wrong_arguments()
+ call assert_fails('defer delete()', 'E119:')
+ call assert_fails('defer FuncIndex(1)', 'E119:')
+ call assert_fails('defer delete(1, 2, 3)', 'E118:')
+ call assert_fails('defer FuncIndex(1, 2, 3)', 'E118:')
+
+ let lines =<< trim END
+ def DeferFunc0()
+ defer delete()
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E119:')
+ let lines =<< trim END
+ def DeferFunc3()
+ defer delete(1, 2, 3)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E118:')
+ let lines =<< trim END
+ def DeferFunc2()
+ defer delete(1, 2)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+
+ def g:FuncOneArg(arg: string)
+ echo arg
+ enddef
+
+ let lines =<< trim END
+ def DeferUserFunc0()
+ defer g:FuncOneArg()
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E119:')
+ let lines =<< trim END
+ def DeferUserFunc2()
+ defer g:FuncOneArg(1, 2)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E118:')
+ let lines =<< trim END
+ def DeferUserFunc1()
+ defer g:FuncOneArg(1)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_usercommands.vim b/src/testdir/test_usercommands.vim
new file mode 100644
index 0000000..1e18567
--- /dev/null
+++ b/src/testdir/test_usercommands.vim
@@ -0,0 +1,938 @@
+" Tests for user defined commands
+
+import './vim9.vim' as v9
+
+source check.vim
+source screendump.vim
+
+" Test for <mods> in user defined commands
+function Test_cmdmods()
+ let g:mods = ''
+
+ command! -nargs=* MyCmd let g:mods = '<mods>'
+
+ MyCmd
+ call assert_equal('', g:mods)
+ aboveleft MyCmd
+ call assert_equal('aboveleft', g:mods)
+ abo MyCmd
+ call assert_equal('aboveleft', g:mods)
+ belowright MyCmd
+ call assert_equal('belowright', g:mods)
+ bel MyCmd
+ call assert_equal('belowright', g:mods)
+ botright MyCmd
+ call assert_equal('botright', g:mods)
+ bo MyCmd
+ call assert_equal('botright', g:mods)
+ browse MyCmd
+ call assert_equal('browse', g:mods)
+ bro MyCmd
+ call assert_equal('browse', g:mods)
+ confirm MyCmd
+ call assert_equal('confirm', g:mods)
+ conf MyCmd
+ call assert_equal('confirm', g:mods)
+ hide MyCmd
+ call assert_equal('hide', g:mods)
+ hid MyCmd
+ call assert_equal('hide', g:mods)
+ keepalt MyCmd
+ call assert_equal('keepalt', g:mods)
+ keepa MyCmd
+ call assert_equal('keepalt', g:mods)
+ keepjumps MyCmd
+ call assert_equal('keepjumps', g:mods)
+ keepj MyCmd
+ call assert_equal('keepjumps', g:mods)
+ keepmarks MyCmd
+ call assert_equal('keepmarks', g:mods)
+ kee MyCmd
+ call assert_equal('keepmarks', g:mods)
+ keeppatterns MyCmd
+ call assert_equal('keeppatterns', g:mods)
+ keepp MyCmd
+ call assert_equal('keeppatterns', g:mods)
+ leftabove MyCmd " results in :aboveleft
+ call assert_equal('aboveleft', g:mods)
+ lefta MyCmd
+ call assert_equal('aboveleft', g:mods)
+ lockmarks MyCmd
+ call assert_equal('lockmarks', g:mods)
+ loc MyCmd
+ call assert_equal('lockmarks', g:mods)
+ noautocmd MyCmd
+ call assert_equal('noautocmd', g:mods)
+ noa MyCmd
+ call assert_equal('noautocmd', g:mods)
+ noswapfile MyCmd
+ call assert_equal('noswapfile', g:mods)
+ nos MyCmd
+ call assert_equal('noswapfile', g:mods)
+ rightbelow MyCmd " results in :belowright
+ call assert_equal('belowright', g:mods)
+ rightb MyCmd
+ call assert_equal('belowright', g:mods)
+ " sandbox MyCmd
+ silent MyCmd
+ call assert_equal('silent', g:mods)
+ sil MyCmd
+ call assert_equal('silent', g:mods)
+ silent! MyCmd
+ call assert_equal('silent!', g:mods)
+ sil! MyCmd
+ call assert_equal('silent!', g:mods)
+ tab MyCmd
+ call assert_equal('tab', g:mods)
+ 0tab MyCmd
+ call assert_equal('0tab', g:mods)
+ tab split
+ tab MyCmd
+ call assert_equal('tab', g:mods)
+ 1tab MyCmd
+ call assert_equal('1tab', g:mods)
+ tabprev
+ tab MyCmd
+ call assert_equal('tab', g:mods)
+ 2tab MyCmd
+ call assert_equal('2tab', g:mods)
+ 2tabclose
+ topleft MyCmd
+ call assert_equal('topleft', g:mods)
+ to MyCmd
+ call assert_equal('topleft', g:mods)
+ unsilent MyCmd
+ call assert_equal('unsilent', g:mods)
+ uns MyCmd
+ call assert_equal('unsilent', g:mods)
+ verbose MyCmd
+ call assert_equal('verbose', g:mods)
+ verb MyCmd
+ call assert_equal('verbose', g:mods)
+ 0verbose MyCmd
+ call assert_equal('0verbose', g:mods)
+ 3verbose MyCmd
+ call assert_equal('3verbose', g:mods)
+ 999verbose MyCmd
+ call assert_equal('999verbose', g:mods)
+ vertical MyCmd
+ call assert_equal('vertical', g:mods)
+ vert MyCmd
+ call assert_equal('vertical', g:mods)
+ horizontal MyCmd
+ call assert_equal('horizontal', g:mods)
+ hor MyCmd
+ call assert_equal('horizontal', g:mods)
+
+ aboveleft belowright botright browse confirm hide keepalt keepjumps
+ \ keepmarks keeppatterns lockmarks noautocmd noswapfile silent
+ \ tab topleft unsilent verbose vertical MyCmd
+
+ call assert_equal('browse confirm hide keepalt keepjumps ' .
+ \ 'keepmarks keeppatterns lockmarks noswapfile unsilent noautocmd ' .
+ \ 'silent verbose aboveleft belowright botright tab topleft vertical',
+ \ g:mods)
+
+ let g:mods = ''
+ command! -nargs=* MyQCmd let g:mods .= '<q-mods> '
+
+ vertical MyQCmd
+ call assert_equal('"vertical" ', g:mods)
+
+ delcommand MyCmd
+ delcommand MyQCmd
+ unlet g:mods
+endfunction
+
+func SaveCmdArgs(...)
+ let g:args = a:000
+endfunc
+
+func Test_f_args()
+ command -nargs=* TestFArgs call SaveCmdArgs(<f-args>)
+
+ TestFArgs
+ call assert_equal([], g:args)
+
+ TestFArgs one two three
+ call assert_equal(['one', 'two', 'three'], g:args)
+
+ TestFArgs one\\two three
+ call assert_equal(['one\two', 'three'], g:args)
+
+ TestFArgs one\ two three
+ call assert_equal(['one two', 'three'], g:args)
+
+ TestFArgs one\"two three
+ call assert_equal(['one\"two', 'three'], g:args)
+
+ delcommand TestFArgs
+endfunc
+
+func Test_q_args()
+ command -nargs=* TestQArgs call SaveCmdArgs(<q-args>)
+
+ TestQArgs
+ call assert_equal([''], g:args)
+
+ TestQArgs one two three
+ call assert_equal(['one two three'], g:args)
+
+ TestQArgs one\\two three
+ call assert_equal(['one\\two three'], g:args)
+
+ TestQArgs one\ two three
+ call assert_equal(['one\ two three'], g:args)
+
+ TestQArgs one\"two three
+ call assert_equal(['one\"two three'], g:args)
+
+ delcommand TestQArgs
+endfunc
+
+func Test_reg_arg()
+ command -nargs=* -reg TestRegArg call SaveCmdArgs("<reg>", "<register>")
+
+ TestRegArg
+ call assert_equal(['', ''], g:args)
+
+ TestRegArg x
+ call assert_equal(['x', 'x'], g:args)
+
+ delcommand TestRegArg
+endfunc
+
+func Test_no_arg()
+ command -nargs=* TestNoArg call SaveCmdArgs("<args>", "<>", "<x>", "<lt>")
+
+ TestNoArg
+ call assert_equal(['', '<>', '<x>', '<'], g:args)
+
+ TestNoArg one
+ call assert_equal(['one', '<>', '<x>', '<'], g:args)
+
+ delcommand TestNoArg
+endfunc
+
+func Test_range_arg()
+ command -range TestRangeArg call SaveCmdArgs(<range>, <line1>, <line2>)
+ new
+ call setline(1, range(100))
+ let lnum = line('.')
+
+ TestRangeArg
+ call assert_equal([0, lnum, lnum], g:args)
+
+ 99TestRangeArg
+ call assert_equal([1, 99, 99], g:args)
+
+ 88,99TestRangeArg
+ call assert_equal([2, 88, 99], g:args)
+
+ call assert_fails('102TestRangeArg', 'E16:')
+
+ bwipe!
+ delcommand TestRangeArg
+endfunc
+
+func Test_Ambiguous()
+ command Doit let g:didit = 'yes'
+ command Dothat let g:didthat = 'also'
+ call assert_fails('Do', 'E464:')
+ Doit
+ call assert_equal('yes', g:didit)
+ Dothat
+ call assert_equal('also', g:didthat)
+ unlet g:didit
+ unlet g:didthat
+
+ delcommand Doit
+ Do
+ call assert_equal('also', g:didthat)
+ delcommand Dothat
+
+ call assert_fails("\x4ei\041", ' you demand a ')
+endfunc
+
+func Test_redefine_on_reload()
+ call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists', 'D')
+ call assert_equal(0, exists(':ExistingCommand'))
+ source Xcommandexists
+ call assert_equal(2, exists(':ExistingCommand'))
+ " Redefining a command when reloading a script is OK.
+ source Xcommandexists
+ call assert_equal(2, exists(':ExistingCommand'))
+
+ " But redefining in another script is not OK.
+ call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists2', 'D')
+ call assert_fails('source Xcommandexists2', 'E174:')
+
+ " And defining twice in one script is not OK.
+ delcommand ExistingCommand
+ call assert_equal(0, exists(':ExistingCommand'))
+ call writefile([
+ \ 'command ExistingCommand echo "yes"',
+ \ 'command ExistingCommand echo "no"',
+ \ ], 'Xcommandexists')
+ call assert_fails('source Xcommandexists', 'E174:')
+ call assert_equal(2, exists(':ExistingCommand'))
+
+ delcommand ExistingCommand
+endfunc
+
+func Test_CmdUndefined()
+ call assert_fails('Doit', 'E492:')
+ au CmdUndefined Doit :command Doit let g:didit = 'yes'
+ Doit
+ call assert_equal('yes', g:didit)
+ delcommand Doit
+
+ call assert_fails('Dothat', 'E492:')
+ au CmdUndefined * let g:didnot = 'yes'
+ call assert_fails('Dothat', 'E492:')
+ call assert_equal('yes', g:didnot)
+endfunc
+
+func Test_CmdErrors()
+ call assert_fails('com! docmd :', 'E183:')
+ call assert_fails('com! \<Tab> :', 'E182:')
+ call assert_fails('com! _ :', 'E182:')
+ call assert_fails('com! X :', 'E841:')
+ call assert_fails('com! - DoCmd :', 'E175:')
+ call assert_fails('com! -xxx DoCmd :', 'E181:')
+ call assert_fails('com! -addr DoCmd :', 'E179:')
+ call assert_fails('com! -addr=asdf DoCmd :', 'E180:')
+ call assert_fails('com! -complete DoCmd :', 'E179:')
+ call assert_fails('com! -complete=xxx DoCmd :', 'E180:')
+ call assert_fails('com! -complete=custom DoCmd :', 'E467:')
+ call assert_fails('com! -complete=customlist DoCmd :', 'E467:')
+ call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:')
+ call assert_fails('com! -nargs=x DoCmd :', 'E176:')
+ call assert_fails('com! -count=1 -count=2 DoCmd :', 'E177:')
+ call assert_fails('com! -count=x DoCmd :', 'E178:')
+ call assert_fails('com! -range=x DoCmd :', 'E178:')
+
+ com! -complete=file DoCmd :
+ call assert_match('E1208:', v:warningmsg)
+ let v:warningmsg = ''
+ com! -nargs=0 -complete=file DoCmd :
+ call assert_match('E1208:', v:warningmsg)
+
+ let lines =<< trim END
+ vim9script
+ com! -complete=file DoCmd :
+ END
+ call v9.CheckScriptFailure(lines, 'E1208', 2)
+
+ let lines =<< trim END
+ vim9script
+ com! -nargs=0 -complete=file DoCmd :
+ END
+ call v9.CheckScriptFailure(lines, 'E1208', 2)
+
+ com! -nargs=0 DoCmd :
+ call assert_fails('DoCmd x', 'E488:')
+
+ com! -nargs=1 DoCmd :
+ call assert_fails('DoCmd', 'E471:')
+
+ com! -nargs=+ DoCmd :
+ call assert_fails('DoCmd', 'E471:')
+
+ call assert_fails('com DoCmd :', 'E174:')
+ comclear
+ call assert_fails('delcom DoCmd', 'E184:')
+
+ " These used to leak memory
+ call assert_fails('com! -complete=custom,CustomComplete _ :', 'E182:')
+ call assert_fails('com! -complete=custom,CustomComplete docmd :', 'E183:')
+ call assert_fails('com! -complete=custom,CustomComplete -xxx DoCmd :', 'E181:')
+endfunc
+
+func CustomComplete(A, L, P)
+ return "January\nFebruary\nMars\n"
+endfunc
+
+func CustomCompleteList(A, L, P)
+ return [ "Monday", "Tuesday", "Wednesday", {}, test_null_string()]
+endfunc
+
+func Test_CmdCompletion()
+ call feedkeys(":com -\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com -addr bang bar buffer complete count keepscript nargs range register', @:)
+
+ call feedkeys(":com -nargs=0 -\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com -nargs=0 -addr bang bar buffer complete count keepscript nargs range register', @:)
+
+ call feedkeys(":com -nargs=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com -nargs=* + 0 1 ?', @:)
+
+ call feedkeys(":com -addr=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com -addr=arguments buffers lines loaded_buffers other quickfix tabs windows', @:)
+
+ call feedkeys(":com -complete=co\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com -complete=color command compiler', @:)
+
+ " try completion for unsupported argument values
+ call feedkeys(":com -newarg=\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"com -newarg=\t", @:)
+
+ " command completion after the name in a user defined command
+ call feedkeys(":com MyCmd chist\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"com MyCmd chistory", @:)
+
+ " delete the Check commands to avoid them showing up
+ call feedkeys(":com Check\<C-A>\<C-B>\"\<CR>", 'tx')
+ let cmds = substitute(@:, '"com ', '', '')->split()
+ for cmd in cmds
+ exe 'delcommand ' .. cmd
+ endfor
+ delcommand MissingFeature
+
+ command! DoCmd1 :
+ command! DoCmd2 :
+ call feedkeys(":com \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com DoCmd1 DoCmd2', @:)
+
+ call feedkeys(":DoC\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd1 DoCmd2', @:)
+
+ call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"delcom DoCmd1 DoCmd2', @:)
+
+ " try argument completion for a command without completion
+ call feedkeys(":DoCmd1 \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"DoCmd1 \t", @:)
+
+ delcom DoCmd1
+ call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"delcom DoCmd2', @:)
+
+ call feedkeys(":com DoC\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com DoCmd2', @:)
+
+ delcom DoCmd2
+ call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"delcom DoC', @:)
+
+ call feedkeys(":com DoC\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"com DoC', @:)
+
+ com! -nargs=1 -complete=behave DoCmd :
+ call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd mswin xterm', @:)
+
+ " Test for file name completion
+ com! -nargs=1 -complete=file DoCmd :
+ call feedkeys(":DoCmd READM\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd README.txt', @:)
+
+ " Test for buffer name completion
+ com! -nargs=1 -complete=buffer DoCmd :
+ let bnum = bufadd('BufForUserCmd')
+ call setbufvar(bnum, '&buflisted', 1)
+ call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd BufForUserCmd', @:)
+ bwipe BufForUserCmd
+ call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd BufFor', @:)
+
+ com! -nargs=* -complete=custom,CustomComplete DoCmd :
+ call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd January February Mars', @:)
+
+ com! -nargs=? -complete=customlist,CustomCompleteList DoCmd :
+ call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoCmd Monday Tuesday Wednesday', @:)
+
+ com! -nargs=+ -complete=custom,CustomCompleteList DoCmd :
+ call assert_fails("call feedkeys(':DoCmd \<C-D>', 'tx')", 'E730:')
+
+ com! -nargs=+ -complete=customlist,CustomComp DoCmd :
+ call assert_fails("call feedkeys(':DoCmd \<C-D>', 'tx')", 'E117:')
+
+ " custom completion without a function
+ com! -nargs=? -complete=custom, DoCmd
+ call assert_beeps("call feedkeys(':DoCmd \t', 'tx')")
+
+ " custom completion failure with the wrong function
+ com! -nargs=? -complete=custom,min DoCmd
+ call assert_fails("call feedkeys(':DoCmd \t', 'tx')", 'E118:')
+
+ " custom completion for a pattern with a backslash
+ let g:ArgLead = ''
+ func! CustCompl(A, L, P)
+ let g:ArgLead = a:A
+ return ['one', 'two', 'three']
+ endfunc
+ com! -nargs=? -complete=customlist,CustCompl DoCmd
+ call feedkeys(":DoCmd a\\\t", 'xt')
+ call assert_equal('a\', g:ArgLead)
+ delfunc CustCompl
+
+ delcom DoCmd
+endfunc
+
+func CallExecute(A, L, P)
+ " Drop first '\n'
+ return execute('echo "hi"')[1:]
+endfunc
+
+func Test_use_execute_in_completion()
+ command! -nargs=* -complete=custom,CallExecute DoExec :
+ call feedkeys(":DoExec \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"DoExec hi', @:)
+ delcommand DoExec
+endfunc
+
+func Test_addr_all()
+ command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(line('$'), g:a2)
+
+ command! -addr=arguments DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ args one two three
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(3, g:a2)
+
+ command! -addr=buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ for low in range(1, bufnr('$'))
+ if buflisted(low)
+ break
+ endif
+ endfor
+ call assert_equal(low, g:a1)
+ call assert_equal(bufnr('$'), g:a2)
+
+ command! -addr=loaded_buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ for low in range(1, bufnr('$'))
+ if bufloaded(low)
+ break
+ endif
+ endfor
+ call assert_equal(low, g:a1)
+ for up in range(bufnr('$'), 1, -1)
+ if bufloaded(up)
+ break
+ endif
+ endfor
+ call assert_equal(up, g:a2)
+
+ command! -addr=windows DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ new
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(winnr('$'), g:a2)
+ bwipe
+
+ command! -addr=tabs DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ tabnew
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(len(gettabinfo()), g:a2)
+ bwipe
+
+ command! -addr=other DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ DoSomething
+ call assert_equal(line('.'), g:a1)
+ call assert_equal(line('.'), g:a2)
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(line('$'), g:a2)
+
+ delcommand DoSomething
+endfunc
+
+func Test_command_list()
+ command! DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :",
+ \ execute('command DoCmd'))
+
+ " Test with various -range= and -count= argument values.
+ command! -range DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . :",
+ \ execute('command DoCmd'))
+ command! -range=% DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 % :",
+ \ execute('command! DoCmd'))
+ command! -range=2 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 2 :",
+ \ execute('command DoCmd'))
+ command! -count=2 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 2c ? :",
+ \ execute('command DoCmd'))
+
+ " Test with various -addr= argument values.
+ command! -addr=lines DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . :",
+ \ execute('command DoCmd'))
+ command! -addr=arguments DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . arg :",
+ \ execute('command DoCmd'))
+ command! -addr=buffers DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . buf :",
+ \ execute('command DoCmd'))
+ command! -addr=loaded_buffers DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . load :",
+ \ execute('command DoCmd'))
+ command! -addr=windows DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . win :",
+ \ execute('command DoCmd'))
+ command! -addr=tabs DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . tab :",
+ \ execute('command DoCmd'))
+ command! -addr=other DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . ? :",
+ \ execute('command DoCmd'))
+
+ " Test with various -complete= argument values (non-exhaustive list)
+ command! -nargs=1 -complete=arglist DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 1 arglist :",
+ \ execute('command DoCmd'))
+ command! -nargs=* -complete=augroup DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd * augroup :",
+ \ execute('command DoCmd'))
+ command! -nargs=? -complete=custom,CustomComplete DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd ? custom :",
+ \ execute('command DoCmd'))
+ command! -nargs=+ -complete=customlist,CustomComplete DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd + customlist :",
+ \ execute('command DoCmd'))
+
+ " Test with various -narg= argument values.
+ command! -nargs=0 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -nargs=1 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 1 :",
+ \ execute('command DoCmd'))
+ command! -nargs=* DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd * :",
+ \ execute('command DoCmd'))
+ command! -nargs=? DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd ? :",
+ \ execute('command DoCmd'))
+ command! -nargs=+ DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd + :",
+ \ execute('command DoCmd'))
+
+ " Test with other arguments.
+ command! -bang DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n! DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -bar DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n| DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -register DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n\" DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -buffer DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\nb DoCmd 0 :"
+ \ .. "\n\" DoCmd 0 :",
+ \ execute('command DoCmd'))
+ comclear
+
+ " Test with many args.
+ command! -bang -bar -register -buffer -nargs=+ -complete=environment -addr=windows -count=3 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n!\"b|DoCmd + 3c win environment :",
+ \ execute('command DoCmd'))
+ comclear
+
+ " Test with special characters in command definition.
+ command! DoCmd :<cr><tab><c-d>
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :<CR><Tab><C-D>",
+ \ execute('command DoCmd'))
+
+ " Test output in verbose mode.
+ command! -nargs=+ -complete=customlist,SomeFunc DoCmd :ls
+ call assert_match("^\n"
+ \ .. " Name Args Address Complete Definition\n"
+ \ .. " DoCmd + customlist,SomeFunc :ls\n"
+ \ .. "\tLast set from .*/test_usercommands.vim line \\d\\+$",
+ \ execute('verbose command DoCmd'))
+
+ comclear
+ call assert_equal("\nNo user-defined commands found", execute(':command Xxx'))
+ call assert_equal("\nNo user-defined commands found", execute('command'))
+endfunc
+
+" Test for a custom user completion returning the wrong value type
+func Test_usercmd_custom()
+ func T1(a, c, p)
+ return "a\nb\n"
+ endfunc
+ command -nargs=* -complete=customlist,T1 TCmd1
+ call assert_fails('call feedkeys(":TCmd1 \<C-A>\<C-B>\"\<CR>", "xt")', 'E1303: Custom list completion function does not return a List but a string')
+ call assert_equal('"TCmd1 ', @:)
+ delcommand TCmd1
+ delfunc T1
+
+ func T2(a, c, p)
+ return {}
+ endfunc
+ command -nargs=* -complete=customlist,T2 TCmd2
+ call assert_fails('call feedkeys(":TCmd2 \<C-A>\<C-B>\"\<CR>", "xt")', 'E1303: Custom list completion function does not return a List but a dict')
+ call assert_equal('"TCmd2 ', @:)
+ delcommand TCmd2
+ delfunc T2
+endfunc
+
+func Test_usercmd_with_block()
+ command DoSomething {
+ g:didit = 'yes' # comment
+ # comment line
+ g:didmore = 'more'
+ }
+ DoSomething
+ call assert_equal('yes', g:didit)
+ call assert_equal('more', g:didmore)
+ unlet g:didit
+ unlet g:didmore
+ delcommand DoSomething
+
+ command DoMap {
+ echo [1, 2, 3]->map((_, v) => v + 1)
+ }
+ DoMap
+ delcommand DoMap
+
+ let lines =<< trim END
+ command DoesNotEnd {
+ echo 'hello'
+ END
+ call v9.CheckScriptFailure(lines, 'E1026:')
+ delcommand DoesNotEnd
+
+ let lines =<< trim END
+ command HelloThere {
+ echo 'hello' | echo 'there'
+ }
+ HelloThere
+ END
+ call v9.CheckScriptSuccess(lines)
+ delcommand HelloThere
+
+ let lines =<< trim END
+ command EchoCond {
+ const test: string = true
+ ? 'true'
+ : 'false'
+ g:result = test
+ }
+ EchoCond
+ END
+ call v9.CheckScriptSuccess(lines)
+ call assert_equal('true', g:result)
+ unlet g:result
+
+ call feedkeys(":EchoCond\<CR>", 'xt')
+ call assert_equal('true', g:result)
+
+ delcommand EchoCond
+ unlet g:result
+
+ let lines =<< trim END
+ command BadCommand {
+ echo {
+ 'key': 'value',
+ }
+ }
+ BadCommand
+ END
+ call v9.CheckScriptFailure(lines, 'E1128:')
+ delcommand BadCommand
+endfunc
+
+func Test_delcommand_buffer()
+ command Global echo 'global'
+ command -buffer OneBuffer echo 'one'
+ new
+ command -buffer TwoBuffer echo 'two'
+ call assert_equal(0, exists(':OneBuffer'))
+ call assert_equal(2, exists(':Global'))
+ call assert_equal(2, exists(':TwoBuffer'))
+ delcommand -buffer TwoBuffer
+ call assert_equal(0, exists(':TwoBuffer'))
+ call assert_fails('delcommand -buffer Global', 'E1237:')
+ call assert_fails('delcommand -buffer OneBuffer', 'E1237:')
+ bwipe!
+ call assert_equal(2, exists(':OneBuffer'))
+ delcommand -buffer OneBuffer
+ call assert_equal(0, exists(':OneBuffer'))
+ call assert_fails('delcommand -buffer Global', 'E1237:')
+ delcommand Global
+ call assert_equal(0, exists(':Global'))
+endfunc
+
+def Test_count_with_quotes()
+ command -count GetCount g:nr = <count>
+ execute("GetCount 1'2")
+ assert_equal(12, g:nr)
+ execute("GetCount 1'234'567")
+ assert_equal(1'234'567, g:nr)
+
+ execute("GetCount 1'234'567'890'123'456'789'012")
+ assert_equal(v:sizeoflong == 8 ? 9223372036854775807 : 2147483647, g:nr)
+
+ # TODO: test with negative number once this is supported
+
+ assert_fails("GetCount '12", "E488:")
+ assert_fails("GetCount 12'", "E488:")
+ assert_fails("GetCount 1''2", "E488:")
+
+ assert_fails(":1'2GetCount", 'E492:')
+ new
+ setline(1, 'text')
+ normal ma
+ execute(":1, 'aprint")
+ bwipe!
+
+ unlet g:nr
+ delcommand GetCount
+enddef
+
+func DefCmd(name)
+ if len(a:name) > 30
+ return
+ endif
+ exe 'command ' .. a:name .. ' call DefCmd("' .. a:name .. 'x")'
+ echo a:name
+ exe a:name
+endfunc
+
+func Test_recursive_define()
+ call DefCmd('Command')
+
+ let name = 'Command'
+ while len(name) <= 30
+ exe 'delcommand ' .. name
+ let name ..= 'x'
+ endwhile
+endfunc
+
+" Test for using buffer-local ambiguous user-defined commands
+func Test_buflocal_ambiguous_usercmd()
+ new
+ command -buffer -nargs=1 -complete=sign TestCmd1 echo "Hello"
+ command -buffer -nargs=1 -complete=sign TestCmd2 echo "World"
+
+ call assert_fails("call feedkeys(':TestCmd\<CR>', 'xt')", 'E464:')
+ call feedkeys(":TestCmd \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"TestCmd ', @:)
+
+ delcommand TestCmd1
+ delcommand TestCmd2
+ bw!
+endfunc
+
+" Test for using buffer-local user command from cmdwin.
+func Test_buflocal_usercmd_cmdwin()
+ new
+ command -buffer TestCmd edit Test
+ " This used to crash Vim
+ call assert_fails("norm q::TestCmd\<CR>", 'E11:')
+ bw!
+endfunc
+
+" Test for using a multibyte character in a user command
+func Test_multibyte_in_usercmd()
+ command SubJapanesePeriodToDot exe "%s/\u3002/./g"
+ new
+ call setline(1, "Hello\u3002")
+ SubJapanesePeriodToDot
+ call assert_equal('Hello.', getline(1))
+ bw!
+ delcommand SubJapanesePeriodToDot
+endfunc
+
+" Declaring a variable in a {} uses Vim9 script rules, even when defined in a
+" legacy script.
+func Test_block_declaration_legacy_script()
+ let lines =<< trim END
+ command -range Rename {
+ var save = @a
+ @a = 'something'
+ g:someExpr = @a
+ @a = save
+ }
+ END
+ call writefile(lines, 'Xlegacy', 'D')
+ source Xlegacy
+
+ let lines =<< trim END
+ let @a = 'saved'
+ Rename
+ call assert_equal('something', g:someExpr)
+ call assert_equal('saved', @a)
+
+ let g:someExpr = 'xxx'
+ let @a = 'also'
+ Rename
+ call assert_equal('something', g:someExpr)
+ call assert_equal('also', @a)
+ END
+ call writefile(lines, 'Xother', 'D')
+ source Xother
+
+ unlet g:someExpr
+ delcommand Rename
+endfunc
+
+func Test_comclear_while_listing()
+ call CheckRunVimInTerminal()
+
+ let lines =<< trim END
+ set nocompatible
+ comclear
+ for i in range(1, 999)
+ exe 'command ' .. 'Foo' .. i .. ' bar'
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('comclear')})
+ END
+ call writefile(lines, 'Xcommandclear', 'D')
+ let buf = RunVimInTerminal('-S Xcommandclear', {'rows': 10})
+
+ " this was using freed memory
+ call term_sendkeys(buf, ":command\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "j")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "G")
+ call term_sendkeys(buf, "\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_utf8.vim b/src/testdir/test_utf8.vim
new file mode 100644
index 0000000..16bf60d
--- /dev/null
+++ b/src/testdir/test_utf8.vim
@@ -0,0 +1,331 @@
+" Tests for Unicode manipulations
+
+source check.vim
+source view_util.vim
+source screendump.vim
+
+" Visual block Insert adjusts for multi-byte char
+func Test_visual_block_insert()
+ new
+ call setline(1, ["aaa", "ã‚ã‚ã‚", "bbb"])
+ exe ":norm! gg0l\<C-V>jjIx\<Esc>"
+ call assert_equal(['axaa', ' xã‚ã‚ã‚', 'bxbb'], getline(1, '$'))
+ bwipeout!
+endfunc
+
+" Test for built-in functions strchars() and strcharlen()
+func Test_strchars()
+ let inp = ["a", "ã‚ã„a", "A\u20dd", "A\u20dd\u20dd", "\u20dd"]
+ let exp = [[1, 1, 1], [3, 3, 3], [2, 2, 1], [3, 3, 1], [1, 1, 1]]
+ for i in range(len(inp))
+ call assert_equal(exp[i][0], strchars(inp[i]))
+ call assert_equal(exp[i][1], inp[i]->strchars(0))
+ call assert_equal(exp[i][2], strchars(inp[i], 1))
+ endfor
+
+ let exp = [1, 3, 1, 1, 1]
+ for i in range(len(inp))
+ call assert_equal(exp[i], inp[i]->strcharlen())
+ call assert_equal(exp[i], strcharlen(inp[i]))
+ endfor
+
+ call assert_fails("let v=strchars('abc', [])", 'E745:')
+ call assert_fails("let v=strchars('abc', 2)", 'E1023:')
+endfunc
+
+" Test for customlist completion
+func CustomComplete1(lead, line, pos)
+ return ['ã‚', 'ã„']
+endfunc
+
+func CustomComplete2(lead, line, pos)
+ return ['ã‚ãŸã—', 'ã‚ãŸã¾', 'ã‚ãŸã‚Šã‚']
+endfunc
+
+func CustomComplete3(lead, line, pos)
+ return ['Nã“', 'Nã‚“', 'Nã¶']
+endfunc
+
+func Test_customlist_completion()
+ command -nargs=1 -complete=customlist,CustomComplete1 Test1 echo
+ call feedkeys(":Test1 \<C-L>\<C-B>\"\<CR>", 'itx')
+ call assert_equal('"Test1 ', getreg(':'))
+
+ command -nargs=1 -complete=customlist,CustomComplete2 Test2 echo
+ call feedkeys(":Test2 \<C-L>\<C-B>\"\<CR>", 'itx')
+ call assert_equal('"Test2 ã‚ãŸ', getreg(':'))
+
+ command -nargs=1 -complete=customlist,CustomComplete3 Test3 echo
+ call feedkeys(":Test3 \<C-L>\<C-B>\"\<CR>", 'itx')
+ call assert_equal('"Test3 N', getreg(':'))
+
+ call garbagecollect(1)
+endfunc
+
+" Yank one 3 byte character and check the mark columns.
+func Test_getvcol()
+ new
+ call setline(1, "x\u2500x")
+ normal 0lvy
+ call assert_equal(2, col("'["))
+ call assert_equal(4, col("']"))
+ call assert_equal(2, virtcol("'["))
+ call assert_equal(2, virtcol("']"))
+endfunc
+
+func Test_list2str_str2list_utf8()
+ " One Unicode codepoint
+ let s = "\u3042\u3044"
+ let l = [0x3042, 0x3044]
+ call assert_equal(l, str2list(s, 1))
+ call assert_equal(s, list2str(l, 1))
+ if &enc ==# 'utf-8'
+ call assert_equal(str2list(s), str2list(s, 1))
+ call assert_equal(list2str(l), list2str(l, 1))
+ endif
+
+ " With composing characters
+ let s = "\u304b\u3099\u3044"
+ let l = [0x304b, 0x3099, 0x3044]
+ call assert_equal(l, str2list(s, 1))
+ call assert_equal(s, l->list2str(1))
+ if &enc ==# 'utf-8'
+ call assert_equal(str2list(s), str2list(s, 1))
+ call assert_equal(list2str(l), list2str(l, 1))
+ endif
+
+ " Null list is the same as an empty list
+ call assert_equal('', list2str([]))
+ call assert_equal('', list2str(test_null_list()))
+endfunc
+
+func Test_list2str_str2list_latin1()
+ " When 'encoding' is not multi-byte can still get utf-8 string.
+ " But we need to create the utf-8 string while 'encoding' is utf-8.
+ let s = "\u3042\u3044"
+ let l = [0x3042, 0x3044]
+
+ let save_encoding = &encoding
+ set encoding=latin1
+
+ let lres = str2list(s, 1)
+ let sres = list2str(l, 1)
+ call assert_equal([65, 66, 67], str2list("ABC"))
+
+ " Try converting a list to a string in latin-1 encoding
+ call assert_equal([1, 2, 3], str2list(list2str([1, 2, 3])))
+
+ let &encoding = save_encoding
+ call assert_equal(l, lres)
+ call assert_equal(s, sres)
+endfunc
+
+func Test_screenchar_utf8()
+ new
+
+ " 1-cell, with composing characters
+ call setline(1, ["ABC\u0308"])
+ redraw
+ call assert_equal([0x0041], screenchars(1, 1))
+ call assert_equal([0x0042], 1->screenchars(2))
+ call assert_equal([0x0043, 0x0308], screenchars(1, 3))
+ call assert_equal("A", screenstring(1, 1))
+ call assert_equal("B", screenstring(1, 2))
+ call assert_equal("C\u0308", screenstring(1, 3))
+
+ " 2-cells, with composing characters
+ let text = "\u3042\u3044\u3046\u3099"
+ call setline(1, text)
+ redraw
+ call assert_equal([0x3042], screenchars(1, 1))
+ call assert_equal([0], screenchars(1, 2))
+ call assert_equal([0x3044], screenchars(1, 3))
+ call assert_equal([0], screenchars(1, 4))
+ call assert_equal([0x3046, 0x3099], screenchars(1, 5))
+
+ call assert_equal("\u3042", screenstring(1, 1))
+ call assert_equal("", screenstring(1, 2))
+ call assert_equal("\u3044", screenstring(1, 3))
+ call assert_equal("", screenstring(1, 4))
+ call assert_equal("\u3046\u3099", screenstring(1, 5))
+
+ call assert_equal([text . ' '], ScreenLines(1, 8))
+
+ bwipe!
+endfunc
+
+func Test_setcellwidths()
+ call setcellwidths([
+ \ [0x1330, 0x1330, 2],
+ \ [9999, 10000, 1],
+ \ [0x1337, 0x1339, 2],
+ \])
+
+ call assert_equal(2, strwidth("\u1330"))
+ call assert_equal(1, strwidth("\u1336"))
+ call assert_equal(2, strwidth("\u1337"))
+ call assert_equal(2, strwidth("\u1339"))
+ call assert_equal(1, strwidth("\u133a"))
+
+ for aw in ['single', 'double']
+ exe 'set ambiwidth=' . aw
+ " Handle \u0080 to \u009F as control chars even on MS-Windows.
+ set isprint=@,161-255
+
+ call setcellwidths([])
+ " Control chars
+ call assert_equal(4, strwidth("\u0081"))
+ call assert_equal(6, strwidth("\uFEFF"))
+ " Ambiguous width chars
+ call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u00A1"))
+ call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u2010"))
+
+ call setcellwidths([[0x81, 0x81, 1], [0xA1, 0xA1, 1],
+ \ [0x2010, 0x2010, 1], [0xFEFF, 0xFEFF, 1]])
+ " Control chars
+ call assert_equal(4, strwidth("\u0081"))
+ call assert_equal(6, strwidth("\uFEFF"))
+ " Ambiguous width chars
+ call assert_equal(1, strwidth("\u00A1"))
+ call assert_equal(1, strwidth("\u2010"))
+
+ call setcellwidths([[0x81, 0x81, 2], [0xA1, 0xA1, 2],
+ \ [0x2010, 0x2010, 2], [0xFEFF, 0xFEFF, 2]])
+ " Control chars
+ call assert_equal(4, strwidth("\u0081"))
+ call assert_equal(6, strwidth("\uFEFF"))
+ " Ambiguous width chars
+ call assert_equal(2, strwidth("\u00A1"))
+ call assert_equal(2, strwidth("\u2010"))
+ endfor
+ set ambiwidth& isprint&
+
+ call setcellwidths([])
+
+ call assert_fails('call setcellwidths(1)', 'E1211:')
+
+ call assert_fails('call setcellwidths([1, 2, 0])', 'E1109:')
+
+ call assert_fails('call setcellwidths([[0x101]])', 'E1110:')
+ call assert_fails('call setcellwidths([[0x101, 0x102]])', 'E1110:')
+ call assert_fails('call setcellwidths([[0x101, 0x102, 1, 4]])', 'E1110:')
+ call assert_fails('call setcellwidths([["a"]])', 'E1110:')
+
+ call assert_fails('call setcellwidths([[0x102, 0x101, 1]])', 'E1111:')
+
+ call assert_fails('call setcellwidths([[0x101, 0x102, 0]])', 'E1112:')
+ call assert_fails('call setcellwidths([[0x101, 0x102, 3]])', 'E1112:')
+
+ call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x115, 0x116, 2]])', 'E1113:')
+ call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x122, 0x123, 2]])', 'E1113:')
+
+ call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:')
+
+ set listchars=tab:--\\u2192
+ call assert_fails('call setcellwidths([[0x2192, 0x2192, 2]])', 'E834:')
+
+ set fillchars=stl:\\u2501
+ call assert_fails('call setcellwidths([[0x2501, 0x2501, 2]])', 'E835:')
+
+ set listchars&
+ set fillchars&
+ call setcellwidths([])
+endfunc
+
+func Test_getcellwidths()
+ call setcellwidths([])
+ call assert_equal([], getcellwidths())
+
+ let widthlist = [
+ \ [0x1330, 0x1330, 2],
+ \ [9999, 10000, 1],
+ \ [0x1337, 0x1339, 2],
+ \]
+ let widthlistsorted = [
+ \ [0x1330, 0x1330, 2],
+ \ [0x1337, 0x1339, 2],
+ \ [9999, 10000, 1],
+ \]
+ call setcellwidths(widthlist)
+ call assert_equal(widthlistsorted, getcellwidths())
+
+ call setcellwidths([])
+endfunc
+
+func Test_setcellwidths_dump()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, "\ue5ffDesktop")
+ END
+ call writefile(lines, 'XCellwidths', 'D')
+ let buf = RunVimInTerminal('-S XCellwidths', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_setcellwidths_dump_1', {})
+
+ call term_sendkeys(buf, ":call setcellwidths([[0xe5ff, 0xe5ff, 2]])\<CR>")
+ call VerifyScreenDump(buf, 'Test_setcellwidths_dump_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_print_overlong()
+ " Text with more composing characters than MB_MAXBYTES.
+ new
+ call setline(1, 'axxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
+ s/x/\=nr2char(1629)/g
+ print
+ bwipe!
+endfunc
+
+func Test_recording_with_select_mode_utf8()
+ call Run_test_recording_with_select_mode_utf8()
+endfunc
+
+func Run_test_recording_with_select_mode_utf8()
+ new
+
+ " No escaping
+ call feedkeys("qacc12345\<Esc>gH哦\<Esc>q", "tx")
+ call assert_equal("哦", getline(1))
+ call assert_equal("cc12345\<Esc>gH哦\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("哦", getline(1))
+
+ " 固 is 0xE5 0x9B 0xBA where 0x9B is CSI
+ call feedkeys("qacc12345\<Esc>gH固\<Esc>q", "tx")
+ call assert_equal("固", getline(1))
+ call assert_equal("cc12345\<Esc>gH固\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("固", getline(1))
+
+ " å›› is 0xE5 0x9B 0x9B where 0x9B is CSI
+ call feedkeys("qacc12345\<Esc>gHå››\<Esc>q", "tx")
+ call assert_equal("å››", getline(1))
+ call assert_equal("cc12345\<Esc>gHå››\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("å››", getline(1))
+
+ " 倒 is 0xE5 0x80 0x92 where 0x80 is K_SPECIAL
+ call feedkeys("qacc12345\<Esc>gH倒\<Esc>q", "tx")
+ call assert_equal("倒", getline(1))
+ call assert_equal("cc12345\<Esc>gH倒\<Esc>", @a)
+ call setline(1, 'asdf')
+ normal! @a
+ call assert_equal("倒", getline(1))
+
+ bwipe!
+endfunc
+
+" This must be done as one of the last tests, because it starts the GUI, which
+" cannot be undone.
+func Test_zz_recording_with_select_mode_utf8_gui()
+ CheckCanRunGui
+
+ gui -f
+ call Run_test_recording_with_select_mode_utf8()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_utf8_comparisons.vim b/src/testdir/test_utf8_comparisons.vim
new file mode 100644
index 0000000..3431226
--- /dev/null
+++ b/src/testdir/test_utf8_comparisons.vim
@@ -0,0 +1,96 @@
+" Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c)
+" Also test "g~ap".
+
+func Ch(a, op, b, expected)
+ call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected,
+ \ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected))
+endfunc
+
+func Chk(a, b, result)
+ if a:result == 0
+ call Ch(a:a, '==?', a:b, 1)
+ call Ch(a:a, '!=?', a:b, 0)
+ call Ch(a:a, '<=?', a:b, 1)
+ call Ch(a:a, '>=?', a:b, 1)
+ call Ch(a:a, '<?', a:b, 0)
+ call Ch(a:a, '>?', a:b, 0)
+ elseif a:result > 0
+ call Ch(a:a, '==?', a:b, 0)
+ call Ch(a:a, '!=?', a:b, 1)
+ call Ch(a:a, '<=?', a:b, 0)
+ call Ch(a:a, '>=?', a:b, 1)
+ call Ch(a:a, '<?', a:b, 0)
+ call Ch(a:a, '>?', a:b, 1)
+ else
+ call Ch(a:a, '==?', a:b, 0)
+ call Ch(a:a, '!=?', a:b, 1)
+ call Ch(a:a, '<=?', a:b, 1)
+ call Ch(a:a, '>=?', a:b, 0)
+ call Ch(a:a, '<?', a:b, 1)
+ call Ch(a:a, '>?', a:b, 0)
+ endif
+endfunc
+
+func Check(a, b, result)
+ call Chk(a:a, a:b, a:result)
+ call Chk(a:b, a:a, -a:result)
+endfunc
+
+func LT(a, b)
+ call Check(a:a, a:b, -1)
+endfunc
+
+func GT(a, b)
+ call Check(a:a, a:b, 1)
+endfunc
+
+func EQ(a, b)
+ call Check(a:a, a:b, 0)
+endfunc
+
+func Test_comparisons()
+ call EQ('', '')
+ call LT('', 'a')
+ call EQ('abc', 'abc')
+ call EQ('Abc', 'abC')
+ call LT('ab', 'abc')
+ call LT('AB', 'abc')
+ call LT('ab', 'aBc')
+ call EQ('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xb9\xd0\xa6\xd0\xa3\xd0\xba\xd0\x95\xd0\xbd')
+ call LT('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xaf\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd')
+ call EQ('\xe2\x84\xaa', 'k')
+ call LT('\xe2\x84\xaa', 'kkkkkk')
+ call EQ('\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa', 'kkk')
+ call LT('kk', '\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa')
+ call EQ('\xe2\x84\xaa\xe2\x84\xa6k\xe2\x84\xaak\xcf\x89', 'k\xcf\x89\xe2\x84\xaakk\xe2\x84\xa6')
+ call EQ('Abc\x80', 'AbC\x80')
+ call LT('Abc\x80', 'AbC\x81')
+ call LT('Abc', 'AbC\x80')
+ call LT('abc\x80DEF', 'abc\x80def') " case folding stops at the first bad character
+ call LT('\xc3XYZ', '\xc3xyz')
+ call EQ('\xef\xbc\xba', '\xef\xbd\x9a') " FF3A (upper), FF5A (lower)
+ call GT('\xef\xbc\xba', '\xef\xbc\xff') " first string is ok and equals \xef\xbd\x9a after folding, second string is illegal and was left unchanged, then the strings were bytewise compared
+ call LT('\xc3', '\xc3\x83')
+ call EQ('\xc3\xa3xYz', '\xc3\x83XyZ')
+ for n in range(0x60, 0xFF)
+ call LT(printf('xYz\x%.2X', n-1), printf('XyZ\x%.2X', n))
+ endfor
+ for n in range(0x80, 0xBF)
+ call EQ(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n))
+ endfor
+ for n in range(0xC0, 0xFF)
+ call LT(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n))
+ endfor
+endfunc
+
+" test that g~ap changes one paragraph only.
+func Test_gap()
+ new
+ " setup text
+ call feedkeys("iabcd\<cr>\<cr>defg", "tx")
+ " modify only first line
+ call feedkeys("gg0g~ap", "tx")
+ call assert_equal(["ABCD", "", "defg"], getline(1,3))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_vartabs.vim b/src/testdir/test_vartabs.vim
new file mode 100644
index 0000000..e15a072
--- /dev/null
+++ b/src/testdir/test_vartabs.vim
@@ -0,0 +1,458 @@
+" Test for variable tabstops
+
+source check.vim
+CheckFeature vartabs
+
+source view_util.vim
+
+func s:compare_lines(expect, actual)
+ call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
+endfunc
+
+func Test_vartabs()
+ new
+ %d
+
+ " Test normal operation of tabstops ...
+ set ts=4
+ call setline(1, join(split('aaaaa', '\zs'), "\t"))
+ retab 8
+ let expect = "a a\<tab>a a\<tab>a"
+ call assert_equal(expect, getline(1))
+
+ " ... and softtabstops
+ set ts=8 sts=6
+ exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
+ let expect = "b b\<tab> b\<tab> b\<tab>b"
+ call assert_equal(expect, getline(1))
+
+ " Test variable tabstops.
+ set sts=0 vts=4,8,4,8
+ exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
+ retab 8
+ let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c"
+ call assert_equal(expect, getline(1))
+
+ set et vts=4,8,4,8
+ exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
+ let expect = "d d d d d d"
+ call assert_equal(expect, getline(1))
+
+ " Changing ts should have no effect if vts is in use.
+ call cursor(1, 1)
+ set ts=6
+ exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
+ let expect = "e e e e e e"
+ call assert_equal(expect, getline(1))
+
+ " Clearing vts should revert to using ts.
+ set vts=
+ exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
+ let expect = "f f f f f f"
+ call assert_equal(expect, getline(1))
+
+ " Test variable softtabstops.
+ set noet ts=8 vsts=12,2,6
+ exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
+ let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g"
+ call assert_equal(expect, getline(1))
+
+ " Variable tabstops and softtabstops combined.
+ set vsts=6,12,8 vts=4,6,8
+ exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
+ let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, not using vts.
+ set ts=8 sts=0 vts= vsts=
+ exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
+ retab 4
+ let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
+ retab 4
+ let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, not using vts.
+ set ts=6 sts=0 vts= vsts=
+ exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
+ retab 4,8
+ let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
+ retab 4,8
+ let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l"
+ call assert_equal(expect, getline(1))
+
+ " Test for 'retab' with vts
+ set ts=8 sts=0 vts=5,3,6,2 vsts=
+ exe "norm! S l"
+ .retab!
+ call assert_equal("\t\t\t\tl", getline(1))
+
+ " Test for 'retab' with same values as vts
+ set ts=8 sts=0 vts=5,3,6,2 vsts=
+ exe "norm! S l"
+ .retab! 5,3,6,2
+ call assert_equal("\t\t\t\tl", getline(1))
+
+ " Check that global and local values are set.
+ set ts=4 vts=6 sts=8 vsts=10
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that local values only are set.
+ setlocal ts=5 vts=7 sts=9 vsts=11
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that global values only are set.
+ setglobal ts=6 vts=8 sts=10 vsts=12
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 6)
+ call assert_equal(&vts, '8')
+ call assert_equal(&sts, 10)
+ call assert_equal(&vsts, '12')
+ bwipeout!
+
+ set ts& vts& sts& vsts& et&
+ bwipeout!
+endfunc
+
+func Test_retab_invalid_arg()
+ new
+ call setline(1, "\ttext")
+ retab 0
+ call assert_fails("retab -8", 'E487: Argument must be positive')
+ call assert_fails("retab 10000", 'E475:')
+ call assert_fails("retab 720575940379279360", 'E475:')
+ bwipe!
+endfunc
+
+func Test_vartabs_breakindent()
+ CheckOption breakindent
+ new
+ %d
+
+ " Test normal operation of tabstops ...
+ set ts=4
+ call setline(1, join(split('aaaaa', '\zs'), "\t"))
+ retab 8
+ let expect = "a a\<tab>a a\<tab>a"
+ call assert_equal(expect, getline(1))
+
+ " ... and softtabstops
+ set ts=8 sts=6
+ exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b"
+ let expect = "b b\<tab> b\<tab> b\<tab>b"
+ call assert_equal(expect, getline(1))
+
+ " Test variable tabstops.
+ set sts=0 vts=4,8,4,8
+ exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c"
+ retab 8
+ let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c"
+ call assert_equal(expect, getline(1))
+
+ set et vts=4,8,4,8
+ exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d"
+ let expect = "d d d d d d"
+ call assert_equal(expect, getline(1))
+
+ " Changing ts should have no effect if vts is in use.
+ call cursor(1, 1)
+ set ts=6
+ exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e"
+ let expect = "e e e e e e"
+ call assert_equal(expect, getline(1))
+
+ " Clearing vts should revert to using ts.
+ set vts=
+ exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f"
+ let expect = "f f f f f f"
+ call assert_equal(expect, getline(1))
+
+ " Test variable softtabstops.
+ set noet ts=8 vsts=12,2,6
+ exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g"
+ let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g"
+ call assert_equal(expect, getline(1))
+
+ " Variable tabstops and softtabstops combined.
+ set vsts=6,12,8 vts=4,6,8
+ exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h"
+ let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, not using vts.
+ set ts=8 sts=0 vts= vsts=
+ exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i"
+ retab 4
+ let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i"
+ call assert_equal(expect, getline(1))
+
+ " Retab with a single value, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j"
+ retab 4
+ let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, not using vts.
+ set ts=6 sts=0 vts= vsts=
+ exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k"
+ retab 4,8
+ let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k"
+ call assert_equal(expect, getline(1))
+
+ " Retab with multiple values, using vts.
+ set ts=8 sts=0 vts=6 vsts=
+ exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l"
+ retab 4,8
+ let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l"
+ call assert_equal(expect, getline(1))
+
+ " Check that global and local values are set.
+ set ts=4 vts=6 sts=8 vsts=10
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that local values only are set.
+ setlocal ts=5 vts=7 sts=9 vsts=11
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 4)
+ call assert_equal(&vts, '6')
+ call assert_equal(&sts, 8)
+ call assert_equal(&vsts, '10')
+ bwipeout!
+
+ " Check that global values only are set.
+ setglobal ts=6 vts=8 sts=10 vsts=12
+ call assert_equal(&ts, 5)
+ call assert_equal(&vts, '7')
+ call assert_equal(&sts, 9)
+ call assert_equal(&vsts, '11')
+ new
+ call assert_equal(&ts, 6)
+ call assert_equal(&vts, '8')
+ call assert_equal(&sts, 10)
+ call assert_equal(&vsts, '12')
+ bwipeout!
+
+ bwipeout!
+endfunc
+
+func Test_vartabs_linebreak()
+ if winwidth(0) < 40
+ return
+ endif
+ new
+ 40vnew
+ %d
+ setl linebreak vartabstop=10,20,30,40
+ call setline(1, "\tx\tx\tx\tx")
+
+ let expect = [' x ',
+ \ 'x x ',
+ \ 'x ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect, lines)
+ setl list listchars=tab:>-
+ let expect = ['>---------x>------------------ ',
+ \ 'x>------------------x>------------------',
+ \ 'x ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect, lines)
+ setl linebreak vartabstop=40
+ let expect = ['>---------------------------------------',
+ \ 'x>--------------------------------------',
+ \ 'x>--------------------------------------',
+ \ 'x>--------------------------------------',
+ \ 'x ']
+ let lines = ScreenLines([1, 5], winwidth(0))
+ call s:compare_lines(expect, lines)
+
+ " cleanup
+ bw!
+ bw!
+ set nolist listchars&vim
+endfunc
+
+func Test_vartabs_shiftwidth()
+ "return
+ if winwidth(0) < 40
+ return
+ endif
+ new
+ 40vnew
+ %d
+" setl varsofttabstop=10,20,30,40
+ setl shiftwidth=0 vartabstop=10,20,30,40
+ call setline(1, "x")
+
+ " Check without any change.
+ let expect = ['x ']
+ let lines = ScreenLines(1, winwidth(0))
+ call s:compare_lines(expect, lines)
+ " Test 1:
+ " shiftwidth depends on the indent, first check with cursor at the end of the
+ " line (which is the same as the start of the line, since there is only one
+ " character).
+ norm! $>>
+ let expect1 = [' x ']
+ let lines = ScreenLines(1, winwidth(0))
+ call s:compare_lines(expect1, lines)
+ call assert_equal(10, shiftwidth())
+ call assert_equal(10, shiftwidth(1))
+ call assert_equal(20, shiftwidth(virtcol('.')))
+ norm! $>>
+ let expect2 = [' x ', '~ ']
+ let lines = ScreenLines([1, 2], winwidth(0))
+ call s:compare_lines(expect2, lines)
+ call assert_equal(20, shiftwidth(virtcol('.')-2))
+ call assert_equal(30, virtcol('.')->shiftwidth())
+ norm! $>>
+ let expect3 = [' ', ' x ', '~ ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect3, lines)
+ call assert_equal(30, shiftwidth(virtcol('.')-2))
+ call assert_equal(40, shiftwidth(virtcol('.')))
+ norm! $>>
+ let expect4 = [' ', ' ', ' x ']
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call assert_equal(40, shiftwidth(virtcol('.')))
+ call s:compare_lines(expect4, lines)
+
+ " Test 2: Put the cursor at the first column, result should be the same
+ call setline(1, "x")
+ norm! 0>>
+ let lines = ScreenLines(1, winwidth(0))
+ call s:compare_lines(expect1, lines)
+ norm! 0>>
+ let lines = ScreenLines([1, 2], winwidth(0))
+ call s:compare_lines(expect2, lines)
+ norm! 0>>
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect3, lines)
+ norm! 0>>
+ let lines = ScreenLines([1, 3], winwidth(0))
+ call s:compare_lines(expect4, lines)
+
+ call assert_fails('call shiftwidth([])', 'E745:')
+
+ " cleanup
+ bw!
+ bw!
+endfunc
+
+func Test_vartabs_failures()
+ call assert_fails('set vts=8,')
+ call assert_fails('set vsts=8,')
+ call assert_fails('set vts=8,,8')
+ call assert_fails('set vsts=8,,8')
+ call assert_fails('set vts=8,,8,')
+ call assert_fails('set vsts=8,,8,')
+ call assert_fails('set vts=,8')
+ call assert_fails('set vsts=,8')
+endfunc
+
+func Test_vartabs_reset()
+ set vts=8
+ set all&
+ call assert_equal('', &vts)
+endfunc
+
+func s:SaveCol(l)
+ call add(a:l, [col('.'), virtcol('.')])
+ return ''
+endfunc
+
+" Test for 'varsofttabstop'
+func Test_varsofttabstop()
+ new
+ inoremap <expr> <F2> s:SaveCol(g:cols)
+
+ set backspace=indent,eol,start
+ set varsofttabstop=6,2,5,3
+ let g:cols = []
+ call feedkeys("a\t\<F2>\t\<F2>\t\<F2>\t\<F2> ", 'xt')
+ call assert_equal("\t\t ", getline(1))
+ call assert_equal([[7, 7], [2, 9], [7, 14], [3, 17]], g:cols)
+
+ let g:cols = []
+ call feedkeys("a\<bs>\<F2>\<bs>\<F2>\<bs>\<F2>\<bs>\<F2>\<bs>\<F2>", 'xt')
+ call assert_equal('', getline(1))
+ call assert_equal([[3, 17], [7, 14], [2, 9], [7, 7], [1, 1]], g:cols)
+
+ set varsofttabstop&
+ set backspace&
+ iunmap <F2>
+ close!
+endfunc
+
+" Setting 'shiftwidth' to a negative value, should set it to either the value
+" of 'tabstop' (if 'vartabstop' is not set) or to the first value in
+" 'vartabstop'
+func Test_shiftwidth_vartabstop()
+ setlocal tabstop=7 vartabstop=
+ call assert_fails('set shiftwidth=-1', 'E487:')
+ call assert_equal(7, &shiftwidth)
+ setlocal tabstop=7 vartabstop=5,7,10
+ call assert_fails('set shiftwidth=-1', 'E487:')
+ call assert_equal(5, &shiftwidth)
+ setlocal shiftwidth& vartabstop& tabstop&
+endfunc
+
+func Test_vartabstop_latin1()
+ let save_encoding = &encoding
+ new
+ set encoding=iso8859-1
+ set compatible linebreak list revins smarttab
+ set vartabstop=400
+ exe "norm i00\t\<C-D>"
+ bwipe!
+ let &encoding = save_encoding
+ set nocompatible linebreak& list& revins& smarttab& vartabstop&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_version.vim b/src/testdir/test_version.vim
new file mode 100644
index 0000000..958c126
--- /dev/null
+++ b/src/testdir/test_version.vim
@@ -0,0 +1,26 @@
+" Test :version Ex command
+
+so check.vim
+so shared.vim
+
+func Test_version()
+ " version should always return the same string.
+ let v1 = execute('version')
+ let v2 = execute('version')
+ call assert_equal(v1, v2)
+
+ call assert_match("^\n\nVIM - Vi IMproved .*", v1)
+endfunc
+
+func Test_version_redirect()
+ CheckNotGui
+ CheckCanRunGui
+ CheckUnix
+
+ call RunVim([], [], '--clean -g --version >Xversion 2>&1')
+ call assert_match('Features included', readfile('Xversion')->join())
+
+ call delete('Xversion')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
new file mode 100644
index 0000000..4f4e58e
--- /dev/null
+++ b/src/testdir/test_vim9_assign.vim
@@ -0,0 +1,2825 @@
+" Test Vim9 assignments
+
+source check.vim
+import './vim9.vim' as v9
+source term_util.vim
+
+let s:appendToMe = 'xxx'
+let s:addToMe = 111
+let s:newVar = ''
+let g:existing = 'yes'
+let g:inc_counter = 1
+let $SOME_ENV_VAR = 'some'
+let g:alist = [7]
+let g:adict = #{a: 1}
+let g:astring = 'text'
+
+def Test_assignment_bool()
+ var bool1: bool = true
+ assert_equal(v:true, bool1)
+ var bool2: bool = false
+ assert_equal(v:false, bool2)
+
+ var bool3: bool = 0
+ assert_equal(false, bool3)
+ var bool4: bool = 1
+ assert_equal(true, bool4)
+
+ var bool5: bool = 1 && true
+ assert_equal(true, bool5)
+ var bool6: bool = 0 && 1
+ assert_equal(false, bool6)
+ var bool7: bool = 0 || 1 && true
+ assert_equal(true, bool7)
+
+ var lines =<< trim END
+ vim9script
+ def GetFlag(): bool
+ var flag: bool = 1
+ return flag
+ enddef
+ var flag: bool = GetFlag()
+ assert_equal(true, flag)
+ flag = 0
+ assert_equal(false, flag)
+ flag = 1
+ assert_equal(true, flag)
+ flag = 1 || true
+ assert_equal(true, flag)
+ flag = 1 && false
+ assert_equal(false, flag)
+
+ var cp: bool = &cp
+ var fen: bool = &l:fen
+ END
+ v9.CheckScriptSuccess(lines)
+ v9.CheckDefAndScriptFailure(['var x: bool = 2'], 'E1012:')
+ v9.CheckDefAndScriptFailure(['var x: bool = -1'], 'E1012:')
+ v9.CheckDefAndScriptFailure(['var x: bool = [1]'], 'E1012:')
+ v9.CheckDefAndScriptFailure(['var x: bool = {}'], 'E1012:')
+ v9.CheckDefAndScriptFailure(['var x: bool = "x"'], 'E1012:')
+
+ v9.CheckDefAndScriptFailure(['var x: bool = "x"', '', 'eval 0'], 'E1012:', 1)
+enddef
+
+def Test_syntax()
+ var name = 234
+ var other: list<string> = ['asdf']
+enddef
+
+def Test_assignment()
+ v9.CheckDefFailure(['var x:string'], 'E1069:')
+ v9.CheckDefFailure(['var x:string = "x"'], 'E1069:')
+ v9.CheckDefFailure(['var a:string = "x"'], 'E1069:')
+ v9.CheckDefFailure(['var lambda = () => "lambda"'], 'E704:')
+ v9.CheckScriptFailure(['var x = "x"'], 'E1124:')
+
+ # lower case name is OK for a list
+ var lambdaLines =<< trim END
+ var lambdaList: list<func> = [g:Test_syntax]
+ lambdaList[0] = () => "lambda"
+ END
+ v9.CheckDefAndScriptSuccess(lambdaLines)
+
+ var nr: number = 1234
+ v9.CheckDefFailure(['var nr: number = "asdf"'], 'E1012:')
+
+ var a: number = 6 #comment
+ assert_equal(6, a)
+
+ if has('channel')
+ var chan1: channel
+ assert_equal('fail', ch_status(chan1))
+
+ var job1: job
+ assert_equal('fail', job_status(job1))
+
+ # calling job_start() is in test_vim9_fails.vim, it causes leak reports
+ endif
+ var float1: float = 3.4
+ var Funky1: func
+ var Funky2: func = function('len')
+ var Party2: func = funcref('g:Test_syntax')
+
+ g:newvar = 'new' #comment
+ assert_equal('new', g:newvar)
+
+ assert_equal('yes', g:existing)
+ g:existing = 'no'
+ assert_equal('no', g:existing)
+
+ v:char = 'abc'
+ assert_equal('abc', v:char)
+
+ $ENVVAR = 'foobar'
+ assert_equal('foobar', $ENVVAR)
+ $ENVVAR = ''
+
+ var lines =<< trim END
+ vim9script
+ $ENVVAR = 'barfoo'
+ assert_equal('barfoo', $ENVVAR)
+ $ENVVAR = ''
+ END
+ v9.CheckScriptSuccess(lines)
+
+ appendToMe ..= 'yyy'
+ assert_equal('xxxyyy', appendToMe)
+ addToMe += 222
+ assert_equal(333, addToMe)
+ newVar = 'new'
+ assert_equal('new', newVar)
+
+ set ts=7
+ var ts: number = &ts
+ assert_equal(7, ts)
+ &ts += 1
+ assert_equal(8, &ts)
+ &ts -= 3
+ assert_equal(5, &ts)
+ &ts *= 2
+ assert_equal(10, &ts)
+ &ts /= 3
+ assert_equal(3, &ts)
+ set ts=10
+ &ts %= 4
+ assert_equal(2, &ts)
+
+ var f100: float = 100.0
+ f100 /= 5
+ assert_equal(20.0, f100)
+
+ var f200: float = 200.0
+ f200 /= 5.0
+ assert_equal(40.0, f200)
+
+ v9.CheckDefFailure(['var nr: number = 200', 'nr /= 5.0'], 'E1012:')
+
+ lines =<< trim END
+ &ts = 6
+ &ts += 3
+ assert_equal(9, &ts)
+
+ &l:ts = 6
+ assert_equal(6, &ts)
+ &l:ts += 2
+ assert_equal(8, &ts)
+
+ &g:ts = 6
+ assert_equal(6, &g:ts)
+ &g:ts += 2
+ assert_equal(8, &g:ts)
+
+ &number = true
+ assert_equal(true, &number)
+ &number = 0
+ assert_equal(false, &number)
+ &number = 1
+ assert_equal(true, &number)
+ &number = false
+ assert_equal(false, &number)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefFailure(['&notex += 3'], 'E113:')
+ v9.CheckDefFailure(['&ts ..= "xxx"'], 'E1019:')
+ v9.CheckDefFailure(['&ts = [7]'], 'E1012:')
+ v9.CheckDefExecFailure(['&ts = g:alist'], 'E1012: Type mismatch; expected number but got list<number>')
+ v9.CheckDefFailure(['&ts = "xx"'], 'E1012:')
+ v9.CheckDefExecFailure(['&ts = g:astring'], 'E1012: Type mismatch; expected number but got string')
+ v9.CheckDefFailure(['&path += 3'], 'E1012:')
+ v9.CheckDefExecFailure(['&bs = "asdf"'], 'E474:')
+ # test freeing ISN_STOREOPT
+ v9.CheckDefFailure(['&ts = 3', 'var asdf'], 'E1022:')
+ &ts = 8
+
+ lines =<< trim END
+ var save_TI = &t_TI
+ &t_TI = ''
+ assert_equal('', &t_TI)
+ &t_TI = 'xxx'
+ assert_equal('xxx', &t_TI)
+ &t_TI = save_TI
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefFailure(['&t_TI = 123'], 'E1012:')
+ v9.CheckScriptFailure(['vim9script', '&t_TI = 123'], 'E928:')
+
+ v9.CheckDefFailure(['var s:var = 123'], 'E1101:')
+ v9.CheckDefFailure(['var s:var: number'], 'E1101:')
+
+ v9.CheckDefAndScriptFailure(['var $VAR: number'], ['E1016:', 'E475:'])
+
+ lines =<< trim END
+ vim9script
+ def SomeFunc()
+ s:var = 123
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+
+ g:inc_counter += 1
+ assert_equal(2, g:inc_counter)
+
+ var f: float
+ f += 1
+ assert_equal(1.0, f)
+
+ $SOME_ENV_VAR ..= 'more'
+ assert_equal('somemore', $SOME_ENV_VAR)
+ v9.CheckDefFailure(['$SOME_ENV_VAR += "more"'], 'E1051:')
+ v9.CheckDefFailure(['$SOME_ENV_VAR += 123'], 'E1012:')
+
+ v:errmsg = 'none'
+ v:errmsg ..= 'again'
+ assert_equal('noneagain', v:errmsg)
+ v9.CheckDefFailure(['v:errmsg += "more"'], 'E1051:')
+ v9.CheckDefFailure(['v:errmsg += 123'], 'E1012:')
+
+ var text =<< trim END
+ some text
+ END
+enddef
+
+def Test_float_and_number()
+ var lines =<< trim END
+ var f: float
+ f += 2
+ f -= 1
+ assert_equal(1.0, f)
+ ++f
+ --f
+ assert_equal(1.0, f)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+let g:someNumber = 43
+
+def Test_assign_concat()
+ var lines =<< trim END
+ var s = '-'
+ s ..= 99
+ s ..= true
+ s ..= '-'
+ s ..= v:null
+ s ..= g:someNumber
+ assert_equal('-99true-null43', s)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var s = '-'
+ s ..= [1, 2]
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105: Cannot convert list to string', 'E734: Wrong variable type for .='], 2)
+ lines =<< trim END
+ var s = '-'
+ s ..= {a: 2}
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105: Cannot convert dict to string', 'E734: Wrong variable type for .='], 2)
+
+ lines =<< trim END
+ var ls: list<string> = []
+ ls[-1] ..= 'foo'
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E684: List index out of range: -1', 2)
+enddef
+
+def Test_assign_register()
+ var lines =<< trim END
+ @c = 'areg'
+ @c ..= 'add'
+ assert_equal('aregadd', @c)
+
+ @@ = 'some text'
+ assert_equal('some text', getreg('"'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefFailure(['@a += "more"'], 'E1051:')
+ v9.CheckDefFailure(['@a += 123'], 'E1012:')
+enddef
+
+def Test_reserved_name()
+ var more_names = ['null_job', 'null_channel']
+ if !has('job')
+ more_names = []
+ endif
+
+ for name in ['true',
+ 'false',
+ 'null',
+ 'null_blob',
+ 'null_dict',
+ 'null_function',
+ 'null_list',
+ 'null_partial',
+ 'null_string',
+ ] + more_names
+ v9.CheckDefExecAndScriptFailure(['var ' .. name .. ' = 0'], 'E1034:')
+ v9.CheckDefExecAndScriptFailure(['var ' .. name .. ': bool'], 'E1034:')
+ endfor
+enddef
+
+def Test_null_values()
+ var lines =<< trim END
+ var b: blob = null_blob
+ var dn: dict<number> = null_dict
+ var ds: dict<string> = null_dict
+ var ln: list<number> = null_list
+ var ls: list<string> = null_list
+ var Ff: func(string): string = null_function
+ var Fp: func(number): number = null_partial
+ var s: string = null_string
+ if has('job')
+ var j: job = null_job
+ var c: channel = null_channel
+ endif
+
+ var d: dict<func> = {a: function('tr'), b: null_function}
+
+ var bl: list<blob> = [0z12, null_blob]
+ var dnl: list<dict<number>> = [{a: 1}, null_dict]
+ var dsl: list<dict<string>> = [{a: 'x'}, null_dict]
+ var lnl: list<list<number>> = [[1], null_list]
+ var lsl: list<list<string>> = [['x'], null_list]
+ def Len(v: string): number
+ return len(v)
+ enddef
+ var Ffl: list<func(string): number> = [Len, null_function]
+ var Fpl: list<func(string): number> = [Len, null_partial]
+ var sl: list<string> = ['x', null_string]
+ if has('job')
+ var jl: list<job> = [null_job]
+ var cl: list<channel> = [null_channel]
+ endif
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_type_with_extra_white()
+ var lines =<< trim END
+ const x : number = 3
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1059')
+enddef
+
+def Test_keep_type_after_assigning_null()
+ var lines =<< trim END
+ var b: blob
+ b = null_blob
+ b = 'text'
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected blob but got string')
+
+ lines =<< trim END
+ var l: list<number>
+ l = null_list
+ l = ['text']
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected list<number> but got list<string>')
+
+ lines =<< trim END
+ var d: dict<string>
+ d = null_dict
+ d = {a: 1, b: 2}
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected dict<string> but got dict<number>')
+enddef
+
+def Test_skipped_assignment()
+ var lines =<< trim END
+ for x in []
+ var i: number = 1
+ while false
+ i += 1
+ endwhile
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_assign_keep_type()
+ var lines =<< trim END
+ vim9script
+ var l: list<number> = [123]
+ l = [123]
+ l->add('string')
+ END
+ v9.CheckScriptFailure(lines, 'E1012:', 4)
+enddef
+
+def Test_assign_unpack()
+ var lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2] = [1, 2]
+ assert_equal(1, v1)
+ assert_equal(2, v2)
+
+ [v1, _, v2, _] = [1, 99, 2, 77]
+ assert_equal(1, v1)
+ assert_equal(2, v2)
+
+ [v1, v2; _] = [1, 2, 3, 4, 5]
+ assert_equal(1, v1)
+ assert_equal(2, v2)
+
+ var _x: number
+ [_x, v2] = [6, 7]
+ assert_equal(6, _x)
+ assert_equal(7, v2)
+
+ var reslist = []
+ for text in ['aaa {bbb} ccc', 'ddd {eee} fff']
+ var before: string
+ var middle: string
+ var after: string
+ [_, before, middle, after; _] = text->matchlist('\(.\{-\}\){\(.\{-\}\)}\(.*\)')
+ reslist->add(before)->add(middle)->add(after)
+ endfor
+ assert_equal(['aaa ', 'bbb', ' ccc', 'ddd ', 'eee', ' fff'], reslist)
+
+ var a = 1
+ var b = 3
+ [a, b] += [2, 4]
+ assert_equal(3, a)
+ assert_equal(7, b)
+
+ [a, b] -= [1, 2]
+ assert_equal(2, a)
+ assert_equal(5, b)
+
+ [a, b] *= [3, 2]
+ assert_equal(6, a)
+ assert_equal(10, b)
+
+ [a, b] /= [2, 4]
+ assert_equal(3, a)
+ assert_equal(2, b)
+
+ [a, b] = [17, 15]
+ [a, b] %= [5, 3]
+ assert_equal(2, a)
+ assert_equal(0, b)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2] = [1, 2, 3]
+ END
+ v9.CheckDefFailure(lines, 'E1093: Expected 2 items but got 3', 3)
+
+ lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2] = [1]
+ END
+ v9.CheckDefFailure(lines, 'E1093: Expected 2 items but got 1', 3)
+
+ lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2; _] = [1]
+ END
+ v9.CheckDefFailure(lines, 'E1093: Expected 2 items but got 1', 3)
+
+ lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2] =
+ END
+ v9.CheckDefFailure(lines, 'E1097:', 5)
+
+ lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2] = xxx
+ END
+ v9.CheckDefFailure(lines, 'E1001:', 3)
+
+ lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2] = popup_clear()
+ END
+ v9.CheckDefFailure(lines, 'E1031:', 3)
+
+ lines =<< trim END
+ [v1, v2] = [1, 2]
+ END
+ v9.CheckDefFailure(lines, 'E1089', 1)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E1089', 2)
+
+ lines =<< trim END
+ var v1: number
+ var v2: number
+ [v1, v2] = ''
+ END
+ v9.CheckDefFailure(lines, 'E1012: Type mismatch; expected list<any> but got string', 3)
+
+ lines =<< trim END
+ g:values = [false, 0]
+ var x: bool
+ var y: string
+ [x, y] = g:values
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1163: Variable 2: type mismatch, expected string but got number')
+
+ lines =<< trim END
+ var x: number
+ var y: number
+ var z: string
+ [x, y, z] = [1, 2, 3]
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1163: Variable 3: type mismatch, expected string but got number')
+
+ lines =<< trim END
+ var x: number
+ var y: string
+ var z: string
+ [x, y, z] = [1, '2', 3]
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1163: Variable 3: type mismatch, expected string but got number')
+enddef
+
+def Test_assign_linebreak()
+ var nr: number
+ nr =
+ 123
+ assert_equal(123, nr)
+
+ var n2: number
+ [nr, n2] =
+ [12, 34]
+ assert_equal(12, nr)
+ assert_equal(34, n2)
+
+ v9.CheckDefFailure(["var x = #"], 'E1097:', 3)
+
+ var lines =<< trim END
+ var x: list<string> = ['a']
+ var y: list<number> = x
+ ->copy()
+ ->copy()
+ END
+ v9.CheckDefExecFailure(lines, 'E1012:', 4)
+
+ lines =<< trim END
+ var x: any
+ x.key = 1
+ + 2
+ + 3
+ + 4
+ + 5
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1148:', 'E1203:'], 2)
+enddef
+
+def Test_assign_index()
+ # list of list
+ var l1: list<number>
+ l1[0] = 123
+ assert_equal([123], l1)
+
+ var l2: list<list<number>>
+ l2[0] = []
+ l2[0][0] = 123
+ assert_equal([[123]], l2)
+
+ var l3: list<list<list<number>>>
+ l3[0] = []
+ l3[0][0] = []
+ l3[0][0][0] = 123
+ assert_equal([[[123]]], l3)
+
+ var lines =<< trim END
+ var l3: list<list<number>>
+ l3[0] = []
+ l3[0][0] = []
+ END
+ v9.CheckDefFailure(lines, 'E1012: Type mismatch; expected number but got list<unknown>', 3)
+
+ # dict of dict
+ var d1: dict<number>
+ d1.one = 1
+ assert_equal({one: 1}, d1)
+
+ var d2: dict<dict<number>>
+ d2.one = {}
+ d2.one.two = 123
+ assert_equal({one: {two: 123}}, d2)
+
+ var d3: dict<dict<dict<number>>>
+ d3.one = {}
+ d3.one.two = {}
+ d3.one.two.three = 123
+ assert_equal({one: {two: {three: 123}}}, d3)
+
+ # blob
+ var bl: blob = 0z11223344
+ bl[0] = 0x77
+ assert_equal(0z77223344, bl)
+ bl[-2] = 0x66
+ assert_equal(0z77226644, bl)
+
+ lines =<< trim END
+ g:val = '22'
+ var bl = 0z11
+ bl[1] = g:val
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1030: Using a String as a Number: "22"')
+
+ # should not read the next line when generating "a.b"
+ var a = {}
+ a.b = {}
+ a.b.c = {}
+ ->copy()
+
+ lines =<< trim END
+ var d3: dict<dict<number>>
+ d3.one = {}
+ d3.one.two = {}
+ END
+ v9.CheckDefFailure(lines, 'E1012: Type mismatch; expected number but got dict<unknown>', 3)
+
+ lines =<< trim END
+ var lines: list<string>
+ lines['a'] = 'asdf'
+ END
+ v9.CheckDefFailure(lines, 'E1012:', 2)
+
+ lines =<< trim END
+ var lines: string
+ lines[9] = 'asdf'
+ END
+ v9.CheckDefFailure(lines, 'E1141:', 2)
+
+ # list of dict
+ var ld: list<dict<number>>
+ ld[0] = {}
+ ld[0].one = 123
+ assert_equal([{one: 123}], ld)
+
+ lines =<< trim END
+ var ld: list<dict<number>>
+ ld[0] = []
+ END
+ v9.CheckDefFailure(lines, 'E1012: Type mismatch; expected dict<number> but got list<unknown>', 2)
+
+ # dict of list
+ var dl: dict<list<number>>
+ dl.one = []
+ dl.one[0] = 123
+ assert_equal({one: [123]}, dl)
+
+ lines =<< trim END
+ var dl: dict<list<number>>
+ dl.one = {}
+ END
+ v9.CheckDefFailure(lines, 'E1012: Type mismatch; expected list<number> but got dict<unknown>', 2)
+
+ lines =<< trim END
+ g:l = [1, 2]
+ g:l['x'] = 3
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E39:', 'E1030:'], 2)
+
+ lines =<< trim END
+ var bl: blob = test_null_blob()
+ bl[1] = 8
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1184:', 'E979:'], 2)
+
+ lines =<< trim END
+ g:bl = 'not a blob'
+ g:bl[1 : 2] = 8
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E897:', 'E689:'], 2)
+enddef
+
+def Test_init_in_for_loop()
+ var lines =<< trim END
+ var l: list<number> = []
+ for i in [3, 4]
+ var n: number
+ add(l, n)
+ n = 123
+ endfor
+ assert_equal([0, 0], l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l: list<number> = []
+ for i in [3, 4]
+ var n: number = 0
+ add(l, n)
+ n = 123
+ endfor
+ assert_equal([0, 0], l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l: list<number> = []
+ for i in [3, 4]
+ var n: number = 3
+ add(l, n)
+ n = 123
+ endfor
+ assert_equal([3, 3], l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_redir_is_not_assign()
+ if false
+ redir => res
+ echo var_job
+ redir END
+ endif
+enddef
+
+def Test_extend_list()
+ # using uninitialized list assigns empty list
+ var lines =<< trim END
+ var l1: list<number>
+ var l2 = l1
+ assert_true(l1 is l2)
+ l1 += [123]
+ assert_equal([123], l1)
+ assert_true(l1 is l2)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var list: list<string>
+ extend(list, ['x'])
+ assert_equal(['x'], list)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # appending to uninitialized list from a function works
+ lines =<< trim END
+ vim9script
+ var list: list<string>
+ def Func()
+ list += ['a', 'b']
+ enddef
+ Func()
+ assert_equal(['a', 'b'], list)
+ END
+ v9.CheckScriptSuccess(lines)
+ lines =<< trim END
+ vim9script
+ var list: list<string>
+ def Func()
+ extend(list, ['x', 'b'])
+ enddef
+ Func()
+ assert_equal(['x', 'b'], list)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # initialized to null, with type, does not default to empty list
+ lines =<< trim END
+ vim9script
+ var l: list<string> = test_null_list()
+ extend(l, ['x'])
+ END
+ v9.CheckScriptFailure(lines, 'E1134:', 3)
+
+ # initialized to null, without type, does not default to empty list
+ lines =<< trim END
+ vim9script
+ var l = null_list
+ extend(l, ['x'])
+ END
+ v9.CheckScriptFailure(lines, 'E1134:', 3)
+
+ # assigned null, does not default to empty list
+ lines =<< trim END
+ vim9script
+ var l: list<string>
+ l = null_list
+ extend(l, ['x'])
+ END
+ v9.CheckScriptFailure(lines, 'E1134:', 4)
+
+ lines =<< trim END
+ vim9script
+ extend(test_null_list(), ['x'])
+ END
+ v9.CheckScriptFailure(lines, 'E1134:', 2)
+
+ # using global var has no declared type
+ g:myList = []
+ g:myList->extend([1])
+ g:myList->extend(['x'])
+ assert_equal([1, 'x'], g:myList)
+ unlet g:myList
+
+ # using declared list gives an error
+ lines =<< trim END
+ var l: list<number>
+ g:myList = l
+ g:myList->extend([1])
+ g:myList->extend(['x'])
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>', 4)
+ unlet g:myList
+
+ lines =<< trim END
+ vim9script
+ var lds = [1, 2, 3]
+ def Func()
+ echo lds->extend(['x'])
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1013:')
+enddef
+
+def Test_extend_dict()
+ var lines =<< trim END
+ vim9script
+ var d: dict<number>
+ extend(d, {a: 1})
+ assert_equal({a: 1}, d)
+
+ var d2: dict<number>
+ d2['one'] = 1
+ assert_equal({one: 1}, d2)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var d: dict<string> = test_null_dict()
+ extend(d, {a: 'x'})
+ END
+ v9.CheckScriptFailure(lines, 'E1133:', 3)
+
+ lines =<< trim END
+ vim9script
+ extend(test_null_dict(), {a: 'x'})
+ END
+ v9.CheckScriptFailure(lines, 'E1133:', 2)
+enddef
+
+def Test_single_letter_vars()
+ # single letter variables
+ var a: number = 123
+ a = 123
+ assert_equal(123, a)
+ var b: number
+ b = 123
+ assert_equal(123, b)
+ var g: number
+ g = 123
+ assert_equal(123, g)
+ var s: number
+ s = 123
+ assert_equal(123, s)
+ var t: number
+ t = 123
+ assert_equal(123, t)
+ var v: number
+ v = 123
+ assert_equal(123, v)
+ var w: number
+ w = 123
+ assert_equal(123, w)
+enddef
+
+def Test_vim9_single_char_vars()
+ var lines =<< trim END
+ vim9script
+
+ # single character variable declarations work
+ var a: string
+ var b: number
+ var l: list<any>
+ var s: string
+ var t: number
+ var v: number
+ var w: number
+
+ # script-local variables can be used without s: prefix
+ a = 'script-a'
+ b = 111
+ l = [1, 2, 3]
+ s = 'script-s'
+ t = 222
+ v = 333
+ w = 444
+
+ assert_equal('script-a', a)
+ assert_equal(111, b)
+ assert_equal([1, 2, 3], l)
+ assert_equal('script-s', s)
+ assert_equal(222, t)
+ assert_equal(333, v)
+ assert_equal(444, w)
+ END
+ writefile(lines, 'Xsinglechar', 'D')
+ source Xsinglechar
+enddef
+
+def Test_assignment_list()
+ var list1: list<bool> = [false, true, false]
+ var list2: list<number> = [1, 2, 3]
+ var list3: list<string> = ['sdf', 'asdf']
+ var list4: list<any> = ['yes', true, 1234]
+ var list5: list<blob> = [0z01, 0z02]
+
+ var listS: list<string> = []
+ var listN: list<number> = []
+
+ assert_equal([1, 2, 3], list2)
+ list2[-1] = 99
+ assert_equal([1, 2, 99], list2)
+ list2[-2] = 88
+ assert_equal([1, 88, 99], list2)
+ list2[-3] = 77
+ assert_equal([77, 88, 99], list2)
+ list2 += [100]
+ assert_equal([77, 88, 99, 100], list2)
+
+ list3 += ['end']
+ assert_equal(['sdf', 'asdf', 'end'], list3)
+
+ v9.CheckDefExecFailure(['var ll = [1, 2, 3]', 'll[-4] = 6'], 'E684:')
+ v9.CheckDefExecFailure(['var ll = [1, 2, 3]', 'unlet ll[8 : 9]'], 'E684:')
+ v9.CheckDefExecFailure(['var ll = [1, 2, 3]', 'unlet ll[1 : -9]'], 'E684:')
+ v9.CheckDefExecFailure(['var ll = [1, 2, 3]', 'unlet ll[2 : 1]'], 'E684:')
+
+ # type becomes list<any>
+ var somelist = rand() > 0 ? [1, 2, 3] : ['a', 'b', 'c']
+
+ # type is list<any> even though initializer is list<number>
+ var anyList: list<any> = [0]
+ assert_equal([0, 'x'], extend(anyList, ['x']))
+
+ var lines =<< trim END
+ var d = {dd: test_null_list()}
+ d.dd[0] = 0
+ END
+ v9.CheckDefExecFailure(lines, 'E1147:', 2)
+
+ lines =<< trim END
+ def OneArg(x: bool)
+ enddef
+ def TwoArgs(x: bool, y: bool)
+ enddef
+ var fl: list<func(bool, bool, bool)> = [OneArg, TwoArgs]
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1012:', 5)
+enddef
+
+def Test_list_declaration()
+ var [v1, v2] = [1, 2]
+ v1 += 3
+ assert_equal(4, v1)
+ v2 *= 3
+ assert_equal(6, v2)
+
+ var lines =<< trim END
+ var [v1, v2] = [1]
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1093: Expected 2 items but got 1', 'E688:'])
+ lines =<< trim END
+ var testlist = [1]
+ var [v1, v2] = testlist
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1093: Expected 2 items but got 1', 'E688:'])
+ lines =<< trim END
+ var [v1, v2] = [1, 2, 3]
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1093: Expected 2 items but got 3', 'E687:'])
+ lines =<< trim END
+ var testlist = [1, 2, 3]
+ var [v1, v2] = testlist
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1093: Expected 2 items but got 3', 'E687:'])
+
+ var [vnr, vstr] = [123, 'text']
+ vnr += 3
+ assert_equal(126, vnr)
+ vstr ..= 'end'
+ assert_equal('textend', vstr)
+
+ var [vnr2: number, vstr2: string] = [123, 'text']
+ vnr2 += 3
+ assert_equal(126, vnr2)
+ vstr2 ..= 'end'
+ assert_equal('textend', vstr2)
+
+ var [vnr3: number; vlist: list<string>] = [123, 'foo', 'bar']
+ vnr3 += 5
+ assert_equal(128, vnr3)
+ assert_equal(['foo', 'bar'], vlist)
+
+ lines =<< trim END
+ var [vnr2: number, vstr2: number] = [123, 'text']
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1163: Variable 2: type mismatch, expected number but got string', 'E1012: Type mismatch; expected number but got string'])
+ lines =<< trim END
+ var testlist = [234, 'text']
+ var [vnr2: number, vstr2: number] = testlist
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1163: Variable 2: type mismatch, expected number but got string', 'E1012: Type mismatch; expected number but got string'])
+enddef
+
+def PartFuncBool(b: bool): string
+ return 'done'
+enddef
+
+def Test_assignment_partial()
+ var lines =<< trim END
+ var Partial: func(): string = function(g:PartFuncBool, [true])
+ assert_equal('done', Partial())
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Func(b: bool)
+ enddef
+ var Ref: func = function(Func, [true])
+ assert_equal('func()', typename(Ref))
+ Ref()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ var nres: any
+ var sres: any
+ def Func(nr: number, s = '')
+ nres = nr
+ sres = s
+ enddef
+
+ var n: number
+ var Ref = function(Func, [n])
+ Ref('x')
+ assert_equal(0, nres)
+ assert_equal('x', sres)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ def Func(nr: number, s = '')
+ enddef
+
+ var n: number
+ var Ref = function(Func, [n])
+ Ref(0)
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected string but got number')
+enddef
+
+def Test_assignment_list_any_index()
+ var l: list<number> = [1, 2]
+ for [x, y, _]
+ in [[0, 1, ''], [1, 3, '']]
+ l[x] = l[x] + y
+ endfor
+ assert_equal([2, 5], l)
+enddef
+
+def Test_assignment_list_vim9script()
+ var lines =<< trim END
+ vim9script
+ var v1: number
+ var v2: number
+ var v3: number
+ [v1, v2, v3] = [1, 2, 3]
+ assert_equal([1, 2, 3], [v1, v2, v3])
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_assignment_dict()
+ var dict1: dict<bool> = {one: false, two: true}
+ var dict2: dict<number> = {one: 1, two: 2}
+ var dict3: dict<string> = {key: 'value'}
+ var dict4: dict<any> = {one: 1, two: '2'}
+ var dict5: dict<blob> = {one: 0z01, two: 0z02}
+
+ # check the type is OK
+ var events: dict<string> = v:event
+
+ # overwrite
+ dict3['key'] = 'another'
+ assert_equal(dict3, {key: 'another'})
+ dict3.key = 'yet another'
+ assert_equal(dict3, {key: 'yet another'})
+
+ # member "any" can also be a dict and assigned to
+ var anydict: dict<any> = {nest: {}, nr: 0}
+ anydict.nest['this'] = 123
+ anydict.nest.that = 456
+ assert_equal({nest: {this: 123, that: 456}, nr: 0}, anydict)
+
+ var lines =<< trim END
+ var dd = {}
+ dd.two = 2
+ assert_equal({two: 2}, dd)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = {dd: {}}
+ d.dd[0] = 2
+ d.dd['x'] = 3
+ d.dd.y = 4
+ assert_equal({dd: {0: 2, x: 3, y: 4}}, d)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var key = 'foo'
+ g:[key] = 'value'
+ assert_equal('value', g:foo)
+ unlet g:foo
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var dd = {one: 1}
+ dd.one) = 2
+ END
+ v9.CheckDefFailure(lines, 'E488:', 2)
+
+ lines =<< trim END
+ var dd = {one: 1}
+ var dd.one = 2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1017:', 2)
+
+ # empty key can be used
+ var dd = {}
+ dd[""] = 6
+ assert_equal({['']: 6}, dd)
+
+ # type becomes dict<any>
+ var somedict = rand() > 0 ? {a: 1, b: 2} : {a: 'a', b: 'b'}
+
+ # type is dict<any> even though initializer is dict<number>
+ var anyDict: dict<any> = {a: 0}
+ assert_equal({a: 0, b: 'x'}, extend(anyDict, {b: 'x'}))
+
+ # using global var, which has no declared type
+ g:myDict = {}
+ g:myDict->extend({a: 1})
+ g:myDict->extend({b: 'x'})
+ assert_equal({a: 1, b: 'x'}, g:myDict)
+ unlet g:myDict
+
+ # using list with declared type gives an error
+ lines =<< trim END
+ var d: dict<number>
+ g:myDict = d
+ g:myDict->extend({a: 1})
+ g:myDict->extend({b: 'x'})
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict<number> but got dict<string>', 4)
+ unlet g:myDict
+
+ # assignment to script-local dict
+ lines =<< trim END
+ vim9script
+ var test: dict<any> = {}
+ def FillDict(): dict<any>
+ test['a'] = 43
+ return test
+ enddef
+ assert_equal({a: 43}, FillDict())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var test: dict<any>
+ def FillDict(): dict<any>
+ test['a'] = 43
+ return test
+ enddef
+ FillDict()
+ assert_equal({a: 43}, test)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # assignment to global dict
+ lines =<< trim END
+ vim9script
+ g:test = {}
+ def FillDict(): dict<any>
+ g:test['a'] = 43
+ return g:test
+ enddef
+ assert_equal({a: 43}, FillDict())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # assignment to buffer dict
+ lines =<< trim END
+ vim9script
+ b:test = {}
+ def FillDict(): dict<any>
+ b:test['a'] = 43
+ return b:test
+ enddef
+ assert_equal({a: 43}, FillDict())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = {dd: test_null_dict()}
+ d.dd[0] = 0
+ END
+ v9.CheckDefExecFailure(lines, 'E1103:', 2)
+
+ lines =<< trim END
+ var d = {dd: 'string'}
+ d.dd[0] = 0
+ END
+ v9.CheckDefExecFailure(lines, 'E1148:', 2)
+
+ lines =<< trim END
+ var n: any
+ n.key = 5
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1148:', 'E1203: Dot can only be used on a dictionary: n.key = 5'], 2)
+enddef
+
+def Test_assignment_local()
+ # Test in a separated file in order not to the current buffer/window/tab is
+ # changed.
+ var script_lines: list<string> =<< trim END
+ let b:existing = 'yes'
+ let w:existing = 'yes'
+ let t:existing = 'yes'
+
+ def Test_assignment_local_internal()
+ b:newvar = 'new'
+ assert_equal('new', b:newvar)
+ assert_equal('yes', b:existing)
+ b:existing = 'no'
+ assert_equal('no', b:existing)
+ b:existing ..= 'NO'
+ assert_equal('noNO', b:existing)
+
+ w:newvar = 'new'
+ assert_equal('new', w:newvar)
+ assert_equal('yes', w:existing)
+ w:existing = 'no'
+ assert_equal('no', w:existing)
+ w:existing ..= 'NO'
+ assert_equal('noNO', w:existing)
+
+ t:newvar = 'new'
+ assert_equal('new', t:newvar)
+ assert_equal('yes', t:existing)
+ t:existing = 'no'
+ assert_equal('no', t:existing)
+ t:existing ..= 'NO'
+ assert_equal('noNO', t:existing)
+ enddef
+ call Test_assignment_local_internal()
+ END
+ v9.CheckScriptSuccess(script_lines)
+enddef
+
+def Test_assignment_default()
+ # Test default values.
+ var thebool: bool
+ assert_equal(v:false, thebool)
+
+ var thenumber: number
+ assert_equal(0, thenumber)
+
+ var thefloat: float
+ assert_equal(0.0, thefloat)
+
+ var thestring: string
+ assert_equal('', thestring)
+
+ var theblob: blob
+ assert_equal(0z, theblob)
+
+ var Thefunc: func
+ assert_equal(test_null_function(), Thefunc)
+
+ var thelist: list<any>
+ assert_equal([], thelist)
+
+ var thedict: dict<any>
+ assert_equal({}, thedict)
+
+ if has('channel')
+ var thejob: job
+ assert_equal(test_null_job(), thejob)
+
+ var thechannel: channel
+ assert_equal(test_null_channel(), thechannel)
+
+ if has('unix') && executable('cat')
+ # check with non-null job and channel, types must match
+ thejob = job_start("cat ", {})
+ thechannel = job_getchannel(thejob)
+ job_stop(thejob, 'kill')
+ endif
+ endif
+
+ var nr = 1234 | nr = 5678
+ assert_equal(5678, nr)
+enddef
+
+def Test_script_var_default()
+ var lines =<< trim END
+ vim9script
+ var l: list<number>
+ var li = [1, 2]
+ var bl: blob
+ var bli = 0z12
+ var d: dict<number>
+ var di = {'a': 1, 'b': 2}
+ def Echo()
+ assert_equal([], l)
+ assert_equal([1, 2], li)
+ assert_equal(0z, bl)
+ assert_equal(0z12, bli)
+ assert_equal({}, d)
+ assert_equal({'a': 1, 'b': 2}, di)
+ enddef
+ Echo()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+let s:scriptvar = 'init'
+
+def Test_assignment_var_list()
+ var lines =<< trim END
+ var v1: string
+ var v2: string
+ var vrem: list<string>
+ [v1] = ['aaa']
+ assert_equal('aaa', v1)
+
+ [v1, v2] = ['one', 'two']
+ assert_equal('one', v1)
+ assert_equal('two', v2)
+
+ [v1, v2; vrem] = ['one', 'two']
+ assert_equal('one', v1)
+ assert_equal('two', v2)
+ assert_equal([], vrem)
+
+ [v1, v2; vrem] = ['one', 'two', 'three']
+ assert_equal('one', v1)
+ assert_equal('two', v2)
+ assert_equal(['three'], vrem)
+
+ [&ts, &sw] = [3, 4]
+ assert_equal(3, &ts)
+ assert_equal(4, &sw)
+ set ts=8 sw=4
+
+ [@a, @z] = ['aa', 'zz']
+ assert_equal('aa', @a)
+ assert_equal('zz', @z)
+
+ [$SOME_VAR, $OTHER_VAR] = ['some', 'other']
+ assert_equal('some', $SOME_VAR)
+ assert_equal('other', $OTHER_VAR)
+
+ [g:globalvar, b:bufvar, w:winvar, t:tabvar, v:errmsg] =
+ ['global', 'buf', 'win', 'tab', 'error']
+ assert_equal('global', g:globalvar)
+ assert_equal('buf', b:bufvar)
+ assert_equal('win', w:winvar)
+ assert_equal('tab', t:tabvar)
+ assert_equal('error', v:errmsg)
+ unlet g:globalvar
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ [g:globalvar, scriptvar, b:bufvar] = ['global', 'script', 'buf']
+ assert_equal('global', g:globalvar)
+ assert_equal('script', scriptvar)
+ assert_equal('buf', b:bufvar)
+
+ lines =<< trim END
+ vim9script
+ var scriptvar = 'init'
+ [g:globalvar, scriptvar, w:winvar] = ['global', 'script', 'win']
+ assert_equal('global', g:globalvar)
+ assert_equal('script', scriptvar)
+ assert_equal('win', w:winvar)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_assignment_empty_list()
+ var lines =<< trim END
+ var l2: list<any> = []
+ var l: list<string>
+ l = l2
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_assignment_vim9script()
+ var lines =<< trim END
+ vim9script
+ def Func(): list<number>
+ return [1, 2]
+ enddef
+ var name1: number
+ var name2: number
+ [name1, name2] =
+ Func()
+ assert_equal(1, name1)
+ assert_equal(2, name2)
+ var ll =
+ Func()
+ assert_equal([1, 2], ll)
+
+ @/ = 'text'
+ assert_equal('text', @/)
+ @0 = 'zero'
+ assert_equal('zero', @0)
+ @1 = 'one'
+ assert_equal('one', @1)
+ @9 = 'nine'
+ assert_equal('nine', @9)
+ @- = 'minus'
+ assert_equal('minus', @-)
+ if has('clipboard_working')
+ @* = 'star'
+ assert_equal('star', @*)
+ @+ = 'plus'
+ assert_equal('plus', @+)
+ endif
+
+ var a: number = 123
+ assert_equal(123, a)
+ var s: string = 'yes'
+ assert_equal('yes', s)
+ var b: number = 42
+ assert_equal(42, b)
+ var w: number = 43
+ assert_equal(43, w)
+ var t: number = 44
+ assert_equal(44, t)
+
+ var to_var = 0
+ to_var = 3
+ assert_equal(3, to_var)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var n: number
+ def Func()
+ n = 'string'
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected number but got string')
+enddef
+
+def Mess(): string
+ v:foldstart = 123
+ return 'xxx'
+enddef
+
+def Test_assignment_failure()
+ v9.CheckDefFailure(['var name=234'], 'E1004:')
+ v9.CheckDefFailure(['var name =234'], 'E1004:')
+ v9.CheckDefFailure(['var name= 234'], 'E1004:')
+
+ v9.CheckScriptFailure(['vim9script', 'var name=234'], 'E1004:')
+ v9.CheckScriptFailure(['vim9script', 'var name=234'], "before and after '='")
+ v9.CheckScriptFailure(['vim9script', 'var name =234'], 'E1004:')
+ v9.CheckScriptFailure(['vim9script', 'var name= 234'], 'E1004:')
+ v9.CheckScriptFailure(['vim9script', 'var name = 234', 'name+=234'], 'E1004:')
+ v9.CheckScriptFailure(['vim9script', 'var name = 234', 'name+=234'], "before and after '+='")
+ v9.CheckScriptFailure(['vim9script', 'var name = "x"', 'name..="y"'], 'E1004:')
+ v9.CheckScriptFailure(['vim9script', 'var name = "x"', 'name..="y"'], "before and after '..='")
+
+ v9.CheckDefFailure(['var true = 1'], 'E1034:')
+ v9.CheckDefFailure(['var false = 1'], 'E1034:')
+ v9.CheckDefFailure(['var null = 1'], 'E1034:')
+ v9.CheckDefFailure(['var this = 1'], 'E1034:')
+
+ v9.CheckDefFailure(['[a; b; c] = g:list'], 'E1001:')
+ v9.CheckDefFailure(['var [a; b; c] = g:list'], 'E1080:')
+ v9.CheckDefExecFailure(['var a: number',
+ '[a] = test_null_list()'], 'E1093:')
+ v9.CheckDefExecFailure(['var a: number',
+ '[a] = []'], 'E1093:')
+ v9.CheckDefExecFailure(['var x: number',
+ 'var y: number',
+ '[x, y] = [1]'], 'E1093:')
+ v9.CheckDefExecFailure(['var x: string',
+ 'var y: string',
+ '[x, y] = ["x"]'], 'E1093:')
+ v9.CheckDefExecFailure(['var x: number',
+ 'var y: number',
+ 'var z: list<number>',
+ '[x, y; z] = [1]'], 'E1093:')
+
+ v9.CheckDefFailure(['var somevar'], "E1022:")
+ v9.CheckDefFailure(['var &tabstop = 4'], 'E1052:')
+ v9.CheckDefFailure(['&g:option = 5'], 'E113:')
+ v9.CheckScriptFailure(['vim9script', 'var &tabstop = 4'], 'E1052:')
+
+ v9.CheckDefFailure(['var $VAR = 5'], 'E1016: Cannot declare an environment variable:')
+ v9.CheckScriptFailure(['vim9script', 'var $ENV = "xxx"'], 'E1016:')
+
+ if has('dnd')
+ v9.CheckDefFailure(['var @~ = 5'], 'E1066:')
+ else
+ v9.CheckDefFailure(['var @~ = 5'], 'E354:')
+ v9.CheckDefFailure(['@~ = 5'], 'E354:')
+ endif
+ v9.CheckDefFailure(['var @a = 5'], 'E1066:')
+ v9.CheckDefFailure(['var @/ = "x"'], 'E1066:')
+ v9.CheckScriptFailure(['vim9script', 'var @a = "abc"'], 'E1066:')
+
+ v9.CheckDefFailure(['var g:var = 5'], 'E1016: Cannot declare a global variable:')
+ v9.CheckDefFailure(['var w:var = 5'], 'E1016: Cannot declare a window variable:')
+ v9.CheckDefFailure(['var b:var = 5'], 'E1016: Cannot declare a buffer variable:')
+ v9.CheckDefFailure(['var t:var = 5'], 'E1016: Cannot declare a tab variable:')
+
+ v9.CheckDefFailure(['var anr = 4', 'anr ..= "text"'], 'E1019:')
+ v9.CheckDefFailure(['var xnr += 4'], 'E1020:', 1)
+ v9.CheckScriptFailure(['vim9script', 'var xnr += 4'], 'E1020:')
+ v9.CheckDefFailure(["var xnr = xnr + 1"], 'E1001:', 1)
+ v9.CheckScriptFailure(['vim9script', 'var xnr = xnr + 4'], 'E121:')
+
+ v9.CheckScriptFailure(['vim9script', 'def Func()', 'var dummy = notfound', 'enddef', 'defcompile'], 'E1001:')
+
+ v9.CheckDefFailure(['var name: list<string> = [123]'], 'expected list<string> but got list<number>')
+ v9.CheckDefFailure(['var name: list<number> = ["xx"]'], 'expected list<number> but got list<string>')
+
+ v9.CheckDefFailure(['var name: dict<string> = {key: 123}'], 'expected dict<string> but got dict<number>')
+ v9.CheckDefFailure(['var name: dict<number> = {key: "xx"}'], 'expected dict<number> but got dict<string>')
+
+ v9.CheckDefFailure(['var name = feedkeys("0")'], 'E1031:')
+ v9.CheckDefFailure(['var name: number = feedkeys("0")'], 'expected number but got void')
+
+ v9.CheckDefFailure(['var name: dict <number>'], 'E1068:')
+ v9.CheckDefFailure(['var name: dict<number'], 'E1009: Missing > after type: <number')
+
+ assert_fails('s/^/\=g:Mess()/n', 'E794:')
+ v9.CheckDefFailure(['var name: dict<number'], 'E1009:')
+
+ v9.CheckDefFailure(['w:foo: number = 10'],
+ 'E1016: Cannot declare a window variable: w:foo')
+ v9.CheckDefFailure(['t:foo: bool = true'],
+ 'E1016: Cannot declare a tab variable: t:foo')
+ v9.CheckDefFailure(['b:foo: string = "x"'],
+ 'E1016: Cannot declare a buffer variable: b:foo')
+ v9.CheckDefFailure(['g:foo: number = 123'],
+ 'E1016: Cannot declare a global variable: g:foo')
+
+ v9.CheckScriptFailure(['vim9script', 'w:foo: number = 123'],
+ 'E1304: Cannot use type with this variable: w:foo:')
+ v9.CheckScriptFailure(['vim9script', 't:foo: number = 123'],
+ 'E1304: Cannot use type with this variable: t:foo:')
+ v9.CheckScriptFailure(['vim9script', 'b:foo: number = 123'],
+ 'E1304: Cannot use type with this variable: b:foo:')
+ v9.CheckScriptFailure(['vim9script', 'g:foo: number = 123'],
+ 'E1304: Cannot use type with this variable: g:foo:')
+
+ v9.CheckScriptFailure(['vim9script', 'const w:FOO: number = 123'],
+ 'E1304: Cannot use type with this variable: w:FOO:')
+ v9.CheckScriptFailure(['vim9script', 'const t:FOO: number = 123'],
+ 'E1304: Cannot use type with this variable: t:FOO:')
+ v9.CheckScriptFailure(['vim9script', 'const b:FOO: number = 123'],
+ 'E1304: Cannot use type with this variable: b:FOO:')
+ v9.CheckScriptFailure(['vim9script', 'const g:FOO: number = 123'],
+ 'E1304: Cannot use type with this variable: g:FOO:')
+enddef
+
+def Test_assign_list()
+ var lines =<< trim END
+ var l: list<string> = []
+ l[0] = 'value'
+ assert_equal('value', l[0])
+
+ l[1] = 'asdf'
+ assert_equal('value', l[0])
+ assert_equal('asdf', l[1])
+ assert_equal('asdf', l[-1])
+ assert_equal('value', l[-2])
+
+ var nrl: list<number> = []
+ for i in range(5)
+ nrl[i] = i
+ endfor
+ assert_equal([0, 1, 2, 3, 4], nrl)
+
+ var ul: list<any>
+ ul[0] = 1
+ ul[1] = 2
+ ul[2] = 3
+ assert_equal([1, 2, 3], ul)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l = [1, 2]
+ g:idx = 'x'
+ l[g:idx : 1] = [0]
+ echo l
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1012: Type mismatch; expected number but got string', 'E1030: Using a String as a Number: "x"'])
+
+ lines =<< trim END
+ var l = [1, 2]
+ g:idx = 3
+ l[g:idx : 1] = [0]
+ echo l
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E684: List index out of range: 3')
+
+ lines =<< trim END
+ var l = [1, 2]
+ g:idx = 'y'
+ l[1 : g:idx] = [0]
+ echo l
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1012: Type mismatch; expected number but got string', 'E1030: Using a String as a Number: "y"'])
+
+ v9.CheckDefFailure(["var l: list<number> = ['', true]"], 'E1012: Type mismatch; expected list<number> but got list<any>', 1)
+ v9.CheckDefFailure(["var l: list<list<number>> = [['', true]]"], 'E1012: Type mismatch; expected list<list<number>> but got list<list<any>>', 1)
+enddef
+
+def Test_assign_dict()
+ var lines =<< trim END
+ var d: dict<string> = {}
+ d['key'] = 'value'
+ assert_equal('value', d['key'])
+
+ d[123] = 'qwerty'
+ assert_equal('qwerty', d[123])
+ assert_equal('qwerty', d['123'])
+
+ var nrd: dict<number> = {}
+ for i in range(3)
+ nrd[i] = i
+ endfor
+ assert_equal({0: 0, 1: 1, 2: 2}, nrd)
+
+ d.somekey = 'someval'
+ assert_equal({key: 'value', '123': 'qwerty', somekey: 'someval'}, d)
+ unlet d.somekey
+ assert_equal({key: 'value', '123': 'qwerty'}, d)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefFailure(["var d: dict<number> = {a: '', b: true}"], 'E1012: Type mismatch; expected dict<number> but got dict<any>', 1)
+ v9.CheckDefFailure(["var d: dict<dict<number>> = {x: {a: '', b: true}}"], 'E1012: Type mismatch; expected dict<dict<number>> but got dict<dict<any>>', 1)
+ v9.CheckDefFailure(["var d = {x: 1}", "d[1 : 2] = {y: 2}"], 'E1165: Cannot use a range with an assignment: d[1 : 2] =', 2)
+enddef
+
+def Test_assign_dict_unknown_type()
+ var lines =<< trim END
+ vim9script
+ var mylist = []
+ mylist += [{one: 'one'}]
+ def Func()
+ var dd = mylist[0]
+ assert_equal('one', dd.one)
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var mylist = [[]]
+ mylist[0] += [{one: 'one'}]
+ def Func()
+ var dd = mylist[0][0]
+ assert_equal('one', dd.one)
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_assign_dict_with_op()
+ var lines =<< trim END
+ var ds: dict<string> = {a: 'x'}
+ ds['a'] ..= 'y'
+ ds.a ..= 'z'
+ assert_equal('xyz', ds.a)
+
+ var dn: dict<number> = {a: 9}
+ dn['a'] += 2
+ assert_equal(11, dn.a)
+ dn.a += 2
+ assert_equal(13, dn.a)
+
+ dn['a'] -= 3
+ assert_equal(10, dn.a)
+ dn.a -= 2
+ assert_equal(8, dn.a)
+
+ dn['a'] *= 2
+ assert_equal(16, dn.a)
+ dn.a *= 2
+ assert_equal(32, dn.a)
+
+ dn['a'] /= 3
+ assert_equal(10, dn.a)
+ dn.a /= 2
+ assert_equal(5, dn.a)
+
+ dn['a'] %= 3
+ assert_equal(2, dn.a)
+ dn.a %= 6
+ assert_equal(2, dn.a)
+
+ var dd: dict<dict<list<any>>>
+ dd.a = {}
+ dd.a.b = [0]
+ dd.a.b += [1]
+ assert_equal({a: {b: [0, 1]}}, dd)
+
+ var dab = {a: ['b']}
+ dab.a[0] ..= 'c'
+ assert_equal({a: ['bc']}, dab)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_assign_list_with_op()
+ var lines =<< trim END
+ var ls: list<string> = ['x']
+ ls[0] ..= 'y'
+ assert_equal('xy', ls[0])
+
+ var ln: list<number> = [9]
+ ln[0] += 2
+ assert_equal(11, ln[0])
+
+ ln[0] -= 3
+ assert_equal(8, ln[0])
+
+ ln[0] *= 2
+ assert_equal(16, ln[0])
+
+ ln[0] /= 3
+ assert_equal(5, ln[0])
+
+ ln[0] %= 3
+ assert_equal(2, ln[0])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_assign_with_op_fails()
+ var lines =<< trim END
+ var s = 'abc'
+ s[1] += 'x'
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1141:', 'E689:'], 2)
+
+ lines =<< trim END
+ var s = 'abc'
+ s[1] ..= 'x'
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1141:', 'E689:'], 2)
+
+ lines =<< trim END
+ var dd: dict<dict<list<any>>>
+ dd.a = {}
+ dd.a.b += [1]
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E716:', 3)
+enddef
+
+def Test_assign_lambda()
+ # check if assign a lambda to a variable which type is func or any.
+ var lines =<< trim END
+ vim9script
+ var FuncRef = () => 123
+ assert_equal(123, FuncRef())
+ var FuncRef_Func: func = () => 123
+ assert_equal(123, FuncRef_Func())
+ var FuncRef_Any: any = () => 123
+ assert_equal(123, FuncRef_Any())
+ var FuncRef_Number: func(): number = () => 321
+ assert_equal(321, FuncRef_Number())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ var Ref: func(number)
+ Ref = (j) => !j
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected func(number) but got func(any): bool')
+
+ lines =<< trim END
+ echo filter([1, 2, 3], (_, v: string) => v + 1)
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1051:')
+enddef
+
+def Test_heredoc()
+ # simple heredoc
+ var lines =<< trim END
+ var text =<< trim TEXT # comment
+ abc
+ TEXT
+ assert_equal(['abc'], text)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # empty heredoc
+ lines =<< trim END
+ var text =<< trim TEXT
+ TEXT
+ assert_equal([], text)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # heredoc with a single empty line
+ lines =<< trim END
+ var text =<< trim TEXT
+
+ TEXT
+ assert_equal([''], text)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefFailure(['var lines =<< trim END X', 'END'], 'E488:')
+ v9.CheckDefFailure(['var lines =<< trim END " comment', 'END'], 'E488:')
+
+ lines =<< trim [END]
+ def Func()
+ var&lines =<< trim END
+ x
+ x
+ enddef
+ defcompile
+ [END]
+ v9.CheckScriptFailure(lines, 'E1145: Missing heredoc end marker: END')
+ delfunc! g:Func
+
+ lines =<< trim [END]
+ def Func()
+ var lines =<< trim END
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ x
+ enddef
+ call Func()
+ [END]
+ v9.CheckScriptFailure(lines, 'E1145: Missing heredoc end marker: END')
+ delfunc! g:Func
+
+ lines =<< trim END
+ var lines: number =<< trim STOP
+ aaa
+ bbb
+ STOP
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected number but got list<string>', 1)
+
+ lines =<< trim END
+ var lines=<< STOP
+ xxx
+ STOP
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''=<<'' at "=<< STOP"', 1)
+ lines =<< trim END
+ var lines =<<STOP
+ xxx
+ STOP
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''=<<'' at "=<<STOP"', 1)
+ lines =<< trim END
+ var lines=<<STOP
+ xxx
+ STOP
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''=<<'' at "=<<STOP"', 1)
+enddef
+
+def Test_var_func_call()
+ var lines =<< trim END
+ vim9script
+ func GetValue()
+ if exists('g:count')
+ let g:count += 1
+ else
+ let g:count = 1
+ endif
+ return 'this'
+ endfunc
+ var val: string = GetValue()
+ # env var is always a string
+ var env = $TERM
+ END
+ writefile(lines, 'Xfinished', 'D')
+ source Xfinished
+ # GetValue() is not called during discovery phase
+ assert_equal(1, g:count)
+
+ unlet g:count
+enddef
+
+def Test_var_missing_type()
+ var lines =<< trim END
+ vim9script
+ var name = g:unknown
+ END
+ v9.CheckScriptFailure(lines, 'E121:')
+
+ lines =<< trim END
+ vim9script
+ var nr: number = 123
+ var name = nr
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_var_declaration()
+ var lines =<< trim END
+ vim9script
+ var name: string
+ g:var_uninit = name
+ name = 'text'
+ g:var_test = name
+ # prefixing s: is not allowed
+ name = 'prefixed'
+ g:var_prefixed = name
+
+ const FOO: number = 123
+ assert_equal(123, FOO)
+ const FOOS = 'foos'
+ assert_equal('foos', FOOS)
+ final FLIST = [1]
+ assert_equal([1], FLIST)
+ FLIST[0] = 11
+ assert_equal([11], FLIST)
+
+ const g:FOOS = 'gfoos'
+ assert_equal('gfoos', g:FOOS)
+ final g:FLIST = [2]
+ assert_equal([2], g:FLIST)
+ g:FLIST[0] = 22
+ assert_equal([22], g:FLIST)
+
+ def SetGlobalConst()
+ const g:globConst = 123
+ enddef
+ SetGlobalConst()
+ assert_equal(123, g:globConst)
+ assert_true(islocked('g:globConst'))
+
+ const w:FOOS = 'wfoos'
+ assert_equal('wfoos', w:FOOS)
+ final w:FLIST = [3]
+ assert_equal([3], w:FLIST)
+ w:FLIST[0] = 33
+ assert_equal([33], w:FLIST)
+
+ var s:other: number
+ other = 1234
+ g:other_var = other
+
+ var xyz: string # comment
+
+ # type is inferred
+ var dict = {['a']: 222}
+ def GetDictVal(key: any)
+ g:dict_val = dict[key]
+ enddef
+ GetDictVal('a')
+
+ final adict: dict<string> = {}
+ def ChangeAdict()
+ adict.foo = 'foo'
+ enddef
+ ChangeAdict()
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal('', g:var_uninit)
+ assert_equal('text', g:var_test)
+ assert_equal('prefixed', g:var_prefixed)
+ assert_equal(1234, g:other_var)
+ assert_equal(222, g:dict_val)
+
+ unlet g:var_uninit
+ unlet g:var_test
+ unlet g:var_prefixed
+ unlet g:other_var
+ unlet g:globConst
+ unlet g:FOOS
+ unlet g:FLIST
+ unlet w:FOOS
+ unlet w:FLIST
+enddef
+
+def Test_create_list_after_const()
+ const a = 1
+ g:ll = []
+ assert_equal(0, islocked('g:ll'))
+ unlet g:ll
+enddef
+
+def Test_var_declaration_fails()
+ var lines =<< trim END
+ vim9script
+ final var: string
+ END
+ v9.CheckScriptFailure(lines, 'E1125:')
+
+ lines =<< trim END
+ vim9script
+ const g:constvar = 'string'
+ g:constvar = 'xx'
+ END
+ v9.CheckScriptFailure(lines, 'E741:')
+ unlet g:constvar
+
+ lines =<< trim END
+ vim9script
+ var name = 'one'
+ lockvar name
+ def SetLocked()
+ name = 'two'
+ enddef
+ SetLocked()
+ END
+ v9.CheckScriptFailure(lines, 'E741: Value is locked: name', 1)
+
+ lines =<< trim END
+ let s:legacy = 'one'
+ lockvar s:legacy
+ def SetLocked()
+ s:legacy = 'two'
+ enddef
+ call SetLocked()
+ END
+ v9.CheckScriptFailure(lines, 'E741: Value is locked: s:legacy', 1)
+
+ lines =<< trim END
+ vim9script
+ def SetGlobalConst()
+ const g:globConst = 123
+ enddef
+ SetGlobalConst()
+ g:globConst = 234
+ END
+ v9.CheckScriptFailure(lines, 'E741: Value is locked: g:globConst', 6)
+ unlet g:globConst
+
+ lines =<< trim END
+ vim9script
+ const cdict: dict<string> = {}
+ def Change()
+ cdict.foo = 'foo'
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E46:')
+
+ lines =<< trim END
+ vim9script
+ final w:finalvar = [9]
+ w:finalvar = [8]
+ END
+ v9.CheckScriptFailure(lines, 'E1122:')
+ unlet w:finalvar
+
+ lines =<< trim END
+ vim9script
+ const var: string
+ END
+ v9.CheckScriptFailure(lines, 'E1021:')
+
+ lines =<< trim END
+ vim9script
+ var 9var: string
+ END
+ v9.CheckScriptFailure(lines, 'E488:')
+
+ v9.CheckDefFailure(['var foo.bar = 2'], 'E1087:')
+ v9.CheckDefFailure(['var foo[3] = 2'], 'E1087:')
+ v9.CheckDefFailure(['const foo: number'], 'E1021:')
+
+ lines =<< trim END
+ va foo = 123
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1065:', 1)
+
+ lines =<< trim END
+ var foo: func(number
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E110:', 1)
+
+ lines =<< trim END
+ var foo: func(number): func(
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E110:', 1)
+
+ for type in ['num_ber',
+ 'anys', 'ani',
+ 'bools', 'boel',
+ 'blobs', 'blub',
+ 'channels', 'channol',
+ 'dicts', 'duct',
+ 'floats', 'floot',
+ 'funcs', 'funk',
+ 'jobs', 'jop',
+ 'lists', 'last',
+ 'numbers', 'numbar',
+ 'strings', 'strung',
+ 'voids', 'viod']
+ v9.CheckDefAndScriptFailure([$'var foo: {type}'], 'E1010:', 1)
+ endfor
+enddef
+
+def Test_var_declaration_inferred()
+ # check that type is set on the list so that extend() fails
+ var lines =<< trim END
+ vim9script
+ def GetList(): list<number>
+ var l = [1, 2, 3]
+ return l
+ enddef
+ echo GetList()->extend(['x'])
+ END
+ v9.CheckScriptFailure(lines, 'E1013:', 6)
+
+ lines =<< trim END
+ vim9script
+ def GetNr(): number
+ return 5
+ enddef
+ def TestOne()
+ var some = [function('len'), GetNr]
+ g:res = typename(some)
+ enddef
+ TestOne()
+ assert_equal('list<func(): number>', g:res)
+
+ def TestTwo()
+ var some = [function('len'), GetNr]
+ g:res = typename(some)
+ enddef
+ TestTwo()
+ assert_equal('list<func(): number>', g:res)
+ unlet g:res
+
+ # FIXME: why is the type different?
+ var first = [function('len'), GetNr]
+ assert_equal('list<func(...): number>', typename(first))
+ var second = [GetNr, function('len')]
+ assert_equal('list<func(...): number>', typename(second))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_script_local_in_legacy()
+ # OK to define script-local later but before compiling
+ var lines =<< trim END
+ def SetLater()
+ legvar = 'two'
+ enddef
+ let s:legvar = 'one'
+ defcompile
+ call SetLater()
+ call assert_equal('two', s:legvar)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # OK to leave out s: prefix when script-local already defined
+ lines =<< trim END
+ let s:legvar = 'one'
+ def SetNoPrefix()
+ legvar = 'two'
+ enddef
+ call SetNoPrefix()
+ call assert_equal('two', s:legvar)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # Not OK to leave out s: prefix when script-local defined after compiling
+ lines =<< trim END
+ def SetLaterNoPrefix()
+ legvar = 'two'
+ enddef
+ defcompile
+ let s:legvar = 'one'
+ END
+ v9.CheckScriptFailure(lines, 'E476:', 1)
+
+ edit! Xslfile
+ lines =<< trim END
+ var edit: bool
+ legacy edit
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_var_type_check()
+ var lines =<< trim END
+ vim9script
+ var name: string
+ name = 1234
+ END
+ v9.CheckScriptFailure(lines, 'E1012:')
+
+ lines =<< trim END
+ vim9script
+ var name:string
+ END
+ v9.CheckScriptFailure(lines, 'E1069:')
+
+ v9.CheckDefAndScriptFailure(['var n:number = 42'], 'E1069:')
+
+ lines =<< trim END
+ vim9script
+ var name: asdf
+ END
+ v9.CheckScriptFailure(lines, 'E1010:')
+
+ lines =<< trim END
+ vim9script
+ var l: list<number>
+ l = []
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var d: dict<number>
+ d = {}
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var d = {a: 1, b: [2]}
+ def Func(b: bool)
+ var l: list<number> = b ? d.b : [3]
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+let g:dict_number = #{one: 1, two: 2}
+
+def Test_var_list_dict_type()
+ var ll: list<number>
+ ll = [1, 2, 2, 3, 3, 3]->uniq()
+ ll->assert_equal([1, 2, 3])
+
+ var dd: dict<number>
+ dd = g:dict_number
+ dd->assert_equal(g:dict_number)
+
+ var lines =<< trim END
+ var ll: list<number>
+ ll = [1, 2, 3]->map('"one"')
+ END
+ v9.CheckDefExecFailure(lines, 'E1012: Type mismatch; expected list<number> but got list<string>')
+enddef
+
+def Test_cannot_use_let()
+ v9.CheckDefAndScriptFailure(['let a = 34'], 'E1126:', 1)
+enddef
+
+def Test_unlet()
+ g:somevar = 'yes'
+ assert_true(exists('g:somevar'))
+ unlet g:somevar
+ assert_false(exists('g:somevar'))
+ unlet! g:somevar
+
+ # also works for script-local variable in legacy Vim script
+ s:somevar = 'legacy'
+ assert_true(exists('s:somevar'))
+ unlet s:somevar
+ assert_false(exists('s:somevar'))
+ unlet! s:somevar
+
+ if 0
+ unlet g:does_not_exist
+ endif
+
+ v9.CheckDefExecFailure(['unlet v:notfound.key'], 'E1001:')
+
+ v9.CheckDefExecFailure([
+ 'var dd = 111',
+ 'unlet dd',
+ ], 'E1081:', 2)
+
+ # dict unlet
+ var dd = {a: 1, b: 2, c: 3, 4: 4}
+ unlet dd['a']
+ unlet dd.c
+ unlet dd[4]
+ assert_equal({b: 2}, dd)
+
+ # null key works like empty string
+ dd = {'': 1, x: 9}
+ unlet dd[null_string]
+ assert_equal({x: 9}, dd)
+
+ # list unlet
+ var ll = [1, 2, 3, 4]
+ unlet ll[1]
+ unlet ll[-1]
+ assert_equal([1, 3], ll)
+
+ ll = [1, 2, 3, 4]
+ unlet ll[0 : 1]
+ assert_equal([3, 4], ll)
+
+ ll = [1, 2, 3, 4]
+ unlet ll[2 : 8]
+ assert_equal([1, 2], ll)
+
+ ll = [1, 2, 3, 4]
+ unlet ll[-2 : -1]
+ assert_equal([1, 2], ll)
+
+ g:nrdict = {1: 1, 2: 2}
+ g:idx = 1
+ unlet g:nrdict[g:idx]
+ assert_equal({2: 2}, g:nrdict)
+ unlet g:nrdict
+ unlet g:idx
+
+ v9.CheckDefFailure([
+ 'var ll = [1, 2]',
+ 'll[1 : 2] = 7',
+ ], 'E1012: Type mismatch; expected list<number> but got number', 2)
+ v9.CheckDefFailure([
+ 'var dd = {a: 1}',
+ 'unlet dd["a" : "a"]',
+ ], 'E1166:', 2)
+ v9.CheckDefExecFailure([
+ 'unlet g:adict[0 : 1]',
+ ], 'E1148:', 1)
+ v9.CheckDefFailure([
+ 'var ll = [1, 2]',
+ 'unlet ll[0:1]',
+ ], 'E1004:', 2)
+ v9.CheckDefFailure([
+ 'var ll = [1, 2]',
+ 'unlet ll[0 :1]',
+ ], 'E1004:', 2)
+ v9.CheckDefFailure([
+ 'var ll = [1, 2]',
+ 'unlet ll[0: 1]',
+ ], 'E1004:', 2)
+
+ v9.CheckDefExecFailure([
+ 'g:ll = [1, 2]',
+ 'g:idx = "x"',
+ 'unlet g:ll[g:idx]',
+ ], 'E1029: Expected number but got string', 3)
+
+ v9.CheckDefExecFailure([
+ 'g:ll = [1, 2, 3]',
+ 'g:idx = "x"',
+ 'unlet g:ll[g:idx : 2]',
+ ], 'E1029: Expected number but got string', 3)
+
+ v9.CheckDefExecFailure([
+ 'g:ll = [1, 2, 3]',
+ 'g:idx = "x"',
+ 'unlet g:ll[0 : g:idx]',
+ ], 'E1029: Expected number but got string', 3)
+
+ # command recognized as assignment when skipping, should not give an error
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'for i in []',
+ " put =''",
+ 'endfor'])
+
+ v9.CheckDefFailure([
+ 'var ll = [1, 2]',
+ 'unlet ll["x" : 1]',
+ ], 'E1012:', 2)
+ v9.CheckDefFailure([
+ 'var ll = [1, 2]',
+ 'unlet ll[0 : "x"]',
+ ], 'E1012:', 2)
+
+ # list of dict unlet
+ var dl = [{a: 1, b: 2}, {c: 3}]
+ unlet dl[0]['b']
+ assert_equal([{a: 1}, {c: 3}], dl)
+
+ v9.CheckDefExecFailure([
+ 'var ll = test_null_list()',
+ 'unlet ll[0]',
+ ], 'E684:', 2)
+ v9.CheckDefExecFailure([
+ 'var ll = [1]',
+ 'unlet ll[2]',
+ ], 'E684:', 2)
+ v9.CheckDefExecFailure([
+ 'var ll = [1]',
+ 'unlet ll[g:astring]',
+ ], 'E1012:', 2)
+ v9.CheckDefExecFailure([
+ 'var dd = test_null_dict()',
+ 'unlet dd["a"]',
+ ], 'E716:', 2)
+ v9.CheckDefExecFailure([
+ 'var dd = {a: 1}',
+ 'unlet dd["b"]',
+ ], 'E716:', 2)
+ v9.CheckDefExecFailure([
+ 'var dd = {a: 1}',
+ 'unlet dd[g:alist]',
+ ], 'E1105:', 2)
+
+ v9.CheckDefExecFailure([
+ 'g:dd = {"a": 1, 2: 2}',
+ 'unlet g:dd[0z11]',
+ ], 'E1029:', 2)
+ v9.CheckDefExecFailure([
+ 'g:str = "a string"',
+ 'unlet g:str[0]',
+ ], 'E1148: Cannot index a string', 2)
+
+ # can compile unlet before variable exists
+ g:someDict = {key: 'val'}
+ var k = 'key'
+ unlet g:someDict[k]
+ assert_equal({}, g:someDict)
+ unlet g:someDict
+ assert_false(exists('g:someDict'))
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var svar = 123',
+ 'unlet svar',
+ ], 'E1081:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var svar = 123',
+ 'unlet s:svar',
+ ], 'E1268:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var svar = 123',
+ 'def Func()',
+ ' unlet svar',
+ 'enddef',
+ 'defcompile',
+ ], 'E1081:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var svar = 123',
+ 'func Func()',
+ ' unlet s:svar',
+ 'endfunc',
+ 'Func()',
+ ], 'E1081:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var svar = 123',
+ 'def Func()',
+ ' unlet s:svar',
+ 'enddef',
+ 'defcompile',
+ ], 'E1081:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'def Delcount(dict: dict<any>)',
+ ' unlet dict.count',
+ 'enddef',
+ 'Delcount(v:)',
+ ], 'E742:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'def DelChangedtick(dict: dict<any>)',
+ ' unlet dict.changedtick',
+ 'enddef',
+ 'DelChangedtick(b:)',
+ ], 'E795:')
+
+ writefile(['vim9script', 'export var svar = 1234'], 'XunletExport.vim', 'D')
+ var lines =<< trim END
+ vim9script
+ import './XunletExport.vim' as exp
+ def UnletSvar()
+ unlet exp.svar
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1260:', 1)
+
+ $ENVVAR = 'foobar'
+ assert_equal('foobar', $ENVVAR)
+ unlet $ENVVAR
+ assert_equal('', $ENVVAR)
+enddef
+
+def Test_expr_error_no_assign()
+ var lines =<< trim END
+ vim9script
+ var x = invalid
+ echo x
+ END
+ v9.CheckScriptFailureList(lines, ['E121:', 'E121:'])
+
+ lines =<< trim END
+ vim9script
+ var x = 1 / 0
+ echo x
+ END
+ v9.CheckScriptFailure(lines, 'E1154:')
+
+ lines =<< trim END
+ vim9script
+ var x = 1 % 0
+ echo x
+ END
+ v9.CheckScriptFailure(lines, 'E1154:')
+
+ lines =<< trim END
+ var x: string 'string'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E488:')
+enddef
+
+
+def Test_assign_command_modifier()
+ var lines =<< trim END
+ var verbose = 0
+ verbose = 1
+ assert_equal(1, verbose)
+ silent verbose = 2
+ assert_equal(2, verbose)
+ silent verbose += 2
+ assert_equal(4, verbose)
+ silent verbose -= 1
+ assert_equal(3, verbose)
+
+ var topleft = {one: 1}
+ sandbox topleft.one = 3
+ assert_equal({one: 3}, topleft)
+ leftabove topleft[' '] = 4
+ assert_equal({one: 3, ' ': 4}, topleft)
+
+ var x: number
+ var y: number
+ silent [x, y] = [1, 2]
+ assert_equal(1, x)
+ assert_equal(2, y)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_assign_alt_buf_register()
+ var lines =<< trim END
+ edit 'file_b1'
+ var b1 = bufnr()
+ edit 'file_b2'
+ var b2 = bufnr()
+ assert_equal(b1, bufnr('#'))
+ @# = b2
+ assert_equal(b2, bufnr('#'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_script_funcref_case()
+ var lines =<< trim END
+ var Len = (s: string): number => len(s) + 1
+ assert_equal(5, Len('asdf'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var len = (s: string): number => len(s) + 1
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E704:')
+
+ lines =<< trim END
+ vim9script
+ var Len = (s: string): number => len(s) + 2
+ assert_equal(6, Len('asdf'))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var len = (s: string): number => len(s) + 1
+ END
+ v9.CheckScriptFailure(lines, 'E704:')
+enddef
+
+def Test_script_funcref_runtime_type_check()
+ var lines =<< trim END
+ vim9script
+ def FuncWithNumberArg(n: number)
+ enddef
+ def Test()
+ var Ref: func(string) = function(FuncWithNumberArg)
+ enddef
+ defcompile
+ END
+ # OK at compile time
+ v9.CheckScriptSuccess(lines)
+
+ # Type check fails at runtime
+ v9.CheckScriptFailure(lines + ['Test()'], 'E1012: Type mismatch; expected func(string) but got func(number)')
+enddef
+
+def Test_inc_dec()
+ var lines =<< trim END
+ var nr = 7
+ ++nr
+ assert_equal(8, nr)
+ --nr
+ assert_equal(7, nr)
+ ++nr | ++nr
+ assert_equal(9, nr)
+ ++nr # comment
+ assert_equal(10, nr)
+
+ var ll = [1, 2]
+ --ll[0]
+ ++ll[1]
+ assert_equal([0, 3], ll)
+
+ g:count = 1
+ ++g:count
+ --g:count
+ assert_equal(1, g:count)
+ unlet g:count
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var nr = 7
+ ++ nr
+ END
+ v9.CheckDefAndScriptFailure(lines, "E1202: No white space allowed after '++': ++ nr")
+enddef
+
+def Test_abort_after_error()
+ # should abort after strpart() fails, not give another type error
+ var lines =<< trim END
+ vim9script
+ var x: string
+ x = strpart(1, 2)
+ END
+ writefile(lines, 'Xtestscript', 'D')
+ var expected = 'E1174: String required for argument 1'
+ assert_fails('so Xtestscript', [expected, expected], 3)
+enddef
+
+def Test_using_s_var_in_function()
+ var lines =<< trim END
+ vim9script
+ var scriptlevel = 123
+ def SomeFunc()
+ echo s:scriptlevel
+ enddef
+ SomeFunc()
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+
+ # OK in legacy script
+ lines =<< trim END
+ let s:scriptlevel = 123
+ def s:SomeFunc()
+ echo s:scriptlevel
+ enddef
+ call s:SomeFunc()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var scriptlevel = 123
+ def SomeFunc()
+ s:scriptlevel = 456
+ enddef
+ SomeFunc()
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+
+ # OK in legacy script
+ lines =<< trim END
+ let s:scriptlevel = 123
+ def s:SomeFunc()
+ s:scriptlevel = 456
+ enddef
+ call s:SomeFunc()
+ call assert_equal(456, s:scriptlevel)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+let g:someVar = 'X'
+
+" Test for heredoc with Vim expressions.
+" This messes up highlighting, keep it near the end.
+def Test_heredoc_expr()
+ var lines =<< trim CODE
+ var s = "local"
+ var a1 = "1"
+ var a2 = "2"
+ var a3 = "3"
+ var a4 = ""
+ var code =<< trim eval END
+ var a = {5 + 10}
+ var b = {min([10, 6])} + {max([4, 6])}
+ var c = "{s}"
+ var d = x{a1}x{a2}x{a3}x{a4}
+ END
+ assert_equal(['var a = 15', 'var b = 6 + 6', 'var c = "local"', 'var d = x1x2x3x'], code)
+ CODE
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim CODE
+ var code =<< eval trim END
+ var s = "{$SOME_ENV_VAR}"
+ END
+ assert_equal(['var s = "somemore"'], code)
+ CODE
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim CODE
+ var code =<< eval END
+ var s = "{$SOME_ENV_VAR}"
+ END
+ assert_equal([' var s = "somemore"'], code)
+ CODE
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim CODE
+ var code =<< eval trim END
+ let a = {{abc}}
+ let b = {g:someVar}
+ let c = {{
+ END
+ assert_equal(['let a = {abc}', 'let b = X', 'let c = {'], code)
+ CODE
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim LINES
+ var text =<< eval trim END
+ let b = {
+ END
+ LINES
+ v9.CheckDefAndScriptFailure(lines, "E1279: Missing '}'")
+
+ lines =<< trim LINES
+ var text =<< eval trim END
+ let b = {abc
+ END
+ LINES
+ v9.CheckDefAndScriptFailure(lines, "E1279: Missing '}'")
+
+ lines =<< trim LINES
+ var text =<< eval trim END
+ let b = {}
+ END
+ LINES
+ v9.CheckDefAndScriptFailure(lines, 'E15: Invalid expression: "}"')
+enddef
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
new file mode 100644
index 0000000..1e2b378
--- /dev/null
+++ b/src/testdir/test_vim9_builtin.vim
@@ -0,0 +1,4949 @@
+" Test using builtin functions in the Vim9 script language.
+
+source check.vim
+source screendump.vim
+import './vim9.vim' as v9
+
+" Test for passing too many or too few arguments to builtin functions
+func Test_internalfunc_arg_error()
+ let l =<< trim END
+ def! FArgErr(): float
+ return ceil(1.1, 2)
+ enddef
+ defcompile
+ END
+ call writefile(l, 'Xinvalidarg', 'D')
+ call assert_fails('so Xinvalidarg', 'E118:', '', 1, 'FArgErr')
+ let l =<< trim END
+ def! FArgErr(): float
+ return ceil()
+ enddef
+ defcompile
+ END
+ call writefile(l, 'Xinvalidarg')
+ call assert_fails('so Xinvalidarg', 'E119:', '', 1, 'FArgErr')
+endfunc
+
+" Test for builtin functions returning different types
+func Test_InternalFuncRetType()
+ let lines =<< trim END
+ def RetFloat(): float
+ return ceil(1.456)
+ enddef
+
+ def RetListAny(): list<any>
+ return items({k: 'v'})
+ enddef
+
+ def RetListString(): list<string>
+ return split('a:b:c', ':')
+ enddef
+
+ def RetListDictAny(): list<dict<any>>
+ return getbufinfo()
+ enddef
+
+ def RetDictNumber(): dict<number>
+ return wordcount()
+ enddef
+
+ def RetDictString(): dict<string>
+ return environ()
+ enddef
+ END
+ call writefile(lines, 'Xscript', 'D')
+ source Xscript
+
+ call RetFloat()->assert_equal(2.0)
+ call RetListAny()->assert_equal([['k', 'v']])
+ call RetListString()->assert_equal(['a', 'b', 'c'])
+ call RetListDictAny()->assert_notequal([])
+ call RetDictNumber()->assert_notequal({})
+ call RetDictString()->assert_notequal({})
+endfunc
+
+def Test_abs()
+ assert_equal(0, abs(0))
+ assert_equal(2, abs(-2))
+ assert_equal(3, abs(3))
+ v9.CheckDefAndScriptFailure(['abs("text")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal(0, abs(0))
+ assert_equal(2.0, abs(-2.0))
+ assert_equal(3.0, abs(3.0))
+enddef
+
+def Test_add()
+ v9.CheckDefAndScriptFailure(['add({}, 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1226: List or Blob required for argument 1'])
+ v9.CheckDefAndScriptFailure(['add([])'], 'E119:')
+ v9.CheckDefExecFailure([
+ 'var ln: list<number> = [1]',
+ 'add(ln, "a")'],
+ 'E1012: Type mismatch; expected number but got string')
+ assert_equal([1, 'a'], add([1], 'a'))
+ assert_equal(0z1234, add(0z12, 0x34))
+
+ var lines =<< trim END
+ vim9script
+ g:thelist = [1]
+ lockvar g:thelist
+ def TryChange()
+ g:thelist->add(2)
+ enddef
+ TryChange()
+ END
+ v9.CheckScriptFailure(lines, 'E741:')
+enddef
+
+def Test_add_blob()
+ var b1: blob = 0z12
+ add(b1, 0x34)
+ assert_equal(0z1234, b1)
+
+ var b2: blob # defaults to empty blob
+ add(b2, 0x67)
+ assert_equal(0z67, b2)
+
+ var lines =<< trim END
+ var b: blob
+ add(b, "x")
+ END
+ v9.CheckDefFailure(lines, 'E1012:', 2)
+
+ lines =<< trim END
+ add(test_null_blob(), 123)
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1131:', 1)
+
+ lines =<< trim END
+ var b: blob = test_null_blob()
+ add(b, 123)
+ END
+ v9.CheckDefExecFailure(lines, 'E1131:', 2)
+
+ # Getting variable with NULL blob fails
+ lines =<< trim END
+ vim9script
+ var b: blob = test_null_blob()
+ add(b, 123)
+ END
+ v9.CheckScriptFailure(lines, 'E1131:', 3)
+enddef
+
+def Test_add_list()
+ var l: list<number> # defaults to empty list
+ add(l, 9)
+ assert_equal([9], l)
+
+ var lines =<< trim END
+ var l: list<number>
+ add(l, "x")
+ END
+ v9.CheckDefFailure(lines, 'E1012:', 2)
+
+ lines =<< trim END
+ add(test_null_list(), 123)
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1130:', 1)
+
+ lines =<< trim END
+ var l: list<number> = test_null_list()
+ add(l, 123)
+ END
+ v9.CheckDefExecFailure(lines, 'E1130:', 2)
+
+ # Getting an uninitialized variable allocates a new list at script level
+ lines =<< trim END
+ vim9script
+ var l: list<number>
+ add(l, 123)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # Adding to a variable set to a NULL list fails
+ lines =<< trim END
+ vim9script
+ var l: list<number> = test_null_list()
+ add(l, 123)
+ END
+ v9.CheckScriptFailure(lines, 'E1130:', 3)
+
+ lines =<< trim END
+ vim9script
+ var l: list<string> = ['a']
+ l->add(123)
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got number', 3)
+
+ lines =<< trim END
+ vim9script
+ var l: list<string>
+ l->add(123)
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got number', 3)
+enddef
+
+def Test_add_const()
+ var lines =<< trim END
+ const l = [1, 2]
+ add(l, 3)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+
+ lines =<< trim END
+ final l = [1, 2]
+ add(l, 3)
+ assert_equal([1, 2, 3], l)
+ END
+ v9.CheckDefSuccess(lines)
+
+ lines =<< trim END
+ const b = 0z0102
+ add(b, 0z03)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const blob')
+enddef
+
+
+def Test_and()
+ v9.CheckDefAndScriptFailure(['and("x", 0x2)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['and(0x1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_append()
+ new
+ setline(1, range(3))
+ var res1: number = append(1, 'one')
+ assert_equal(0, res1)
+ var res2: bool = append(3, 'two')
+ assert_equal(false, res2)
+ assert_equal(['0', 'one', '1', 'two', '2'], getline(1, 6))
+
+ append(0, 'zero')
+ assert_equal('zero', getline(1))
+ append(0, {a: 10})
+ assert_equal("{'a': 10}", getline(1))
+ append(0, function('min'))
+ assert_equal("function('min')", getline(1))
+ v9.CheckDefAndScriptFailure(['append([1], "x")'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['append("", "x")'], 'E1209: Invalid value for a line number')
+ v9.CheckDefExecAndScriptFailure(['append(".a", "x")'], 'E1209: Invalid value for a line number')
+ # only get one error
+ assert_fails('append("''aa", "x")', ['E1209: Invalid value for a line number: "''aa"', 'E1209:'])
+ v9.CheckDefExecAndScriptFailure(['append(-1, "x")'], 'E966: Invalid line number: -1')
+ bwipe!
+enddef
+
+def Test_appendbufline()
+ new
+ var bnum: number = bufnr()
+ :wincmd w
+ appendbufline(bnum, 0, range(3))
+ var res1: number = appendbufline(bnum, 1, 'one')
+ assert_equal(0, res1)
+ var res2: bool = appendbufline(bnum, 3, 'two')
+ assert_equal(false, res2)
+ assert_equal(['0', 'one', '1', 'two', '2', ''], getbufline(bnum, 1, '$'))
+ appendbufline(bnum, 0, 'zero')
+ assert_equal(['zero'], getbufline(bnum, 1))
+ v9.CheckDefAndScriptFailure(['appendbufline([1], 1, "x")'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['appendbufline(1, [1], "x")'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['appendbufline(' .. bnum .. ', -1, "x")'], 'E966: Invalid line number: -1')
+ v9.CheckDefExecAndScriptFailure(['appendbufline(' .. bnum .. ', "$a", "x")'], 'E1030: Using a String as a Number: "$a"')
+ assert_fails('appendbufline(' .. bnum .. ', "$a", "x")', ['E1030: Using a String as a Number: "$a"', 'E1030:'])
+ v9.CheckDefAndScriptFailure(['appendbufline(1, 1, {"a": 10})'], ['E1013: Argument 3: type mismatch, expected string but got dict<number>', 'E1224: String, Number or List required for argument 3'])
+ bnum->bufwinid()->win_gotoid()
+ appendbufline('', 0, 'numbers')
+ getline(1)->assert_equal('numbers')
+ bwipe!
+enddef
+
+def Test_argc()
+ v9.CheckDefAndScriptFailure(['argc("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_arglistid()
+ v9.CheckDefAndScriptFailure(['arglistid("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['arglistid(1, "y")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['arglistid("x", "y")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_argv()
+ v9.CheckDefAndScriptFailure(['argv("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['argv(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['argv("x", "y")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_assert_beeps()
+ v9.CheckDefAndScriptFailure(['assert_beeps(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+enddef
+
+def Test_assert_equalfile()
+ v9.CheckDefAndScriptFailure(['assert_equalfile(1, "f2")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['assert_equalfile("f1", true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['assert_equalfile("f1", "f2", ["a"])'], ['E1013: Argument 3: type mismatch, expected string but got list<string>', 'E1174: String required for argument 3'])
+enddef
+
+def Test_assert_exception()
+ v9.CheckDefAndScriptFailure(['assert_exception({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['assert_exception("E1:", v:null)'], ['E1013: Argument 2: type mismatch, expected string but got special', 'E1174: String required for argument 2'])
+enddef
+
+def Test_assert_fails()
+ v9.CheckDefAndScriptFailure(['assert_fails([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['assert_fails("a", true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1222: String or List required for argument 2'])
+ v9.CheckDefAndScriptFailure(['assert_fails("a", "b", "c", "d")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ v9.CheckDefAndScriptFailure(['assert_fails("a", "b", "c", 4, 5)'], ['E1013: Argument 5: type mismatch, expected string but got number', 'E1174: String required for argument 5'])
+enddef
+
+def Test_assert_inrange()
+ v9.CheckDefAndScriptFailure(['assert_inrange("a", 2, 3)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['assert_inrange(1, "b", 3)'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['assert_inrange(1, 2, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['assert_inrange(1, 2, 3, 4)'], ['E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4'])
+enddef
+
+def Test_assert_match()
+ v9.CheckDefAndScriptFailure(['assert_match({}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', ''])
+ v9.CheckDefAndScriptFailure(['assert_match("a", 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', ''])
+ v9.CheckDefAndScriptFailure(['assert_match("a", "b", null)'], ['E1013: Argument 3: type mismatch, expected string but got special', ''])
+enddef
+
+def Test_assert_nobeep()
+ v9.CheckDefAndScriptFailure(['assert_nobeep(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+enddef
+
+def Test_assert_notmatch()
+ v9.CheckDefAndScriptFailure(['assert_notmatch({}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', ''])
+ v9.CheckDefAndScriptFailure(['assert_notmatch("a", 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', ''])
+ v9.CheckDefAndScriptFailure(['assert_notmatch("a", "b", null)'], ['E1013: Argument 3: type mismatch, expected string but got special', ''])
+enddef
+
+def Test_assert_report()
+ v9.CheckDefAndScriptFailure(['assert_report([1, 2])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_autocmd_add()
+ v9.CheckDefAndScriptFailure(['autocmd_add({})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+enddef
+
+def Test_autocmd_delete()
+ v9.CheckDefAndScriptFailure(['autocmd_delete({})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+enddef
+
+def Test_autocmd_get()
+ v9.CheckDefAndScriptFailure(['autocmd_get(10)'], ['E1013: Argument 1: type mismatch, expected dict<any> but got number', 'E1206: Dictionary required for argument 1'])
+enddef
+
+def Test_balloon_show()
+ CheckGui
+ CheckFeature balloon_eval
+
+ assert_fails('balloon_show(10)', 'E1222:')
+ assert_fails('balloon_show(true)', 'E1222:')
+
+ v9.CheckDefAndScriptFailure(['balloon_show(1.2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['balloon_show({"a": 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1222: String or List required for argument 1'])
+enddef
+
+def Test_balloon_split()
+ CheckFeature balloon_eval_term
+
+ assert_fails('balloon_split([])', 'E1174:')
+ assert_fails('balloon_split(true)', 'E1174:')
+enddef
+
+def Test_blob2list()
+ assert_equal(['x', 'x'], blob2list(0z1234)->map((_, _) => 'x'))
+ v9.CheckDefAndScriptFailure(['blob2list(10)'], ['E1013: Argument 1: type mismatch, expected blob but got number', 'E1238: Blob required for argument 1'])
+enddef
+
+def Test_browse()
+ CheckFeature browse
+
+ v9.CheckDefAndScriptFailure(['browse(2, "title", "dir", "file")'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['browse(true, 2, "dir", "file")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['browse(true, "title", 3, "file")'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ v9.CheckDefAndScriptFailure(['browse(true, "title", "dir", 4)'], ['E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4'])
+enddef
+
+def Test_browsedir()
+ if has('browse')
+ v9.CheckDefAndScriptFailure(['browsedir({}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['browsedir("a", [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+ endif
+enddef
+
+def Test_bufadd()
+ assert_fails('bufadd([])', 'E1174:')
+enddef
+
+def Test_bufexists()
+ assert_fails('bufexists(true)', 'E1220:')
+ bufexists('')->assert_false()
+enddef
+
+def Test_buflisted()
+ var res: bool = buflisted('asdf')
+ assert_equal(false, res)
+ assert_fails('buflisted(true)', 'E1220:')
+ assert_fails('buflisted([])', 'E1220:')
+ buflisted('')->assert_false()
+enddef
+
+def Test_bufload()
+ assert_fails('bufload([])', 'E1220:')
+ bufload('')->assert_equal(0)
+enddef
+
+def Test_bufloaded()
+ assert_fails('bufloaded(true)', 'E1220:')
+ assert_fails('bufloaded([])', 'E1220:')
+ bufloaded('')->assert_false()
+enddef
+
+def Test_bufname()
+ split SomeFile
+ bufname('%')->assert_equal('SomeFile')
+ edit OtherFile
+ bufname('#')->assert_equal('SomeFile')
+ close
+ assert_fails('bufname(true)', 'E1220:')
+ assert_fails('bufname([])', 'E1220:')
+enddef
+
+let s:bufnr_res = 0
+
+def Test_bufnr()
+ var buf = bufnr()
+ bufnr('%')->assert_equal(buf)
+
+ # check the lock is not taken over through the stack
+ const nr = 10
+ bufnr_res = bufnr()
+ bufnr_res = 12345
+
+ buf = bufnr('Xdummy', true)
+ buf->assert_notequal(-1)
+ exe 'bwipe! ' .. buf
+ v9.CheckDefAndScriptFailure(['bufnr([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['bufnr(1, 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+enddef
+
+def Test_bufwinid()
+ var origwin = win_getid()
+ below split SomeFile
+ var SomeFileID = win_getid()
+ below split OtherFile
+ below split SomeFile
+ bufwinid('SomeFile')->assert_equal(SomeFileID)
+
+ win_gotoid(origwin)
+ only
+ bwipe SomeFile
+ bwipe OtherFile
+
+ assert_fails('bufwinid(true)', 'E1220:')
+ assert_fails('bufwinid([])', 'E1220:')
+enddef
+
+def Test_bufwinnr()
+ assert_fails('bufwinnr(true)', 'E1220:')
+ assert_fails('bufwinnr([])', 'E1220:')
+enddef
+
+def Test_byte2line()
+ v9.CheckDefAndScriptFailure(['byte2line("1")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['byte2line([])'], ['E1013: Argument 1: type mismatch, expected number but got list<unknown>', 'E1210: Number required for argument 1'])
+ byte2line(0)->assert_equal(-1)
+enddef
+
+def Test_byteidx()
+ v9.CheckDefAndScriptFailure(['byteidx(1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['byteidx("a", "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ byteidx('', 0)->assert_equal(0)
+ byteidx('', 1)->assert_equal(-1)
+enddef
+
+def Test_byteidxcomp()
+ v9.CheckDefAndScriptFailure(['byteidxcomp(1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['byteidxcomp("a", "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_call_call()
+ var l = [3, 2, 1]
+ call('reverse', [l])
+ l->assert_equal([1, 2, 3])
+
+ var lines =<< trim END
+ vim9script
+ def Outer()
+ def g:Inner()
+ g:done = 'Inner'
+ enddef
+ call(g:Inner, [])
+ enddef
+ Outer()
+ assert_equal('Inner', g:done)
+ unlet g:done
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:Inner
+
+ v9.CheckDefExecAndScriptFailure(['call(123, [2])'], 'E1256: String or function required for argument 1')
+ v9.CheckDefExecAndScriptFailure(['call(true, [2])'], 'E1256: String or function required for argument 1')
+ v9.CheckDefAndScriptFailure(['call("reverse", 2)'], ['E1013: Argument 2: type mismatch, expected list<any> but got number', 'E1211: List required for argument 2'])
+ v9.CheckDefAndScriptFailure(['call("reverse", [2], [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+enddef
+
+def Test_ch_canread()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_canread(10)'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ endif
+enddef
+
+def Test_ch_close()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_close("c")'], ['E1013: Argument 1: type mismatch, expected channel but got string', 'E1217: Channel or Job required for argument 1'])
+ endif
+enddef
+
+def Test_ch_close_in()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_close_in(true)'], ['E1013: Argument 1: type mismatch, expected channel but got bool', 'E1217: Channel or Job required for argument 1'])
+ endif
+enddef
+
+def Test_ch_evalexpr()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_evalexpr(1, "a")'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_evalexpr(test_null_channel(), 1, [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+ endif
+enddef
+
+def Test_ch_evalraw()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_evalraw(1, "")'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_evalraw(test_null_channel(), 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1221: String or Blob required for argument 2'])
+ v9.CheckDefAndScriptFailure(['ch_evalraw(test_null_channel(), "", [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+ endif
+enddef
+
+def Test_ch_getbufnr()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_getbufnr(1, "a")'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_getbufnr(test_null_channel(), 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ # test empty string argument for ch_getbufnr()
+ var job: job = job_start(&shell)
+ g:WaitForAssert(() => assert_equal('run', job_status(job)))
+ job->ch_getbufnr('')->assert_equal(-1)
+ job_stop(job)
+ endif
+enddef
+
+def Test_ch_getjob()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_getjob(1)'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_getjob({"a": 10})'], ['E1013: Argument 1: type mismatch, expected channel but got dict<number>', 'E1217: Channel or Job required for argument 1'])
+ assert_equal(0, ch_getjob(test_null_channel()))
+ endif
+enddef
+
+def Test_ch_info()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_info([1])'], ['E1013: Argument 1: type mismatch, expected channel but got list<number>', 'E1217: Channel or Job required for argument 1'])
+ endif
+enddef
+
+def Test_ch_log()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_log(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_log("a", 1)'], ['E1013: Argument 2: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 2'])
+ endif
+enddef
+
+def Test_ch_logfile()
+ if !has('channel')
+ CheckFeature channel
+ else
+ assert_fails('ch_logfile(true)', 'E1174:')
+ assert_fails('ch_logfile("foo", true)', 'E1174:')
+ ch_logfile('', '')->assert_equal(0)
+
+ v9.CheckDefAndScriptFailure(['ch_logfile(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_logfile("a", true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1174: String required for argument 2'])
+ endif
+enddef
+
+def Test_ch_open()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_open({"a": 10}, "a")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_open("a", [1])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['ch_open("")'], 'E475: Invalid argument')
+ endif
+enddef
+
+def Test_ch_read()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_read(1)'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_read(test_null_channel(), [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+ endif
+enddef
+
+def Test_ch_readblob()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_readblob(1)'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_readblob(test_null_channel(), [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+ endif
+enddef
+
+def Test_ch_readraw()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_readraw(1)'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_readraw(test_null_channel(), [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+ endif
+enddef
+
+def Test_ch_sendexpr()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_sendexpr(1, "a")'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_sendexpr(test_null_channel(), 1, [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+ endif
+enddef
+
+def Test_ch_sendraw()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_sendraw(1, "")'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_sendraw(test_null_channel(), 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1221: String or Blob required for argument 2'])
+ v9.CheckDefAndScriptFailure(['ch_sendraw(test_null_channel(), "", [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+ endif
+enddef
+
+def Test_ch_setoptions()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_setoptions(1, {})'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_setoptions(test_null_channel(), [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+ endif
+enddef
+
+def Test_ch_status()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['ch_status(1)'], ['E1013: Argument 1: type mismatch, expected channel but got number', 'E1217: Channel or Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['ch_status(test_null_channel(), [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+ endif
+enddef
+
+def Test_char2nr()
+ char2nr('ã‚', true)->assert_equal(12354)
+
+ assert_fails('char2nr(true)', 'E1174:')
+ v9.CheckDefAndScriptFailure(['char2nr(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['char2nr("a", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ assert_equal(97, char2nr('a', 1))
+ assert_equal(97, char2nr('a', 0))
+ assert_equal(97, char2nr('a', true))
+ assert_equal(97, char2nr('a', false))
+ char2nr('')->assert_equal(0)
+enddef
+
+def Test_charclass()
+ assert_fails('charclass(true)', 'E1174:')
+ charclass('')->assert_equal(0)
+enddef
+
+def Test_charcol()
+ v9.CheckDefAndScriptFailure(['charcol(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['charcol({a: 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['charcol(".", [])'], ['E1013: Argument 2: type mismatch, expected number but got list<unknown>', 'E1210: Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['charcol("")'], 'E1209: Invalid value for a line number')
+ new
+ setline(1, ['abcdefgh'])
+ cursor(1, 4)
+ assert_equal(4, charcol('.'))
+ assert_equal(9, charcol([1, '$']))
+ assert_equal(0, charcol([10, '$']))
+ bw!
+enddef
+
+def Test_charidx()
+ v9.CheckDefAndScriptFailure(['charidx(0z10, 1)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['charidx("a", "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['charidx("a", 1, "")'], ['E1013: Argument 3: type mismatch, expected bool but got string', 'E1212: Bool required for argument 3'])
+ charidx('', 0)->assert_equal(-1)
+ charidx('', 1)->assert_equal(-1)
+enddef
+
+def Test_chdir()
+ assert_fails('chdir(true)', 'E1174:')
+enddef
+
+def Test_cindent()
+ v9.CheckDefAndScriptFailure(['cindent([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['cindent(null)'], ['E1013: Argument 1: type mismatch, expected string but got special', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['cindent("")'], 'E1209: Invalid value for a line number')
+ assert_equal(-1, cindent(0))
+ assert_equal(0, cindent('.'))
+enddef
+
+def Test_clearmatches()
+ v9.CheckDefAndScriptFailure(['clearmatches("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_col()
+ new
+ setline(1, 'abcdefgh')
+ cursor(1, 4)
+ assert_equal(4, col('.'))
+ col([1, '$'])->assert_equal(9)
+ assert_equal(0, col([10, '$']))
+
+ assert_fails('col(true)', 'E1222:')
+
+ v9.CheckDefAndScriptFailure(['col(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['col({a: 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['col(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['col(".", [])'], ['E1013: Argument 2: type mismatch, expected number but got list<unknown>', 'E1210: Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['col("")'], 'E1209: Invalid value for a line number')
+ bw!
+enddef
+
+def Test_complete()
+ v9.CheckDefAndScriptFailure(['complete("1", [])'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['complete(1, {})'], ['E1013: Argument 2: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 2'])
+enddef
+
+def Test_complete_add()
+ v9.CheckDefAndScriptFailure(['complete_add([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1223: String or Dictionary required for argument 1'])
+enddef
+
+def Test_complete_info()
+ v9.CheckDefAndScriptFailure(['complete_info("")'], ['E1013: Argument 1: type mismatch, expected list<string> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['complete_info({})'], ['E1013: Argument 1: type mismatch, expected list<string> but got dict<unknown>', 'E1211: List required for argument 1'])
+ assert_equal({'pum_visible': 0, 'mode': '', 'selected': -1, 'items': []}, complete_info())
+ assert_equal({'mode': '', 'items': []}, complete_info(['mode', 'items']))
+enddef
+
+def Test_confirm()
+ if !has('dialog_con') && !has('dialog_gui')
+ CheckFeature dialog_con
+ endif
+
+ assert_fails('confirm(true)', 'E1174:')
+ assert_fails('confirm("yes", true)', 'E1174:')
+ assert_fails('confirm("yes", "maybe", 2, true)', 'E1174:')
+ v9.CheckDefAndScriptFailure(['confirm(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['confirm("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['confirm("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['confirm("a", "b", 3, 4)'], ['E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4'])
+enddef
+
+def Test_copy_return_type()
+ var l = copy([1, 2, 3])
+ var res = 0
+ for n in l
+ res += n
+ endfor
+ res->assert_equal(6)
+
+ var dl = deepcopy([1, 2, 3])
+ res = 0
+ for n in dl
+ res += n
+ endfor
+ res->assert_equal(6)
+
+ dl = deepcopy([1, 2, 3], true)
+
+ # after a copy() the type can change, but not the item itself
+ var nl: list<number> = [1, 2]
+ assert_equal([1, 2, 'x'], nl->copy()->extend(['x']))
+
+ var lines =<< trim END
+ var nll: list<list<number>> = [[1, 2]]
+ nll->copy()[0]->extend(['x'])
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected list<number> but got list<string> in extend()')
+
+ var nd: dict<number> = {a: 1, b: 2}
+ assert_equal({a: 1, b: 2, c: 'x'}, nd->copy()->extend({c: 'x'}))
+ lines =<< trim END
+ var ndd: dict<dict<number>> = {a: {x: 1, y: 2}}
+ ndd->copy()['a']->extend({z: 'x'})
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict<number> but got dict<string> in extend()')
+
+ # after a deepcopy() the item type can also change
+ var nll: list<list<number>> = [[1, 2]]
+ assert_equal([1, 2, 'x'], nll->deepcopy()[0]->extend(['x']))
+
+ var ndd: dict<dict<number>> = {a: {x: 1, y: 2}}
+ assert_equal({x: 1, y: 2, z: 'x'}, ndd->deepcopy()['a']->extend({z: 'x'}))
+
+ var ldn: list<dict<number>> = [{a: 0}]->deepcopy()
+ assert_equal([{a: 0}], ldn)
+enddef
+
+def Test_count()
+ count('ABC ABC ABC', 'b', true)->assert_equal(3)
+ count('ABC ABC ABC', 'b', false)->assert_equal(0)
+ v9.CheckDefAndScriptFailure(['count(10, 1)'], 'E1225: String, List or Dictionary required for argument 1')
+ v9.CheckDefAndScriptFailure(['count("a", [1], 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ v9.CheckDefAndScriptFailure(['count("a", [1], 0, "b")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ count([1, 2, 2, 3], 2)->assert_equal(2)
+ count([1, 2, 2, 3], 2, false, 2)->assert_equal(1)
+ count({a: 1.1, b: 2.2, c: 1.1}, 1.1)->assert_equal(2)
+enddef
+
+def Test_cscope_connection()
+ CheckFeature cscope
+ assert_equal(0, cscope_connection())
+ v9.CheckDefAndScriptFailure(['cscope_connection("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['cscope_connection(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['cscope_connection(1, "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+enddef
+
+def Test_cursor()
+ new
+ setline(1, range(4))
+ cursor(2, 1)
+ assert_equal(2, getcurpos()[1])
+ cursor('$', 1)
+ assert_equal(4, getcurpos()[1])
+ cursor([2, 1])
+ assert_equal(2, getcurpos()[1])
+
+ var lines =<< trim END
+ cursor('2', 1)
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1209:')
+ v9.CheckDefAndScriptFailure(['cursor(0z10, 1)'], ['E1013: Argument 1: type mismatch, expected number but got blob', 'E1224: String, Number or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['cursor(1, "2")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['cursor(1, 2, "3")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['cursor("", 2)'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_debugbreak()
+ CheckMSWindows
+ v9.CheckDefAndScriptFailure(['debugbreak("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_deepcopy()
+ v9.CheckDefAndScriptFailure(['deepcopy({}, 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+enddef
+
+def Test_delete()
+ var res: bool = delete('doesnotexist')
+ assert_equal(true, res)
+
+ v9.CheckDefAndScriptFailure(['delete(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['delete("a", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['delete("")'], 'E474: Invalid argument')
+enddef
+
+def Test_deletebufline()
+ v9.CheckDefAndScriptFailure(['deletebufline([], 2)'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['deletebufline("a", [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['deletebufline("a", 2, 0z10)'], ['E1013: Argument 3: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 3'])
+ new
+ setline(1, ['one', 'two'])
+ deletebufline('', 1)
+ getline(1, '$')->assert_equal(['two'])
+
+ assert_fails('deletebufline("", "$a", "$b")', ['E1030: Using a String as a Number: "$a"', 'E1030: Using a String as a Number: "$a"'])
+ assert_fails('deletebufline("", "$", "$b")', ['E1030: Using a String as a Number: "$b"', 'E1030: Using a String as a Number: "$b"'])
+
+ bwipe!
+enddef
+
+def Test_diff_filler()
+ v9.CheckDefAndScriptFailure(['diff_filler([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['diff_filler(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['diff_filler("")'], 'E1209: Invalid value for a line number')
+ assert_equal(0, diff_filler(1))
+ assert_equal(0, diff_filler('.'))
+enddef
+
+def Test_diff_hlID()
+ v9.CheckDefAndScriptFailure(['diff_hlID(0z10, 1)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['diff_hlID(1, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['diff_hlID("", 10)'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_digraph_get()
+ v9.CheckDefAndScriptFailure(['digraph_get(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['digraph_get("")'], 'E1214: Digraph must be just two characters')
+enddef
+
+def Test_digraph_getlist()
+ v9.CheckDefAndScriptFailure(['digraph_getlist(10)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['digraph_getlist("")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1'])
+enddef
+
+def Test_digraph_set()
+ v9.CheckDefAndScriptFailure(['digraph_set(10, "a")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['digraph_set("ab", 0z10)'], ['E1013: Argument 2: type mismatch, expected string but got blob', 'E1174: String required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['digraph_set("", "a")'], 'E1214: Digraph must be just two characters')
+enddef
+
+def Test_digraph_setlist()
+ v9.CheckDefAndScriptFailure(['digraph_setlist("a")'], ['E1013: Argument 1: type mismatch, expected list<string> but got string', 'E1216: digraph_setlist() argument must be a list of lists with two items'])
+ v9.CheckDefAndScriptFailure(['digraph_setlist({})'], ['E1013: Argument 1: type mismatch, expected list<string> but got dict<unknown>', 'E1216: digraph_setlist() argument must be a list of lists with two items'])
+enddef
+
+def Test_echoraw()
+ v9.CheckDefAndScriptFailure(['echoraw(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['echoraw(["x"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_escape()
+ v9.CheckDefAndScriptFailure(['escape(10, " ")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['escape(true, false)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['escape("a", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ assert_equal('a\:b', escape("a:b", ":"))
+ escape('abc', '')->assert_equal('abc')
+ escape('', ':')->assert_equal('')
+enddef
+
+def Test_eval()
+ v9.CheckDefAndScriptFailure(['eval(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['eval(null)'], ['E1013: Argument 1: type mismatch, expected string but got special', 'E1174: String required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['eval("")'], 'E15: Invalid expression')
+ assert_equal(2, eval('1 + 1'))
+enddef
+
+def Test_executable()
+ assert_false(executable(""))
+ assert_false(executable(test_null_string()))
+
+ v9.CheckDefExecFailure(['echo executable(123)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo executable(true)'], 'E1013:')
+enddef
+
+def Test_execute()
+ var res = execute("echo 'hello'")
+ assert_equal("\nhello", res)
+ res = execute(["echo 'here'", "echo 'there'"])
+ assert_equal("\nhere\nthere", res)
+
+ v9.CheckDefAndScriptFailure(['execute(123)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1222: String or List required for argument 1'])
+ v9.CheckDefFailure(['execute([123])'], 'E1013: Argument 1: type mismatch, expected list<string> but got list<number>')
+ v9.CheckDefExecFailure(['echo execute(["xx", 123])'], 'E492')
+ v9.CheckDefAndScriptFailure(['execute("xx", 123)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+enddef
+
+def Test_exepath()
+ v9.CheckDefExecFailure(['echo exepath(true)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo exepath(v:null)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo exepath("")'], 'E1175:')
+enddef
+
+command DoSomeCommand let g:didSomeCommand = 4
+
+def Test_exists()
+ v9.CheckDefAndScriptFailure(['exists(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ call assert_equal(1, exists('&tabstop'))
+
+ var lines =<< trim END
+ if exists('+newoption')
+ if &newoption == 'ok'
+ endif
+ endif
+ END
+ v9.CheckDefFailure(lines, 'E113:')
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_exists_compiled()
+ call assert_equal(1, exists_compiled('&tabstop'))
+ v9.CheckDefAndScriptFailure(['exists_compiled(10)'], ['E1232:', 'E1233:'])
+ v9.CheckDefAndScriptFailure(['exists_compiled(v:progname)'], ['E1232:', 'E1233:'])
+
+ if exists_compiled('+newoption')
+ if &newoption == 'ok'
+ endif
+ endif
+ if exists_compiled('&newoption')
+ if &newoption == 'ok'
+ endif
+ endif
+ if exists_compiled('+tabstop')
+ assert_equal(8, &tabstop)
+ else
+ assert_report('tabstop option not existing?')
+ endif
+ if exists_compiled('&tabstop')
+ assert_equal(8, &tabstop)
+ else
+ assert_report('tabstop option not existing?')
+ endif
+
+ if exists_compiled(':DoSomeCommand') >= 2
+ DoSomeCommand
+ endif
+ assert_equal(4, g:didSomeCommand)
+ if exists_compiled(':NoSuchCommand') >= 2
+ NoSuchCommand
+ endif
+
+ var found = false
+ if exists_compiled('*CheckFeature')
+ found = true
+ endif
+ assert_false(found)
+ found = false
+ if exists_compiled('*g:CheckFeature')
+ found = true
+ endif
+ assert_true(found)
+
+ if exists_compiled('*NoSuchFunction')
+ NoSuchFunction()
+ endif
+ if exists_compiled('*no_such_function')
+ no_such_function()
+ endif
+enddef
+
+def Test_expand()
+ split SomeFile
+ expand('%', true, true)->assert_equal(['SomeFile'])
+ close
+ v9.CheckDefAndScriptFailure(['expand(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['expand("a", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ v9.CheckDefAndScriptFailure(['expand("a", true, 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ expand('')->assert_equal('')
+
+ var caught = false
+ try
+ echo expand("<sfile>")
+ catch /E1245:/
+ caught = true
+ endtry
+ assert_true(caught)
+enddef
+
+def Test_expandcmd()
+ $FOO = "blue"
+ assert_equal("blue sky", expandcmd("`=$FOO .. ' sky'`"))
+
+ assert_equal("yes", expandcmd("`={a: 'yes'}['a']`"))
+ expandcmd('')->assert_equal('')
+
+ v9.CheckDefAndScriptFailure(['expandcmd([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['expandcmd("abc", [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_extend_arg_types()
+ g:number_one = 1
+ g:string_keep = 'keep'
+ var lines =<< trim END
+ assert_equal([1, 2, 3], extend([1, 2], [3]))
+ assert_equal([3, 1, 2], extend([1, 2], [3], 0))
+ assert_equal([1, 3, 2], extend([1, 2], [3], 1))
+ assert_equal([1, 3, 2], extend([1, 2], [3], g:number_one))
+
+ assert_equal({a: 1, b: 2, c: 3}, extend({a: 1, b: 2}, {c: 3}))
+ assert_equal({a: 1, b: 4}, extend({a: 1, b: 2}, {b: 4}))
+ assert_equal({a: 1, b: 2}, extend({a: 1, b: 2}, {b: 4}, 'keep'))
+ assert_equal({a: 1, b: 2}, extend({a: 1, b: 2}, {b: 4}, g:string_keep))
+
+ # mix of types is OK without a declaration
+
+ var res: list<dict<any>>
+ extend(res, mapnew([1, 2], (_, v) => ({})))
+ assert_equal([{}, {}], res)
+
+ var dany: dict<any> = {a: 0}
+ dany->extend({b: 'x'})
+ assert_equal({a: 0, b: 'x'}, dany)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ assert_equal([1, 2, "x"], extend([1, 2], ["x"]))
+ assert_equal([1, "b", 1], extend([1], ["b", 1]))
+
+ assert_equal({a: 1, b: "x"}, extend({a: 1}, {b: "x"}))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(['extend("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E712: Argument of extend() must be a List or Dictionary'])
+ v9.CheckDefAndScriptFailure(['extend([1, 2], 3)'], ['E1013: Argument 2: type mismatch, expected list<any> but got number', 'E712: Argument of extend() must be a List or Dictionary'])
+ v9.CheckDefAndScriptFailure(['var ll = [1, 2]', 'extend(ll, ["x"])'], ['E1013: Argument 2: type mismatch, expected list<number> but got list<string>', 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>'])
+ v9.CheckDefFailure(['extend([1, 2], [3], "x")'], 'E1013: Argument 3: type mismatch, expected number but got string')
+
+ v9.CheckDefFailure(['extend({a: 1}, 42)'], 'E1013: Argument 2: type mismatch, expected dict<any> but got number')
+ v9.CheckDefFailure(['extend({a: 1}, {b: 2}, 1)'], 'E1013: Argument 3: type mismatch, expected string but got number')
+
+ v9.CheckScriptFailure(['vim9script', 'var l = [1]', 'extend(l, ["b", 1])'], 'E1013: Argument 2: type mismatch, expected list<number> but got list<any> in extend()')
+enddef
+
+func g:ExtendDict(d)
+ call extend(a:d, #{xx: 'x'})
+endfunc
+
+def Test_extend_dict_item_type()
+ var lines =<< trim END
+ var d: dict<number> = {a: 1}
+ extend(d, {b: 2})
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var d: dict<number> = {a: 1}
+ extend(d, {b: 'x'})
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict<number> but got dict<string>', 2)
+
+ lines =<< trim END
+ var d: dict<number> = {a: 1}
+ g:ExtendDict(d)
+ END
+ v9.CheckDefExecFailure(lines, 'E1012: Type mismatch; expected number but got string', 0)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var d: dict<bool>
+ extend(d, {b: 0})
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict<bool> but got dict<number>', 2)
+enddef
+
+func g:ExtendList(l)
+ call extend(a:l, ['x'])
+endfunc
+
+def Test_extend_list_item_type()
+ var lines =<< trim END
+ var l: list<number> = [1]
+ extend(l, [2])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l: list<number> = [1]
+ extend(l, ['x'])
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>', 2)
+
+ lines =<< trim END
+ var l: list<number> = [1]
+ g:ExtendList(l)
+ END
+ v9.CheckDefExecFailure(lines, 'E1012: Type mismatch; expected number but got string', 0)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var l: list<bool>
+ extend(l, [0])
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected list<bool> but got list<number>', 2)
+enddef
+
+def Test_extend_return_type()
+ var l = extend([1, 2], [3])
+ var res = 0
+ for n in l
+ res += n
+ endfor
+ res->assert_equal(6)
+enddef
+
+def Test_extend_with_error_function()
+ var lines =<< trim END
+ vim9script
+ def F()
+ {
+ var m = 10
+ }
+ echo m
+ enddef
+
+ def Test()
+ var d: dict<any> = {}
+ d->extend({A: 10, Func: function('F', [])})
+ d.Func()
+ enddef
+
+ Test()
+ END
+ v9.CheckScriptFailure(lines, 'E1001: Variable not found: m')
+enddef
+
+def Test_extend_const()
+ var lines =<< trim END
+ const l = [1, 2]
+ extend(l, [3])
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+
+ lines =<< trim END
+ const d = {a: 1, b: 2}
+ extend(d, {c: 3})
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const dict<number>')
+
+ lines =<< trim END
+ final d = {a: 1, b: 2}
+ extend(d, {c: 3})
+ assert_equal({a: 1, b: 2, c: 3}, d)
+ END
+ v9.CheckDefSuccess(lines)
+
+ # item in a for loop is final
+ lines =<< trim END
+ var l: list<dict<any>> = [{n: 1}]
+ for item in l
+ item->extend({x: 2})
+ endfor
+ END
+ v9.CheckDefSuccess(lines)
+enddef
+
+def Test_extendnew()
+ assert_equal([1, 2, 'a'], extendnew([1, 2], ['a']))
+ assert_equal({one: 1, two: 'a'}, extendnew({one: 1}, {two: 'a'}))
+
+ v9.CheckDefAndScriptFailure(['extendnew({a: 1}, 42)'], ['E1013: Argument 2: type mismatch, expected dict<number> but got number', 'E712: Argument of extendnew() must be a List or Dictionary'])
+ v9.CheckDefAndScriptFailure(['extendnew({a: 1}, [42])'], ['E1013: Argument 2: type mismatch, expected dict<number> but got list<number>', 'E712: Argument of extendnew() must be a List or Dictionary'])
+ v9.CheckDefAndScriptFailure(['extendnew([1, 2], "x")'], ['E1013: Argument 2: type mismatch, expected list<number> but got string', 'E712: Argument of extendnew() must be a List or Dictionary'])
+ v9.CheckDefAndScriptFailure(['extendnew([1, 2], {x: 1})'], ['E1013: Argument 2: type mismatch, expected list<number> but got dict<number>', 'E712: Argument of extendnew() must be a List or Dictionary'])
+enddef
+
+def Test_feedkeys()
+ v9.CheckDefAndScriptFailure(['feedkeys(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['feedkeys("x", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['feedkeys([], {})'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+ g:TestVar = 1
+ feedkeys(":g:TestVar = 789\n", 'xt')
+ assert_equal(789, g:TestVar)
+ unlet g:TestVar
+enddef
+
+def Test_filereadable()
+ assert_false(filereadable(""))
+ assert_false(filereadable(test_null_string()))
+
+ v9.CheckDefExecFailure(['echo filereadable(123)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo filereadable(true)'], 'E1013:')
+enddef
+
+def Test_filewritable()
+ assert_false(filewritable(""))
+ assert_false(filewritable(test_null_string()))
+
+ v9.CheckDefExecFailure(['echo filewritable(123)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo filewritable(true)'], 'E1013:')
+enddef
+
+def Test_finddir()
+ mkdir('Xtestdir')
+ finddir('Xtestdir', '**', -1)->assert_equal(['Xtestdir'])
+ var lines =<< trim END
+ var l: list<string> = finddir('nothing', '*;', -1)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+ delete('Xtestdir', 'rf')
+
+ v9.CheckDefAndScriptFailure(['finddir(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['finddir(v:null)'], ['E1013: Argument 1: type mismatch, expected string but got special', 'E1174: String required for argument 1'])
+ v9.CheckDefExecFailure(['echo finddir("")'], 'E1175:')
+ v9.CheckDefAndScriptFailure(['finddir("a", [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['finddir("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ finddir('abc', '')->assert_equal('')
+
+ v9.CheckDefFailure(['var s: list<string> = finddir("foo")'], 'E1012: Type mismatch; expected list<string> but got string')
+ v9.CheckDefFailure(['var s: list<string> = finddir("foo", "path")'], 'E1012: Type mismatch; expected list<string> but got string')
+ # with third argument only runtime type checking
+ v9.CheckDefCompileSuccess(['var s: list<string> = finddir("foo", "path", 1)'])
+enddef
+
+def Test_findfile()
+ findfile('runtest.vim', '**', -1)->assert_equal(['runtest.vim'])
+ var lines =<< trim END
+ var l: list<string> = findfile('nothing', '*;', -1)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefExecFailure(['findfile(true)'], 'E1013: Argument 1: type mismatch, expected string but got bool')
+ v9.CheckDefExecFailure(['findfile(v:null)'], 'E1013: Argument 1: type mismatch, expected string but got special')
+ v9.CheckDefExecFailure(['findfile("")'], 'E1175:')
+ v9.CheckDefAndScriptFailure(['findfile("a", [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['findfile("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ findfile('abc', '')->assert_equal('')
+enddef
+
+def Test_flatten()
+ var lines =<< trim END
+ echo flatten([1, 2, 3])
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1158:')
+enddef
+
+def Test_flattennew()
+ var lines =<< trim END
+ var l = [1, [2, [3, 4]], 5]
+ call assert_equal([1, 2, 3, 4, 5], flattennew(l))
+ call assert_equal([1, [2, [3, 4]], 5], l)
+
+ call assert_equal([1, 2, [3, 4], 5], flattennew(l, 1))
+ call assert_equal([1, [2, [3, 4]], 5], l)
+
+ var ll: list<list<string>> = [['a', 'b', 'c']]
+ assert_equal(['a', 'b', 'c'], ll->flattennew())
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(['flattennew({})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['flattennew([], "1")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+" Test for float functions argument type
+def Test_float_funcs_args()
+ # acos()
+ v9.CheckDefAndScriptFailure(['acos("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('1.570796', string(acos(0.0)))
+ # asin()
+ v9.CheckDefAndScriptFailure(['asin("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(asin(0.0)))
+ # atan()
+ v9.CheckDefAndScriptFailure(['atan("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(atan(0.0)))
+ # atan2()
+ v9.CheckDefAndScriptFailure(['atan2("a", 1.1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('-2.356194', string(atan2(-1, -1)))
+ v9.CheckDefAndScriptFailure(['atan2(1.2, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['atan2(1.2)'], ['E119:', 'E119:'])
+ # ceil()
+ v9.CheckDefAndScriptFailure(['ceil("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('2.0', string(ceil(2.0)))
+ # cos()
+ v9.CheckDefAndScriptFailure(['cos("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('1.0', string(cos(0.0)))
+ # cosh()
+ v9.CheckDefAndScriptFailure(['cosh("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('1.0', string(cosh(0.0)))
+ # exp()
+ v9.CheckDefAndScriptFailure(['exp("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('1.0', string(exp(0.0)))
+ # float2nr()
+ v9.CheckDefAndScriptFailure(['float2nr("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal(1, float2nr(1.234))
+ # floor()
+ v9.CheckDefAndScriptFailure(['floor("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('2.0', string(floor(2.0)))
+ # fmod()
+ v9.CheckDefAndScriptFailure(['fmod(1.1, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['fmod("a", 1.1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['fmod(1.1)'], ['E119:', 'E119:'])
+ assert_equal('0.13', string(fmod(12.33, 1.22)))
+ # isinf()
+ v9.CheckDefAndScriptFailure(['isinf("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal(1, isinf(1.0 / 0.0))
+ # isnan()
+ v9.CheckDefAndScriptFailure(['isnan("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_true(isnan(0.0 / 0.0))
+ # log()
+ v9.CheckDefAndScriptFailure(['log("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(log(1.0)))
+ # log10()
+ v9.CheckDefAndScriptFailure(['log10("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(log10(1.0)))
+ # pow()
+ v9.CheckDefAndScriptFailure(['pow("a", 1.1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['pow(1.1, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['pow(1.1)'], ['E119:', 'E119:'])
+ assert_equal('1.0', string(pow(0.0, 0.0)))
+ # round()
+ v9.CheckDefAndScriptFailure(['round("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('2.0', string(round(2.1)))
+ # sin()
+ v9.CheckDefAndScriptFailure(['sin("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(sin(0.0)))
+ # sinh()
+ v9.CheckDefAndScriptFailure(['sinh("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(sinh(0.0)))
+ # sqrt()
+ v9.CheckDefAndScriptFailure(['sqrt("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(sqrt(0.0)))
+ # tan()
+ v9.CheckDefAndScriptFailure(['tan("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(tan(0.0)))
+ # tanh()
+ v9.CheckDefAndScriptFailure(['tanh("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('0.0', string(tanh(0.0)))
+ # trunc()
+ v9.CheckDefAndScriptFailure(['trunc("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1219: Float or Number required for argument 1'])
+ assert_equal('2.0', string(trunc(2.1)))
+enddef
+
+def Test_fnameescape()
+ v9.CheckDefAndScriptFailure(['fnameescape(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal('\+a\%b\|', fnameescape('+a%b|'))
+ fnameescape('')->assert_equal('')
+enddef
+
+def Test_fnamemodify()
+ v9.CheckDefSuccess(['echo fnamemodify(test_null_string(), ":p")'])
+ v9.CheckDefSuccess(['echo fnamemodify("", ":p")'])
+ v9.CheckDefSuccess(['echo fnamemodify("file", test_null_string())'])
+ v9.CheckDefSuccess(['echo fnamemodify("file", "")'])
+
+ v9.CheckDefExecFailure(['echo fnamemodify(true, ":p")'], 'E1013: Argument 1: type mismatch, expected string but got bool')
+ v9.CheckDefExecFailure(['echo fnamemodify(v:null, ":p")'], 'E1013: Argument 1: type mismatch, expected string but got special')
+ v9.CheckDefExecFailure(['echo fnamemodify("file", true)'], 'E1013: Argument 2: type mismatch, expected string but got bool')
+enddef
+
+def Wrong_dict_key_type(items: list<number>): list<number>
+ return filter(items, (_, val) => get({[val]: 1}, 'x'))
+enddef
+
+def Test_filter()
+ assert_equal([], filter([1, 2, 3], '0'))
+ assert_equal([1, 2, 3], filter([1, 2, 3], '1'))
+ assert_equal({b: 20}, filter({a: 10, b: 20}, 'v:val == 20'))
+
+ def GetFiltered(): list<number>
+ var Odd: func = (_, v) => v % 2
+ return range(3)->filter(Odd)
+ enddef
+ assert_equal([1], GetFiltered())
+
+ var lines =<< trim END
+ vim9script
+ def Func(): list<string>
+ var MatchWord: func: bool = (_, v) => true
+ var l = ['xxx']
+ return l->filter(MatchWord)
+ enddef
+ assert_equal(['xxx'], Func())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(['filter(1.1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got float', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['filter([1, 2], 4)'], ['E1256: String or function required for argument 2', 'E1024: Using a Number as a String'])
+
+ lines =<< trim END
+ def F(i: number, v: any): string
+ return 'bad'
+ enddef
+ echo filter([1, 2, 3], F)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?any): bool but got func(number, any): string', 'E1135: Using a String as a Bool:'])
+
+ # check first function argument type
+ lines =<< trim END
+ var l = [1, 2, 3]
+ filter(l, (i: string, v: number) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): bool but got func(string, number): bool', 'E1013: Argument 1: type mismatch, expected string but got number'])
+ lines =<< trim END
+ var d = {a: 1}
+ filter(d, (i: number, v: number) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?string, ?number): bool but got func(number, number): bool', 'E1013: Argument 1: type mismatch, expected number but got string'])
+ lines =<< trim END
+ var b = 0z1122
+ filter(b, (i: string, v: number) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): bool but got func(string, number): bool', 'E1013: Argument 1: type mismatch, expected string but got number'])
+ lines =<< trim END
+ var s = 'text'
+ filter(s, (i: string, v: string) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?string): bool but got func(string, string): bool', 'E1013: Argument 1: type mismatch, expected string but got number'])
+
+ # check second function argument type
+ lines =<< trim END
+ var l = [1, 2, 3]
+ filter(l, (i: number, v: string) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): bool but got func(number, string): bool', 'E1013: Argument 2: type mismatch, expected string but got number'])
+ lines =<< trim END
+ var d = {a: 1}
+ filter(d, (i: string, v: string) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?string, ?number): bool but got func(string, string): bool', 'E1013: Argument 2: type mismatch, expected string but got number'])
+ lines =<< trim END
+ var b = 0z1122
+ filter(b, (i: number, v: string) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): bool but got func(number, string): bool', 'E1013: Argument 2: type mismatch, expected string but got number'])
+ lines =<< trim END
+ var s = 'text'
+ filter(s, (i: number, v: number) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?string): bool but got func(number, number): bool', 'E1013: Argument 2: type mismatch, expected number but got string'])
+enddef
+
+def Test_filter_wrong_dict_key_type()
+ assert_fails('g:Wrong_dict_key_type([1, v:null, 3])', 'E1013:')
+enddef
+
+def Test_filter_return_type()
+ var l = filter([1, 2, 3], (_, _) => 1)
+ var res = 0
+ for n in l
+ res += n
+ endfor
+ res->assert_equal(6)
+enddef
+
+def Test_filter_missing_argument()
+ var dict = {aa: [1], ab: [2], ac: [3], de: [4]}
+ var res = dict->filter((k, _) => k =~ 'a' && k !~ 'b')
+ res->assert_equal({aa: [1], ac: [3]})
+enddef
+
+def Test_filter_const()
+ var lines =<< trim END
+ const l = [1, 2, 3]
+ filter(l, 'v:val == 2')
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+
+ lines =<< trim END
+ const d = {a: 1, b: 2}
+ filter(d, 'v:val == 2')
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const dict<number>')
+enddef
+
+def Test_foldclosed()
+ v9.CheckDefAndScriptFailure(['foldclosed(function("min"))'], ['E1013: Argument 1: type mismatch, expected string but got func(...): unknown', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['foldclosed("")'], 'E1209: Invalid value for a line number')
+ assert_equal(-1, foldclosed(1))
+ assert_equal(-1, foldclosed('$'))
+enddef
+
+def Test_foldclosedend()
+ v9.CheckDefAndScriptFailure(['foldclosedend(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['foldclosedend("")'], 'E1209: Invalid value for a line number')
+ assert_equal(-1, foldclosedend(1))
+ assert_equal(-1, foldclosedend('w0'))
+enddef
+
+def Test_foldlevel()
+ v9.CheckDefAndScriptFailure(['foldlevel(0z10)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['foldlevel("")'], 'E1209: Invalid value for a line number')
+ assert_equal(0, foldlevel(1))
+ assert_equal(0, foldlevel('.'))
+enddef
+
+def Test_foldtextresult()
+ v9.CheckDefAndScriptFailure(['foldtextresult(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['foldtextresult("")'], 'E1209: Invalid value for a line number')
+ assert_equal('', foldtextresult(1))
+ assert_equal('', foldtextresult('.'))
+enddef
+
+def Test_fullcommand()
+ assert_equal('next', fullcommand('n'))
+ assert_equal('noremap', fullcommand('no'))
+ assert_equal('noremap', fullcommand('nor'))
+ assert_equal('normal', fullcommand('norm'))
+
+ assert_equal('', fullcommand('k'))
+ assert_equal('keepmarks', fullcommand('ke'))
+ assert_equal('keepmarks', fullcommand('kee'))
+ assert_equal('keepmarks', fullcommand('keep'))
+ assert_equal('keepjumps', fullcommand('keepj'))
+
+ assert_equal('dlist', fullcommand('dl'))
+ assert_equal('', fullcommand('dp'))
+ assert_equal('delete', fullcommand('del'))
+ assert_equal('', fullcommand('dell'))
+ assert_equal('', fullcommand('delp'))
+
+ assert_equal('srewind', fullcommand('sre'))
+ assert_equal('scriptnames', fullcommand('scr'))
+ assert_equal('', fullcommand('scg'))
+ fullcommand('')->assert_equal('')
+
+ assert_equal('', fullcommand('en'))
+ legacy call assert_equal('endif', fullcommand('en'))
+ assert_equal('endif', fullcommand('en', 0))
+ legacy call assert_equal('endif', fullcommand('en', 0))
+ assert_equal('', fullcommand('en', 1))
+ legacy call assert_equal('', fullcommand('en', 1))
+enddef
+
+def Test_funcref()
+ v9.CheckDefAndScriptFailure(['funcref("reverse", 2)'], ['E1013: Argument 2: type mismatch, expected list<any> but got number', 'E1211: List required for argument 2'])
+ v9.CheckDefAndScriptFailure(['funcref("reverse", [2], [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+
+ var lines =<< trim END
+ vim9script
+ def UseBool(b: bool)
+ enddef
+ def GetRefOk()
+ var Ref1: func(bool) = funcref(UseBool)
+ var Ref2: func(bool) = funcref('UseBool')
+ enddef
+ def GetRefBad()
+ # only fails at runtime
+ var Ref1: func(number) = funcref(UseBool)
+ enddef
+ defcompile
+ GetRefOk()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def UseBool(b: bool)
+ enddef
+ def GetRefBad()
+ # only fails at runtime
+ var Ref1: func(number) = funcref(UseBool)
+ enddef
+ GetRefBad()
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected func(number) but got func(bool)')
+enddef
+
+def Test_function()
+ v9.CheckDefExecAndScriptFailure(['function(123)'], 'E1256: String or function required for argument 1')
+
+ v9.CheckDefAndScriptFailure(['function("reverse", 2)'], ['E1013: Argument 2: type mismatch, expected list<any> but got number', 'E1211: List required for argument 2'])
+ v9.CheckDefAndScriptFailure(['function("reverse", [2], [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+
+ var lines =<< trim END
+ vim9script
+ def UseBool(b: bool)
+ enddef
+ def GetRefOk()
+ var Ref1: func(bool) = function(UseBool)
+ var Ref2: func(bool) = function('UseBool')
+ enddef
+ def GetRefBad()
+ # only fails at runtime
+ var Ref1: func(number) = function(UseBool)
+ enddef
+ defcompile
+ GetRefOk()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def UseBool(b: bool)
+ enddef
+ def GetRefBad()
+ # only fails at runtime
+ var Ref1: func(number) = function(UseBool)
+ enddef
+ GetRefBad()
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected func(number) but got func(bool)')
+enddef
+
+def Test_garbagecollect()
+ garbagecollect(true)
+ v9.CheckDefAndScriptFailure(['garbagecollect("1")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['garbagecollect(20)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+enddef
+
+def Test_get()
+ v9.CheckDefAndScriptFailure(['get("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E896: Argument of get() must be a List, Dictionary or Blob'])
+ [3, 5, 2]->get(1)->assert_equal(5)
+ [3, 5, 2]->get(3)->assert_equal(0)
+ [3, 5, 2]->get(3, 9)->assert_equal(9)
+ assert_equal(get(0z102030, 2), 0x30)
+ {a: 7, b: 11, c: 13}->get('c')->assert_equal(13)
+ {10: 'a', 20: 'b', 30: 'd'}->get(20)->assert_equal('b')
+ function('max')->get('name')->assert_equal('max')
+ var F: func = function('min', [[5, 8, 6]])
+ F->get('name')->assert_equal('min')
+ F->get('args')->assert_equal([[5, 8, 6]])
+
+ var lines =<< trim END
+ vim9script
+ def DoThat(): number
+ var Getqflist: func = function('getqflist', [{id: 42}])
+ return Getqflist()->get('id', 77)
+ enddef
+ assert_equal(0, DoThat())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_getbufinfo()
+ var bufinfo = getbufinfo(bufnr())
+ getbufinfo('%')->assert_equal(bufinfo)
+
+ edit Xtestfile1
+ hide edit Xtestfile2
+ hide enew
+ getbufinfo({bufloaded: true, buflisted: true, bufmodified: false})
+ ->len()->assert_equal(3)
+ bwipe Xtestfile1 Xtestfile2
+ v9.CheckDefAndScriptFailure(['getbufinfo(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1'])
+enddef
+
+def Test_getbufline()
+ e SomeFile
+ var buf = bufnr()
+ sp Otherfile
+ var lines = ['aaa', 'bbb', 'ccc']
+ setbufline(buf, 1, lines)
+ getbufline('#', 1, '$')->assert_equal(lines)
+ getbufline(-1, '$', '$')->assert_equal([])
+ getbufline(-1, 1, '$')->assert_equal([])
+
+ getbufoneline('#', 1)->assert_equal(lines[0])
+
+ assert_equal([7, 7, 7], getbufline('#', 1, '$')->map((_, _) => 7))
+
+ assert_fails('getbufline("", "$a", "$b")', ['E1030: Using a String as a Number: "$a"', 'E1030: Using a String as a Number: "$a"'])
+ assert_fails('getbufline("", "$", "$b")', ['E1030: Using a String as a Number: "$b"', 'E1030: Using a String as a Number: "$b"'])
+ bwipe!
+
+ assert_fails('getbufoneline("", "$a")', ['E1030: Using a String as a Number: "$a"', 'E1030: Using a String as a Number: "$a"'])
+ bwipe!
+
+ v9.CheckDefAndScriptFailure(['getbufline([], 2)'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getbufline("a", [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['getbufline("a", 2, 0z10)'], ['E1013: Argument 3: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 3'])
+
+ v9.CheckDefAndScriptFailure(['getbufoneline([], 2)'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getbufoneline("a", [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 2'])
+enddef
+
+def Test_getbufvar()
+ v9.CheckDefAndScriptFailure(['getbufvar(true, "v")'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getbufvar(1, 2, 3)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+enddef
+
+def Test_getchangelist()
+ new
+ setline(1, 'some text')
+ var changelist = bufnr()->getchangelist()
+ getchangelist('%')->assert_equal(changelist)
+ bwipe!
+enddef
+
+def Test_getchar()
+ while getchar(0)
+ endwhile
+ getchar(true)->assert_equal(0)
+ getchar(1)->assert_equal(0)
+ v9.CheckDefAndScriptFailure(['getchar(2)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getchar("1")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1'])
+enddef
+
+def Test_getcharpos()
+ assert_equal(['x', 'x', 'x', 'x'], getcharpos('.')->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['getcharpos(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getcharpos(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['getcharpos("")'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_getcharstr()
+ v9.CheckDefAndScriptFailure(['getcharstr(2)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getcharstr("1")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1'])
+enddef
+
+def Test_getcompletion()
+ set wildignore=*.vim,*~
+ var l = getcompletion('run', 'file', true)
+ l->assert_equal([])
+ set wildignore&
+ v9.CheckDefAndScriptFailure(['getcompletion(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getcompletion("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['getcompletion("a", "b", 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['getcompletion("a", "")'], 'E475: Invalid argument')
+ getcompletion('', 'messages')->assert_equal(['clear'])
+enddef
+
+def Test_getcurpos()
+ assert_equal(['x', 'x', 'x', 'x', 'x'], getcurpos()->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['getcurpos("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_getcursorcharpos()
+ assert_equal(['x', 'x', 'x', 'x', 'x'], getcursorcharpos()->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['getcursorcharpos("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_getcwd()
+ v9.CheckDefAndScriptFailure(['getcwd("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getcwd("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getcwd(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_getenv()
+ if getenv('does-not_exist') == ''
+ assert_report('getenv() should return null')
+ endif
+ if getenv('does-not_exist') == null
+ else
+ assert_report('getenv() should return null')
+ endif
+ $SOMEENVVAR = 'some'
+ assert_equal('some', getenv('SOMEENVVAR'))
+ assert_notequal(null, getenv('SOMEENVVAR'))
+ unlet $SOMEENVVAR
+ getenv('')->assert_equal(v:null)
+enddef
+
+def Test_getfontname()
+ v9.CheckDefAndScriptFailure(['getfontname(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ #getfontname('')->assert_equal('')
+enddef
+
+def Test_getfperm()
+ assert_equal('', getfperm(""))
+ assert_equal('', getfperm(test_null_string()))
+
+ v9.CheckDefExecFailure(['echo getfperm(true)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo getfperm(v:null)'], 'E1013:')
+enddef
+
+def Test_getfsize()
+ assert_equal(-1, getfsize(""))
+ assert_equal(-1, getfsize(test_null_string()))
+
+ v9.CheckDefExecFailure(['echo getfsize(true)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo getfsize(v:null)'], 'E1013:')
+enddef
+
+def Test_getftime()
+ assert_equal(-1, getftime(""))
+ assert_equal(-1, getftime(test_null_string()))
+
+ v9.CheckDefExecFailure(['echo getftime(true)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo getftime(v:null)'], 'E1013:')
+enddef
+
+def Test_getftype()
+ assert_equal('', getftype(""))
+ assert_equal('', getftype(test_null_string()))
+
+ v9.CheckDefExecFailure(['echo getftype(true)'], 'E1013:')
+ v9.CheckDefExecFailure(['echo getftype(v:null)'], 'E1013:')
+enddef
+
+def Test_getjumplist()
+ v9.CheckDefAndScriptFailure(['getjumplist("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getjumplist("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getjumplist(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_getline()
+ var lines =<< trim END
+ new
+ setline(1, ['hello', 'there', 'again'])
+ assert_equal('hello', getline(1))
+ assert_equal('hello', getline('.'))
+
+ normal 2Gvjv
+ assert_equal('there', getline("'<"))
+ assert_equal('again', getline("'>"))
+
+ assert_equal([3, 3, 3], getline(1, 3)->map((_, _) => 3))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ echo getline('1')
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1209:')
+ v9.CheckDefAndScriptFailure(['getline(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getline(1, true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['getline("")'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_getloclist()
+ v9.CheckDefAndScriptFailure(['getloclist("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getloclist(1, [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_getloclist_return_type()
+ var l = getloclist(1)
+ l->assert_equal([])
+
+ var d = getloclist(1, {items: 0})
+ d->assert_equal({items: []})
+enddef
+
+def Test_getmarklist()
+ v9.CheckDefAndScriptFailure(['getmarklist([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ assert_equal([], getmarklist(10000))
+ assert_fails('getmarklist("a%b@#")', 'E94:')
+enddef
+
+def Test_getmatches()
+ v9.CheckDefAndScriptFailure(['getmatches("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_getpos()
+ assert_equal(['x', 'x', 'x', 'x'], getpos('.')->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['getpos(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal([0, 1, 1, 0], getpos('.'))
+ v9.CheckDefExecFailure(['getpos("a")'], 'E1209:')
+ v9.CheckDefExecAndScriptFailure(['getpos("")'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_getqflist()
+ v9.CheckDefAndScriptFailure(['getqflist([])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 1'])
+ call assert_equal({}, getqflist({}))
+enddef
+
+def Test_getqflist_return_type()
+ var l = getqflist()
+ l->assert_equal([])
+
+ var d = getqflist({items: 0})
+ d->assert_equal({items: []})
+enddef
+
+def Test_getreg()
+ var lines = ['aaa', 'bbb', 'ccc']
+ setreg('a', lines)
+ getreg('a', true, true)->assert_equal(lines)
+ assert_equal([7, 7, 7], getreg('a', true, true)->map((_, _) => 7))
+
+ assert_fails('getreg("ab")', 'E1162:')
+ v9.CheckDefAndScriptFailure(['getreg(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getreg(".", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ v9.CheckDefAndScriptFailure(['getreg(".", 1, "b")'], ['E1013: Argument 3: type mismatch, expected bool but got string', 'E1212: Bool required for argument 3'])
+ @" = 'A1B2C3'
+ getreg('')->assert_equal('A1B2C3')
+enddef
+
+def Test_getreg_return_type()
+ var s1: string = getreg('"')
+ var s2: string = getreg('"', 1)
+ var s3: list<string> = getreg('"', 1, 1)
+enddef
+
+def Test_getreginfo()
+ var text = 'abc'
+ setreg('a', text)
+ getreginfo('a')->assert_equal({regcontents: [text], regtype: 'v', isunnamed: false})
+ assert_fails('getreginfo("ab")', 'E1162:')
+ @" = 'D1E2F3'
+ getreginfo('').regcontents->assert_equal(['D1E2F3'])
+enddef
+
+def Test_getregtype()
+ var lines = ['aaa', 'bbb', 'ccc']
+ setreg('a', lines)
+ getregtype('a')->assert_equal('V')
+ assert_fails('getregtype("ab")', 'E1162:')
+ setreg('"', 'ABCD', 'b')
+ getregtype('')->assert_equal("\<C-V>4")
+enddef
+
+def Test_getscriptinfo()
+ v9.CheckDefAndScriptFailure(['getscriptinfo("x")'], ['E1013: Argument 1: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 1'])
+
+ var lines1 =<< trim END
+ vim9script
+ g:loaded_script_id = expand("<SID>")
+ var XscriptVar = [1, {v: 2}]
+ func XgetScriptVar()
+ return XscriptVar
+ endfunc
+ func Xscript_legacy_func1()
+ endfunc
+ def Xscript_def_func1()
+ enddef
+ func g:Xscript_legacy_func2()
+ endfunc
+ def g:Xscript_def_func2()
+ enddef
+ END
+ writefile(lines1, 'X22script92', 'D')
+
+ var lines2 =<< trim END
+ source X22script92
+ var sid = matchstr(g:loaded_script_id, '<SNR>\zs\d\+\ze_')->str2nr()
+
+ var l = getscriptinfo({sid: sid, name: 'ignored'})
+ assert_match('X22script92$', l[0].name)
+ assert_equal(g:loaded_script_id, $"<SNR>{l[0].sid}_")
+ assert_equal(999999, l[0].version)
+ assert_equal(0, l[0].sourced)
+ assert_equal({XscriptVar: [1, {v: 2}]}, l[0].variables)
+ var funcs = ['Xscript_legacy_func2',
+ $"<SNR>{sid}_Xscript_legacy_func1",
+ $"<SNR>{sid}_Xscript_def_func1",
+ 'Xscript_def_func2',
+ $"<SNR>{sid}_XgetScriptVar"]
+ for f in funcs
+ assert_true(index(l[0].functions, f) != -1)
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines2)
+enddef
+
+def Test_gettabinfo()
+ v9.CheckDefAndScriptFailure(['gettabinfo("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_gettabvar()
+ v9.CheckDefAndScriptFailure(['gettabvar("a", "b")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['gettabvar(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+enddef
+
+def Test_gettabwinvar()
+ v9.CheckDefAndScriptFailure(['gettabwinvar("a", 2, "c")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['gettabwinvar(1, "b", "c", [])'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['gettabwinvar(1, 1, 3, {})'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+enddef
+
+def Test_gettagstack()
+ v9.CheckDefAndScriptFailure(['gettagstack("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_gettext()
+ v9.CheckDefAndScriptFailure(['gettext(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['gettext("")'], 'E1175: Non-empty string required for argument 1')
+ assert_equal('abc', gettext("abc"))
+ assert_fails('gettext("")', 'E1175:')
+enddef
+
+def Test_getwininfo()
+ v9.CheckDefAndScriptFailure(['getwininfo("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_getwinpos()
+ assert_equal(['x', 'x'], getwinpos()->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['getwinpos("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_getwinvar()
+ v9.CheckDefAndScriptFailure(['getwinvar("a", "b")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['getwinvar(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+enddef
+
+def Test_glob()
+ glob('runtest.vim', true, true, true)->assert_equal(['runtest.vim'])
+ v9.CheckDefAndScriptFailure(['glob(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['glob("a", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ v9.CheckDefAndScriptFailure(['glob("a", 1, "b")'], ['E1013: Argument 3: type mismatch, expected bool but got string', 'E1212: Bool required for argument 3'])
+ v9.CheckDefAndScriptFailure(['glob("a", 1, true, 2)'], ['E1013: Argument 4: type mismatch, expected bool but got number', 'E1212: Bool required for argument 4'])
+ glob('')->assert_equal('')
+enddef
+
+def Test_glob2regpat()
+ v9.CheckDefAndScriptFailure(['glob2regpat(null)'], ['E1013: Argument 1: type mismatch, expected string but got special', 'E1174: String required for argument 1'])
+ glob2regpat('')->assert_equal('^$')
+enddef
+
+def Test_globpath()
+ globpath('.', 'runtest.vim', true, true, true)->assert_equal(['./runtest.vim'])
+ v9.CheckDefAndScriptFailure(['globpath(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['globpath("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['globpath("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected bool but got string', 'E1212: Bool required for argument 3'])
+ v9.CheckDefAndScriptFailure(['globpath("a", "b", true, "d")'], ['E1013: Argument 4: type mismatch, expected bool but got string', 'E1212: Bool required for argument 4'])
+ v9.CheckDefAndScriptFailure(['globpath("a", "b", true, false, "e")'], ['E1013: Argument 5: type mismatch, expected bool but got string', 'E1212: Bool required for argument 5'])
+ globpath('', '')->assert_equal('')
+enddef
+
+def Test_has()
+ has('eval', true)->assert_equal(1)
+ v9.CheckDefAndScriptFailure(['has(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['has("a", "b")'], ['E1013: Argument 2: type mismatch, expected bool but got string', 'E1212: Bool required for argument 2'])
+ has('')->assert_equal(0)
+enddef
+
+def Test_has_key()
+ var d = {123: 'xx'}
+ assert_true(has_key(d, '123'))
+ assert_true(has_key(d, 123))
+ assert_false(has_key(d, 'x'))
+ assert_false(has_key(d, 99))
+
+ v9.CheckDefAndScriptFailure(['has_key([1, 2], "k")'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['has_key({"a": 10}, ["a"])'], ['E1013: Argument 2: type mismatch, expected string but got list<string>', 'E1220: String or Number required for argument 2'])
+enddef
+
+def Test_haslocaldir()
+ v9.CheckDefAndScriptFailure(['haslocaldir("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['haslocaldir("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['haslocaldir(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_hasmapto()
+ hasmapto('foobar', 'i', true)->assert_equal(0)
+ iabbrev foo foobar
+ hasmapto('foobar', 'i', true)->assert_equal(1)
+ iunabbrev foo
+ v9.CheckDefAndScriptFailure(['hasmapto(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['hasmapto("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['hasmapto("a", "b", 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ hasmapto('', '')->assert_equal(0)
+enddef
+
+def Test_histadd()
+ v9.CheckDefAndScriptFailure(['histadd(1, "x")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['histadd(":", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ histadd("search", 'skyblue')
+ assert_equal('skyblue', histget('/', -1))
+ histadd("search", '')->assert_equal(0)
+enddef
+
+def Test_histdel()
+ v9.CheckDefAndScriptFailure(['histdel(1, "x")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['histdel(":", true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 2'])
+ histdel('search', '')->assert_equal(0)
+enddef
+
+def Test_histget()
+ v9.CheckDefAndScriptFailure(['histget(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['histget("a", "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_histnr()
+ v9.CheckDefAndScriptFailure(['histnr(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal(-1, histnr('abc'))
+enddef
+
+def Test_hlID()
+ v9.CheckDefAndScriptFailure(['hlID(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal(0, hlID('NonExistingHighlight'))
+ hlID('')->assert_equal(0)
+enddef
+
+def Test_hlexists()
+ v9.CheckDefAndScriptFailure(['hlexists([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+ assert_equal(0, hlexists('NonExistingHighlight'))
+ hlexists('')->assert_equal(0)
+enddef
+
+def Test_hlget()
+ v9.CheckDefAndScriptFailure(['hlget([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+ hlget('')->assert_equal([])
+enddef
+
+def Test_hlset()
+ v9.CheckDefAndScriptFailure(['hlset("id")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1211: List required for argument 1'])
+ hlset([])->assert_equal(0)
+enddef
+
+def Test_iconv()
+ v9.CheckDefAndScriptFailure(['iconv(1, "from", "to")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['iconv("abc", 10, "to")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['iconv("abc", "from", 20)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ assert_equal('abc', iconv('abc', 'fromenc', 'toenc'))
+ iconv('', '', '')->assert_equal('')
+enddef
+
+def Test_indent()
+ v9.CheckDefAndScriptFailure(['indent([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['indent(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['indent("")'], 'E1209: Invalid value for a line number')
+ v9.CheckDefExecAndScriptFailure(['indent(-1)'], 'E966: Invalid line number: -1')
+ assert_equal(0, indent(1))
+enddef
+
+def Test_index()
+ index(['a', 'b', 'a', 'B'], 'b', 2, true)->assert_equal(3)
+ v9.CheckDefAndScriptFailure(['index("a", "a")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1226: List or Blob required for argument 1'])
+ v9.CheckDefFailure(['index(["1"], 1)'], 'E1013: Argument 2: type mismatch, expected string but got number')
+ v9.CheckDefAndScriptFailure(['index(0z10, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['index([1], 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['index(0z1020, 10, 1, 2)'], ['E1013: Argument 4: type mismatch, expected bool but got number', 'E1212: Bool required for argument 4'])
+enddef
+
+def Test_indexof()
+ var l = [{color: 'red'}, {color: 'blue'}, {color: 'green'}, {color: 'blue'}]
+ indexof(l, (i, v) => v.color == 'blue')->assert_equal(1)
+ indexof(l, (i, v) => v.color == 'blue', {startidx: 1})->assert_equal(1)
+ indexof(l, (i, v) => v.color == 'blue', {startidx: 2})->assert_equal(3)
+ var b = 0zdeadbeef
+ indexof(b, "v:val == 0xef")->assert_equal(3)
+
+ def TestIdx1(k: number, v: dict<any>): bool
+ return v.color == 'blue'
+ enddef
+ indexof(l, TestIdx1)->assert_equal(1)
+
+ var lines =<< trim END
+ def TestIdx(v: dict<any>): bool
+ return v.color == 'blue'
+ enddef
+
+ indexof([{color: "red"}], TestIdx)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E176: Invalid number of arguments', 'E118: Too many arguments for function'])
+
+ lines =<< trim END
+ def TestIdx(k: number, v: dict<any>)
+ enddef
+
+ indexof([{color: "red"}], TestIdx)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?any): bool', 'E1031: Cannot use void value'])
+
+ lines =<< trim END
+ def TestIdx(k: number, v: dict<any>): string
+ return "abc"
+ enddef
+
+ indexof([{color: "red"}], TestIdx)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?any): bool', 'E1135: Using a String as a Bool'])
+enddef
+
+def Test_input()
+ v9.CheckDefAndScriptFailure(['input(5)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['input(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['input("p", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['input("p", "q", 20)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+enddef
+
+def Test_inputdialog()
+ v9.CheckDefAndScriptFailure(['inputdialog(5)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['inputdialog(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['inputdialog("p", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['inputdialog("p", "q", 20)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+enddef
+
+def Test_inputlist()
+ v9.CheckDefAndScriptFailure(['inputlist(10)'], ['E1013: Argument 1: type mismatch, expected list<string> but got number', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['inputlist("abc")'], ['E1013: Argument 1: type mismatch, expected list<string> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefFailure(['inputlist([1, 2, 3])'], 'E1013: Argument 1: type mismatch, expected list<string> but got list<number>')
+ feedkeys("2\<CR>", 't')
+ var r: number = inputlist(['a', 'b', 'c'])
+ assert_equal(2, r)
+enddef
+
+def Test_inputsecret()
+ v9.CheckDefAndScriptFailure(['inputsecret(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['inputsecret("Pass:", 20)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ feedkeys("\<CR>", 't')
+ var ans: string = inputsecret('Pass:', '123')
+ assert_equal('123', ans)
+enddef
+
+let s:number_one = 1
+let s:number_two = 2
+let s:string_keep = 'keep'
+
+def Test_insert()
+ var l = insert([2, 1], 3)
+ var res = 0
+ for n in l
+ res += n
+ endfor
+ res->assert_equal(6)
+
+ var m: any = []
+ insert(m, 4)
+ call assert_equal([4], m)
+ extend(m, [6], 0)
+ call assert_equal([6, 4], m)
+
+ var lines =<< trim END
+ insert(test_null_list(), 123)
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1130:', 1)
+
+ lines =<< trim END
+ insert(test_null_blob(), 123)
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1131:', 1)
+
+ assert_equal([1, 2, 3], insert([2, 3], 1))
+ assert_equal([1, 2, 3], insert([2, 3], number_one))
+ assert_equal([1, 2, 3], insert([1, 2], 3, 2))
+ assert_equal([1, 2, 3], insert([1, 2], 3, number_two))
+ assert_equal(['a', 'b', 'c'], insert(['b', 'c'], 'a'))
+ assert_equal(0z1234, insert(0z34, 0x12))
+
+ v9.CheckDefAndScriptFailure(['insert("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1226: List or Blob required for argument 1'])
+ v9.CheckDefFailure(['insert([2, 3], "a")'], 'E1013: Argument 2: type mismatch, expected number but got string')
+ v9.CheckDefAndScriptFailure(['insert([2, 3], 1, "x")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+enddef
+
+def Test_invert()
+ v9.CheckDefAndScriptFailure(['invert("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_isdirectory()
+ v9.CheckDefAndScriptFailure(['isdirectory(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1174: String required for argument 1'])
+ assert_false(isdirectory('NonExistingDir'))
+ assert_false(isdirectory(''))
+enddef
+
+def Test_islocked()
+ v9.CheckDefAndScriptFailure(['islocked(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['var n1: number = 10', 'islocked(n1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ g:v1 = 10
+ assert_false(islocked('g:v1'))
+ lockvar g:v1
+ assert_true(islocked('g:v1'))
+ unlet g:v1
+ islocked('')->assert_equal(-1)
+enddef
+
+def Test_items()
+ v9.CheckDefFailure(['123->items()'], 'E1225:')
+ assert_equal([['a', 10], ['b', 20]], {'a': 10, 'b': 20}->items())
+ assert_equal([], {}->items())
+ assert_equal(['x', 'x'], {'a': 10, 'b': 20}->items()->map((_, _) => 'x'))
+
+ assert_equal([[0, 'a'], [1, 'b']], ['a', 'b']->items())
+ assert_equal([], []->items())
+ assert_equal([], test_null_list()->items())
+
+ assert_equal([[0, 'a'], [1, '웃'], [2, 'cÌ']], 'a웃cÌ'->items())
+ assert_equal([], ''->items())
+ assert_equal([], test_null_string()->items())
+enddef
+
+def Test_job_getchannel()
+ if !has('job')
+ CheckFeature job
+ else
+ v9.CheckDefAndScriptFailure(['job_getchannel("a")'], ['E1013: Argument 1: type mismatch, expected job but got string', 'E1218: Job required for argument 1'])
+ assert_fails('job_getchannel(test_null_job())', 'E916: Not a valid job')
+ endif
+enddef
+
+def Test_job_info()
+ if !has('job')
+ CheckFeature job
+ else
+ v9.CheckDefAndScriptFailure(['job_info("a")'], ['E1013: Argument 1: type mismatch, expected job but got string', 'E1218: Job required for argument 1'])
+ assert_fails('job_info(test_null_job())', 'E916: Not a valid job')
+ endif
+enddef
+
+" Test_job_info_return_type() is in test_vim9_fails.vim
+
+def Test_job_setoptions()
+ if !has('job')
+ CheckFeature job
+ else
+ v9.CheckDefAndScriptFailure(['job_setoptions(test_null_channel(), {})'], ['E1013: Argument 1: type mismatch, expected job but got channel', 'E1218: Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['job_setoptions(test_null_job(), [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+ assert_equal('fail', job_status(test_null_job()))
+ endif
+enddef
+
+def Test_job_status()
+ if !has('job')
+ CheckFeature job
+ else
+ v9.CheckDefAndScriptFailure(['job_status("a")'], ['E1013: Argument 1: type mismatch, expected job but got string', 'E1218: Job required for argument 1'])
+ assert_equal('fail', job_status(test_null_job()))
+ endif
+enddef
+
+def Test_job_stop()
+ if !has('job')
+ CheckFeature job
+ else
+ v9.CheckDefAndScriptFailure(['job_stop("a")'], ['E1013: Argument 1: type mismatch, expected job but got string', 'E1218: Job required for argument 1'])
+ v9.CheckDefAndScriptFailure(['job_stop(test_null_job(), true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 2'])
+ endif
+enddef
+
+def Test_join()
+ v9.CheckDefAndScriptFailure(['join("abc")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['join([], 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ join([''], '')->assert_equal('')
+enddef
+
+def Test_js_decode()
+ v9.CheckDefAndScriptFailure(['js_decode(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal([1, 2], js_decode('[1,2]'))
+ js_decode('')->assert_equal(v:none)
+enddef
+
+def Test_json_decode()
+ v9.CheckDefAndScriptFailure(['json_decode(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1174: String required for argument 1'])
+ assert_equal(1.0, json_decode('1.0'))
+ json_decode('')->assert_equal(v:none)
+enddef
+
+def Test_keys()
+ assert_equal([7, 7], keys({a: 1, b: 2})->map((_, _) => 7))
+
+ v9.CheckDefAndScriptFailure(['keys([])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 1'])
+ assert_equal(['a'], {a: 'v'}->keys())
+ assert_equal([], {}->keys())
+enddef
+
+def Test_keys_return_type()
+ const var: list<string> = {a: 1, b: 2}->keys()
+ var->assert_equal(['a', 'b'])
+enddef
+
+def Test_len()
+ v9.CheckDefAndScriptFailure(['len(true)'], ['E1013: Argument 1: type mismatch, expected list<any> but got bool', 'E701: Invalid type for len()'])
+ assert_equal(2, "ab"->len())
+ assert_equal(3, 456->len())
+ assert_equal(0, []->len())
+ assert_equal(1, {a: 10}->len())
+ assert_equal(4, 0z20304050->len())
+enddef
+
+def Test_libcall()
+ CheckFeature libcall
+ v9.CheckDefAndScriptFailure(['libcall(1, "b", 3)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['libcall("a", 2, 3)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['libcall("a", "b", 1.1)'], ['E1013: Argument 3: type mismatch, expected string but got float', 'E1220: String or Number required for argument 3'])
+enddef
+
+def Test_libcallnr()
+ CheckFeature libcall
+ v9.CheckDefAndScriptFailure(['libcallnr(1, "b", 3)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['libcallnr("a", 2, 3)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['libcallnr("a", "b", 1.1)'], ['E1013: Argument 3: type mismatch, expected string but got float', 'E1220: String or Number required for argument 3'])
+enddef
+
+def Test_line()
+ assert_fails('line(true)', 'E1174:')
+ v9.CheckDefAndScriptFailure(['line(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['line(".", "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['line("")'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_line2byte()
+ v9.CheckDefAndScriptFailure(['line2byte(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['line2byte("")'], 'E1209: Invalid value for a line number')
+ assert_equal(-1, line2byte(1))
+ assert_equal(-1, line2byte(10000))
+enddef
+
+def Test_lispindent()
+ v9.CheckDefAndScriptFailure(['lispindent({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['lispindent("")'], 'E1209: Invalid value for a line number')
+ v9.CheckDefExecAndScriptFailure(['lispindent(-1)'], 'E966: Invalid line number: -1')
+ assert_equal(0, lispindent(1))
+enddef
+
+def Test_list2blob()
+ v9.CheckDefAndScriptFailure(['list2blob(10)'], ['E1013: Argument 1: type mismatch, expected list<number> but got number', 'E1211: List required for argument 1'])
+ v9.CheckDefFailure(['list2blob([0z10, 0z02])'], 'E1013: Argument 1: type mismatch, expected list<number> but got list<blob>')
+enddef
+
+def Test_list2str_str2list_utf8()
+ var s = "\u3042\u3044"
+ var l = [0x3042, 0x3044]
+ str2list(s, true)->assert_equal(l)
+ list2str(l, true)->assert_equal(s)
+enddef
+
+def Test_list2str()
+ v9.CheckDefAndScriptFailure(['list2str(".", true)'], ['E1013: Argument 1: type mismatch, expected list<number> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['list2str([1], 0z10)'], ['E1013: Argument 2: type mismatch, expected bool but got blob', 'E1212: Bool required for argument 2'])
+enddef
+
+def s:SID(): number
+ return expand('<SID>')
+ ->matchstr('<SNR>\zs\d\+\ze_$')
+ ->str2nr()
+enddef
+
+def Test_listener_add()
+ v9.CheckDefAndScriptFailure(['listener_add("1", true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 2'])
+enddef
+
+def Test_listener_flush()
+ v9.CheckDefAndScriptFailure(['listener_flush([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_listener_remove()
+ v9.CheckDefAndScriptFailure(['listener_remove("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_luaeval()
+ if !has('lua')
+ CheckFeature lua
+ endif
+ v9.CheckDefAndScriptFailure(['luaeval(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ if exists_compiled('*luaeval')
+ luaeval('')->assert_equal(v:null)
+ endif
+enddef
+
+def Test_map()
+ if has('channel')
+ v9.CheckDefAndScriptFailure(['map(test_null_channel(), "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got channel', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ endif
+ v9.CheckDefAndScriptFailure(['map(1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['map([1, 2], 4)'], ['E1256: String or function required for argument 2', 'E1024: Using a Number as a String'])
+
+ # type of dict remains dict<any> even when type of values changes
+ # same for list
+ var lines =<< trim END
+ var d: dict<any> = {a: 0}
+ d->map((k, v) => true)
+ d->map((k, v) => 'x')
+ assert_equal({a: 'x'}, d)
+
+ d = {a: 0}
+ g:gd = d
+ map(g:gd, (k, v) => true)
+ assert_equal({a: true}, g:gd)
+
+ var l: list<any> = [0]
+ l->map((k, v) => true)
+ l->map((k, v) => 'x')
+ assert_equal(['x'], l)
+
+ l = [1]
+ g:gl = l
+ map(g:gl, (k, v) => true)
+ assert_equal([true], g:gl)
+
+ assert_equal(['x'], [[1, 2]]->map((_, v) => 'x'))
+ assert_equal(['x'], [{a: 0}]->map((_, v) => 'x'))
+ assert_equal({a: 'x'}, {a: [1, 2]}->map((_, v) => 'x'))
+ assert_equal({a: 'x'}, {a: {b: 2}}->map((_, v) => 'x'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_map_failure()
+ CheckFeature job
+
+ var lines =<< trim END
+ vim9script
+ writefile([], 'Xtmpfile')
+ silent e Xtmpfile
+ var d = {[bufnr('%')]: {a: 0}}
+ au BufReadPost * Func()
+ def Func()
+ if d->has_key('')
+ endif
+ eval d[expand('<abuf>')]->mapnew((_, v: dict<job>) => 0)
+ enddef
+ e
+ END
+ v9.CheckScriptFailure(lines, 'E1013:')
+ au! BufReadPost
+ delete('Xtmpfile')
+
+ lines =<< trim END
+ var d: dict<number> = {a: 1}
+ g:gd = d
+ map(g:gd, (k, v) => true)
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected number but got bool')
+enddef
+
+def Test_map_const()
+ var lines =<< trim END
+ const l = [1, 2, 3]
+ map(l, 'SomeFunc')
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+
+ lines =<< trim END
+ const d = {a: 1, b: 2}
+ map(d, 'SomeFunc')
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const dict<number>')
+enddef
+
+def Test_map_function_arg()
+ var lines =<< trim END
+ def MapOne(i: number, v: string): string
+ return i .. ':' .. v
+ enddef
+ var l = ['a', 'b', 'c']
+ map(l, MapOne)
+ assert_equal(['0:a', '1:b', '2:c'], l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ range(3)->map((a, b, c) => a + b + c)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E176:', 'E1190: One argument too few'])
+ lines =<< trim END
+ range(3)->map((a, b, c, d) => a + b + c + d)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E176:', 'E1190: 2 arguments too few'])
+
+ # declared list cannot change type
+ lines =<< trim END
+ def Map(i: number, v: number): string
+ return 'bad'
+ enddef
+ var ll: list<number> = [1, 2, 3]
+ echo map(ll, Map)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(number, number): string', 'E1012: Type mismatch; expected number but got string in map()'])
+
+ # not declared list can change type
+ echo [1, 2, 3]->map((..._) => 'x')
+enddef
+
+def Test_map_item_type()
+ var lines =<< trim END
+ var l = ['a', 'b', 'c']
+ map(l, (k, v) => k .. '/' .. v )
+ assert_equal(['0/a', '1/b', '2/c'], l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l: list<number> = [0]
+ echo map(l, (_, v) => [])
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(any, any): list<unknown>', 'E1012: Type mismatch; expected number but got list<unknown> in map()'], 2)
+
+ lines =<< trim END
+ var l: list<number> = range(2)
+ echo map(l, (_, v) => [])
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(any, any): list<unknown>', 'E1012: Type mismatch; expected number but got list<unknown> in map()'], 2)
+
+ lines =<< trim END
+ var d: dict<number> = {key: 0}
+ echo map(d, (_, v) => [])
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?string, ?number): number but got func(any, any): list<unknown>', 'E1012: Type mismatch; expected number but got list<unknown> in map()'], 2)
+enddef
+
+def Test_maparg()
+ var lnum = str2nr(expand('<sflnum>'))
+ map foo bar
+ maparg('foo', '', false, true)->assert_equal({
+ lnum: lnum + 1,
+ script: 0,
+ mode: ' ',
+ silent: 0,
+ noremap: 0,
+ lhs: 'foo',
+ lhsraw: 'foo',
+ nowait: 0,
+ expr: 0,
+ sid: SID(),
+ scriptversion: 999999,
+ rhs: 'bar',
+ buffer: 0,
+ abbr: 0,
+ mode_bits: 0x47})
+ unmap foo
+ v9.CheckDefAndScriptFailure(['maparg(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['maparg("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['maparg("a", "b", 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ v9.CheckDefAndScriptFailure(['maparg("a", "b", true, 2)'], ['E1013: Argument 4: type mismatch, expected bool but got number', 'E1212: Bool required for argument 4'])
+ maparg('')->assert_equal('')
+
+ # value argument type is checked at compile time
+ var lines =<< trim END
+ var l = [123]
+ l->map((i: number, v: string) => 0)
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(number, string): number')
+
+ lines =<< trim END
+ var d = {a: 123}
+ d->map((i: string, v: string) => 0)
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?string, ?number): number but got func(string, string): number')
+
+ lines =<< trim END
+ var s = 'abc'
+ s->map((i: number, v: number) => 'x')
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?string): string but got func(number, number): string')
+
+ lines =<< trim END
+ var s = 0z1122
+ s->map((i: number, v: string) => 0)
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(number, string): number')
+
+ # index argument type is checked at compile time
+ lines =<< trim END
+ ['x']->map((i: string, v: string) => 'y')
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?any): any but got func(string, string): string')
+
+ lines =<< trim END
+ {a: 1}->map((i: number, v: number) => 0)
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?string, ?any): any but got func(number, number): number')
+
+ lines =<< trim END
+ 'abc'->map((i: string, v: string) => 'x')
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?string): string but got func(string, string): string')
+
+ lines =<< trim END
+ 0z1122->map((i: string, v: number) => 0)
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(string, number): number')
+enddef
+
+def Test_maparg_mapset()
+ nnoremap <F3> :echo "hit F3"<CR>
+ var mapsave = maparg('<F3>', 'n', false, true)
+ mapset('n', false, mapsave)
+
+ nunmap <F3>
+enddef
+
+def Test_mapcheck()
+ iabbrev foo foobar
+ mapcheck('foo', 'i', true)->assert_equal('foobar')
+ iunabbrev foo
+ v9.CheckDefAndScriptFailure(['mapcheck(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['mapcheck("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['mapcheck("a", "b", 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ mapcheck('')->assert_equal('')
+ mapcheck('', '')->assert_equal('')
+enddef
+
+def Test_mapnew()
+ if has('channel')
+ v9.CheckDefAndScriptFailure(['mapnew(test_null_job(), "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got job', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+ endif
+ v9.CheckDefAndScriptFailure(['mapnew(1, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1251: List, Dictionary, Blob or String required for argument 1'])
+enddef
+
+def Test_mapset()
+ v9.CheckDefAndScriptFailure(['mapset(1, true, {})'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1223: String or Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['mapset("a", 2, {})'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ v9.CheckDefAndScriptFailure(['mapset("a", false, [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+enddef
+
+def Test_match()
+ v9.CheckDefAndScriptFailure(['match(0z12, "p")'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['match(["s"], [2])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['match("s", "p", "q")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['match("s", "p", 1, "r")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ assert_equal(2, match('ab12cd', '12'))
+ assert_equal(-1, match('ab12cd', '34'))
+ assert_equal(6, match('ab12cd12ef', '12', 4))
+ assert_equal(2, match('abcd', '..', 0, 3))
+ assert_equal(1, match(['a', 'b', 'c'], 'b'))
+ assert_equal(-1, match(['a', 'b', 'c'], 'd'))
+ assert_equal(3, match(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2))
+ assert_equal(5, match(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2, 2))
+ match('', 'a')->assert_equal(-1)
+ match('abc', '')->assert_equal(0)
+ match('', '')->assert_equal(0)
+enddef
+
+def Test_matchadd()
+ v9.CheckDefAndScriptFailure(['matchadd(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchadd("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchadd("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['matchadd("a", "b", 1, "d")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ v9.CheckDefAndScriptFailure(['matchadd("a", "b", 1, 1, [])'], ['E1013: Argument 5: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 5'])
+ matchadd('', 'a')->assert_equal(-1)
+ matchadd('Search', '')->assert_equal(-1)
+enddef
+
+def Test_matchaddpos()
+ v9.CheckDefAndScriptFailure(['matchaddpos(1, [1])'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchaddpos("a", "b")'], ['E1013: Argument 2: type mismatch, expected list<any> but got string', 'E1211: List required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchaddpos("a", [1], "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['matchaddpos("a", [1], 1, "d")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ v9.CheckDefAndScriptFailure(['matchaddpos("a", [1], 1, 1, [])'], ['E1013: Argument 5: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 5'])
+ matchaddpos('', [1])->assert_equal(-1)
+enddef
+
+def Test_matcharg()
+ v9.CheckDefAndScriptFailure(['matcharg("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_matchdelete()
+ v9.CheckDefAndScriptFailure(['matchdelete("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchdelete("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchdelete(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_matchend()
+ v9.CheckDefAndScriptFailure(['matchend(0z12, "p")'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchend(["s"], [2])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchend("s", "p", "q")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['matchend("s", "p", 1, "r")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ assert_equal(4, matchend('ab12cd', '12'))
+ assert_equal(-1, matchend('ab12cd', '34'))
+ assert_equal(8, matchend('ab12cd12ef', '12', 4))
+ assert_equal(4, matchend('abcd', '..', 0, 3))
+ assert_equal(1, matchend(['a', 'b', 'c'], 'b'))
+ assert_equal(-1, matchend(['a', 'b', 'c'], 'd'))
+ assert_equal(3, matchend(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2))
+ assert_equal(5, matchend(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2, 2))
+ matchend('', 'a')->assert_equal(-1)
+ matchend('abc', '')->assert_equal(0)
+ matchend('', '')->assert_equal(0)
+enddef
+
+def Test_matchfuzzy()
+ v9.CheckDefAndScriptFailure(['matchfuzzy({}, "p")'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchfuzzy([], 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchfuzzy([], "a", [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+ matchfuzzy(['abc', 'xyz'], '')->assert_equal([])
+enddef
+
+def Test_matchfuzzypos()
+ v9.CheckDefAndScriptFailure(['matchfuzzypos({}, "p")'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchfuzzypos([], 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchfuzzypos([], "a", [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+ matchfuzzypos(['abc', 'xyz'], '')->assert_equal([[], [], []])
+enddef
+
+def Test_matchlist()
+ v9.CheckDefAndScriptFailure(['matchlist(0z12, "p")'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchlist(["s"], [2])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchlist("s", "p", "q")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['matchlist("s", "p", 1, "r")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ var l: list<string> = ['12', '', '', '', '', '', '', '', '', '']
+ assert_equal(l, matchlist('ab12cd', '12'))
+ assert_equal([], matchlist('ab12cd', '34'))
+ assert_equal(l, matchlist('ab12cd12ef', '12', 4))
+ l[0] = 'cd'
+ assert_equal(l, matchlist('abcd', '..', 0, 3))
+ l[0] = 'b'
+ assert_equal(l, matchlist(['a', 'b', 'c'], 'b'))
+ assert_equal([], matchlist(['a', 'b', 'c'], 'd'))
+ assert_equal(l, matchlist(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2))
+ assert_equal(l, matchlist(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2, 2))
+ matchlist('', 'a')->assert_equal([])
+ matchlist('abc', '')->assert_equal(repeat([''], 10))
+ matchlist('', '')->assert_equal(repeat([''], 10))
+enddef
+
+def Test_matchstr()
+ v9.CheckDefAndScriptFailure(['matchstr(0z12, "p")'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchstr(["s"], [2])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchstr("s", "p", "q")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['matchstr("s", "p", 1, "r")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ assert_equal('12', matchstr('ab12cd', '12'))
+ assert_equal('', matchstr('ab12cd', '34'))
+ assert_equal('12', matchstr('ab12cd12ef', '12', 4))
+ assert_equal('cd', matchstr('abcd', '..', 0, 3))
+ assert_equal('b', matchstr(['a', 'b', 'c'], 'b'))
+ assert_equal('', matchstr(['a', 'b', 'c'], 'd'))
+ assert_equal('b', matchstr(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2))
+ assert_equal('b', matchstr(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2, 2))
+ matchstr('', 'a')->assert_equal('')
+ matchstr('abc', '')->assert_equal('')
+ matchstr('', '')->assert_equal('')
+enddef
+
+def Test_matchstrpos()
+ v9.CheckDefAndScriptFailure(['matchstrpos(0z12, "p")'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['matchstrpos(["s"], [2])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['matchstrpos("s", "p", "q")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['matchstrpos("s", "p", 1, "r")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ assert_equal(['12', 2, 4], matchstrpos('ab12cd', '12'))
+ assert_equal(['', -1, -1], matchstrpos('ab12cd', '34'))
+ assert_equal(['12', 6, 8], matchstrpos('ab12cd12ef', '12', 4))
+ assert_equal(['cd', 2, 4], matchstrpos('abcd', '..', 0, 3))
+ assert_equal(['b', 1, 0, 1], matchstrpos(['a', 'b', 'c'], 'b'))
+ assert_equal(['', -1, -1, -1], matchstrpos(['a', 'b', 'c'], 'd'))
+ assert_equal(['b', 3, 0, 1],
+ matchstrpos(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2))
+ assert_equal(['b', 5, 0, 1],
+ matchstrpos(['a', 'b', 'c', 'b', 'd', 'b'], 'b', 2, 2))
+ matchstrpos('', 'a')->assert_equal(['', -1, -1])
+ matchstrpos('abc', '')->assert_equal(['', 0, 0])
+ matchstrpos('', '')->assert_equal(['', 0, 0])
+enddef
+
+def Test_max()
+ g:flag = true
+ var l1: list<number> = g:flag
+ ? [1, max([2, 3])]
+ : [4, 5]
+ assert_equal([1, 3], l1)
+
+ g:flag = false
+ var l2: list<number> = g:flag
+ ? [1, max([2, 3])]
+ : [4, 5]
+ assert_equal([4, 5], l2)
+ v9.CheckDefAndScriptFailure(['max(5)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1227: List or Dictionary required for argument 1'])
+enddef
+
+def Test_menu_info()
+ v9.CheckDefAndScriptFailure(['menu_info(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['menu_info(10, "n")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['menu_info("File", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ assert_equal({}, menu_info('aMenu'))
+enddef
+
+def Test_min()
+ g:flag = true
+ var l1: list<number> = g:flag
+ ? [1, min([2, 3])]
+ : [4, 5]
+ assert_equal([1, 2], l1)
+
+ g:flag = false
+ var l2: list<number> = g:flag
+ ? [1, min([2, 3])]
+ : [4, 5]
+ assert_equal([4, 5], l2)
+ v9.CheckDefAndScriptFailure(['min(5)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1227: List or Dictionary required for argument 1'])
+enddef
+
+def Test_mkdir()
+ v9.CheckDefAndScriptFailure(['mkdir(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['mkdir("a", {})'], ['E1013: Argument 2: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['mkdir("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['mkdir("")'], 'E1175: Non-empty string required for argument 1')
+ delete('a', 'rf')
+enddef
+
+def Test_mode()
+ v9.CheckDefAndScriptFailure(['mode("1")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['mode(2)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+enddef
+
+def Test_mzeval()
+ if !has('mzscheme')
+ CheckFeature mzscheme
+ endif
+ v9.CheckDefAndScriptFailure(['mzeval(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_nextnonblank()
+ v9.CheckDefAndScriptFailure(['nextnonblank(null)'], ['E1013: Argument 1: type mismatch, expected string but got special', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['nextnonblank("")'], 'E1209: Invalid value for a line number')
+ assert_equal(0, nextnonblank(1))
+enddef
+
+def Test_nr2char()
+ nr2char(97, true)->assert_equal('a')
+ v9.CheckDefAndScriptFailure(['nr2char("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['nr2char(1, "a")'], ['E1013: Argument 2: type mismatch, expected bool but got string', 'E1212: Bool required for argument 2'])
+enddef
+
+def Test_or()
+ v9.CheckDefAndScriptFailure(['or("x", 0x2)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['or(0x1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_pathshorten()
+ v9.CheckDefAndScriptFailure(['pathshorten(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['pathshorten("a", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ pathshorten('')->assert_equal('')
+enddef
+
+def Test_perleval()
+ if !has('perl')
+ CheckFeature perl
+ endif
+ v9.CheckDefAndScriptFailure(['perleval(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_popup_atcursor()
+ v9.CheckDefAndScriptFailure(['popup_atcursor({"a": 10}, {})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1224: String, Number or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_atcursor("a", [1, 2])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 2'])
+
+ # Pass variable of type 'any' to popup_atcursor()
+ var what: any = 'Hello'
+ var popupID = what->popup_atcursor({moved: 'any'})
+ assert_equal(0, popupID->popup_getoptions().tabpage)
+ popupID->popup_close()
+enddef
+
+def Test_popup_beval()
+ v9.CheckDefAndScriptFailure(['popup_beval({"a": 10}, {})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1224: String, Number or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_beval("a", [1, 2])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_popup_clear()
+ v9.CheckDefAndScriptFailure(['popup_clear(["a"])'], ['E1013: Argument 1: type mismatch, expected bool but got list<string>', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_clear(2)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+enddef
+
+def Test_popup_close()
+ v9.CheckDefAndScriptFailure(['popup_close("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_popup_create()
+ # Pass variable of type 'any' to popup_create()
+ var what: any = 'Hello'
+ var popupID = what->popup_create({})
+ assert_equal(0, popupID->popup_getoptions().tabpage)
+ popupID->popup_close()
+enddef
+
+def Test_popup_dialog()
+ v9.CheckDefAndScriptFailure(['popup_dialog({"a": 10}, {})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1224: String, Number or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_dialog("a", [1, 2])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_popup_filter_menu()
+ v9.CheckDefAndScriptFailure(['popup_filter_menu("x", "")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_filter_menu(1, 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ var id: number = popup_menu(["one", "two", "three"], {})
+ popup_filter_menu(id, '')
+ popup_close(id)
+enddef
+
+def Test_popup_filter_yesno()
+ v9.CheckDefAndScriptFailure(['popup_filter_yesno("x", "")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_filter_yesno(1, 1)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+enddef
+
+def Test_popup_getoptions()
+ v9.CheckDefAndScriptFailure(['popup_getoptions("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_getoptions(true)'], ['E1013: Argument 1: type mismatch, expected number but got bool', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_popup_getpos()
+ v9.CheckDefAndScriptFailure(['popup_getpos("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_getpos(true)'], ['E1013: Argument 1: type mismatch, expected number but got bool', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_popup_hide()
+ v9.CheckDefAndScriptFailure(['popup_hide("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_hide(true)'], ['E1013: Argument 1: type mismatch, expected number but got bool', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_popup_locate()
+ v9.CheckDefAndScriptFailure(['popup_locate("a", 20)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_locate(10, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_popup_menu()
+ v9.CheckDefAndScriptFailure(['popup_menu({"a": 10}, {})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1224: String, Number or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_menu("a", [1, 2])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_popup_move()
+ v9.CheckDefAndScriptFailure(['popup_move("x", {})'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_move(1, [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_popup_notification()
+ v9.CheckDefAndScriptFailure(['popup_notification({"a": 10}, {})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1224: String, Number or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_notification("a", [1, 2])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_popup_setoptions()
+ v9.CheckDefAndScriptFailure(['popup_setoptions("x", {})'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_setoptions(1, [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_popup_settext()
+ v9.CheckDefAndScriptFailure(['popup_settext("x", [])'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_settext(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1222: String or List required for argument 2'])
+enddef
+
+def Test_popup_show()
+ v9.CheckDefAndScriptFailure(['popup_show("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['popup_show(true)'], ['E1013: Argument 1: type mismatch, expected number but got bool', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_prevnonblank()
+ v9.CheckDefAndScriptFailure(['prevnonblank(null)'], ['E1013: Argument 1: type mismatch, expected string but got special', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['prevnonblank("")'], 'E1209: Invalid value for a line number')
+ assert_equal(0, prevnonblank(1))
+enddef
+
+def Test_printf()
+ v9.CheckDefAndScriptFailure(['printf([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ printf(0x10)->assert_equal('16')
+ assert_equal(" abc", "abc"->printf("%4s"))
+enddef
+
+def Test_prompt_getprompt()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['prompt_getprompt([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ assert_equal('', prompt_getprompt('NonExistingBuf'))
+ endif
+enddef
+
+def Test_prompt_setcallback()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['prompt_setcallback(true, "1")'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ endif
+enddef
+
+def Test_prompt_setinterrupt()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['prompt_setinterrupt(true, "1")'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ endif
+enddef
+
+def Test_prompt_setprompt()
+ if !has('channel')
+ CheckFeature channel
+ else
+ v9.CheckDefAndScriptFailure(['prompt_setprompt([], "p")'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prompt_setprompt(1, [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+ endif
+enddef
+
+def Test_prop_add()
+ v9.CheckDefAndScriptFailure(['prop_add("a", 2, {})'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_add(1, "b", {})'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['prop_add(1, 2, [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+enddef
+
+def Test_prop_add_list()
+ v9.CheckDefAndScriptFailure(['prop_add_list([], [])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_add_list({}, {})'], ['E1013: Argument 2: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 2'])
+enddef
+
+def Test_prop_clear()
+ v9.CheckDefAndScriptFailure(['prop_clear("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_clear(1, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['prop_clear(1, 2, [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+enddef
+
+def Test_prop_find()
+ v9.CheckDefAndScriptFailure(['prop_find([1, 2])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_find([1, 2], "k")'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_find({"a": 10}, ["a"])'], ['E1013: Argument 2: type mismatch, expected string but got list<string>', 'E1174: String required for argument 2'])
+ assert_fails("prop_find({}, '')", 'E474:')
+enddef
+
+def Test_prop_list()
+ v9.CheckDefAndScriptFailure(['prop_list("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_list(1, [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_prop_remove()
+ v9.CheckDefAndScriptFailure(['prop_remove([])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_remove({}, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['prop_remove({}, 1, "b")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+enddef
+
+def Test_prop_type_add()
+ v9.CheckDefAndScriptFailure(['prop_type_add({"a": 10}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_type_add("a", "b")'], ['E1013: Argument 2: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 2'])
+ assert_fails("prop_type_add('', {highlight: 'Search'})", 'E475:')
+enddef
+
+def Test_prop_type_change()
+ v9.CheckDefAndScriptFailure(['prop_type_change({"a": 10}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_type_change("a", "b")'], ['E1013: Argument 2: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 2'])
+ assert_fails("prop_type_change('', {highlight: 'Search'})", 'E475:')
+enddef
+
+def Test_prop_type_delete()
+ v9.CheckDefAndScriptFailure(['prop_type_delete({"a": 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_type_delete({"a": 10}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_type_delete("a", "b")'], ['E1013: Argument 2: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 2'])
+ assert_fails("prop_type_delete('')", 'E475:')
+enddef
+
+def Test_prop_type_get()
+ v9.CheckDefAndScriptFailure(['prop_type_get({"a": 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_type_get({"a": 10}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_type_get("a", "b")'], ['E1013: Argument 2: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 2'])
+ assert_fails("prop_type_get('')", 'E475:')
+enddef
+
+def Test_prop_type_list()
+ v9.CheckDefAndScriptFailure(['prop_type_list(["a"])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<string>', 'E1206: Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['prop_type_list(2)'], ['E1013: Argument 1: type mismatch, expected dict<any> but got number', 'E1206: Dictionary required for argument 1'])
+enddef
+
+def Test_py3eval()
+ if !has('python3')
+ CheckFeature python3
+ endif
+ v9.CheckDefAndScriptFailure(['py3eval([2])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_pyeval()
+ if !has('python')
+ CheckFeature python
+ endif
+ v9.CheckDefAndScriptFailure(['pyeval([2])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_pyxeval()
+ if !has('python') && !has('python3')
+ CheckFeature python
+ endif
+ v9.CheckDefAndScriptFailure(['pyxeval([2])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_rand()
+ v9.CheckDefAndScriptFailure(['rand(10)'], ['E1013: Argument 1: type mismatch, expected list<number> but got number', 'E1211: List required for argument 1'])
+ v9.CheckDefFailure(['rand(["a"])'], 'E1013: Argument 1: type mismatch, expected list<number> but got list<string>')
+ assert_true(rand() >= 0)
+ assert_true(rand(srand()) >= 0)
+enddef
+
+def Test_range()
+ v9.CheckDefAndScriptFailure(['range("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['range(10, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['range(10, 20, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+
+ # returns a list<number> but it's not declared as such
+ assert_equal(['x', 'x'], range(2)->map((i, v) => 'x'))
+enddef
+
+def Test_readdir()
+ eval expand('sautest')->readdir((e) => e[0] !=# '.')
+ eval expand('sautest')->readdirex((e) => e.name[0] !=# '.')
+ v9.CheckDefAndScriptFailure(['readdir(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['readdir("a", "1", [3])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+ if has('unix')
+ # only fails on Unix-like systems
+ assert_fails('readdir("")', 'E484: Can''t open file')
+ endif
+enddef
+
+def Test_readdirex()
+ v9.CheckDefAndScriptFailure(['readdirex(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['readdirex("a", "1", [3])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+ if has('unix')
+ # only fails on Unix-like systems
+ assert_fails('readdirex("")', 'E484: Can''t open file')
+ endif
+enddef
+
+def Test_readblob()
+ var blob = 0z12341234
+ writefile(blob, 'Xreadblob', 'D')
+ var read: blob = readblob('Xreadblob')
+ assert_equal(blob, read)
+
+ var lines =<< trim END
+ var read: list<string> = readblob('Xreadblob')
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected list<string> but got blob', 1)
+ v9.CheckDefExecAndScriptFailure(['readblob("")'], 'E484: Can''t open file <empty>')
+enddef
+
+def Test_readfile()
+ var text = ['aaa', 'bbb', 'ccc']
+ writefile(text, 'Xreadfile', 'D')
+ var read: list<string> = readfile('Xreadfile')
+ assert_equal(text, read)
+ assert_equal([7, 7, 7], readfile('Xreadfile')->map((_, _) => 7))
+
+ var lines =<< trim END
+ var read: dict<string> = readfile('Xreadfile')
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected dict<string> but got list<string>', 1)
+
+ v9.CheckDefAndScriptFailure(['readfile("a", 0z10)'], ['E1013: Argument 2: type mismatch, expected string but got blob', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['readfile("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['readfile("")'], 'E1175: Non-empty string required for argument 1')
+enddef
+
+def Test_reduce()
+ v9.CheckDefAndScriptFailure(['reduce({a: 10}, "1")'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E1252: String, List or Blob required for argument 1'])
+ assert_equal(6, [1, 2, 3]->reduce((r, c) => r + c, 0))
+ assert_equal(11, 0z0506->reduce((r, c) => r + c, 0))
+enddef
+
+def Test_reltime()
+ CheckFeature reltime
+
+ v9.CheckDefExecAndScriptFailure(['[]->reltime()'], 'E474:')
+ v9.CheckDefExecAndScriptFailure(['[]->reltime([])'], 'E474:')
+
+ v9.CheckDefAndScriptFailure(['reltime("x")'], ['E1013: Argument 1: type mismatch, expected list<number> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefFailure(['reltime(["x", "y"])'], 'E1013: Argument 1: type mismatch, expected list<number> but got list<string>')
+ v9.CheckDefAndScriptFailure(['reltime([1, 2], 10)'], ['E1013: Argument 2: type mismatch, expected list<number> but got number', 'E1211: List required for argument 2'])
+ v9.CheckDefFailure(['reltime([1, 2], ["a", "b"])'], 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>')
+ var start: list<any> = reltime()
+ assert_true(type(reltime(start)) == v:t_list)
+ var end: list<any> = reltime()
+ assert_true(type(reltime(start, end)) == v:t_list)
+enddef
+
+def Test_reltimefloat()
+ CheckFeature reltime
+
+ v9.CheckDefExecAndScriptFailure(['[]->reltimefloat()'], 'E474:')
+
+ v9.CheckDefAndScriptFailure(['reltimefloat("x")'], ['E1013: Argument 1: type mismatch, expected list<number> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefFailure(['reltimefloat([1.1])'], 'E1013: Argument 1: type mismatch, expected list<number> but got list<float>')
+ assert_true(type(reltimefloat(reltime())) == v:t_float)
+enddef
+
+def Test_reltimestr()
+ CheckFeature reltime
+
+ v9.CheckDefExecAndScriptFailure(['[]->reltimestr()'], 'E474:')
+
+ v9.CheckDefAndScriptFailure(['reltimestr(true)'], ['E1013: Argument 1: type mismatch, expected list<number> but got bool', 'E1211: List required for argument 1'])
+ v9.CheckDefFailure(['reltimestr([true])'], 'E1013: Argument 1: type mismatch, expected list<number> but got list<bool>')
+ assert_true(type(reltimestr(reltime())) == v:t_string)
+enddef
+
+def Test_remote_expr()
+ CheckFeature clientserver
+ CheckEnv DISPLAY
+ v9.CheckDefAndScriptFailure(['remote_expr(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['remote_expr("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['remote_expr("a", "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ v9.CheckDefAndScriptFailure(['remote_expr("a", "b", "c", "d")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ v9.CheckDefExecAndScriptFailure(['remote_expr("", "")'], 'E241: Unable to send to ')
+enddef
+
+def Test_remote_foreground()
+ CheckFeature clientserver
+ # remote_foreground() doesn't fail on MS-Windows
+ CheckNotMSWindows
+ CheckEnv DISPLAY
+
+ v9.CheckDefAndScriptFailure(['remote_foreground(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_fails('remote_foreground("NonExistingServer")', 'E241:')
+ assert_fails('remote_foreground("")', 'E241:')
+enddef
+
+def Test_remote_peek()
+ CheckFeature clientserver
+ CheckEnv DISPLAY
+ v9.CheckDefAndScriptFailure(['remote_peek(0z10)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['remote_peek("a5b6c7", [1])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['remote_peek("")'], 'E573: Invalid server id used')
+enddef
+
+def Test_remote_read()
+ CheckFeature clientserver
+ CheckEnv DISPLAY
+ v9.CheckDefAndScriptFailure(['remote_read(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['remote_read("a", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['remote_read("")'], 'E573: Invalid server id used')
+enddef
+
+def Test_remote_send()
+ CheckFeature clientserver
+ CheckEnv DISPLAY
+ v9.CheckDefAndScriptFailure(['remote_send(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['remote_send("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['remote_send("a", "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ assert_fails('remote_send("", "")', 'E241:')
+enddef
+
+def Test_remote_startserver()
+ CheckFeature clientserver
+ CheckEnv DISPLAY
+ v9.CheckDefAndScriptFailure(['remote_startserver({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_remove_literal_list()
+ var l: list<number> = [1, 2, 3, 4]
+ assert_equal([1, 2], remove(l, 0, 1))
+ assert_equal([3, 4], l)
+enddef
+
+def Test_remove_const()
+ var lines =<< trim END
+ const l = [1, 2, 3, 4]
+ remove(l, 1)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+
+ lines =<< trim END
+ const d = {a: 1, b: 2}
+ remove(d, 'a')
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const dict<number>')
+
+ lines =<< trim END
+ const b = 0z010203
+ remove(b, 1)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const blob')
+enddef
+
+def Test_remove()
+ v9.CheckDefAndScriptFailure(['remove("a", 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1228: List, Dictionary or Blob required for argument 1'])
+ v9.CheckDefAndScriptFailure(['remove([], "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['remove([], 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['remove({}, 1.1)'], ['E1013: Argument 2: type mismatch, expected string but got float', 'E1220: String or Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['remove(0z10, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['remove(0z20, 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ var l: any = [1, 2, 3, 4]
+ remove(l, 1)
+ assert_equal([1, 3, 4], l)
+ remove(l, 0, 1)
+ assert_equal([4], l)
+ var b: any = 0z1234.5678.90
+ remove(b, 1)
+ assert_equal(0z1256.7890, b)
+ remove(b, 1, 2)
+ assert_equal(0z1290, b)
+ var d: any = {a: 10, b: 20, c: 30}
+ remove(d, 'b')
+ assert_equal({a: 10, c: 30}, d)
+ var d2: any = {1: 'a', 2: 'b', 3: 'c'}
+ remove(d2, 2)
+ assert_equal({1: 'a', 3: 'c'}, d2)
+
+ # using declared type
+ var x: string = range(2)->extend(['x'])->remove(2)
+ assert_equal('x', x)
+enddef
+
+def Test_remove_return_type()
+ var l: list<number> = remove({one: [1, 2], two: [3, 4]}, 'one')
+ l->assert_equal([1, 2])
+
+ var ll: list<number> = remove(range(3), 0, 1)
+ ll->assert_equal([0, 1])
+enddef
+
+def Test_rename()
+ v9.CheckDefAndScriptFailure(['rename(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['rename("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ rename('', '')->assert_equal(0)
+enddef
+
+def Test_repeat()
+ v9.CheckDefAndScriptFailure(['repeat(1.1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1301: String, Number, List or Blob required for argument 1'])
+ v9.CheckDefAndScriptFailure(['repeat({a: 10}, 2)'], ['E1013: Argument 1: type mismatch, expected string but got dict<', 'E1301: String, Number, List or Blob required for argument 1'])
+ var lines =<< trim END
+ assert_equal('aaa', repeat('a', 3))
+ assert_equal('111', repeat(1, 3))
+ assert_equal([1, 1, 1], repeat([1], 3))
+ assert_equal(0z000102000102000102, repeat(0z000102, 3))
+ assert_equal(0z000000, repeat(0z00, 3))
+ var s = '-'
+ s ..= repeat(5, 3)
+ assert_equal('-555', s)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_resolve()
+ v9.CheckDefAndScriptFailure(['resolve([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+ assert_equal('SomeFile', resolve('SomeFile'))
+ resolve('')->assert_equal('')
+enddef
+
+def Test_reverse()
+ v9.CheckDefAndScriptFailure(['reverse(10)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1226: List or Blob required for argument 1'])
+ v9.CheckDefAndScriptFailure(['reverse("abc")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1226: List or Blob required for argument 1'])
+enddef
+
+def Test_reverse_return_type()
+ var l = reverse([1, 2, 3])
+ var res = 0
+ for n in l
+ res += n
+ endfor
+ res->assert_equal(6)
+enddef
+
+def Test_reverse_const()
+ var lines =<< trim END
+ const l = [1, 2, 3, 4]
+ reverse(l)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+
+ lines =<< trim END
+ const b = 0z010203
+ reverse(b)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const blob')
+enddef
+
+def Test_rubyeval()
+ if !has('ruby')
+ CheckFeature ruby
+ endif
+ v9.CheckDefAndScriptFailure(['rubyeval([2])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_screenattr()
+ v9.CheckDefAndScriptFailure(['screenattr("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['screenattr(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_screenchar()
+ v9.CheckDefAndScriptFailure(['screenchar("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['screenchar(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_screenchars()
+ assert_equal(['x'], screenchars(1, 1)->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['screenchars("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['screenchars(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_screenpos()
+ v9.CheckDefAndScriptFailure(['screenpos("a", 1, 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['screenpos(1, "b", 1)'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['screenpos(1, 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ assert_equal({col: 1, row: 1, endcol: 1, curscol: 1}, screenpos(1, 1, 1))
+enddef
+
+def Test_screenstring()
+ v9.CheckDefAndScriptFailure(['screenstring("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['screenstring(1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_search()
+ new
+ setline(1, ['foo', 'bar'])
+ var val = 0
+ # skip expr returns boolean
+ search('bar', 'W', 0, 0, () => val == 1)->assert_equal(2)
+ :1
+ search('bar', 'W', 0, 0, () => val == 0)->assert_equal(0)
+ # skip expr returns number, only 0 and 1 are accepted
+ :1
+ search('bar', 'W', 0, 0, () => 0)->assert_equal(2)
+ :1
+ search('bar', 'W', 0, 0, () => 1)->assert_equal(0)
+ assert_fails("search('bar', '', 0, 0, () => -1)", 'E1023:')
+ assert_fails("search('bar', '', 0, 0, () => -1)", 'E1023:')
+
+ setline(1, "find this word")
+ normal gg
+ var col = 7
+ assert_equal(1, search('this', '', 0, 0, 'col(".") > col'))
+ normal 0
+ assert_equal([1, 6], searchpos('this', '', 0, 0, 'col(".") > col'))
+
+ col = 5
+ normal 0
+ assert_equal(0, search('this', '', 0, 0, 'col(".") > col'))
+ normal 0
+ assert_equal([0, 0], searchpos('this', '', 0, 0, 'col(".") > col'))
+ bwipe!
+ v9.CheckDefAndScriptFailure(['search(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['search("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['search("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['search("a", "b", 3, "d")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ new
+ setline(1, "match this")
+ v9.CheckDefAndScriptFailure(['search("a", "", 9, 0, [0])'], ['E1013: Argument 5: type mismatch, expected func(...): any but got list<number>', 'E730: Using a List as a String'])
+ bwipe!
+enddef
+
+def Test_searchcount()
+ new
+ setline(1, "foo bar")
+ :/foo
+ searchcount({recompute: true})
+ ->assert_equal({
+ exact_match: 1,
+ current: 1,
+ total: 1,
+ maxcount: 99,
+ incomplete: 0})
+ bwipe!
+ v9.CheckDefAndScriptFailure(['searchcount([1])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 1'])
+enddef
+
+def Test_searchdecl()
+ searchdecl('blah', true, true)->assert_equal(1)
+ v9.CheckDefAndScriptFailure(['searchdecl(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['searchdecl("a", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ v9.CheckDefAndScriptFailure(['searchdecl("a", true, 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+
+ # search for an empty string declaration
+ var lines: list<string> =<< trim END
+ int var1;
+
+ {
+ int var2;
+ var1 = 10;
+ }
+ END
+ new
+ setline(1, lines)
+ cursor(5, 4)
+ searchdecl('')
+ assert_equal([3, 1], [line('.'), col('.')])
+ bw!
+enddef
+
+def Test_searchpair()
+ new
+ setline(1, "here { and } there")
+
+ normal f{
+ var col = 15
+ assert_equal(1, searchpair('{', '', '}', '', 'col(".") > col'))
+ assert_equal(12, col('.'))
+ normal 0f{
+ assert_equal([1, 12], searchpairpos('{', '', '}', '', 'col(".") > col'))
+
+ col = 8
+ normal 0f{
+ assert_equal(0, searchpair('{', '', '}', '', 'col(".") > col'))
+ assert_equal(6, col('.'))
+ normal 0f{
+ assert_equal([0, 0], searchpairpos('{', '', '}', '', 'col(".") > col'))
+
+ # searchpair with empty strings
+ normal 8|
+ assert_equal(0, searchpair('', '', ''))
+ assert_equal([0, 0], searchpairpos('', '', ''))
+
+ assert_equal(['x', 'x'], searchpairpos('', '', '')->map((_, _) => 'x'))
+
+ var lines =<< trim END
+ vim9script
+ setline(1, '()')
+ normal gg
+ func RetList()
+ return [0]
+ endfunc
+ def Fail()
+ try
+ searchpairpos('(', '', ')', 'nW', 'RetList()')
+ catch
+ g:caught = 'yes'
+ endtry
+ enddef
+ Fail()
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal('yes', g:caught)
+ unlet g:caught
+ bwipe!
+
+ lines =<< trim END
+ echo searchpair("a", "b", "c", "d", "f", 33)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1001: Variable not found: f', 'E475: Invalid argument: d'])
+
+ var errors = ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1']
+ v9.CheckDefAndScriptFailure(['searchpair(1, "b", "c")'], errors)
+ v9.CheckDefAndScriptFailure(['searchpairpos(1, "b", "c")'], errors)
+
+ errors = ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2']
+ v9.CheckDefAndScriptFailure(['searchpair("a", 2, "c")'], errors)
+ v9.CheckDefAndScriptFailure(['searchpairpos("a", 2, "c")'], errors)
+
+ errors = ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3']
+ v9.CheckDefAndScriptFailure(['searchpair("a", "b", 3)'], errors)
+ v9.CheckDefAndScriptFailure(['searchpairpos("a", "b", 3)'], errors)
+
+ errors = ['E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4']
+ v9.CheckDefAndScriptFailure(['searchpair("a", "b", "c", 4)'], errors)
+
+ new
+ setline(1, "match this")
+ errors = ['E1013: Argument 5: type mismatch, expected func(...): any but got list<number>', 'E730: Using a List as a String']
+ v9.CheckDefAndScriptFailure(['searchpair("a", "b", "c", "r", [0])'], errors)
+ v9.CheckDefAndScriptFailure(['searchpairpos("a", "b", "c", "r", [0])'], errors)
+ bwipe!
+
+ errors = ['E1013: Argument 6: type mismatch, expected number but got string', 'E1210: Number required for argument 6']
+ v9.CheckDefAndScriptFailure(['searchpair("a", "b", "c", "r", "1", "f")'], errors)
+ v9.CheckDefAndScriptFailure(['searchpairpos("a", "b", "c", "r", "1", "f")'], errors)
+
+ errors = ['E1013: Argument 7: type mismatch, expected number but got string', 'E1210: Number required for argument 7']
+ v9.CheckDefAndScriptFailure(['searchpair("a", "b", "c", "r", "1", 3, "g")'], errors)
+ v9.CheckDefAndScriptFailure(['searchpairpos("a", "b", "c", "r", "1", 3, "g")'], errors)
+enddef
+
+def Test_searchpos()
+ assert_equal(['x', 'x'], searchpos('.')->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['searchpos(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['searchpos("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['searchpos("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['searchpos("a", "b", 3, "d")'], ['E1013: Argument 4: type mismatch, expected number but got string', 'E1210: Number required for argument 4'])
+ new
+ setline(1, "match this")
+ v9.CheckDefAndScriptFailure(['searchpos("a", "", 9, 0, [0])'], ['E1013: Argument 5: type mismatch, expected func(...): any but got list<number>', 'E730: Using a List as a String'])
+ bwipe!
+enddef
+
+def Test_server2client()
+ CheckFeature clientserver
+ CheckEnv DISPLAY
+ v9.CheckDefAndScriptFailure(['server2client(10, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['server2client("a", 10)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['server2client("", "a")'], 'E573: Invalid server id used')
+ v9.CheckDefExecAndScriptFailure(['server2client("", "")'], 'E573: Invalid server id used')
+enddef
+
+def Test_shellescape()
+ v9.CheckDefAndScriptFailure(['shellescape(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['shellescape("a", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ if has('unix')
+ assert_equal("''", shellescape(''))
+ endif
+enddef
+
+def Test_set_get_bufline()
+ # similar to Test_setbufline_getbufline()
+ var lines =<< trim END
+ new
+ var b = bufnr('%')
+ hide
+ assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
+ assert_equal(['foo'], getbufline(b, 1))
+ assert_equal(['bar'], getbufline(b, '$'))
+ assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ exe "bd!" b
+ assert_equal([], getbufline(b, 1, 2))
+
+ split Xtest
+ setline(1, ['a', 'b', 'c'])
+ b = bufnr('%')
+ wincmd w
+
+ assert_equal(1, setbufline(b, 5, 'x'))
+ assert_equal(1, setbufline(b, 5, ['x']))
+ assert_equal(0, setbufline(b, 5, []))
+ assert_equal(0, setbufline(b, 5, test_null_list()))
+
+ assert_equal(1, 'x'->setbufline(bufnr('$') + 1, 1))
+ assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1))
+ assert_equal(1, []->setbufline(bufnr('$') + 1, 1))
+ assert_equal(1, test_null_list()->setbufline(bufnr('$') + 1, 1))
+
+ assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$'))
+
+ assert_equal(0, setbufline(b, 4, ['d', 'e']))
+ assert_equal(['c'], b->getbufline(3))
+ assert_equal(['d'], getbufline(b, 4))
+ assert_equal(['e'], getbufline(b, 5))
+ assert_equal([], getbufline(b, 6))
+ assert_equal([], getbufline(b, 2, 1))
+
+ if has('job')
+ setbufline(b, 2, [function('eval'), {key: 123}, string(test_null_job())])
+ assert_equal(["function('eval')",
+ "{'key': 123}",
+ "no process"],
+ getbufline(b, 2, 4))
+ endif
+
+ exe 'bwipe! ' .. b
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_setbufvar()
+ setbufvar(bufnr('%'), '&syntax', 'vim')
+ &syntax->assert_equal('vim')
+ setbufvar(bufnr('%'), '&ts', 16)
+ &ts->assert_equal(16)
+ setbufvar(bufnr('%'), '&ai', true)
+ &ai->assert_equal(true)
+ setbufvar(bufnr('%'), '&ft', 'filetype')
+ &ft->assert_equal('filetype')
+
+ settabwinvar(1, 1, '&syntax', 'vam')
+ &syntax->assert_equal('vam')
+ settabwinvar(1, 1, '&ts', 15)
+ &ts->assert_equal(15)
+ setlocal ts=8
+ settabwinvar(1, 1, '&list', false)
+ &list->assert_equal(false)
+ settabwinvar(1, 1, '&list', true)
+ &list->assert_equal(true)
+ setlocal list&
+
+ setbufvar('%', 'myvar', 123)
+ getbufvar('%', 'myvar')->assert_equal(123)
+
+ v9.CheckDefAndScriptFailure(['setbufvar(true, "v", 3)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setbufvar(1, 2, 3)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ assert_fails('setbufvar("%", "", 10)', 'E461: Illegal variable name')
+enddef
+
+def Test_setbufline()
+ new
+ var bnum = bufnr('%')
+ :wincmd w
+ setbufline(bnum, 1, range(1, 3))
+ setbufline(bnum, 4, 'one')
+ setbufline(bnum, 5, 10)
+ setbufline(bnum, 6, ['two', 11])
+ assert_equal(['1', '2', '3', 'one', '10', 'two', '11'], getbufline(bnum, 1, '$'))
+ v9.CheckDefAndScriptFailure(['setbufline([1], 1, "x")'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setbufline(1, [1], "x")'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['setbufline(' .. bnum .. ', -1, "x")'], 'E966: Invalid line number: -1')
+ v9.CheckDefAndScriptFailure(['setbufline(1, 1, {"a": 10})'], ['E1013: Argument 3: type mismatch, expected string but got dict<number>', 'E1224: String, Number or List required for argument 3'])
+ bnum->bufwinid()->win_gotoid()
+ setbufline('', 1, 'nombres')
+ getline(1)->assert_equal('nombres')
+ bw!
+enddef
+
+def Test_setcellwidths()
+ v9.CheckDefAndScriptFailure(['setcellwidths(1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setcellwidths({"a": 10})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E1211: List required for argument 1'])
+enddef
+
+def Test_setcharpos()
+ v9.CheckDefAndScriptFailure(['setcharpos(1, [])'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefFailure(['setcharpos(".", ["a"])'], 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>')
+ v9.CheckDefAndScriptFailure(['setcharpos(".", 1)'], ['E1013: Argument 2: type mismatch, expected list<number> but got number', 'E1211: List required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['setcharpos("", [0, 1, 1, 1])'], 'E474: Invalid argument')
+enddef
+
+def Test_setcharsearch()
+ v9.CheckDefAndScriptFailure(['setcharsearch("x")'], ['E1013: Argument 1: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setcharsearch([])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 1'])
+ var d: dict<any> = {char: 'x', forward: 1, until: 1}
+ setcharsearch(d)
+ assert_equal(d, getcharsearch())
+enddef
+
+def Test_setcmdline()
+ v9.CheckDefAndScriptSuccess(['setcmdline("ls", 2)'])
+ v9.CheckDefAndScriptFailure(['setcmdline(123)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setcmdline("ls", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_setcmdpos()
+ v9.CheckDefAndScriptFailure(['setcmdpos("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_setcursorcharpos()
+ v9.CheckDefAndScriptFailure(['setcursorcharpos(0z10, 1)'], ['E1013: Argument 1: type mismatch, expected number but got blob', 'E1224: String, Number or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setcursorcharpos(1, "2")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['setcursorcharpos(1, 2, "3")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['setcursorcharpos("", 10)'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_setenv()
+ v9.CheckDefAndScriptFailure(['setenv(1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal(0, setenv('', ''))
+ assert_equal(0, setenv('', v:null))
+enddef
+
+def Test_setfperm()
+ v9.CheckDefAndScriptFailure(['setfperm(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setfperm("a", 0z10)'], ['E1013: Argument 2: type mismatch, expected string but got blob', 'E1174: String required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['setfperm("Xfile", "")'], 'E475: Invalid argument')
+ v9.CheckDefExecAndScriptFailure(['setfperm("", "")'], 'E475: Invalid argument')
+ assert_equal(0, setfperm('', 'rw-r--r--'))
+enddef
+
+def Test_setline()
+ new
+ setline(1, range(1, 4))
+ assert_equal(['1', '2', '3', '4'], getline(1, '$'))
+ setline(1, ['a', 'b', 'c', 'd'])
+ assert_equal(['a', 'b', 'c', 'd'], getline(1, '$'))
+ setline(1, 'one')
+ assert_equal(['one', 'b', 'c', 'd'], getline(1, '$'))
+ setline(1, 10)
+ assert_equal(['10', 'b', 'c', 'd'], getline(1, '$'))
+ v9.CheckDefAndScriptFailure(['setline([1], "x")'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['setline("", "x")'], 'E1209: Invalid value for a line number')
+ v9.CheckDefExecAndScriptFailure(['setline(-1, "x")'], 'E966: Invalid line number: -1')
+ assert_fails('setline(".a", "x")', ['E1209:', 'E1209:'])
+ bw!
+enddef
+
+def Test_setloclist()
+ var items = [{filename: '/tmp/file', lnum: 1, valid: true}]
+ var what = {items: items}
+ setqflist([], ' ', what)
+ setloclist(0, [], ' ', what)
+ v9.CheckDefAndScriptFailure(['setloclist("1", [])'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setloclist(1, 2)'], ['E1013: Argument 2: type mismatch, expected list<any> but got number', 'E1211: List required for argument 2'])
+ v9.CheckDefAndScriptFailure(['setloclist(1, [], 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ v9.CheckDefAndScriptFailure(['setloclist(1, [], "a", [])'], ['E1013: Argument 4: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 4'])
+enddef
+
+def Test_setmatches()
+ v9.CheckDefAndScriptFailure(['setmatches({})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setmatches([], "1")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_setpos()
+ v9.CheckDefAndScriptFailure(['setpos(1, [])'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefFailure(['setpos(".", ["a"])'], 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>')
+ v9.CheckDefAndScriptFailure(['setpos(".", 1)'], ['E1013: Argument 2: type mismatch, expected list<number> but got number', 'E1211: List required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['setpos("", [0, 1, 1, 1])'], 'E474: Invalid argument')
+enddef
+
+def Test_setqflist()
+ v9.CheckDefAndScriptFailure(['setqflist(1, "")'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setqflist([], 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['setqflist([], "", [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+enddef
+
+def Test_setreg()
+ setreg('a', ['aaa', 'bbb', 'ccc'])
+ var reginfo = getreginfo('a')
+ setreg('a', reginfo)
+ getreginfo('a')->assert_equal(reginfo)
+ assert_fails('setreg("ab", 0)', 'E1162:')
+ v9.CheckDefAndScriptFailure(['setreg(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setreg("a", "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ setreg('', '1a2b3c')
+ assert_equal('1a2b3c', @")
+enddef
+
+def Test_settabvar()
+ v9.CheckDefAndScriptFailure(['settabvar("a", "b", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['settabvar(1, 2, "c")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ assert_fails('settabvar(1, "", 10)', 'E461: Illegal variable name')
+enddef
+
+def Test_settabwinvar()
+ v9.CheckDefAndScriptFailure(['settabwinvar("a", 2, "c", true)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['settabwinvar(1, "b", "c", [])'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['settabwinvar(1, 1, 3, {})'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ assert_fails('settabwinvar(1, 1, "", 10)', 'E461: Illegal variable name')
+enddef
+
+def Test_settagstack()
+ v9.CheckDefAndScriptFailure(['settagstack(true, {})'], ['E1013: Argument 1: type mismatch, expected number but got bool', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['settagstack(1, [1])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 2'])
+ v9.CheckDefAndScriptFailure(['settagstack(1, {}, 2)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ assert_fails('settagstack(1, {}, "")', 'E962: Invalid action')
+enddef
+
+def Test_setwinvar()
+ v9.CheckDefAndScriptFailure(['setwinvar("a", "b", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['setwinvar(1, 2, "c")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ assert_fails('setwinvar(1, "", 10)', 'E461: Illegal variable name')
+ assert_fails('setwinvar(0, "&rulerformat", true)', 'E928:')
+enddef
+
+def Test_sha256()
+ v9.CheckDefAndScriptFailure(['sha256(100)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sha256(0zABCD)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1174: String required for argument 1'])
+ assert_equal('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', sha256('abc'))
+ assert_equal('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', sha256(''))
+enddef
+
+def Test_shiftwidth()
+ v9.CheckDefAndScriptFailure(['shiftwidth("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_sign_define()
+ v9.CheckDefAndScriptFailure(['sign_define({"a": 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_define({"a": 10}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_define("a", ["b"])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<string>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_sign_getdefined()
+ v9.CheckDefAndScriptFailure(['sign_getdefined(["x"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_getdefined(2)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ sign_getdefined('')->assert_equal([])
+enddef
+
+def Test_sign_getplaced()
+ v9.CheckDefAndScriptFailure(['sign_getplaced(["x"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_getplaced(1, ["a"])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<string>', 'E1206: Dictionary required for argument 2'])
+ v9.CheckDefAndScriptFailure(['sign_getplaced("a", 1.1)'], ['E1013: Argument 2: type mismatch, expected dict<any> but got float', 'E1206: Dictionary required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['sign_getplaced(bufnr(), {lnum: ""})'], 'E1030: Using a String as a Number:')
+ sign_getplaced('')->assert_equal([{signs: [], bufnr: bufnr()}])
+enddef
+
+def Test_sign_jump()
+ v9.CheckDefAndScriptFailure(['sign_jump("a", "b", "c")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_jump(1, 2, 3)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['sign_jump(1, "b", true)'], ['E1013: Argument 3: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 3'])
+enddef
+
+def Test_sign_place()
+ v9.CheckDefAndScriptFailure(['sign_place("a", "b", "c", "d")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_place(1, 2, "c", "d")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['sign_place(1, "b", 3, "d")'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ v9.CheckDefAndScriptFailure(['sign_place(1, "b", "c", 1.1)'], ['E1013: Argument 4: type mismatch, expected string but got float', 'E1220: String or Number required for argument 4'])
+ v9.CheckDefAndScriptFailure(['sign_place(1, "b", "c", "d", [1])'], ['E1013: Argument 5: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 5'])
+ v9.CheckDefExecAndScriptFailure(['sign_place(0, "", "MySign", bufnr(), {lnum: ""})'], 'E1209: Invalid value for a line number: ""')
+ assert_fails("sign_place(0, '', '', '')", 'E155:')
+enddef
+
+def Test_sign_placelist()
+ v9.CheckDefAndScriptFailure(['sign_placelist("x")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_placelist({"a": 10})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E1211: List required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['sign_placelist([{"name": "MySign", "buffer": bufnr(), "lnum": ""}])'], 'E1209: Invalid value for a line number: ""')
+ assert_fails('sign_placelist([{name: "MySign", buffer: "", lnum: 1}])', 'E155:')
+enddef
+
+def Test_sign_undefine()
+ v9.CheckDefAndScriptFailure(['sign_undefine({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_undefine([1])'], ['E1013: Argument 1: type mismatch, expected list<string> but got list<number>', 'E155: Unknown sign:'])
+enddef
+
+def Test_sign_unplace()
+ v9.CheckDefAndScriptFailure(['sign_unplace({"a": 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_unplace({"a": 10}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_unplace("a", ["b"])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<string>', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_sign_unplacelist()
+ v9.CheckDefAndScriptFailure(['sign_unplacelist("x")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sign_unplacelist({"a": 10})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E1211: List required for argument 1'])
+enddef
+
+def Test_simplify()
+ v9.CheckDefAndScriptFailure(['simplify(100)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ call assert_equal('NonExistingFile', simplify('NonExistingFile'))
+ simplify('')->assert_equal('')
+enddef
+
+def Test_slice()
+ assert_equal('12345', slice('012345', 1))
+ assert_equal('123', slice('012345', 1, 4))
+ assert_equal('1234', slice('012345', 1, -1))
+ assert_equal('1', slice('012345', 1, -4))
+ assert_equal('', slice('012345', 1, -5))
+ assert_equal('', slice('012345', 1, -6))
+
+ assert_equal([1, 2, 3, 4, 5], slice(range(6), 1))
+ assert_equal([1, 2, 3], slice(range(6), 1, 4))
+ assert_equal([1, 2, 3, 4], slice(range(6), 1, -1))
+ assert_equal([1], slice(range(6), 1, -4))
+ assert_equal([], slice(range(6), 1, -5))
+ assert_equal([], slice(range(6), 1, -6))
+
+ var lds: list<dict<string>> = [{key: 'value'}]
+ assert_equal(['val'], lds->slice(0, 1)->map((_, v) => 'val'))
+ assert_equal(['val'], lds[ : ]->map((_, v) => 'val'))
+
+ assert_equal(0z1122334455, slice(0z001122334455, 1))
+ assert_equal(0z112233, slice(0z001122334455, 1, 4))
+ assert_equal(0z11223344, slice(0z001122334455, 1, -1))
+ assert_equal(0z11, slice(0z001122334455, 1, -4))
+ assert_equal(0z, slice(0z001122334455, 1, -5))
+ assert_equal(0z, slice(0z001122334455, 1, -6))
+ v9.CheckDefAndScriptFailure(['slice({"a": 10}, 1)'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<number>', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['slice([1, 2, 3], "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['slice("abc", 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+enddef
+
+def Test_spellsuggest()
+ if !has('spell')
+ CheckFeature spell
+ else
+ spellsuggest('marrch', 1, true)->assert_equal(['March'])
+ endif
+ v9.CheckDefAndScriptFailure(['spellsuggest(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['spellsuggest("a", "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['spellsuggest("a", 1, 0z01)'], ['E1013: Argument 3: type mismatch, expected bool but got blob', 'E1212: Bool required for argument 3'])
+ spellsuggest('')->assert_equal([])
+enddef
+
+def Test_sound_playevent()
+ CheckFeature sound
+ v9.CheckDefAndScriptFailure(['sound_playevent(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+enddef
+
+def Test_sound_playfile()
+ CheckFeature sound
+ v9.CheckDefAndScriptFailure(['sound_playfile(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+enddef
+
+def Test_sound_stop()
+ CheckFeature sound
+ v9.CheckDefAndScriptFailure(['sound_stop("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_soundfold()
+ v9.CheckDefAndScriptFailure(['soundfold(20)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal('abc', soundfold('abc'))
+ assert_equal('', soundfold(''))
+enddef
+
+def Test_sort_return_type()
+ var res: list<number>
+ res = [1, 2, 3]->sort()
+enddef
+
+def Test_sort_argument()
+ var lines =<< trim END
+ var res = ['b', 'a', 'c']->sort('i')
+ res->assert_equal(['a', 'b', 'c'])
+
+ def Compare(a: number, b: number): number
+ return a - b
+ enddef
+ var l = [3, 6, 7, 1, 8, 2, 4, 5]
+ sort(l, Compare)
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8], l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ sort([1, 2, 3], (a: any, b: any) => 1)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def SortedList(): list<number>
+ var Lambda: func: number = (a, b): number => a - b
+ var l = [3, 2, 1]
+ return l->sort(Lambda)
+ enddef
+ SortedList()->assert_equal([1, 2, 3])
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_sort_const()
+ var lines =<< trim END
+ const l = [1, 2, 3, 4]
+ sort(l)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+enddef
+
+def Test_sort_compare_func_fails()
+ v9.CheckDefAndScriptFailure(['sort("a")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['sort([1], "", [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+
+ var lines =<< trim END
+ vim9script
+ echo ['a', 'b', 'c']->sort((a: number, b: number) => 0)
+ END
+ writefile(lines, 'Xbadsort', 'D')
+ assert_fails('source Xbadsort', ['E1013:', 'E702:'])
+
+ lines =<< trim END
+ var l = [1, 2, 3]
+ sort(l, (a: string, b: number) => 1)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(string, number): number', 'E1013: Argument 1: type mismatch, expected string but got number'])
+
+ lines =<< trim END
+ var l = ['a', 'b', 'c']
+ sort(l, (a: string, b: number) => 1)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?string, ?string): number but got func(string, number): number', 'E1013: Argument 2: type mismatch, expected number but got string'])
+
+ lines =<< trim END
+ sort([1, 2, 3], (a: number, b: number) => true)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1013: Argument 2: type mismatch, expected func(?number, ?number): number but got func(number, number): bool', 'E1138: Using a Bool as a Number'])
+enddef
+
+def Test_spellbadword()
+ v9.CheckDefAndScriptFailure(['spellbadword(100)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ spellbadword('good')->assert_equal(['', ''])
+ spellbadword('')->assert_equal(['', ''])
+enddef
+
+def Test_split()
+ split(' aa bb ', '\W\+', true)->assert_equal(['', 'aa', 'bb', ''])
+ v9.CheckDefAndScriptFailure(['split(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['split("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['split("a", "b", 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ split('')->assert_equal([])
+ split('', '')->assert_equal([])
+enddef
+
+def Test_srand()
+ var expected = srand()->len()->range()->map((_, _) => 'x')
+ assert_equal(expected, srand()->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['srand("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ type(srand(100))->assert_equal(v:t_list)
+enddef
+
+def Test_state()
+ v9.CheckDefAndScriptFailure(['state({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 1'])
+ assert_equal('', state('a'))
+enddef
+
+def Test_str2float()
+ str2float("1.00")->assert_equal(1.00)
+ str2float("2e-2")->assert_equal(0.02)
+ str2float('')->assert_equal(0.0)
+
+ v9.CheckDefAndScriptFailure(['str2float(123)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+enddef
+
+def Test_str2list()
+ assert_equal(['x', 'x', 'x'], str2list('abc')->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['str2list(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['str2list("a", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ assert_equal([97], str2list('a'))
+ assert_equal([97], str2list('a', 1))
+ assert_equal([97], str2list('a', true))
+ str2list('')->assert_equal([])
+enddef
+
+def Test_str2nr()
+ str2nr("1'000'000", 10, true)->assert_equal(1000000)
+ str2nr('')->assert_equal(0)
+
+ v9.CheckDefAndScriptFailure(['str2nr(123)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['str2nr("123", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['str2nr("123", 10, "x")'], ['E1013: Argument 3: type mismatch, expected bool but got string', 'E1212: Bool required for argument 3'])
+enddef
+
+def Test_strcharlen()
+ v9.CheckDefAndScriptFailure(['strcharlen([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ "abc"->strcharlen()->assert_equal(3)
+ strcharlen(99)->assert_equal(2)
+enddef
+
+def Test_strcharpart()
+ v9.CheckDefAndScriptFailure(['strcharpart(1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strcharpart("a", "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['strcharpart("a", 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['strcharpart("a", 1, 1, 2)'], ['E1013: Argument 4: type mismatch, expected bool but got number', 'E1212: Bool required for argument 4'])
+ strcharpart('', 0)->assert_equal('')
+enddef
+
+def Test_strchars()
+ strchars("A\u20dd", true)->assert_equal(1)
+ v9.CheckDefAndScriptFailure(['strchars(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strchars("a", 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+ assert_equal(3, strchars('abc'))
+ assert_equal(3, strchars('abc', 1))
+ assert_equal(3, strchars('abc', true))
+ strchars('')->assert_equal(0)
+enddef
+
+def Test_strdisplaywidth()
+ v9.CheckDefAndScriptFailure(['strdisplaywidth(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strdisplaywidth("a", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ strdisplaywidth('')->assert_equal(0)
+enddef
+
+def Test_strftime()
+ if exists('*strftime')
+ v9.CheckDefAndScriptFailure(['strftime(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strftime("a", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ strftime('')->assert_equal('')
+ endif
+enddef
+
+def Test_strgetchar()
+ v9.CheckDefAndScriptFailure(['strgetchar(1, 1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strgetchar("a", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ strgetchar('', 0)->assert_equal(-1)
+ strgetchar('', 1)->assert_equal(-1)
+enddef
+
+def Test_stridx()
+ v9.CheckDefAndScriptFailure(['stridx([1], "b")'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['stridx("a", {})'], ['E1013: Argument 2: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['stridx("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ stridx('', '')->assert_equal(0)
+ stridx('', 'a')->assert_equal(-1)
+ stridx('a', '')->assert_equal(0)
+enddef
+
+def Test_strlen()
+ v9.CheckDefAndScriptFailure(['strlen([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ "abc"->strlen()->assert_equal(3)
+ strlen(99)->assert_equal(2)
+enddef
+
+def Test_strpart()
+ v9.CheckDefAndScriptFailure(['strpart(1, 2)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strpart("a", "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['strpart("a", 1, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ v9.CheckDefAndScriptFailure(['strpart("a", 1, 1, 2)'], ['E1013: Argument 4: type mismatch, expected bool but got number', 'E1212: Bool required for argument 4'])
+ strpart('', 0)->assert_equal('')
+enddef
+
+def Test_strptime()
+ CheckFunction strptime
+ if exists_compiled('*strptime')
+ v9.CheckDefAndScriptFailure(['strptime(10, "2021")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strptime("%Y", 2021)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ assert_true(strptime('%Y', '2021') != 0)
+ assert_true(strptime('%Y', '') == 0)
+ endif
+enddef
+
+def Test_strridx()
+ v9.CheckDefAndScriptFailure(['strridx([1], "b")'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['strridx("a", {})'], ['E1013: Argument 2: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['strridx("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ strridx('', '')->assert_equal(0)
+ strridx('', 'a')->assert_equal(-1)
+ strridx('a', '')->assert_equal(1)
+enddef
+
+def Test_strtrans()
+ v9.CheckDefAndScriptFailure(['strtrans(20)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal('abc', strtrans('abc'))
+ strtrans('')->assert_equal('')
+enddef
+
+def Test_strwidth()
+ v9.CheckDefAndScriptFailure(['strwidth(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal(4, strwidth('abcd'))
+ strwidth('')->assert_equal(0)
+enddef
+
+def Test_submatch()
+ var pat = 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)'
+ var Rep = () => range(10)->mapnew((_, v) => submatch(v, true))->string()
+ var actual = substitute('A123456789', pat, Rep, '')
+ var expected = "[['A123456789'], ['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7'], ['8'], ['9']]"
+ actual->assert_equal(expected)
+ v9.CheckDefAndScriptFailure(['submatch("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['submatch(1, "a")'], ['E1013: Argument 2: type mismatch, expected bool but got string', 'E1212: Bool required for argument 2'])
+enddef
+
+def Test_substitute()
+ var res = substitute('A1234', '\d', 'X', '')
+ assert_equal('AX234', res)
+
+ if has('job')
+ assert_fails('"text"->substitute(".*", () => test_null_job(), "")', 'E908: Using an invalid value as a String: job')
+ assert_fails('"text"->substitute(".*", () => test_null_channel(), "")', 'E908: Using an invalid value as a String: channel')
+ endif
+ v9.CheckDefAndScriptFailure(['substitute(1, "b", "1", "d")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['substitute("a", 2, "1", "d")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['substitute("a", "b", "1", 4)'], ['E1013: Argument 4: type mismatch, expected string but got number', 'E1174: String required for argument 4'])
+ substitute('', '', '', '')->assert_equal('')
+
+ var lines =<< trim END
+ assert_equal("4", substitute("3", '\d', '\=str2nr(submatch(0)) + 1', 'g'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ assert_equal("4", substitute("3", '\d', '\="text" x', 'g'))
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E488: Trailing characters: x')
+enddef
+
+def Test_swapinfo()
+ v9.CheckDefAndScriptFailure(['swapinfo({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1174: String required for argument 1'])
+ call swapinfo('x')->assert_equal({error: 'Cannot open file'})
+ call swapinfo('')->assert_equal({error: 'Cannot open file'})
+enddef
+
+def Test_swapname()
+ v9.CheckDefAndScriptFailure(['swapname([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ assert_fails('swapname("NonExistingBuf")', 'E94:')
+enddef
+
+def Test_synID()
+ new
+ setline(1, "text")
+ synID(1, 1, true)->assert_equal(0)
+ bwipe!
+ v9.CheckDefAndScriptFailure(['synID(0z10, 1, true)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['synID("a", true, false)'], ['E1013: Argument 2: type mismatch, expected number but got bool', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['synID(1, 1, 2)'], ['E1013: Argument 3: type mismatch, expected bool but got number', 'E1212: Bool required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['synID("", 10, true)'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_synIDattr()
+ v9.CheckDefAndScriptFailure(['synIDattr("a", "b")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['synIDattr(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['synIDattr(1, "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ synIDattr(1, '', '')->assert_equal('')
+enddef
+
+def Test_synIDtrans()
+ v9.CheckDefAndScriptFailure(['synIDtrans("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_synconcealed()
+ v9.CheckDefAndScriptFailure(['synconcealed(0z10, 1)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['synconcealed(1, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ if has('conceal')
+ v9.CheckDefExecAndScriptFailure(['synconcealed("", 4)'], 'E1209: Invalid value for a line number')
+ endif
+enddef
+
+def Test_synstack()
+ v9.CheckDefAndScriptFailure(['synstack(0z10, 1)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['synstack(1, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['synstack("", 4)'], 'E1209: Invalid value for a line number')
+enddef
+
+def Test_system()
+ v9.CheckDefAndScriptFailure(['system(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['system("a", {})'], ['E1013: Argument 2: type mismatch, expected string but got dict<unknown>', 'E1224: String, Number or List required for argument 2'])
+ assert_equal("123\n", system('echo 123'))
+enddef
+
+def Test_systemlist()
+ v9.CheckDefAndScriptFailure(['systemlist(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['systemlist("a", {})'], ['E1013: Argument 2: type mismatch, expected string but got dict<unknown>', 'E1224: String, Number or List required for argument 2'])
+ if has('win32')
+ call assert_equal(["123\r"], systemlist('echo 123'))
+ else
+ call assert_equal(['123'], systemlist('echo 123'))
+ endif
+enddef
+
+def Test_tabpagebuflist()
+ v9.CheckDefAndScriptFailure(['tabpagebuflist("t")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ assert_equal([bufnr('')], tabpagebuflist())
+ assert_equal([bufnr('')], tabpagebuflist(1))
+ assert_equal(['x'], tabpagebuflist()->map((_, _) => 'x'))
+enddef
+
+def Test_tabpagenr()
+ v9.CheckDefAndScriptFailure(['tabpagenr(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['tabpagenr("")'], 'E15: Invalid expression')
+ assert_equal(1, tabpagenr('$'))
+ assert_equal(1, tabpagenr())
+enddef
+
+def Test_tabpagewinnr()
+ v9.CheckDefAndScriptFailure(['tabpagewinnr("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['tabpagewinnr(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['tabpagewinnr(1, "")'], 'E15: Invalid expression')
+enddef
+
+def Test_taglist()
+ v9.CheckDefAndScriptFailure(['taglist([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['taglist("a", [2])'], ['E1013: Argument 2: type mismatch, expected string but got list<number>', 'E1174: String required for argument 2'])
+ taglist('')->assert_equal(0)
+ taglist('', '')->assert_equal(0)
+enddef
+
+def Test_term_dumpload()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_dumpload({"a": 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_dumpload({"a": 10}, "b")'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_dumpload("a", ["b"])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<string>', 'E1206: Dictionary required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['term_dumpload("")'], 'E485: Can''t read file')
+enddef
+
+def Test_term_dumpdiff()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_dumpdiff(1, "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_dumpdiff("a", 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['term_dumpdiff("a", "b", [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['term_dumpdiff("", "")'], 'E485: Can''t read file')
+enddef
+
+def Test_term_dumpwrite()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_dumpwrite(true, "b")'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_dumpwrite(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['term_dumpwrite("a", "b", [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+enddef
+
+def Test_term_getaltscreen()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getaltscreen(true)'], ['E1013: Argument 1: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_getansicolors()
+ CheckRunVimInTerminal
+ CheckFeature termguicolors
+ v9.CheckDefAndScriptFailure(['term_getansicolors(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_getattr()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getattr("x", "a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_getattr(1, 2)'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+enddef
+
+def Test_term_getcursor()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getcursor({"a": 10})'], ['E1013: Argument 1: type mismatch, expected string but got dict<number>', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_getjob()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getjob(0z10)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_getline()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getline(1.1, 1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_getline(1, 1.1)'], ['E1013: Argument 2: type mismatch, expected string but got float', 'E1220: String or Number required for argument 2'])
+enddef
+
+def Test_term_getscrolled()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getscrolled(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_getsize()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getsize(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_getstatus()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_getstatus(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_gettitle()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_gettitle(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+enddef
+
+def Test_term_gettty()
+ if !has('terminal')
+ CheckFeature terminal
+ else
+ var buf = g:Run_shell_in_terminal({})
+ term_gettty(buf, true)->assert_notequal('')
+ g:StopShellInTerminal(buf)
+ endif
+ v9.CheckDefAndScriptFailure(['term_gettty([1])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_gettty(1, 2)'], ['E1013: Argument 2: type mismatch, expected bool but got number', 'E1212: Bool required for argument 2'])
+enddef
+
+def Test_term_scrape()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_scrape(1.1, 1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_scrape(1, 1.1)'], ['E1013: Argument 2: type mismatch, expected string but got float', 'E1220: String or Number required for argument 2'])
+enddef
+
+def Test_term_sendkeys()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_sendkeys([], "p")'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_sendkeys(1, [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+enddef
+
+def Test_term_setansicolors()
+ CheckRunVimInTerminal
+
+ if has('termguicolors') || has('gui')
+ v9.CheckDefAndScriptFailure(['term_setansicolors([], "p")'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_setansicolors(10, {})'], ['E1013: Argument 2: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 2'])
+ else
+ throw 'Skipped: Only works with termguicolors or gui feature'
+ endif
+enddef
+
+def Test_term_setapi()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_setapi([], "p")'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_setapi(1, [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+enddef
+
+def Test_term_setkill()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_setkill([], "p")'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_setkill(1, [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+enddef
+
+def Test_term_setrestore()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_setrestore([], "p")'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_setrestore(1, [])'], ['E1013: Argument 2: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 2'])
+enddef
+
+def Test_term_setsize()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_setsize(1.1, 2, 3)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_setsize(1, "2", 3)'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['term_setsize(1, 2, "3")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+enddef
+
+def Test_term_start()
+ if !has('terminal')
+ CheckFeature terminal
+ else
+ botright new
+ var winnr = winnr()
+ term_start(&shell, {curwin: true})
+ winnr()->assert_equal(winnr)
+ bwipe!
+ endif
+ v9.CheckDefAndScriptFailure(['term_start({})'], ['E1013: Argument 1: type mismatch, expected string but got dict<unknown>', 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_start([], [])'], ['E1013: Argument 2: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 2'])
+ v9.CheckDefAndScriptFailure(['term_start("", "")'], ['E1013: Argument 2: type mismatch, expected dict<any> but got string', 'E1206: Dictionary required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['term_start("")'], 'E474: Invalid argument')
+enddef
+
+def Test_term_wait()
+ CheckRunVimInTerminal
+ v9.CheckDefAndScriptFailure(['term_wait(0z10, 1)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['term_wait(1, "a")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_test_alloc_fail()
+ v9.CheckDefAndScriptFailure(['test_alloc_fail("a", 10, 20)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['test_alloc_fail(10, "b", 20)'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['test_alloc_fail(10, 20, "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+enddef
+
+def Test_test_feedinput()
+ v9.CheckDefAndScriptFailure(['test_feedinput(test_void())'], ['E1013: Argument 1: type mismatch, expected string but got void', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['test_feedinput(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_test_getvalue()
+ v9.CheckDefAndScriptFailure(['test_getvalue(1.1)'], ['E1013: Argument 1: type mismatch, expected string but got float', 'E1174: String required for argument 1'])
+enddef
+
+def Test_test_gui_event()
+ CheckGui
+ v9.CheckDefAndScriptFailure(['test_gui_event([], {})'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['test_gui_event("abc", 1)'], ['E1013: Argument 2: type mismatch, expected dict<any> but got number', 'E1206: Dictionary required for argument 2'])
+enddef
+
+def Test_test_ignore_error()
+ v9.CheckDefAndScriptFailure(['test_ignore_error([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+ test_ignore_error('RESET')
+enddef
+
+def Test_test_option_not_set()
+ v9.CheckDefAndScriptFailure(['test_option_not_set([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+enddef
+
+def Test_test_override()
+ v9.CheckDefAndScriptFailure(['test_override(1, 1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['test_override("a", "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_test_setmouse()
+ v9.CheckDefAndScriptFailure(['test_setmouse("a", 10)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['test_setmouse(10, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_test_settime()
+ v9.CheckDefAndScriptFailure(['test_settime([1])'], ['E1013: Argument 1: type mismatch, expected number but got list<number>', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_test_srand_seed()
+ v9.CheckDefAndScriptFailure(['test_srand_seed([1])'], ['E1013: Argument 1: type mismatch, expected number but got list<number>', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['test_srand_seed("10")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_timer_info()
+ v9.CheckDefAndScriptFailure(['timer_info("id")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ assert_equal([], timer_info(100))
+ assert_equal([], timer_info()->filter((_, t) => t.callback->string() !~ 'TestTimeout'))
+enddef
+
+def Test_timer_pause()
+ v9.CheckDefAndScriptFailure(['timer_pause("x", 1)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['timer_pause(1, "a")'], ['E1013: Argument 2: type mismatch, expected bool but got string', 'E1212: Bool required for argument 2'])
+enddef
+
+def Test_timer_paused()
+ var id = timer_start(50, () => 0)
+ timer_pause(id, true)
+ var info = timer_info(id)
+ info[0]['paused']->assert_equal(1)
+ timer_stop(id)
+enddef
+
+def Test_timer_start()
+ v9.CheckDefAndScriptFailure(['timer_start("a", "1")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['timer_start(1, "1", [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+ v9.CheckDefExecAndScriptFailure(['timer_start(100, 0)'], 'E921:')
+ v9.CheckDefExecAndScriptFailure(['timer_start(100, "")'], 'E921:')
+enddef
+
+def Test_timer_stop()
+ v9.CheckDefAndScriptFailure(['timer_stop("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ assert_equal(0, timer_stop(100))
+enddef
+
+def Test_tolower()
+ v9.CheckDefAndScriptFailure(['tolower(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ tolower('')->assert_equal('')
+enddef
+
+def Test_toupper()
+ v9.CheckDefAndScriptFailure(['toupper(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ toupper('')->assert_equal('')
+enddef
+
+def Test_tr()
+ v9.CheckDefAndScriptFailure(['tr(1, "a", "b")'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['tr("a", 1, "b")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['tr("a", "a", 1)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+ tr('', '', '')->assert_equal('')
+ tr('ab', '', '')->assert_equal('ab')
+ assert_fails("tr('ab', 'ab', '')", 'E475:')
+ assert_fails("tr('ab', '', 'AB')", 'E475:')
+enddef
+
+def Test_trim()
+ v9.CheckDefAndScriptFailure(['trim(["a"])'], ['E1013: Argument 1: type mismatch, expected string but got list<string>', 'E1174: String required for argument 1'])
+ v9.CheckDefAndScriptFailure(['trim("a", ["b"])'], ['E1013: Argument 2: type mismatch, expected string but got list<string>', 'E1174: String required for argument 2'])
+ v9.CheckDefAndScriptFailure(['trim("a", "b", "c")'], ['E1013: Argument 3: type mismatch, expected number but got string', 'E1210: Number required for argument 3'])
+ trim('')->assert_equal('')
+ trim('', '')->assert_equal('')
+enddef
+
+def Test_typename()
+ assert_equal('func([unknown], [unknown]): float', typename(function('pow')))
+ assert_equal('func(...): unknown', test_null_partial()->typename())
+ assert_equal('list<unknown>', test_null_list()->typename())
+ assert_equal('dict<unknown>', test_null_dict()->typename())
+ if has('job')
+ assert_equal('job', test_null_job()->typename())
+ endif
+ if has('channel')
+ assert_equal('channel', test_null_channel()->typename())
+ endif
+enddef
+
+def Test_undofile()
+ v9.CheckDefAndScriptFailure(['undofile(10)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
+ assert_equal('.abc.un~', fnamemodify(undofile('abc'), ':t'))
+ undofile('')->assert_equal('')
+enddef
+
+def Test_uniq()
+ v9.CheckDefAndScriptFailure(['uniq("a")'], ['E1013: Argument 1: type mismatch, expected list<any> but got string', 'E1211: List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['uniq([1], "", [1])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<number>', 'E1206: Dictionary required for argument 3'])
+
+ v9.CheckDefFailure(['var l: list<number> = uniq(["a", "b"])'], 'E1012: Type mismatch; expected list<number> but got list<string>')
+enddef
+
+def Test_uniq_const()
+ var lines =<< trim END
+ const l = [1, 2, 3, 4]
+ uniq(l)
+ END
+ v9.CheckDefFailure(lines, 'E1307: Argument 1: Trying to modify a const list<number>')
+enddef
+
+def Test_values()
+ v9.CheckDefAndScriptFailure(['values([])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 1'])
+ assert_equal([], {}->values())
+ assert_equal(['sun'], {star: 'sun'}->values())
+
+ # the return type of values() is list<member>
+ var lines =<< trim END
+ vim9script
+
+ class Foo
+ this.val: number
+ def Add()
+ echo this.val
+ enddef
+ endclass
+
+ def Process(FooDict: dict<Foo>)
+ for foo in values(FooDict)
+ foo.Add()
+ endfor
+ enddef
+
+ disas Process
+
+ var D = {'x': Foo.new(22)}
+
+ Process(D)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_virtcol()
+ v9.CheckDefAndScriptFailure(['virtcol(1.1)'], [
+ 'E1013: Argument 1: type mismatch, expected string but got float',
+ 'E1222: String or List required for argument 1'])
+ v9.CheckDefAndScriptFailure(['virtcol(".", "a")'], [
+ 'E1013: Argument 2: type mismatch, expected bool but got string',
+ 'E1212: Bool required for argument 2'])
+ v9.CheckDefExecAndScriptFailure(['virtcol("")'],
+ 'E1209: Invalid value for a line number')
+ new
+ setline(1, ['abcde和平fgh'])
+ cursor(1, 4)
+ assert_equal(4, virtcol('.'))
+ assert_equal([4, 4], virtcol('.', 1))
+ cursor(1, 6)
+ assert_equal([6, 7], virtcol('.', 1))
+ assert_equal(4, virtcol([1, 4]))
+ assert_equal(13, virtcol([1, '$']))
+ assert_equal(0, virtcol([10, '$']))
+ bw!
+enddef
+
+def Test_visualmode()
+ v9.CheckDefAndScriptFailure(['visualmode("1")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1'])
+ v9.CheckDefAndScriptFailure(['visualmode(2)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1'])
+enddef
+
+def Test_win_execute()
+ assert_equal("\n" .. winnr(), win_execute(win_getid(), 'echo winnr()'))
+ assert_equal("\n" .. winnr(), 'echo winnr()'->win_execute(win_getid()))
+ assert_equal("\n" .. winnr(), win_execute(win_getid(), 'echo winnr()', 'silent'))
+ assert_equal('', win_execute(342343, 'echo winnr()'))
+ v9.CheckDefAndScriptFailure(['win_execute("a", "b", "c")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['win_execute(1, 2, "c")'], ['E1013: Argument 2: type mismatch, expected string but got number', 'E1222: String or List required for argument 2'])
+ v9.CheckDefAndScriptFailure(['win_execute(1, "b", 3)'], ['E1013: Argument 3: type mismatch, expected string but got number', 'E1174: String required for argument 3'])
+enddef
+
+def Test_win_findbuf()
+ v9.CheckDefAndScriptFailure(['win_findbuf("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ assert_equal([], win_findbuf(1000))
+ assert_equal([win_getid()], win_findbuf(bufnr('')))
+enddef
+
+def Test_win_getid()
+ v9.CheckDefAndScriptFailure(['win_getid(".")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['win_getid(1, ".")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ assert_equal(win_getid(), win_getid(1, 1))
+enddef
+
+def Test_win_gettype()
+ v9.CheckDefAndScriptFailure(['win_gettype("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_win_gotoid()
+ v9.CheckDefAndScriptFailure(['win_gotoid("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+func Test_win_gotoid_in_mapping()
+ CheckScreendump
+
+ " requires a working clipboard and this doesn't work on MacOS
+ if has('clipboard_working') && !has('mac')
+ let @* = 'foo'
+ let lines =<< trim END
+ set cmdheight=2
+ func On_click()
+ call win_gotoid(getmousepos().winid)
+ execute "norm! \<LeftMouse>"
+ endfunc
+ noremap <LeftMouse> <Cmd>call On_click()<CR>
+
+ autocmd SafeState * echo 'reg = "' .. @* .. '"'
+
+ call setline(1, range(20))
+ set nomodified
+ botright new
+ call setline(1, range(21, 40))
+ set nomodified
+
+ func Click()
+ map <silent> <F3> :call test_setmouse(3, 1)<CR>
+ call feedkeys("\<F3>\<LeftMouse>\<LeftRelease>", "xt")
+ endfunc
+ END
+ call writefile(lines, 'Xgotoscript', 'D')
+ let buf = RunVimInTerminal('-S Xgotoscript', #{rows: 15, wait_for_ruler: 0})
+ " wait longer here, since we didn't wait for the ruler
+ call VerifyScreenDump(buf, 'Test_win_gotoid_1', #{wait: 3000})
+ call term_sendkeys(buf, "3Gvl")
+ call VerifyScreenDump(buf, 'Test_win_gotoid_2', {})
+
+ call term_sendkeys(buf, ":call Click()\<CR>")
+ call VerifyScreenDump(buf, 'Test_win_gotoid_3', {})
+
+ call StopVimInTerminal(buf)
+ endif
+endfunc
+
+def Test_win_id2tabwin()
+ v9.CheckDefAndScriptFailure(['win_id2tabwin("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_win_id2win()
+ v9.CheckDefAndScriptFailure(['win_id2win("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_win_screenpos()
+ assert_equal(['x', 'x'], win_screenpos(1)->map((_, _) => 'x'))
+
+ v9.CheckDefAndScriptFailure(['win_screenpos("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_win_splitmove()
+ split
+ win_splitmove(1, 2, {vertical: true, rightbelow: true})
+ close
+ v9.CheckDefAndScriptFailure(['win_splitmove("a", 2)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['win_splitmove(1, "b")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+ v9.CheckDefAndScriptFailure(['win_splitmove(1, 2, [])'], ['E1013: Argument 3: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 3'])
+enddef
+
+def Test_winbufnr()
+ v9.CheckDefAndScriptFailure(['winbufnr("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_winheight()
+ v9.CheckDefAndScriptFailure(['winheight("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_winlayout()
+ v9.CheckDefAndScriptFailure(['winlayout("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_winnr()
+ v9.CheckDefAndScriptFailure(['winnr([])'], ['E1013: Argument 1: type mismatch, expected string but got list<unknown>', 'E1174: String required for argument 1'])
+ v9.CheckDefExecAndScriptFailure(['winnr("")'], 'E15: Invalid expression')
+ assert_equal(1, winnr())
+ assert_equal(1, winnr('$'))
+enddef
+
+def Test_winrestcmd()
+ split
+ var cmd = winrestcmd()
+ wincmd _
+ exe cmd
+ assert_equal(cmd, winrestcmd())
+ close
+enddef
+
+def Test_winrestview()
+ v9.CheckDefAndScriptFailure(['winrestview([])'], ['E1013: Argument 1: type mismatch, expected dict<any> but got list<unknown>', 'E1206: Dictionary required for argument 1'])
+ :%d _
+ setline(1, 'Hello World')
+ winrestview({lnum: 1, col: 6})
+ assert_equal([1, 7], [line('.'), col('.')])
+enddef
+
+def Test_winsaveview()
+ var view: dict<number> = winsaveview()
+
+ var lines =<< trim END
+ var view: list<number> = winsaveview()
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected list<number> but got dict<number>', 1)
+enddef
+
+def Test_winwidth()
+ v9.CheckDefAndScriptFailure(['winwidth("x")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+enddef
+
+def Test_xor()
+ v9.CheckDefAndScriptFailure(['xor("x", 0x2)'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1'])
+ v9.CheckDefAndScriptFailure(['xor(0x1, "x")'], ['E1013: Argument 2: type mismatch, expected number but got string', 'E1210: Number required for argument 2'])
+enddef
+
+def Test_writefile()
+ v9.CheckDefExecAndScriptFailure(['writefile(["a"], "")'], 'E482: Can''t create file <empty>')
+enddef
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
new file mode 100644
index 0000000..bc8a8e1
--- /dev/null
+++ b/src/testdir/test_vim9_class.vim
@@ -0,0 +1,1752 @@
+" Test Vim9 classes
+
+source check.vim
+import './vim9.vim' as v9
+
+def Test_class_basic()
+ var lines =<< trim END
+ class NotWorking
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1316:')
+
+ lines =<< trim END
+ vim9script
+ class notWorking
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1314:')
+
+ lines =<< trim END
+ vim9script
+ class Not@working
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1315:')
+
+ lines =<< trim END
+ vim9script
+ abstract noclass Something
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E475:')
+
+ lines =<< trim END
+ vim9script
+ abstract classy Something
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E475:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ endcl
+ END
+ v9.CheckScriptFailure(lines, 'E1065:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ endclass school's out
+ END
+ v9.CheckScriptFailure(lines, 'E488:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ endclass | echo 'done'
+ END
+ v9.CheckScriptFailure(lines, 'E488:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1317:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this.
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1317:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this .count
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1317:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this. count
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1317:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this.count: number
+ that.count
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1318: Not a valid command in a class: that.count')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this.count
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1022:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ def new()
+ this.state = 0
+ enddef
+ endclass
+ var obj = Something.new()
+ END
+ v9.CheckScriptFailure(lines, 'E1089:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this.count : number
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1059:')
+
+ lines =<< trim END
+ vim9script
+ class Something
+ this.count:number
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1069:')
+
+ lines =<< trim END
+ vim9script
+
+ class TextPosition
+ this.lnum: number
+ this.col: number
+
+ # make a nicely formatted string
+ def ToString(): string
+ return $'({this.lnum}, {this.col})'
+ enddef
+ endclass
+
+ # use the automatically generated new() method
+ var pos = TextPosition.new(2, 12)
+ assert_equal(2, pos.lnum)
+ assert_equal(12, pos.col)
+
+ # call an object method
+ assert_equal('(2, 12)', pos.ToString())
+
+ assert_equal(v:t_class, type(TextPosition))
+ assert_equal(v:t_object, type(pos))
+ assert_equal('class<TextPosition>', typename(TextPosition))
+ assert_equal('object<TextPosition>', typename(pos))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_defined_twice()
+ # class defined twice should fail
+ var lines =<< trim END
+ vim9script
+ class There
+ endclass
+ class There
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1041: Redefining script item: "There"')
+
+ # one class, reload same script twice is OK
+ lines =<< trim END
+ vim9script
+ class There
+ endclass
+ END
+ writefile(lines, 'XclassTwice.vim', 'D')
+ source XclassTwice.vim
+ source XclassTwice.vim
+enddef
+
+def Test_returning_null_object()
+ # this was causing an internal error
+ var lines =<< trim END
+ vim9script
+
+ class BufferList
+ def Current(): any
+ return null_object
+ enddef
+ endclass
+
+ var buffers = BufferList.new()
+ echo buffers.Current()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_interface_wrong_end()
+ var lines =<< trim END
+ vim9script
+ abstract class SomeName
+ this.member = 'text'
+ endinterface
+ END
+ v9.CheckScriptFailure(lines, 'E476: Invalid command: endinterface, expected endclass')
+
+ lines =<< trim END
+ vim9script
+ export interface AnotherName
+ this.member: string
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E476: Invalid command: endclass, expected endinterface')
+enddef
+
+def Test_object_not_set()
+ var lines =<< trim END
+ vim9script
+
+ class State
+ this.value = 'xyz'
+ endclass
+
+ var state: State
+ var db = {'xyz': 789}
+ echo db[state.value]
+ END
+ v9.CheckScriptFailure(lines, 'E1360:')
+
+ lines =<< trim END
+ vim9script
+
+ class Class
+ this.id: string
+ def Method1()
+ echo 'Method1' .. this.id
+ enddef
+ endclass
+
+ var obj: Class
+ def Func()
+ obj.Method1()
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1360:')
+
+ lines =<< trim END
+ vim9script
+
+ class Background
+ this.background = 'dark'
+ endclass
+
+ class Colorscheme
+ this._bg: Background
+
+ def GetBackground(): string
+ return this._bg.background
+ enddef
+ endclass
+
+ var bg: Background # UNINITIALIZED
+ echo Colorscheme.new(bg).GetBackground()
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object<Background> but got object<Unknown>')
+
+ # TODO: this should not give an error but be handled at runtime
+ lines =<< trim END
+ vim9script
+
+ class Class
+ this.id: string
+ def Method1()
+ echo 'Method1' .. this.id
+ enddef
+ endclass
+
+ var obj = null_object
+ def Func()
+ obj.Method1()
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1363:')
+enddef
+
+def Test_class_member_initializer()
+ var lines =<< trim END
+ vim9script
+
+ class TextPosition
+ this.lnum: number = 1
+ this.col: number = 1
+
+ # constructor with only the line number
+ def new(lnum: number)
+ this.lnum = lnum
+ enddef
+ endclass
+
+ var pos = TextPosition.new(3)
+ assert_equal(3, pos.lnum)
+ assert_equal(1, pos.col)
+
+ var instr = execute('disassemble TextPosition.new')
+ assert_match('new\_s*' ..
+ '0 NEW TextPosition size \d\+\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d STORE_THIS 0\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d STORE_THIS 1\_s*' ..
+ 'this.lnum = lnum\_s*' ..
+ '\d LOAD arg\[-1]\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d\+ STOREINDEX object\_s*' ..
+ '\d\+ RETURN object.*',
+ instr)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_member_any_used_as_object()
+ var lines =<< trim END
+ vim9script
+
+ class Inner
+ this.value: number = 0
+ endclass
+
+ class Outer
+ this.inner: any
+ endclass
+
+ def F(outer: Outer)
+ outer.inner.value = 1
+ enddef
+
+ var inner_obj = Inner.new(0)
+ var outer_obj = Outer.new(inner_obj)
+ F(outer_obj)
+ assert_equal(1, inner_obj.value)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class Inner
+ this.value: number = 0
+ endclass
+
+ class Outer
+ this.inner: Inner
+ endclass
+
+ def F(outer: Outer)
+ outer.inner.value = 1
+ enddef
+
+ def Test_assign_to_nested_typed_member()
+ var inner = Inner.new(0)
+ var outer = Outer.new(inner)
+ F(outer)
+ assert_equal(1, inner.value)
+ enddef
+
+ Test_assign_to_nested_typed_member()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_assignment_with_operator()
+ var lines =<< trim END
+ vim9script
+
+ class Foo
+ this.x: number
+
+ def Add(n: number)
+ this.x += n
+ enddef
+ endclass
+
+ var f = Foo.new(3)
+ f.Add(17)
+ assert_equal(20, f.x)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_list_of_objects()
+ var lines =<< trim END
+ vim9script
+
+ class Foo
+ def Add()
+ enddef
+ endclass
+
+ def ProcessList(fooList: list<Foo>)
+ for foo in fooList
+ foo.Add()
+ endfor
+ enddef
+
+ var l: list<Foo> = [Foo.new()]
+ ProcessList(l)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_expr_after_using_object()
+ var lines =<< trim END
+ vim9script
+
+ class Something
+ this.label: string = ''
+ endclass
+
+ def Foo(): Something
+ var v = Something.new()
+ echo 'in Foo(): ' .. typename(v)
+ return v
+ enddef
+
+ Foo()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_default_new()
+ var lines =<< trim END
+ vim9script
+
+ class TextPosition
+ this.lnum: number = 1
+ this.col: number = 1
+ endclass
+
+ var pos = TextPosition.new()
+ assert_equal(1, pos.lnum)
+ assert_equal(1, pos.col)
+
+ pos = TextPosition.new(v:none, v:none)
+ assert_equal(1, pos.lnum)
+ assert_equal(1, pos.col)
+
+ pos = TextPosition.new(3, 22)
+ assert_equal(3, pos.lnum)
+ assert_equal(22, pos.col)
+
+ pos = TextPosition.new(v:none, 33)
+ assert_equal(1, pos.lnum)
+ assert_equal(33, pos.col)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Person
+ this.name: string
+ this.age: number = 42
+ this.education: string = "unknown"
+
+ def new(this.name, this.age = v:none, this.education = v:none)
+ enddef
+ endclass
+
+ var piet = Person.new("Piet")
+ assert_equal("Piet", piet.name)
+ assert_equal(42, piet.age)
+ assert_equal("unknown", piet.education)
+
+ var chris = Person.new("Chris", 4, "none")
+ assert_equal("Chris", chris.name)
+ assert_equal(4, chris.age)
+ assert_equal("none", chris.education)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Person
+ this.name: string
+ this.age: number = 42
+ this.education: string = "unknown"
+
+ def new(this.name, this.age = v:none, this.education = v:none)
+ enddef
+ endclass
+
+ var missing = Person.new()
+ END
+ v9.CheckScriptFailure(lines, 'E119:')
+enddef
+
+def Test_class_object_member_inits()
+ var lines =<< trim END
+ vim9script
+ class TextPosition
+ this.lnum: number
+ this.col = 1
+ this.addcol: number = 2
+ endclass
+
+ var pos = TextPosition.new()
+ assert_equal(0, pos.lnum)
+ assert_equal(1, pos.col)
+ assert_equal(2, pos.addcol)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class TextPosition
+ this.lnum
+ this.col = 1
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1022:')
+
+ lines =<< trim END
+ vim9script
+ class TextPosition
+ this.lnum = v:none
+ this.col = 1
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1330:')
+enddef
+
+def Test_class_object_member_access()
+ var lines =<< trim END
+ vim9script
+ class Triple
+ this._one = 1
+ this.two = 2
+ public this.three = 3
+
+ def GetOne(): number
+ return this._one
+ enddef
+ endclass
+
+ var trip = Triple.new()
+ assert_equal(1, trip.GetOne())
+ assert_equal(2, trip.two)
+ assert_equal(3, trip.three)
+ assert_fails('echo trip._one', 'E1333')
+
+ assert_fails('trip._one = 11', 'E1333')
+ assert_fails('trip.two = 22', 'E1335')
+ trip.three = 33
+ assert_equal(33, trip.three)
+
+ assert_fails('trip.four = 4', 'E1334')
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class MyCar
+ this.make: string
+ this.age = 5
+
+ def new(make_arg: string)
+ this.make = make_arg
+ enddef
+
+ def GetMake(): string
+ return $"make = {this.make}"
+ enddef
+ def GetAge(): number
+ return this.age
+ enddef
+ endclass
+
+ var c = MyCar.new("abc")
+ assert_equal('make = abc', c.GetMake())
+
+ c = MyCar.new("def")
+ assert_equal('make = def', c.GetMake())
+
+ var c2 = MyCar.new("123")
+ assert_equal('make = 123', c2.GetMake())
+
+ def CheckCar()
+ assert_equal("make = def", c.GetMake())
+ assert_equal(5, c.GetAge())
+ enddef
+ CheckCar()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class MyCar
+ this.make: string
+
+ def new(make_arg: string)
+ this.make = make_arg
+ enddef
+ endclass
+
+ var c = MyCar.new("abc")
+ var c = MyCar.new("def")
+ END
+ v9.CheckScriptFailure(lines, 'E1041:')
+
+ lines =<< trim END
+ vim9script
+
+ class Foo
+ this.x: list<number> = []
+
+ def Add(n: number): any
+ this.x->add(n)
+ return this
+ enddef
+ endclass
+
+ echo Foo.new().Add(1).Add(2).x
+ echo Foo.new().Add(1).Add(2)
+ .x
+ echo Foo.new().Add(1)
+ .Add(2).x
+ echo Foo.new()
+ .Add(1).Add(2).x
+ echo Foo.new()
+ .Add(1)
+ .Add(2)
+ .x
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_object_compare()
+ var class_lines =<< trim END
+ vim9script
+ class Item
+ this.nr = 0
+ this.name = 'xx'
+ endclass
+ END
+
+ # used at the script level and in a compiled function
+ var test_lines =<< trim END
+ var i1 = Item.new()
+ assert_equal(i1, i1)
+ assert_true(i1 is i1)
+ var i2 = Item.new()
+ assert_equal(i1, i2)
+ assert_false(i1 is i2)
+ var i3 = Item.new(0, 'xx')
+ assert_equal(i1, i3)
+
+ var io1 = Item.new(1, 'xx')
+ assert_notequal(i1, io1)
+ var io2 = Item.new(0, 'yy')
+ assert_notequal(i1, io2)
+ END
+
+ v9.CheckScriptSuccess(class_lines + test_lines)
+ v9.CheckScriptSuccess(
+ class_lines + ['def Test()'] + test_lines + ['enddef', 'Test()'])
+
+ for op in ['>', '>=', '<', '<=', '=~', '!~']
+ var op_lines = [
+ 'var i1 = Item.new()',
+ 'var i2 = Item.new()',
+ 'echo i1 ' .. op .. ' i2',
+ ]
+ v9.CheckScriptFailure(class_lines + op_lines, 'E1153: Invalid operation for object')
+ v9.CheckScriptFailure(class_lines
+ + ['def Test()'] + op_lines + ['enddef', 'Test()'], 'E1153: Invalid operation for object')
+ endfor
+enddef
+
+def Test_object_type()
+ var lines =<< trim END
+ vim9script
+
+ class One
+ this.one = 1
+ endclass
+ class Two
+ this.two = 2
+ endclass
+ class TwoMore extends Two
+ this.more = 9
+ endclass
+
+ var o: One = One.new()
+ var t: Two = Two.new()
+ var m: TwoMore = TwoMore.new()
+ var tm: Two = TwoMore.new()
+
+ t = m
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class One
+ this.one = 1
+ endclass
+ class Two
+ this.two = 2
+ endclass
+
+ var o: One = Two.new()
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object<One> but got object<Two>')
+
+ lines =<< trim END
+ vim9script
+
+ interface One
+ def GetMember(): number
+ endinterface
+ class Two implements One
+ this.one = 1
+ def GetMember(): number
+ return this.one
+ enddef
+ endclass
+
+ var o: One = Two.new(5)
+ assert_equal(5, o.GetMember())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class Num
+ this.n: number = 0
+ endclass
+
+ def Ref(name: string): func(Num): Num
+ return (arg: Num): Num => {
+ return eval(name)(arg)
+ }
+ enddef
+
+ const Fn = Ref('Double')
+ var Double = (m: Num): Num => Num.new(m.n * 2)
+
+ echo Fn(Num.new(4))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_member()
+ # check access rules
+ var lines =<< trim END
+ vim9script
+ class TextPos
+ this.lnum = 1
+ this.col = 1
+ static counter = 0
+ static _secret = 7
+ public static anybody = 42
+
+ static def AddToCounter(nr: number)
+ counter += nr
+ enddef
+ endclass
+
+ assert_equal(0, TextPos.counter)
+ TextPos.AddToCounter(3)
+ assert_equal(3, TextPos.counter)
+ assert_fails('echo TextPos.noSuchMember', 'E1338:')
+
+ def GetCounter(): number
+ return TextPos.counter
+ enddef
+ assert_equal(3, GetCounter())
+
+ assert_fails('TextPos.noSuchMember = 2', 'E1337:')
+ assert_fails('TextPos.counter = 5', 'E1335:')
+ assert_fails('TextPos.counter += 5', 'E1335:')
+
+ assert_fails('echo TextPos._secret', 'E1333:')
+ assert_fails('TextPos._secret = 8', 'E1333:')
+
+ assert_equal(42, TextPos.anybody)
+ TextPos.anybody = 12
+ assert_equal(12, TextPos.anybody)
+ TextPos.anybody += 5
+ assert_equal(17, TextPos.anybody)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # example in the help
+ lines =<< trim END
+ vim9script
+ class OtherThing
+ this.size: number
+ static totalSize: number
+
+ def new(this.size)
+ totalSize += this.size
+ enddef
+ endclass
+ assert_equal(0, OtherThing.totalSize)
+ var to3 = OtherThing.new(3)
+ assert_equal(3, OtherThing.totalSize)
+ var to7 = OtherThing.new(7)
+ assert_equal(10, OtherThing.totalSize)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # access private member in lambda
+ lines =<< trim END
+ vim9script
+
+ class Foo
+ this._x: number = 0
+
+ def Add(n: number): number
+ const F = (): number => this._x + n
+ return F()
+ enddef
+ endclass
+
+ var foo = Foo.new()
+ assert_equal(5, foo.Add(5))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # check shadowing
+ lines =<< trim END
+ vim9script
+
+ class Some
+ static count = 0
+ def Method(count: number)
+ echo count
+ enddef
+ endclass
+
+ var s = Some.new()
+ s.Method(7)
+ END
+ v9.CheckScriptFailure(lines, 'E1340: Argument already declared in the class: count')
+
+ lines =<< trim END
+ vim9script
+
+ class Some
+ static count = 0
+ def Method(arg: number)
+ var count = 3
+ echo arg count
+ enddef
+ endclass
+
+ var s = Some.new()
+ s.Method(7)
+ END
+ v9.CheckScriptFailure(lines, 'E1341: Variable already declared in the class: count')
+enddef
+
+func Test_class_garbagecollect()
+ let lines =<< trim END
+ vim9script
+
+ class Point
+ this.p = [2, 3]
+ static pl = ['a', 'b']
+ static pd = {a: 'a', b: 'b'}
+ endclass
+
+ echo Point.pl Point.pd
+ call test_garbagecollect_now()
+ echo Point.pl Point.pd
+ END
+ call v9.CheckScriptSuccess(lines)
+endfunc
+
+def Test_class_function()
+ var lines =<< trim END
+ vim9script
+ class Value
+ this.value = 0
+ static objects = 0
+
+ def new(v: number)
+ this.value = v
+ ++objects
+ enddef
+
+ static def GetCount(): number
+ return objects
+ enddef
+ endclass
+
+ assert_equal(0, Value.GetCount())
+ var v1 = Value.new(2)
+ assert_equal(1, Value.GetCount())
+ var v2 = Value.new(7)
+ assert_equal(2, Value.GetCount())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_defcompile()
+ var lines =<< trim END
+ vim9script
+
+ class C
+ def Fo(i: number): string
+ return i
+ enddef
+ endclass
+
+ defcompile C.Fo
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got number')
+
+ lines =<< trim END
+ vim9script
+
+ class C
+ static def Fc(): number
+ return 'x'
+ enddef
+ endclass
+
+ defcompile C.Fc
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected number but got string')
+enddef
+
+def Test_class_object_to_string()
+ var lines =<< trim END
+ vim9script
+ class TextPosition
+ this.lnum = 1
+ this.col = 22
+ endclass
+
+ assert_equal("class TextPosition", string(TextPosition))
+
+ var pos = TextPosition.new()
+ assert_equal("object of TextPosition {lnum: 1, col: 22}", string(pos))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_interface_basics()
+ var lines =<< trim END
+ vim9script
+ interface Something
+ this.value: string
+ static count: number
+ def GetCount(): number
+ endinterface
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ interface SomethingWrong
+ static count = 7
+ endinterface
+ END
+ v9.CheckScriptFailure(lines, 'E1342:')
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ static count: number
+ def Method(count: number)
+ endinterface
+ END
+ v9.CheckScriptFailure(lines, 'E1340: Argument already declared in the class: count')
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ this.value: number
+ def Method(value: number)
+ endinterface
+ END
+ v9.CheckScriptFailure(lines, 'E1340: Argument already declared in the class: value')
+
+ lines =<< trim END
+ vim9script
+ interface somethingWrong
+ static count = 7
+ endinterface
+ END
+ v9.CheckScriptFailure(lines, 'E1343: Interface name must start with an uppercase letter: somethingWrong')
+
+ lines =<< trim END
+ vim9script
+ interface SomethingWrong
+ this.value: string
+ static count = 7
+ def GetCount(): number
+ endinterface
+ END
+ v9.CheckScriptFailure(lines, 'E1344:')
+
+ lines =<< trim END
+ vim9script
+ interface SomethingWrong
+ this.value: string
+ static count: number
+ def GetCount(): number
+ return 5
+ enddef
+ endinterface
+ END
+ v9.CheckScriptFailure(lines, 'E1345: Not a valid command in an interface: return 5')
+
+ lines =<< trim END
+ vim9script
+ export interface EnterExit
+ def Enter(): void
+ def Exit(): void
+ endinterface
+ END
+ writefile(lines, 'XdefIntf.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './XdefIntf.vim' as defIntf
+ export def With(ee: defIntf.EnterExit, F: func)
+ ee.Enter()
+ try
+ F()
+ finally
+ ee.Exit()
+ endtry
+ enddef
+ END
+ v9.CheckScriptSuccess(lines)
+
+ var imported =<< trim END
+ vim9script
+ export abstract class EnterExit
+ def Enter(): void
+ enddef
+ def Exit(): void
+ enddef
+ endclass
+ END
+ writefile(imported, 'XdefIntf2.vim', 'D')
+
+ lines[1] = " import './XdefIntf2.vim' as defIntf"
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_implements_interface()
+ var lines =<< trim END
+ vim9script
+
+ interface Some
+ static count: number
+ def Method(nr: number)
+ endinterface
+
+ class SomeImpl implements Some
+ static count: number
+ def Method(nr: number)
+ echo nr
+ enddef
+ endclass
+
+ interface Another
+ this.member: string
+ endinterface
+
+ class AnotherImpl implements Some, Another
+ this.member = 'abc'
+ static count: number
+ def Method(nr: number)
+ echo nr
+ enddef
+ endclass
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ static counter: number
+ endinterface
+
+ class SomeImpl implements Some implements Some
+ static count: number
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1350:')
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ static counter: number
+ endinterface
+
+ class SomeImpl implements Some, Some
+ static count: number
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1351: Duplicate interface after "implements": Some')
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ static counter: number
+ def Method(nr: number)
+ endinterface
+
+ class SomeImpl implements Some
+ static count: number
+ def Method(nr: number)
+ echo nr
+ enddef
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1348: Member "counter" of interface "Some" not implemented')
+
+ lines =<< trim END
+ vim9script
+
+ interface Some
+ static count: number
+ def Methods(nr: number)
+ endinterface
+
+ class SomeImpl implements Some
+ static count: number
+ def Method(nr: number)
+ echo nr
+ enddef
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1349: Function "Methods" of interface "Some" not implemented')
+
+ # Check different order of members in class and interface works.
+ lines =<< trim END
+ vim9script
+
+ interface Result
+ public this.label: string
+ this.errpos: number
+ endinterface
+
+ # order of members is opposite of interface
+ class Failure implements Result
+ this.errpos: number = 42
+ public this.label: string = 'label'
+ endclass
+
+ def Test()
+ var result: Result = Failure.new()
+
+ assert_equal('label', result.label)
+ assert_equal(42, result.errpos)
+
+ result.label = 'different'
+ assert_equal('different', result.label)
+ assert_equal(42, result.errpos)
+ enddef
+
+ Test()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_call_interface_method()
+ var lines =<< trim END
+ vim9script
+ interface Base
+ def Enter(): void
+ endinterface
+
+ class Child implements Base
+ def Enter(): void
+ g:result ..= 'child'
+ enddef
+ endclass
+
+ def F(obj: Base)
+ obj.Enter()
+ enddef
+
+ g:result = ''
+ F(Child.new())
+ assert_equal('child', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Base
+ def Enter(): void
+ g:result ..= 'base'
+ enddef
+ endclass
+
+ class Child extends Base
+ def Enter(): void
+ g:result ..= 'child'
+ enddef
+ endclass
+
+ def F(obj: Base)
+ obj.Enter()
+ enddef
+
+ g:result = ''
+ F(Child.new())
+ assert_equal('child', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # method of interface returns a value
+ lines =<< trim END
+ vim9script
+ interface Base
+ def Enter(): string
+ endinterface
+
+ class Child implements Base
+ def Enter(): string
+ g:result ..= 'child'
+ return "/resource"
+ enddef
+ endclass
+
+ def F(obj: Base)
+ var r = obj.Enter()
+ g:result ..= r
+ enddef
+
+ g:result = ''
+ F(Child.new())
+ assert_equal('child/resource', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Base
+ def Enter(): string
+ return null_string
+ enddef
+ endclass
+
+ class Child extends Base
+ def Enter(): string
+ g:result ..= 'child'
+ return "/resource"
+ enddef
+ endclass
+
+ def F(obj: Base)
+ var r = obj.Enter()
+ g:result ..= r
+ enddef
+
+ g:result = ''
+ F(Child.new())
+ assert_equal('child/resource', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+
+
+ # No class that implements the interface.
+ lines =<< trim END
+ vim9script
+
+ interface IWithEE
+ def Enter(): any
+ def Exit(): void
+ endinterface
+
+ def With1(ee: IWithEE, F: func)
+ var r = ee.Enter()
+ enddef
+
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_class_used_as_type()
+ var lines =<< trim END
+ vim9script
+
+ class Point
+ this.x = 0
+ this.y = 0
+ endclass
+
+ var p: Point
+ p = Point.new(2, 33)
+ assert_equal(2, p.x)
+ assert_equal(33, p.y)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ interface HasX
+ this.x: number
+ endinterface
+
+ class Point implements HasX
+ this.x = 0
+ this.y = 0
+ endclass
+
+ var p: Point
+ p = Point.new(2, 33)
+ var hx = p
+ assert_equal(2, hx.x)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class Point
+ this.x = 0
+ this.y = 0
+ endclass
+
+ var p: Point
+ p = 'text'
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object<Point> but got string')
+enddef
+
+def Test_class_extends()
+ var lines =<< trim END
+ vim9script
+ class Base
+ this.one = 1
+ def GetOne(): number
+ return this.one
+ enddef
+ endclass
+ class Child extends Base
+ this.two = 2
+ def GetTotal(): number
+ return this.one + this.two
+ enddef
+ endclass
+ var o = Child.new()
+ assert_equal(1, o.one)
+ assert_equal(2, o.two)
+ assert_equal(1, o.GetOne())
+ assert_equal(3, o.GetTotal())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.one = 1
+ endclass
+ class Child extends Base
+ this.two = 2
+ endclass
+ var o = Child.new(3, 44)
+ assert_equal(3, o.one)
+ assert_equal(44, o.two)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.one = 1
+ endclass
+ class Child extends Base extends Base
+ this.two = 2
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1352: Duplicate "extends"')
+
+ lines =<< trim END
+ vim9script
+ class Child extends BaseClass
+ this.two = 2
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1353: Class name not found: BaseClass')
+
+ lines =<< trim END
+ vim9script
+ var SomeVar = 99
+ class Child extends SomeVar
+ this.two = 2
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1354: Cannot extend SomeVar')
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.name: string
+ def ToString(): string
+ return this.name
+ enddef
+ endclass
+
+ class Child extends Base
+ this.age: number
+ def ToString(): string
+ return super.ToString() .. ': ' .. this.age
+ enddef
+ endclass
+
+ var o = Child.new('John', 42)
+ assert_equal('John: 42', o.ToString())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Child
+ this.age: number
+ def ToString(): number
+ return this.age
+ enddef
+ def ToString(): string
+ return this.age
+ enddef
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1355: Duplicate function: ToString')
+
+ lines =<< trim END
+ vim9script
+ class Child
+ this.age: number
+ def ToString(): string
+ return super .ToString() .. ': ' .. this.age
+ enddef
+ endclass
+ var o = Child.new(42)
+ echo o.ToString()
+ END
+ v9.CheckScriptFailure(lines, 'E1356:')
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.name: string
+ def ToString(): string
+ return this.name
+ enddef
+ endclass
+
+ var age = 42
+ def ToString(): string
+ return super.ToString() .. ': ' .. age
+ enddef
+ echo ToString()
+ END
+ v9.CheckScriptFailure(lines, 'E1357:')
+
+ lines =<< trim END
+ vim9script
+ class Child
+ this.age: number
+ def ToString(): string
+ return super.ToString() .. ': ' .. this.age
+ enddef
+ endclass
+ var o = Child.new(42)
+ echo o.ToString()
+ END
+ v9.CheckScriptFailure(lines, 'E1358:')
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.name: string
+ static def ToString(): string
+ return 'Base class'
+ enddef
+ endclass
+
+ class Child extends Base
+ this.age: number
+ def ToString(): string
+ return Base.ToString() .. ': ' .. this.age
+ enddef
+ endclass
+
+ var o = Child.new('John', 42)
+ assert_equal('Base class: 42', o.ToString())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ class Base
+ this.value = 1
+ def new(init: number)
+ this.value = number + 1
+ enddef
+ endclass
+ class Child extends Base
+ def new()
+ this.new(3)
+ enddef
+ endclass
+ var c = Child.new()
+ END
+ v9.CheckScriptFailure(lines, 'E1325: Method not found on class "Child": new(')
+
+ # base class with more than one object member
+ lines =<< trim END
+ vim9script
+
+ class Result
+ this.success: bool
+ this.value: any = null
+ endclass
+
+ class Success extends Result
+ def new(this.value = v:none)
+ this.success = true
+ enddef
+ endclass
+
+ var v = Success.new('asdf')
+ assert_equal("object of Success {success: true, value: 'asdf'}", string(v))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_using_base_class()
+ var lines =<< trim END
+ vim9script
+
+ class BaseEE
+ def Enter(): any
+ return null
+ enddef
+ def Exit(resource: any): void
+ enddef
+ endclass
+
+ class ChildEE extends BaseEE
+ def Enter(): any
+ return 42
+ enddef
+
+ def Exit(resource: number): void
+ g:result ..= '/exit'
+ enddef
+ endclass
+
+ def With(ee: BaseEE)
+ var r = ee.Enter()
+ try
+ g:result ..= r
+ finally
+ g:result ..= '/finally'
+ ee.Exit(r)
+ endtry
+ enddef
+
+ g:result = ''
+ With(ChildEE.new())
+ assert_equal('42/finally/exit', g:result)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:result
+enddef
+
+
+def Test_class_import()
+ var lines =<< trim END
+ vim9script
+ export class Animal
+ this.kind: string
+ this.name: string
+ endclass
+ END
+ writefile(lines, 'Xanimal.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xanimal.vim' as animal
+
+ var a: animal.Animal
+ a = animal.Animal.new('fish', 'Eric')
+ assert_equal('fish', a.kind)
+ assert_equal('Eric', a.name)
+
+ var b: animal.Animal = animal.Animal.new('cat', 'Garfield')
+ assert_equal('cat', b.kind)
+ assert_equal('Garfield', b.name)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_abstract_class()
+ var lines =<< trim END
+ vim9script
+ abstract class Base
+ this.name: string
+ endclass
+ class Person extends Base
+ this.age: number
+ endclass
+ var p: Base = Person.new('Peter', 42)
+ assert_equal('Peter', p.name)
+ assert_equal(42, p.age)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ abstract class Base
+ this.name: string
+ endclass
+ class Person extends Base
+ this.age: number
+ endclass
+ var p = Base.new('Peter')
+ END
+ v9.CheckScriptFailure(lines, 'E1325: Method not found on class "Base": new(')
+
+ lines =<< trim END
+ abstract class Base
+ this.name: string
+ endclass
+ END
+ v9.CheckScriptFailure(lines, 'E1316:')
+enddef
+
+def Test_closure_in_class()
+ var lines =<< trim END
+ vim9script
+
+ class Foo
+ this.y: list<string> = ['B']
+
+ def new()
+ g:result = filter(['A', 'B'], (_, v) => index(this.y, v) == -1)
+ enddef
+ endclass
+
+ Foo.new()
+ assert_equal(['A'], g:result)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_defer_with_object()
+ var lines =<< trim END
+ vim9script
+
+ class CWithEE
+ def Enter()
+ g:result ..= "entered/"
+ enddef
+ def Exit()
+ g:result ..= "exited"
+ enddef
+ endclass
+
+ def With(ee: CWithEE, F: func)
+ ee.Enter()
+ defer ee.Exit()
+ F()
+ enddef
+
+ g:result = ''
+ var obj = CWithEE.new()
+ obj->With(() => {
+ g:result ..= "called/"
+ })
+ assert_equal('entered/called/exited', g:result)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:result
+
+ lines =<< trim END
+ vim9script
+
+ class BaseWithEE
+ def Enter()
+ g:result ..= "entered-base/"
+ enddef
+ def Exit()
+ g:result ..= "exited-base"
+ enddef
+ endclass
+
+ class CWithEE extends BaseWithEE
+ def Enter()
+ g:result ..= "entered-child/"
+ enddef
+ def Exit()
+ g:result ..= "exited-child"
+ enddef
+ endclass
+
+ def With(ee: BaseWithEE, F: func)
+ ee.Enter()
+ defer ee.Exit()
+ F()
+ enddef
+
+ g:result = ''
+ var obj = CWithEE.new()
+ obj->With(() => {
+ g:result ..= "called/"
+ })
+ assert_equal('entered-child/called/exited-child', g:result)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:result
+enddef
+
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim
new file mode 100644
index 0000000..0fe6e7c
--- /dev/null
+++ b/src/testdir/test_vim9_cmd.vim
@@ -0,0 +1,2040 @@
+" Test commands that are not compiled in a :def function
+
+source check.vim
+import './vim9.vim' as v9
+source term_util.vim
+source view_util.vim
+
+def Test_vim9cmd()
+ var lines =<< trim END
+ vim9cmd var x = 123
+ let s:y = 'yes'
+ vim9c assert_equal(123, x)
+ vim9cm assert_equal('yes', y)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ assert_fails('vim9cmd', 'E1164:')
+ assert_fails('legacy', 'E1234:')
+ assert_fails('vim9cmd echo "con" . "cat"', 'E15:')
+
+ lines =<< trim END
+ let str = 'con'
+ vim9cmd str .= 'cat'
+ END
+ v9.CheckScriptFailure(lines, 'E492:')
+
+ lines =<< trim END
+ vim9script
+ legacy echo "con" . "cat"
+ legacy let str = 'con'
+ legacy let str .= 'cat'
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Foo()
+ g:found_bar = "bar"
+ enddef
+ nmap ,; :vim9cmd <SID>Foo()<CR>
+ END
+ v9.CheckScriptSuccess(lines)
+
+ feedkeys(',;', 'xt')
+ assert_equal("bar", g:found_bar)
+ nunmap ,;
+ unlet g:found_bar
+
+ lines =<< trim END
+ vim9script
+ legacy echo 1'000
+ END
+ v9.CheckScriptFailure(lines, 'E115:')
+
+ lines =<< trim END
+ vim9script
+ echo .10
+ END
+ v9.CheckScriptSuccess(lines)
+ lines =<< trim END
+ vim9cmd echo .10
+ END
+ v9.CheckScriptSuccess(lines)
+ lines =<< trim END
+ vim9script
+ legacy echo .10
+ END
+ v9.CheckScriptFailure(lines, 'E15:')
+
+ echo v:version
+ assert_fails('vim9cmd echo version', 'E121:')
+ lines =<< trim END
+ vim9script
+ echo version
+ END
+ v9.CheckScriptFailure(lines, 'E121:')
+ lines =<< trim END
+ vim9script
+ legacy echo version
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_defcompile_fails()
+ assert_fails('defcompile NotExists', 'E1061:')
+ assert_fails('defcompile debug debug Test_defcompile_fails', 'E488:')
+ assert_fails('defcompile profile profile Test_defcompile_fails', 'E488:')
+enddef
+
+defcompile Test_defcompile_fails
+defcompile debug Test_defcompile_fails
+defcompile profile Test_defcompile_fails
+
+def Test_cmdmod_execute()
+ # "legacy" applies not only to the "exe" argument but also to the commands
+ var lines =<< trim END
+ vim9script
+
+ b:undo = 'let g:undone = 1 | let g:undtwo = 2'
+ legacy exe b:undo
+ assert_equal(1, g:undone)
+ assert_equal(2, g:undtwo)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # same for "vim9cmd" modifier
+ lines =<< trim END
+ let b:undo = 'g:undone = 11 | g:undtwo = 22'
+ vim9cmd exe b:undo
+ call assert_equal(11, g:undone)
+ call assert_equal(22, g:undtwo)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet b:undo
+ unlet g:undone
+ unlet g:undtwo
+
+ # "legacy" does not apply to a loaded script
+ lines =<< trim END
+ vim9script
+ export var exported = 'x'
+ END
+ writefile(lines, 'Xvim9import.vim', 'D')
+ lines =<< trim END
+ legacy exe "import './Xvim9import.vim'"
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # "legacy" does not apply to a called function
+ lines =<< trim END
+ vim9script
+
+ def g:TheFunc()
+ if exists('something')
+ echo 'yes'
+ endif
+ enddef
+ legacy exe 'call g:TheFunc()'
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:TheFunc
+
+ # vim9cmd execute(cmd) executes code in vim9 script context
+ lines =<< trim END
+ vim9cmd execute("g:vim9executetest = 'bar'")
+ call assert_equal('bar', g:vim9executetest)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:vim9executetest
+
+ lines =<< trim END
+ vim9cmd execute(["g:vim9executetest1 = 'baz'", "g:vim9executetest2 = 'foo'"])
+ call assert_equal('baz', g:vim9executetest1)
+ call assert_equal('foo', g:vim9executetest2)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:vim9executetest1
+ unlet g:vim9executetest2
+
+ # legacy call execute(cmd) executes code in vim script context
+ lines =<< trim END
+ vim9script
+ legacy call execute("let g:vim9executetest = 'bar'")
+ assert_equal('bar', g:vim9executetest)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:vim9executetest
+
+ lines =<< trim END
+ vim9script
+ legacy call execute(["let g:vim9executetest1 = 'baz'", "let g:vim9executetest2 = 'foo'"])
+ assert_equal('baz', g:vim9executetest1)
+ assert_equal('foo', g:vim9executetest2)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:vim9executetest1
+ unlet g:vim9executetest2
+enddef
+
+def Test_edit_wildcards()
+ var filename = 'Xtest'
+ edit `=filename`
+ assert_equal('Xtest', bufname())
+
+ var filenr = 123
+ edit Xtest`=filenr`
+ assert_equal('Xtest123', bufname())
+
+ filenr = 77
+ edit `=filename``=filenr`
+ assert_equal('Xtest77', bufname())
+
+ edit X`=filename`xx`=filenr`yy
+ assert_equal('XXtestxx77yy', bufname())
+
+ v9.CheckDefFailure(['edit `=xxx`'], 'E1001:')
+ v9.CheckDefFailure(['edit `="foo"'], 'E1083:')
+
+ var files = ['file 1', 'file%2', 'file# 3']
+ args `=files`
+ assert_equal(files, argv())
+
+ filename = 'Xwindo'
+ windo edit `=filename`
+ assert_equal('Xwindo', bufname())
+
+ filename = 'Xtabdo'
+ tabdo edit `=filename`
+ assert_equal('Xtabdo', bufname())
+
+ filename = 'Xargdo'
+ argdo edit `=filename`
+ assert_equal('Xargdo', bufname())
+
+ :%bwipe!
+ filename = 'Xbufdo'
+ bufdo file `=filename`
+ assert_equal('Xbufdo', bufname())
+enddef
+
+def Test_expand_alternate_file()
+ var lines =<< trim END
+ edit Xfileone
+ var bone = bufnr()
+ edit Xfiletwo
+ var btwo = bufnr()
+ edit Xfilethree
+ var bthree = bufnr()
+
+ edit #
+ assert_equal(bthree, bufnr())
+ edit %%
+ assert_equal(btwo, bufnr())
+ edit %% # comment
+ assert_equal(bthree, bufnr())
+ edit %%yy
+ assert_equal('Xfiletwoyy', bufname())
+
+ exe "edit %%" .. bone
+ assert_equal(bone, bufnr())
+ exe "edit %%" .. btwo .. "xx"
+ assert_equal('Xfiletwoxx', bufname())
+
+ next Xfileone Xfiletwo Xfilethree
+ assert_equal('Xfileone', argv(0))
+ assert_equal('Xfiletwo', argv(1))
+ assert_equal('Xfilethree', argv(2))
+ next %%%zz
+ assert_equal('Xfileone', argv(0))
+ assert_equal('Xfiletwo', argv(1))
+ assert_equal('Xfilethreezz', argv(2))
+
+ v:oldfiles = ['Xonefile', 'Xtwofile']
+ edit %%<1
+ assert_equal('Xonefile', bufname())
+ edit %%<2
+ assert_equal('Xtwofile', bufname())
+ assert_fails('edit %%<3', 'E684:')
+
+ edit Xfileone.vim
+ edit Xfiletwo
+ edit %%:r
+ assert_equal('Xfileone', bufname())
+
+ assert_false(bufexists('altfoo'))
+ edit altfoo
+ edit bar
+ assert_true(bufexists('altfoo'))
+ assert_true(buflisted('altfoo'))
+ bdel %%
+ assert_true(bufexists('altfoo'))
+ assert_false(buflisted('altfoo'))
+ bwipe! altfoo
+ bwipe! bar
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_global_backtick_expansion()
+ var name = 'xxx'
+ new
+ setline(1, ['one', 'two', 'three'])
+ set nomod
+ g/two/edit `=name`
+ assert_equal('xxx', bufname())
+ bwipe!
+
+ new
+ setline(1, ['one', 'two', 'three'])
+ g/two/s/^/`=name`/
+ assert_equal('`=name`two', getline(2))
+ bwipe!
+enddef
+
+def Test_folddo_backtick_expansion()
+ new
+ var name = 'xxx'
+ folddoopen edit `=name`
+ assert_equal('xxx', bufname())
+ bwipe!
+
+ new
+ setline(1, ['one', 'two'])
+ set nomodified
+ :1,2fold
+ foldclose
+ folddoclose edit `=name`
+ assert_equal('xxx', bufname())
+ bwipe!
+
+ var lines =<< trim END
+ g:val = 'value'
+ def Test()
+ folddoopen echo `=g:val`
+ enddef
+ call Test()
+ END
+ v9.CheckScriptFailure(lines, 'E15: Invalid expression: "`=g:val`"')
+enddef
+
+def Test_hardcopy_wildcards()
+ CheckUnix
+ CheckFeature postscript
+
+ var outfile = 'print'
+ hardcopy > X`=outfile`.ps
+ assert_true(filereadable('Xprint.ps'))
+
+ delete('Xprint.ps')
+enddef
+
+def Test_syn_include_wildcards()
+ writefile(['syn keyword Found found'], 'Xthemine.vim', 'D')
+ var save_rtp = &rtp
+ &rtp = '.'
+
+ var fname = 'mine'
+ syn include @Group Xthe`=fname`.vim
+ assert_match('Found.* contained found', execute('syn list Found'))
+
+ &rtp = save_rtp
+enddef
+
+def Test_echo_linebreak()
+ var lines =<< trim END
+ vim9script
+ redir @a
+ echo 'one'
+ .. 'two'
+ redir END
+ assert_equal("\nonetwo", @a)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ redir @a
+ echo 11 +
+ 77
+ - 22
+ redir END
+ assert_equal("\n66", @a)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_condition_types()
+ var lines =<< trim END
+ if 'text'
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1135:', 1)
+
+ lines =<< trim END
+ if [1]
+ endif
+ END
+ v9.CheckDefFailure(lines, 'E1012:', 1)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E745:', 2)
+
+ lines =<< trim END
+ g:cond = 'text'
+ if g:cond
+ endif
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1135:', 2)
+
+ lines =<< trim END
+ g:cond = 0
+ if g:cond
+ elseif 'text'
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1135:', 3)
+
+ lines =<< trim END
+ g:cond = 0
+ if g:cond
+ elseif 'text' garbage
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E488:', 3)
+
+ lines =<< trim END
+ g:cond = 0
+ if g:cond
+ elseif [1]
+ endif
+ END
+ v9.CheckDefFailure(lines, 'E1012:', 3)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E745:', 4)
+
+ lines =<< trim END
+ g:cond = 'text'
+ if 0
+ elseif g:cond
+ endif
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1135:', 3)
+
+ lines =<< trim END
+ while 'text'
+ endwhile
+ END
+ v9.CheckDefFailure(lines, 'E1012:', 1)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E1135:', 2)
+
+ lines =<< trim END
+ while [1]
+ endwhile
+ END
+ v9.CheckDefFailure(lines, 'E1012:', 1)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E745:', 2)
+
+ lines =<< trim END
+ g:cond = 'text'
+ while g:cond
+ endwhile
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1135:', 2)
+enddef
+
+def Test_if_linebreak()
+ var lines =<< trim END
+ vim9script
+ if 1 &&
+ true
+ || 1
+ g:res = 42
+ endif
+ assert_equal(42, g:res)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:res
+
+ lines =<< trim END
+ vim9script
+ if 1 &&
+ 0
+ g:res = 0
+ elseif 0 ||
+ 0
+ || 1
+ g:res = 12
+ endif
+ assert_equal(12, g:res)
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:res
+enddef
+
+def Test_while_linebreak()
+ var lines =<< trim END
+ vim9script
+ var nr = 0
+ while nr <
+ 10 + 3
+ nr = nr
+ + 4
+ endwhile
+ assert_equal(16, nr)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var nr = 0
+ while nr
+ <
+ 10
+ +
+ 3
+ nr = nr
+ +
+ 4
+ endwhile
+ assert_equal(16, nr)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_for_linebreak()
+ var lines =<< trim END
+ vim9script
+ var nr = 0
+ for x
+ in
+ [1, 2, 3, 4]
+ nr = nr + x
+ endfor
+ assert_equal(10, nr)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ var nr = 0
+ for x
+ in
+ [1, 2,
+ 3, 4
+ ]
+ nr = nr
+ +
+ x
+ endfor
+ assert_equal(10, nr)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:MethodAfterLinebreak(arg: string)
+ arg
+ ->setline(1)
+enddef
+
+def Test_method_call_linebreak()
+ var lines =<< trim END
+ vim9script
+ var res = []
+ func RetArg(
+ arg
+ )
+ let s:res = a:arg
+ endfunc
+ [1,
+ 2,
+ 3]->RetArg()
+ assert_equal([1, 2, 3], res)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ new
+ var name = [1, 2]
+ name
+ ->copy()
+ ->setline(1)
+ assert_equal(['1', '2'], getline(1, 2))
+ bwipe!
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ new
+ def Foo(): string
+ return 'the text'
+ enddef
+ def Bar(F: func): string
+ return F()
+ enddef
+ def Test()
+ Foo ->Bar()
+ ->setline(1)
+ enddef
+ Test()
+ assert_equal('the text', getline(1))
+ bwipe!
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ new
+ g:shortlist
+ ->copy()
+ ->setline(1)
+ assert_equal(['1', '2'], getline(1, 2))
+ bwipe!
+ END
+ g:shortlist = [1, 2]
+ v9.CheckDefAndScriptSuccess(lines)
+ unlet g:shortlist
+
+ new
+ MethodAfterLinebreak('foobar')
+ assert_equal('foobar', getline(1))
+ bwipe!
+
+ lines =<< trim END
+ vim9script
+ def Foo(): string
+ return '# some text'
+ enddef
+
+ def Bar(F: func): string
+ return F()
+ enddef
+
+ Foo->Bar()
+ ->setline(1)
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal('# some text', getline(1))
+ bwipe!
+enddef
+
+def Test_method_call_whitespace()
+ var lines =<< trim END
+ new
+ var yank = 'text'
+ yank->setline(1)
+ yank ->setline(2)
+ yank-> setline(3)
+ yank -> setline(4)
+ assert_equal(['text', 'text', 'text', 'text'], getline(1, 4))
+ bwipe!
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_method_and_user_command()
+ var lines =<< trim END
+ vim9script
+ def Cmd()
+ g:didFunc = 1
+ enddef
+ command Cmd g:didCmd = 1
+ Cmd
+ assert_equal(1, g:didCmd)
+ Cmd()
+ assert_equal(1, g:didFunc)
+ unlet g:didFunc
+ unlet g:didCmd
+
+ def InDefFunc()
+ Cmd
+ assert_equal(1, g:didCmd)
+ Cmd()
+ assert_equal(1, g:didFunc)
+ unlet g:didFunc
+ unlet g:didCmd
+ enddef
+ InDefFunc()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_option_use_linebreak()
+ var lines =<< trim END
+ new
+ &matchpairs = '(:)'
+ &matchpairs->setline(1)
+ &matchpairs = '[:]'
+ &matchpairs ->setline(2)
+ &matchpairs = '{:}'
+ &matchpairs
+ ->setline(3)
+ assert_equal(['(:)', '[:]', '{:}'], getline(1, '$'))
+ bwipe!
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_use_register()
+ var lines =<< trim END
+ new
+ @a = 'one'
+ @a->setline(1)
+ @b = 'two'
+ @b ->setline(2)
+ @c = 'three'
+ @c
+ ->setline(3)
+ assert_equal(['one', 'two', 'three'], getline(1, '$'))
+ bwipe!
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ @a = 'echo "text"'
+ @a
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+
+ lines =<< trim END
+ @a = 'echo "text"'
+ @a
+
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+
+ lines =<< trim END
+ @a = 'echo "text"'
+ @a
+ # comment
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+
+ lines =<< trim END
+ @/ = 'pattern'
+ @/
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+
+ lines =<< trim END
+ &opfunc = 'nothing'
+ &opfunc
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+ &opfunc = ''
+
+ lines =<< trim END
+ &l:showbreak = 'nothing'
+ &l:showbreak
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+ &l:showbreak = ''
+
+ lines =<< trim END
+ &g:showbreak = 'nothing'
+ &g:showbreak
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+ &g:showbreak = ''
+
+ lines =<< trim END
+ $SomeEnv = 'value'
+ $SomeEnv
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+ $SomeEnv = ''
+
+ lines =<< trim END
+ eval 'value'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 1)
+
+ lines =<< trim END
+ eval "value"
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 1)
+enddef
+
+def Test_environment_use_linebreak()
+ var lines =<< trim END
+ new
+ $TESTENV = 'one'
+ $TESTENV->setline(1)
+ $TESTENV = 'two'
+ $TESTENV ->setline(2)
+ $TESTENV = 'three'
+ $TESTENV
+ ->setline(3)
+ assert_equal(['one', 'two', 'three'], getline(1, '$'))
+ bwipe!
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_skipped_expr_linebreak()
+ if 0
+ var x = []
+ ->map(() => 0)
+ endif
+enddef
+
+def Test_dict_member()
+ var test: dict<list<number>> = {data: [3, 1, 2]}
+ test.data->sort()
+ assert_equal({data: [1, 2, 3]}, test)
+ test.data
+ ->reverse()
+ assert_equal({data: [3, 2, 1]}, test)
+
+ var lines =<< trim END
+ vim9script
+ var test: dict<list<number>> = {data: [3, 1, 2]}
+ test.data->sort()
+ assert_equal({data: [1, 2, 3]}, test)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_bar_after_command()
+ def RedrawAndEcho()
+ var x = 'did redraw'
+ redraw | echo x
+ enddef
+ RedrawAndEcho()
+ assert_match('did redraw', g:Screenline(&lines))
+
+ def CallAndEcho()
+ var x = 'did redraw'
+ reg_executing() | echo x
+ enddef
+ CallAndEcho()
+ assert_match('did redraw', g:Screenline(&lines))
+
+ if has('unix')
+ # bar in filter write command does not start new command
+ def WriteToShell()
+ new
+ setline(1, 'some text')
+ w !cat | cat > Xoutfile
+ bwipe!
+ enddef
+ WriteToShell()
+ assert_equal(['some text'], readfile('Xoutfile'))
+ delete('Xoutfile')
+
+ # bar in filter read command does not start new command
+ def ReadFromShell()
+ new
+ r! echo hello there | cat > Xoutfile
+ r !echo again | cat >> Xoutfile
+ bwipe!
+ enddef
+ ReadFromShell()
+ assert_equal(['hello there', 'again'], readfile('Xoutfile'))
+ delete('Xoutfile')
+ endif
+enddef
+
+def Test_filter_is_not_modifier()
+ var tags = [{a: 1, b: 2}, {x: 3, y: 4}]
+ filter(tags, ( _, v) => has_key(v, 'x') ? 1 : 0 )
+ assert_equal([{x: 3, y: 4}], tags)
+enddef
+
+def Test_command_modifier_filter()
+ var lines =<< trim END
+ final expected = "\nType Name Content\n c \"c piyo"
+ @a = 'hoge'
+ @b = 'fuga'
+ @c = 'piyo'
+
+ assert_equal(execute('filter /piyo/ registers abc'), expected)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # also do this compiled
+ lines =<< trim END
+ @a = 'very specific z3d37dh234 string'
+ filter z3d37dh234 registers
+ assert_match('very specific z3d37dh234 string', g:Screenline(&lines))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ edit foobar
+ redir => g:filter_out
+ filter #foobar# ls
+ redir END
+ assert_match('"foobar"', g:filter_out)
+ unlet g:filter_out
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_win_command_modifiers()
+ assert_equal(1, winnr('$'))
+
+ set splitright
+ vsplit
+ assert_equal(2, winnr())
+ close
+ aboveleft vsplit
+ assert_equal(1, winnr())
+ close
+ set splitright&
+
+ vsplit
+ assert_equal(1, winnr())
+ close
+ belowright vsplit
+ assert_equal(2, winnr())
+ close
+ rightbelow vsplit
+ assert_equal(2, winnr())
+ close
+
+ if has('browse')
+ browse set
+ assert_equal('option-window', expand('%'))
+ close
+ endif
+
+ vsplit
+ botright split
+ assert_equal(3, winnr())
+ assert_equal(&columns, winwidth(0))
+ close
+ close
+
+ vsplit
+ topleft split
+ assert_equal(1, winnr())
+ assert_equal(&columns, winwidth(0))
+ close
+ close
+
+ gettabinfo()->len()->assert_equal(1)
+ tab split
+ gettabinfo()->len()->assert_equal(2)
+ tabclose
+
+ vertical new
+ assert_inrange(&columns / 2 - 2, &columns / 2 + 1, winwidth(0))
+ close
+enddef
+
+func Test_command_modifier_confirm()
+ CheckNotGui
+ CheckRunVimInTerminal
+
+ " Test for saving all the modified buffers
+ let lines =<< trim END
+ call setline(1, 'changed')
+ def Getout()
+ confirm write Xcmodfile
+ enddef
+ END
+ call writefile(lines, 'Xconfirmscript', 'D')
+ call writefile(['empty'], 'Xcmodfile')
+ let buf = RunVimInTerminal('-S Xconfirmscript', {'rows': 8})
+ call term_sendkeys(buf, ":call Getout()\n")
+ call WaitForAssert({-> assert_match('(Y)es, \[N\]o: ', term_getline(buf, 8))}, 1000)
+ call term_sendkeys(buf, "y")
+ call WaitForAssert({-> assert_match('(Y)es, \[N\]o: ', term_getline(buf, 8))}, 1000)
+ call term_sendkeys(buf, "\<CR>")
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+
+ call assert_equal(['changed'], readfile('Xcmodfile'))
+ call delete('Xcmodfile')
+ call delete('.Xcmodfile.swp') " in case Vim was killed
+endfunc
+
+def Test_command_modifiers_keep()
+ if has('unix')
+ def DoTest(addRflag: bool, keepMarks: bool, hasMarks: bool)
+ new
+ setline(1, ['one', 'two', 'three'])
+ normal 1Gma
+ normal 2Gmb
+ normal 3Gmc
+ if addRflag
+ set cpo+=R
+ else
+ set cpo-=R
+ endif
+ if keepMarks
+ keepmarks :%!cat
+ else
+ :%!cat
+ endif
+ if hasMarks
+ assert_equal(1, line("'a"))
+ assert_equal(2, line("'b"))
+ assert_equal(3, line("'c"))
+ else
+ assert_equal(0, line("'a"))
+ assert_equal(0, line("'b"))
+ assert_equal(0, line("'c"))
+ endif
+ quit!
+ enddef
+ DoTest(false, false, true)
+ DoTest(true, false, false)
+ DoTest(false, true, true)
+ DoTest(true, true, true)
+ set cpo&vim
+
+ new
+ setline(1, ['one', 'two', 'three', 'four'])
+ assert_equal(4, line("$"))
+ normal 1Gma
+ normal 2Gmb
+ normal 3Gmc
+ lockmarks :1,2!wc
+ # line is deleted, marks don't move
+ assert_equal(3, line("$"))
+ assert_equal('four', getline(3))
+ assert_equal(1, line("'a"))
+ assert_equal(2, line("'b"))
+ assert_equal(3, line("'c"))
+ quit!
+ endif
+
+ edit Xone
+ edit Xtwo
+ assert_equal('Xone', expand('#'))
+ keepalt edit Xthree
+ assert_equal('Xone', expand('#'))
+
+ normal /a*b*
+ assert_equal('a*b*', histget("search"))
+ keeppatterns normal /c*d*
+ assert_equal('a*b*', histget("search"))
+
+ new
+ setline(1, range(10))
+ :10
+ normal gg
+ assert_equal(10, getpos("''")[1])
+ keepjumps normal 5G
+ assert_equal(10, getpos("''")[1])
+ quit!
+enddef
+
+def Test_bar_line_continuation()
+ var lines =<< trim END
+ au BufNewFile XveryNewFile g:readFile = 1
+ | g:readExtra = 2
+ g:readFile = 0
+ g:readExtra = 0
+ edit XveryNewFile
+ assert_equal(1, g:readFile)
+ assert_equal(2, g:readExtra)
+ bwipe!
+ au! BufNewFile
+
+ au BufNewFile XveryNewFile g:readFile = 1
+ | g:readExtra = 2
+ | g:readMore = 3
+ g:readFile = 0
+ g:readExtra = 0
+ g:readMore = 0
+ edit XveryNewFile
+ assert_equal(1, g:readFile)
+ assert_equal(2, g:readExtra)
+ assert_equal(3, g:readMore)
+ bwipe!
+ au! BufNewFile
+ unlet g:readFile
+ unlet g:readExtra
+ unlet g:readMore
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_command_modifier_other()
+ new Xsomefile
+ setline(1, 'changed')
+ var buf = bufnr()
+ hide edit Xotherfile
+ var info = getbufinfo(buf)
+ assert_equal(1, info[0].hidden)
+ assert_equal(1, info[0].changed)
+ edit Xsomefile
+ bwipe!
+
+ au BufNewFile Xcmofile g:readFile = 1
+ g:readFile = 0
+ edit Xcmofile
+ assert_equal(1, g:readFile)
+ bwipe!
+ g:readFile = 0
+ noautocmd edit Xcmofile
+ assert_equal(0, g:readFile)
+ au! BufNewFile
+ unlet g:readFile
+
+ noswapfile edit XnoSwap
+ assert_equal(false, &l:swapfile)
+ bwipe!
+
+ var caught = false
+ try
+ sandbox !ls
+ catch /E48:/
+ caught = true
+ endtry
+ assert_true(caught)
+
+ :8verbose g:verbose_now = &verbose
+ assert_equal(8, g:verbose_now)
+ unlet g:verbose_now
+enddef
+
+def s:EchoHere()
+ echomsg 'here'
+enddef
+def s:EchoThere()
+ unsilent echomsg 'there'
+enddef
+
+def Test_modifier_silent_unsilent()
+ echomsg 'last one'
+ silent echomsg "text"
+ assert_equal("\nlast one", execute(':1messages'))
+
+ silent! echoerr "error"
+
+ echomsg 'last one'
+ silent EchoHere()
+ assert_equal("\nlast one", execute(':1messages'))
+
+ silent EchoThere()
+ assert_equal("\nthere", execute(':1messages'))
+
+ try
+ silent eval [][0]
+ catch
+ echomsg "caught"
+ endtry
+ assert_equal("\ncaught", execute(':1messages'))
+
+ var lines =<< trim END
+ vim9script
+ set history=11
+ silent! while 0
+ set history=22
+ silent! endwhile
+ assert_equal(11, &history)
+ set history&
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_range_after_command_modifier()
+ v9.CheckScriptFailure(['vim9script', 'silent keepjump 1d _'], 'E1050: Colon required before a range: 1d _', 2)
+ new
+ setline(1, 'xxx')
+ v9.CheckScriptSuccess(['vim9script', 'silent keepjump :1d _'])
+ assert_equal('', getline(1))
+ bwipe!
+
+ var lines =<< trim END
+ legacy /pat/
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E486: Pattern not found: pat')
+enddef
+
+def Test_silent_pattern()
+ new
+ silent! :/pat/put _
+ bwipe!
+enddef
+
+def Test_useless_command_modifier()
+ g:maybe = true
+ var lines =<< trim END
+ if g:maybe
+ silent endif
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1176:', 2)
+
+ lines =<< trim END
+ for i in [0]
+ silent endfor
+ END
+ v9.CheckDefFailure(lines, 'E1176:', 2)
+ v9.CheckScriptSuccess(['vim9script'] + lines)
+
+ lines =<< trim END
+ while g:maybe
+ silent endwhile
+ END
+ v9.CheckDefFailure(lines, 'E1176:', 2)
+ g:maybe = false
+ v9.CheckScriptSuccess(['vim9script'] + lines)
+
+ lines =<< trim END
+ silent try
+ finally
+ endtry
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1176:', 1)
+
+ lines =<< trim END
+ try
+ silent catch
+ endtry
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1176:', 2)
+
+ lines =<< trim END
+ try
+ silent finally
+ endtry
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1176:', 2)
+
+ lines =<< trim END
+ try
+ finally
+ silent endtry
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1176:', 3)
+
+ lines =<< trim END
+ leftabove
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1082:', 1)
+
+ lines =<< trim END
+ leftabove # comment
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1082:', 1)
+enddef
+
+def Test_eval_command()
+ var from = 3
+ var to = 5
+ g:val = 111
+ def Increment(nrs: list<number>)
+ for nr in nrs
+ g:val += nr
+ endfor
+ enddef
+ eval range(from, to)
+ ->Increment()
+ assert_equal(111 + 3 + 4 + 5, g:val)
+ unlet g:val
+
+ var lines =<< trim END
+ vim9script
+ g:caught = 'no'
+ try
+ eval 123 || 0
+ catch
+ g:caught = 'yes'
+ endtry
+ assert_equal('yes', g:caught)
+ unlet g:caught
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_map_command()
+ var lines =<< trim END
+ nnoremap <F3> :echo 'hit F3 #'<CR>
+ assert_equal(":echo 'hit F3 #'<CR>", maparg("<F3>", "n"))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # backslash before bar is not removed
+ lines =<< trim END
+ vim9script
+
+ def Init()
+ noremap <buffer> <F5> <ScriptCmd>MyFunc('a') \| MyFunc('b')<CR>
+ enddef
+ Init()
+ unmap <buffer> <F5>
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_normal_command()
+ new
+ setline(1, 'doesnotexist')
+ var caught = 0
+ try
+ exe "norm! \<C-]>"
+ catch /E433/
+ caught = 2
+ endtry
+ assert_equal(2, caught)
+
+ try
+ exe "norm! 3\<C-]>"
+ catch /E433/
+ caught = 3
+ endtry
+ assert_equal(3, caught)
+ bwipe!
+enddef
+
+def Test_put_command()
+ new
+ @p = 'ppp'
+ put p
+ assert_equal('ppp', getline(2))
+
+ put ='below'
+ assert_equal('below', getline(3))
+ put! ='above'
+ assert_equal('above', getline(3))
+ assert_equal('below', getline(4))
+
+ :2put =['a', 'b', 'c']
+ assert_equal(['ppp', 'a', 'b', 'c', 'above'], getline(2, 6))
+
+ :0put = 'first'
+ assert_equal('first', getline(1))
+ :1put! ='first again'
+ assert_equal('first again', getline(1))
+
+ # compute range at runtime
+ :%del
+ setline(1, range(1, 8))
+ @a = 'aaa'
+ :$-2put a
+ assert_equal('aaa', getline(7))
+
+ setline(1, range(1, 8))
+ :2
+ :+2put! a
+ assert_equal('aaa', getline(4))
+
+ []->mapnew(() => 0)
+ :$put ='end'
+ assert_equal('end', getline('$'))
+
+ bwipe!
+
+ v9.CheckDefFailure(['put =xxx'], 'E1001:')
+enddef
+
+def Test_put_with_linebreak()
+ new
+ var lines =<< trim END
+ vim9script
+ pu =split('abc', '\zs')
+ ->join()
+ END
+ v9.CheckScriptSuccess(lines)
+ getline(2)->assert_equal('a b c')
+ bwipe!
+enddef
+
+def Test_command_star_range()
+ new
+ setline(1, ['xxx foo xxx', 'xxx bar xxx', 'xxx foo xx bar'])
+ setpos("'<", [0, 1, 0, 0])
+ setpos("'>", [0, 3, 0, 0])
+ :*s/\(foo\|bar\)/baz/g
+ getline(1, 3)->assert_equal(['xxx baz xxx', 'xxx baz xxx', 'xxx baz xx baz'])
+
+ bwipe!
+enddef
+
+def Test_f_args()
+ var lines =<< trim END
+ vim9script
+
+ func SaveCmdArgs(...)
+ let g:args = a:000
+ endfunc
+
+ command -nargs=* TestFArgs call SaveCmdArgs(<f-args>)
+
+ TestFArgs
+ assert_equal([], g:args)
+
+ TestFArgs one two three
+ assert_equal(['one', 'two', 'three'], g:args)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_user_command_comment()
+ command -nargs=1 Comd echom <q-args>
+
+ var lines =<< trim END
+ vim9script
+ Comd # comment
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ Comd# comment
+ END
+ v9.CheckScriptFailure(lines, 'E1144:')
+ delcommand Comd
+
+ lines =<< trim END
+ vim9script
+ command Foo echo 'Foo'
+ Foo3Bar
+ END
+ v9.CheckScriptFailure(lines, 'E1144: Command "Foo" is not followed by white space: Foo3Bar')
+
+ delcommand Foo
+enddef
+
+def Test_star_command()
+ var lines =<< trim END
+ vim9script
+ @s = 'g:success = 8'
+ set cpo+=*
+ exe '*s'
+ assert_equal(8, g:success)
+ unlet g:success
+ set cpo-=*
+ assert_fails("exe '*s'", 'E1050:')
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_cmd_argument_without_colon()
+ new Xawcfile
+ setline(1, ['a', 'b', 'c', 'd'])
+ write
+ edit +3 %
+ assert_equal(3, getcurpos()[1])
+ edit +/a %
+ assert_equal(1, getcurpos()[1])
+ bwipe
+ delete('Xawcfile')
+enddef
+
+def Test_ambiguous_user_cmd()
+ command Cmd1 eval 0
+ command Cmd2 eval 0
+ var lines =<< trim END
+ Cmd
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E464:', 1)
+ delcommand Cmd1
+ delcommand Cmd2
+enddef
+
+def Test_command_not_recognized()
+ var lines =<< trim END
+ d.key = 'asdf'
+ END
+ v9.CheckDefFailure(lines, 'E1089: Unknown variable: d', 1)
+
+ lines =<< trim END
+ d['key'] = 'asdf'
+ END
+ v9.CheckDefFailure(lines, 'E1089: Unknown variable: d', 1)
+
+ lines =<< trim END
+ if 0
+ d.key = 'asdf'
+ endif
+ END
+ v9.CheckDefSuccess(lines)
+enddef
+
+def Test_magic_not_used()
+ new
+ for cmd in ['set magic', 'set nomagic']
+ exe cmd
+ setline(1, 'aaa')
+ s/.../bbb/
+ assert_equal('bbb', getline(1))
+ endfor
+
+ set magic
+ setline(1, 'aaa')
+ assert_fails('s/.\M../bbb/', 'E486:')
+ assert_fails('snomagic/.../bbb/', 'E486:')
+ assert_equal('aaa', getline(1))
+
+ bwipe!
+enddef
+
+def Test_gdefault_not_used()
+ new
+ for cmd in ['set gdefault', 'set nogdefault']
+ exe cmd
+ setline(1, 'aaa')
+ s/./b/
+ assert_equal('baa', getline(1))
+ endfor
+
+ set nogdefault
+ bwipe!
+enddef
+
+def s:SomeComplFunc(findstart: number, base: string): any
+ if findstart
+ return 0
+ else
+ return ['aaa', 'bbb']
+ endif
+enddef
+
+def Test_insert_complete()
+ # this was running into an error with the matchparen hack
+ new
+ set completefunc=SomeComplFunc
+ feedkeys("i\<c-x>\<c-u>\<Esc>", 'ntx')
+ assert_equal('aaa', getline(1))
+
+ set completefunc=
+ bwipe!
+enddef
+
+def Test_wincmd()
+ split
+ var id1 = win_getid()
+ if true
+ try | wincmd w | catch | endtry
+ endif
+ assert_notequal(id1, win_getid())
+ close
+
+ split
+ var id = win_getid()
+ split
+ :2wincmd o
+ assert_equal(id, win_getid())
+ only
+
+ split
+ split
+ assert_equal(3, winnr('$'))
+ :2wincmd c
+ assert_equal(2, winnr('$'))
+ only
+
+ split
+ split
+ assert_equal(3, winnr('$'))
+ :2wincmd q
+ assert_equal(2, winnr('$'))
+ only
+enddef
+
+def Test_windo_missing_endif()
+ var lines =<< trim END
+ windo if 1
+ END
+ v9.CheckDefExecFailure(lines, 'E171:', 1)
+enddef
+
+let s:theList = [1, 2, 3]
+
+def Test_lockvar()
+ s:theList[1] = 22
+ assert_equal([1, 22, 3], s:theList)
+ lockvar s:theList
+ assert_fails('theList[1] = 77', 'E741:')
+ unlockvar s:theList
+ s:theList[1] = 44
+ assert_equal([1, 44, 3], s:theList)
+
+ if 0
+ lockvar whatever
+ endif
+
+ g:lockme = [1, 2, 3]
+ lockvar 1 g:lockme
+ g:lockme[1] = 77
+ assert_equal([1, 77, 3], g:lockme)
+
+ lockvar 2 g:lockme
+ var caught = false
+ try
+ g:lockme[1] = 99
+ catch /E1119:/
+ caught = true
+ endtry
+ assert_true(caught)
+ assert_equal([1, 77, 3], g:lockme)
+ unlet g:lockme
+
+ # also for non-materialized list
+ g:therange = range(3)
+ lockvar 2 g:therange
+ caught = false
+ try
+ g:therange[1] = 99
+ catch /E1119:/
+ caught = true
+ endtry
+ assert_true(caught)
+ assert_equal([0, 1, 2], g:therange)
+ unlet g:therange
+
+ # use exclamation mark for locking deeper
+ g:nestedlist = [1, [2, 3], 4]
+ lockvar! g:nestedlist
+ try
+ g:nestedlist[1][0] = 9
+ catch /E1119:/
+ caught = true
+ endtry
+ assert_true(caught)
+ unlet g:nestedlist
+
+ var d = {a: 1, b: 2}
+ d.a = 3
+ d.b = 4
+ assert_equal({a: 3, b: 4}, d)
+ lockvar d.a
+ d.b = 5
+ var ex = ''
+ try
+ d.a = 6
+ catch
+ ex = v:exception
+ endtry
+ assert_match('E1121:', ex)
+ unlockvar d['a']
+ d.a = 7
+ assert_equal({a: 7, b: 5}, d)
+
+ caught = false
+ try
+ lockvar d.c
+ catch /E716/
+ caught = true
+ endtry
+ assert_true(caught)
+
+ var lines =<< trim END
+ vim9script
+ g:bl = 0z1122
+ lockvar g:bl
+ def Tryit()
+ g:bl[1] = 99
+ enddef
+ Tryit()
+ END
+ v9.CheckScriptFailure(lines, 'E741:', 1)
+
+ lines =<< trim END
+ vim9script
+ var theList = [1, 2, 3]
+ def SetList()
+ theList[1] = 22
+ assert_equal([1, 22, 3], theList)
+ lockvar theList
+ theList[1] = 77
+ enddef
+ SetList()
+ END
+ v9.CheckScriptFailure(lines, 'E1119', 4)
+
+ lines =<< trim END
+ vim9script
+ var theList = [1, 2, 3]
+ def AddToList()
+ lockvar theList
+ theList += [4]
+ enddef
+ AddToList()
+ END
+ v9.CheckScriptFailure(lines, 'E741', 2)
+
+ lines =<< trim END
+ vim9script
+ var theList = [1, 2, 3]
+ def AddToList()
+ lockvar theList
+ add(theList, 4)
+ enddef
+ AddToList()
+ END
+ v9.CheckScriptFailure(lines, 'E741', 2)
+
+ # can unlet a locked list item but not change it
+ lines =<< trim END
+ var ll = [1, 2, 3]
+ lockvar ll[1]
+ unlet ll[1]
+ assert_equal([1, 3], ll)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+ lines =<< trim END
+ var ll = [1, 2, 3]
+ lockvar ll[1]
+ ll[1] = 9
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1119:', 'E741'], 3)
+
+ # can unlet a locked dict item but not change it
+ lines =<< trim END
+ var dd = {a: 1, b: 2}
+ lockvar dd.a
+ unlet dd.a
+ assert_equal({b: 2}, dd)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+ lines =<< trim END
+ var dd = {a: 1, b: 2}
+ lockvar dd.a
+ dd.a = 3
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1121:', 'E741'], 3)
+
+ lines =<< trim END
+ var theList = [1, 2, 3]
+ lockvar theList
+ END
+ v9.CheckDefFailure(lines, 'E1178', 2)
+
+ lines =<< trim END
+ var theList = [1, 2, 3]
+ unlockvar theList
+ END
+ v9.CheckDefFailure(lines, 'E1178', 2)
+
+ lines =<< trim END
+ vim9script
+ var name = 'john'
+ lockvar nameX
+ END
+ v9.CheckScriptFailure(lines, 'E1246', 3)
+
+ lines =<< trim END
+ vim9script
+ var name = 'john'
+ def LockIt()
+ lockvar nameX
+ enddef
+ LockIt()
+ END
+ v9.CheckScriptFailure(lines, 'E1246', 1)
+
+ lines =<< trim END
+ vim9script
+ const name = 'john'
+ unlockvar name
+ END
+ v9.CheckScriptFailure(lines, 'E46', 3)
+
+ lines =<< trim END
+ vim9script
+ const name = 'john'
+ def UnLockIt()
+ unlockvar name
+ enddef
+ UnLockIt()
+ END
+ v9.CheckScriptFailure(lines, 'E46', 1)
+
+ lines =<< trim END
+ def _()
+ lockv
+ enddef
+ defcomp
+ END
+ v9.CheckScriptFailure(lines, 'E179', 1)
+
+ lines =<< trim END
+ def T()
+ unlet
+ enddef
+ defcomp
+ END
+ v9.CheckScriptFailure(lines, 'E179', 1)
+enddef
+
+def Test_substitute_expr()
+ var to = 'repl'
+ new
+ setline(1, 'one from two')
+ s/from/\=to
+ assert_equal('one repl two', getline(1))
+
+ setline(1, 'one from two')
+ s/from/\=to .. '_x'
+ assert_equal('one repl_x two', getline(1))
+
+ setline(1, 'one from two from three')
+ var also = 'also'
+ s/from/\=to .. '_' .. also/g#e
+ assert_equal('one repl_also two repl_also three', getline(1))
+
+ setline(1, 'abc abc abc')
+ for choice in [true, false]
+ :1s/abc/\=choice ? 'yes' : 'no'/
+ endfor
+ assert_equal('yes no abc', getline(1))
+
+ setline(1, 'from')
+ v9.CheckDefExecFailure(['s/from/\=g:notexist/'], 'E121: Undefined variable: g:notexist')
+
+ bwipe!
+
+ v9.CheckDefFailure(['s/from/\="x")/'], 'E488:')
+ v9.CheckDefFailure(['s/from/\="x"/9'], 'E488:')
+
+ v9.CheckDefExecFailure(['s/this/\="that"/'], 'E486:')
+
+ # When calling a function the right instruction list needs to be restored.
+ g:cond = true
+ var lines =<< trim END
+ vim9script
+ def Foo()
+ Bar([])
+ enddef
+ def Bar(l: list<number>)
+ if g:cond
+ s/^/\=Rep()/
+ for n in l[:]
+ endfor
+ endif
+ enddef
+ def Rep(): string
+ return 'rep'
+ enddef
+ new
+ Foo()
+ assert_equal('rep', getline(1))
+ bwipe!
+ END
+ v9.CheckScriptSuccess(lines)
+ unlet g:cond
+
+ # List results in multiple lines
+ new
+ setline(1, 'some text here')
+ s/text/\=['aaa', 'bbb', 'ccc']/
+ assert_equal(['some aaa', 'bbb', 'ccc', ' here'], getline(1, '$'))
+ bwipe!
+
+ # inside "if 0" substitute is ignored
+ if 0
+ s/a/\=nothing/ and | some more
+ endif
+enddef
+
+def Test_redir_to_var()
+ var result: string
+ redir => result
+ echo 'something'
+ redir END
+ assert_equal("\nsomething", result)
+
+ redir =>> result
+ echo 'more'
+ redir END
+ assert_equal("\nsomething\nmore", result)
+
+ var d: dict<string>
+ redir => d.redir
+ echo 'dict'
+ redir END
+ assert_equal({redir: "\ndict"}, d)
+
+ var l = ['a', 'b', 'c']
+ redir => l[1]
+ echo 'list'
+ redir END
+ assert_equal(['a', "\nlist", 'c'], l)
+
+ var dl = {l: ['x']}
+ redir => dl.l[0]
+ echo 'dict-list'
+ redir END
+ assert_equal({l: ["\ndict-list"]}, dl)
+
+ redir =>> d.redir
+ echo 'more'
+ redir END
+ assert_equal({redir: "\ndict\nmore"}, d)
+
+ var lines =<< trim END
+ redir => notexist
+ END
+ v9.CheckDefFailure(lines, 'E1089:')
+
+ lines =<< trim END
+ var text: string
+ redir => text
+ END
+ v9.CheckDefFailure(lines, 'E1185:')
+
+ lines =<< trim END
+ var ls = 'asdf'
+ redir => ls[1]
+ redir END
+ END
+ v9.CheckDefFailure(lines, 'E1141:')
+
+ lines =<< trim END
+ var text: string
+ redir => text
+ echo 'hello'
+ redir > Xnopfile
+ redir END
+ END
+ v9.CheckDefFailure(lines, 'E1092:')
+
+ lines =<< trim END
+ var text: number
+ redir => text
+ echo 'hello'
+ redir END
+ END
+ v9.CheckDefFailure(lines, 'E1012:')
+enddef
+
+def Test_echo_void()
+ var lines =<< trim END
+ vim9script
+ def NoReturn()
+ echo 'nothing'
+ enddef
+ echo NoReturn()
+ END
+ v9.CheckScriptFailure(lines, 'E1186:', 5)
+
+ lines =<< trim END
+ vim9script
+ def NoReturn()
+ echo 'nothing'
+ enddef
+ def Try()
+ echo NoReturn()
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1186:', 1)
+enddef
+
+def Test_cmdwin_block()
+ augroup justTesting
+ autocmd BufEnter * {
+ echomsg 'in block'
+ }
+ augroup END
+ feedkeys('q:', 'xt')
+ redraw
+ feedkeys("aclose\<CR>", 'xt')
+
+ au! justTesting
+enddef
+
+def Test_var_not_cmd()
+ var lines =<< trim END
+ g:notexist:cmd
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1016: Cannot declare a global variable: g:notexist', "E1069: White space required after ':'"], 1)
+
+ lines =<< trim END
+ g-pat-cmd
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1241:', 1)
+ lines =<< trim END
+ g.pat.cmd
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1001: Variable not found: g', 'E121: Undefined variable: g'], 1)
+
+ lines =<< trim END
+ s:notexist:repl
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1101: Cannot declare a script variable in a function: s:notexist', "E1069: White space required after ':'"], 1)
+
+ lines =<< trim END
+ notexist:repl
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E476:', 'E492:'], 1)
+
+ lines =<< trim END
+ s-pat-repl
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1241:', 1)
+ lines =<< trim END
+ s.pat.repl
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1001: Variable not found: s', 'E121: Undefined variable: s'], 1)
+
+ lines =<< trim END
+ w:notexist->len()
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E121: Undefined variable: w:notexist', 1)
+
+ lines =<< trim END
+ b:notexist->len()
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E121: Undefined variable: b:notexist', 1)
+
+ lines =<< trim END
+ t:notexist->len()
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E121: Undefined variable: t:notexist', 1)
+enddef
+
+def Test_no_space_after_command()
+ var lines =<< trim END
+ g /pat/cmd
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1242:', 1)
+ lines =<< trim END
+ g #pat#cmd
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1242:', 1)
+
+ new
+ setline(1, 'some pat')
+ lines =<< trim END
+ g#pat#print
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+ lines =<< trim END
+ g# pat#print
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+ bwipe!
+
+ lines =<< trim END
+ s /pat/repl
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1242:', 1)
+ lines =<< trim END
+ s #pat#repl
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1242:', 1)
+ lines =<< trim END
+ s#pat#repl
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E486:', 1)
+ lines =<< trim END
+ s# pat#repl
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E486:', 1)
+enddef
+
+" Test for the 'previewpopup' option
+def Test_previewpopup()
+ set previewpopup=height:10,width:60
+ pedit Xppfile
+ var id = popup_findpreview()
+ assert_notequal(id, 0)
+ assert_match('Xppfile', popup_getoptions(id).title)
+ popup_clear()
+ set previewpopup&
+enddef
+
+def Test_syntax_enable_clear()
+ syntax clear
+ syntax enable
+ highlight clear String
+ assert_equal(true, hlget('String')->get(0, {})->get('default', false))
+ syntax clear
+enddef
+
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
new file mode 100644
index 0000000..6f68798
--- /dev/null
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -0,0 +1,3049 @@
+" Test the :disassemble command, and compilation as a side effect
+
+source check.vim
+import './vim9.vim' as v9
+
+func s:NotCompiled()
+ echo "not"
+endfunc
+
+let s:scriptvar = 4
+let g:globalvar = 'g'
+let b:buffervar = 'b'
+let w:windowvar = 'w'
+let t:tabpagevar = 't'
+
+def s:ScriptFuncLoad(arg: string)
+ var local = 1
+ buffers
+ echo
+ echo arg
+ echo local
+ echo &lines
+ echo v:version
+ echo s:scriptvar
+ echo g:globalvar
+ echo get(g:, "global")
+ echo g:auto#var
+ echo b:buffervar
+ echo get(b:, "buffer")
+ echo w:windowvar
+ echo get(w:, "window")
+ echo t:tabpagevar
+ echo get(t:, "tab")
+ echo &tabstop
+ echo $ENVVAR
+ echo @z
+enddef
+
+def Test_disassemble_load()
+ assert_fails('disass NoFunc', 'E1061:')
+ assert_fails('disass NotCompiled', 'E1091:')
+ assert_fails('disass', 'E471:')
+ assert_fails('disass [', 'E475:')
+ assert_fails('disass 234', 'E129:')
+ assert_fails('disass <XX>foo', 'E129:')
+ assert_fails('disass Test_disassemble_load burp', 'E488:')
+ assert_fails('disass debug debug Test_disassemble_load', 'E488:')
+ assert_fails('disass profile profile Test_disassemble_load', 'E488:')
+
+ var res = execute('disass s:ScriptFuncLoad')
+ assert_match('<SNR>\d*_ScriptFuncLoad.*' ..
+ 'buffers\_s*' ..
+ '\d\+ EXEC \+buffers\_s*' ..
+ 'echo\_s*' ..
+ 'echo arg\_s*' ..
+ '\d\+ LOAD arg\[-1\]\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo local\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo &lines\_s*' ..
+ '\d\+ LOADOPT &lines\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo v:version\_s*' ..
+ '\d\+ LOADV v:version\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo s:scriptvar\_s*' ..
+ '\d\+ LOADS s:scriptvar from .*test_vim9_disassemble.vim\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo g:globalvar\_s*' ..
+ '\d\+ LOADG g:globalvar\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo get(g:, "global")\_s*' ..
+ '\d\+ LOAD g:\_s*' ..
+ '\d\+ PUSHS "global"\_s*' ..
+ '\d\+ BCALL get(argc 2)\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo g:auto#var\_s*' ..
+ '\d\+ LOADAUTO g:auto#var\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo b:buffervar\_s*' ..
+ '\d\+ LOADB b:buffervar\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'echo get(b:, "buffer")\_s*' ..
+ '\d\+ LOAD b:\_s*' ..
+ '\d\+ PUSHS "buffer"\_s*' ..
+ '\d\+ BCALL get(argc 2).*' ..
+ ' LOADW w:windowvar.*' ..
+ 'echo get(w:, "window")\_s*' ..
+ '\d\+ LOAD w:\_s*' ..
+ '\d\+ PUSHS "window"\_s*' ..
+ '\d\+ BCALL get(argc 2).*' ..
+ ' LOADT t:tabpagevar.*' ..
+ 'echo get(t:, "tab")\_s*' ..
+ '\d\+ LOAD t:\_s*' ..
+ '\d\+ PUSHS "tab"\_s*' ..
+ '\d\+ BCALL get(argc 2).*' ..
+ ' LOADENV $ENVVAR.*' ..
+ ' LOADREG @z.*',
+ res)
+enddef
+
+def s:EditExpand()
+ var filename = "file"
+ var filenr = 123
+ edit the`=filename``=filenr`.txt
+enddef
+
+def Test_disassemble_exec_expr()
+ var res = execute('disass s:EditExpand')
+ assert_match('<SNR>\d*_EditExpand\_s*' ..
+ ' var filename = "file"\_s*' ..
+ '\d PUSHS "file"\_s*' ..
+ '\d STORE $0\_s*' ..
+ ' var filenr = 123\_s*' ..
+ '\d STORE 123 in $1\_s*' ..
+ ' edit the`=filename``=filenr`.txt\_s*' ..
+ '\d PUSHS "edit the"\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d LOAD $1\_s*' ..
+ '\d 2STRING stack\[-1\]\_s*' ..
+ '\d\+ PUSHS ".txt"\_s*' ..
+ '\d\+ EXECCONCAT 4\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+if has('python3')
+ def s:PyHeredoc()
+ python3 << EOF
+ print('hello')
+EOF
+ enddef
+
+ def Test_disassemble_python_heredoc()
+ var res = execute('disass s:PyHeredoc')
+ assert_match('<SNR>\d*_PyHeredoc.*' ..
+ " python3 << EOF^@ print('hello')^@EOF\\_s*" ..
+ '\d EXEC_SPLIT python3 << EOF^@ print(''hello'')^@EOF\_s*' ..
+ '\d RETURN void',
+ res)
+ enddef
+endif
+
+def s:Substitute()
+ var expr = "abc"
+ :%s/a/\=expr/&g#c
+enddef
+
+def Test_disassemble_substitute()
+ var res = execute('disass s:Substitute')
+ assert_match('<SNR>\d*_Substitute.*' ..
+ ' var expr = "abc"\_s*' ..
+ '\d PUSHS "abc"\_s*' ..
+ '\d STORE $0\_s*' ..
+ ' :%s/a/\\=expr/&g#c\_s*' ..
+ '\d SUBSTITUTE :%s/a/\\=expr/&g#c\_s*' ..
+ ' 0 LOAD $0\_s*' ..
+ ' -------------\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+
+def s:SearchPair()
+ var col = 8
+ searchpair("{", "", "}", "", "col('.') > col")
+enddef
+
+def Test_disassemble_seachpair()
+ var res = execute('disass s:SearchPair')
+ assert_match('<SNR>\d*_SearchPair.*' ..
+ ' var col = 8\_s*' ..
+ '\d STORE 8 in $0\_s*' ..
+ ' searchpair("{", "", "}", "", "col(''.'') > col")\_s*' ..
+ '\d PUSHS "{"\_s*' ..
+ '\d PUSHS ""\_s*' ..
+ '\d PUSHS "}"\_s*' ..
+ '\d PUSHS ""\_s*' ..
+ '\d INSTR\_s*' ..
+ ' 0 PUSHS "."\_s*' ..
+ ' 1 BCALL col(argc 1)\_s*' ..
+ ' 2 LOAD $0\_s*' ..
+ ' 3 COMPARENR >\_s*' ..
+ ' -------------\_s*' ..
+ '\d BCALL searchpair(argc 5)\_s*' ..
+ '\d DROP\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+
+def s:SubstituteExpr()
+ substitute('a', 'b', '\=123', 'g')
+enddef
+
+def Test_disassemble_substitute_expr()
+ var res = execute('disass s:SubstituteExpr')
+ assert_match('<SNR>\d*_SubstituteExpr.*' ..
+ 'substitute(''a'', ''b'', ''\\=123'', ''g'')\_s*' ..
+ '\d PUSHS "a"\_s*' ..
+ '\d PUSHS "b"\_s*' ..
+ '\d INSTR\_s*' ..
+ ' 0 PUSHNR 123\_s*' ..
+ ' -------------\_s*' ..
+ '\d PUSHS "g"\_s*' ..
+ '\d BCALL substitute(argc 4)\_s*' ..
+ '\d DROP\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:RedirVar()
+ var result: string
+ redir =>> result
+ echo "text"
+ redir END
+enddef
+
+def Test_disassemble_redir_var()
+ var res = execute('disass s:RedirVar')
+ assert_match('<SNR>\d*_RedirVar.*' ..
+ ' var result: string\_s*' ..
+ '\d PUSHS "\[NULL\]"\_s*' ..
+ '\d STORE $0\_s*' ..
+ ' redir =>> result\_s*' ..
+ '\d REDIR\_s*' ..
+ ' echo "text"\_s*' ..
+ '\d PUSHS "text"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ ' redir END\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d REDIR END\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d STORE $0\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:Cexpr()
+ var errors = "list of errors"
+ cexpr errors
+enddef
+
+def Test_disassemble_cexpr()
+ var res = execute('disass s:Cexpr')
+ assert_match('<SNR>\d*_Cexpr.*' ..
+ ' var errors = "list of errors"\_s*' ..
+ '\d PUSHS "list of errors"\_s*' ..
+ '\d STORE $0\_s*' ..
+ ' cexpr errors\_s*' ..
+ '\d CEXPR pre cexpr\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d CEXPR core cexpr "cexpr errors"\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:YankRange()
+ norm! m[jjm]
+ :'[,']yank
+enddef
+
+def Test_disassemble_yank_range()
+ var res = execute('disass s:YankRange')
+ assert_match('<SNR>\d*_YankRange.*' ..
+ ' norm! m\[jjm\]\_s*' ..
+ '\d EXEC norm! m\[jjm\]\_s*' ..
+ ' :''\[,''\]yank\_s*' ..
+ '\d EXEC :''\[,''\]yank\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:PutExpr()
+ :3put ="text"
+enddef
+
+def Test_disassemble_put_expr()
+ var res = execute('disass s:PutExpr')
+ assert_match('<SNR>\d*_PutExpr.*' ..
+ ' :3put ="text"\_s*' ..
+ '\d PUSHS "text"\_s*' ..
+ '\d PUT = 3\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:PutRange()
+ :$-2put a
+ :$-3put! b
+enddef
+
+def Test_disassemble_put_range()
+ var res = execute('disass s:PutRange')
+ assert_match('<SNR>\d*_PutRange.*' ..
+ ' :$-2put a\_s*' ..
+ '\d RANGE $-2\_s*' ..
+ '\d PUT a range\_s*' ..
+
+ ' :$-3put! b\_s*' ..
+ '\d RANGE $-3\_s*' ..
+ '\d PUT b above range\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+def s:ScriptFuncPush()
+ var localbool = true
+ var localspec = v:none
+ var localblob = 0z1234
+ var localfloat = 1.234
+enddef
+
+def Test_disassemble_push()
+ mkdir('Xdisdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xdisdir'
+
+ var lines =<< trim END
+ vim9script
+ END
+ writefile(lines, 'Xdisdir/autoload/autoscript.vim')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'autoscript.vim'
+
+ def AutoloadFunc()
+ &operatorfunc = autoscript.Opfunc
+ enddef
+
+ var res = execute('disass AutoloadFunc')
+ assert_match('<SNR>\d*_AutoloadFunc.*' ..
+ '&operatorfunc = autoscript.Opfunc\_s*' ..
+ '0 AUTOLOAD autoscript#Opfunc\_s*' ..
+ '1 STOREFUNCOPT &operatorfunc\_s*' ..
+ '2 RETURN void',
+ res)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ &rtp = save_rtp
+enddef
+
+def Test_disassemble_import_autoload()
+ writefile(['vim9script'], 'XimportAL.vim', 'D')
+
+ var lines =<< trim END
+ vim9script
+ import autoload './XimportAL.vim'
+
+ def AutoloadFunc()
+ echo XimportAL.SomeFunc()
+ echo XimportAL.someVar
+ XimportAL.someVar = "yes"
+ enddef
+
+ var res = execute('disass AutoloadFunc')
+ assert_match('<SNR>\d*_AutoloadFunc.*' ..
+ 'echo XimportAL.SomeFunc()\_s*' ..
+ '\d SOURCE .*/testdir/XimportAL.vim\_s*' ..
+ '\d PUSHFUNC "<80><fd>R\d\+_SomeFunc"\_s*' ..
+ '\d PCALL top (argc 0)\_s*' ..
+ '\d PCALL end\_s*' ..
+ '\d ECHO 1\_s*' ..
+
+ 'echo XimportAL.someVar\_s*' ..
+ '\d SOURCE .*/testdir/XimportAL.vim\_s*' ..
+ '\d LOADEXPORT s:someVar from .*/testdir/XimportAL.vim\_s*' ..
+ '\d ECHO 1\_s*' ..
+
+ 'XimportAL.someVar = "yes"\_s*' ..
+ '\d\+ PUSHS "yes"\_s*' ..
+ '\d\+ SOURCE .*/testdir/XimportAL.vim\_s*' ..
+ '\d\+ STOREEXPORT someVar in .*/testdir/XimportAL.vim\_s*' ..
+
+ '\d\+ RETURN void',
+ res)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:ScriptFuncStore()
+ var localnr = 1
+ localnr = 2
+ var localstr = 'abc'
+ localstr = 'xyz'
+ v:char = 'abc'
+ s:scriptvar = 'sv'
+ g:globalvar = 'gv'
+ g:auto#var = 'av'
+ b:buffervar = 'bv'
+ w:windowvar = 'wv'
+ t:tabpagevar = 'tv'
+ &tabstop = 8
+ &opfunc = (t) => len(t)
+ $ENVVAR = 'ev'
+ @z = 'rv'
+enddef
+
+def Test_disassemble_store()
+ var res = execute('disass s:ScriptFuncStore')
+ assert_match('<SNR>\d*_ScriptFuncStore.*' ..
+ 'var localnr = 1.*' ..
+ 'localnr = 2.*' ..
+ ' STORE 2 in $0.*' ..
+ 'var localstr = ''abc''.*' ..
+ 'localstr = ''xyz''.*' ..
+ ' STORE $1.*' ..
+ 'v:char = ''abc''.*' ..
+ 'STOREV v:char.*' ..
+ 's:scriptvar = ''sv''.*' ..
+ ' STORES s:scriptvar in .*test_vim9_disassemble.vim.*' ..
+ 'g:globalvar = ''gv''.*' ..
+ ' STOREG g:globalvar.*' ..
+ 'g:auto#var = ''av''.*' ..
+ ' STOREAUTO g:auto#var.*' ..
+ 'b:buffervar = ''bv''.*' ..
+ ' STOREB b:buffervar.*' ..
+ 'w:windowvar = ''wv''.*' ..
+ ' STOREW w:windowvar.*' ..
+ 't:tabpagevar = ''tv''.*' ..
+ ' STORET t:tabpagevar.*' ..
+ '&tabstop = 8\_s*' ..
+ '\d\+ PUSHNR 8\_s*' ..
+ '\d\+ STOREOPT &tabstop\_s*' ..
+ '&opfunc = (t) => len(t)\_s*' ..
+ '\d\+ FUNCREF <lambda>\d\+\_s*' ..
+ '\d\+ STOREFUNCOPT &opfunc\_s*' ..
+ '$ENVVAR = ''ev''\_s*' ..
+ '\d\+ PUSHS "ev"\_s*' ..
+ '\d\+ STOREENV $ENVVAR\_s*' ..
+ '@z = ''rv''.*' ..
+ '\d\+ STOREREG @z.*',
+ res)
+enddef
+
+def s:ScriptFuncStoreMember()
+ var locallist: list<number> = []
+ locallist[0] = 123
+ var localdict: dict<number> = {}
+ localdict["a"] = 456
+ var localblob: blob = 0z1122
+ localblob[1] = 33
+enddef
+
+def Test_disassemble_store_member()
+ var res = execute('disass s:ScriptFuncStoreMember')
+ assert_match('<SNR>\d*_ScriptFuncStoreMember\_s*' ..
+ 'var locallist: list<number> = []\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'locallist\[0\] = 123\_s*' ..
+ '\d PUSHNR 123\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d STOREINDEX list\_s*' ..
+ 'var localdict: dict<number> = {}\_s*' ..
+ '\d NEWDICT size 0\_s*' ..
+ '\d SETTYPE dict<number>\_s*' ..
+ '\d STORE $1\_s*' ..
+ 'localdict\["a"\] = 456\_s*' ..
+ '\d\+ PUSHNR 456\_s*' ..
+ '\d\+ PUSHS "a"\_s*' ..
+ '\d\+ LOAD $1\_s*' ..
+ '\d\+ STOREINDEX dict\_s*' ..
+ 'var localblob: blob = 0z1122\_s*' ..
+ '\d\+ PUSHBLOB 0z1122\_s*' ..
+ '\d\+ STORE $2\_s*' ..
+ 'localblob\[1\] = 33\_s*' ..
+ '\d\+ PUSHNR 33\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ LOAD $2\_s*' ..
+ '\d\+ STOREINDEX blob\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+if has('job')
+ def s:StoreNull()
+ var ss = null_string
+ var bb = null_blob
+ var dd = null_dict
+ var ll = null_list
+ var Ff = null_function
+ var Pp = null_partial
+ var jj = null_job
+ var cc = null_channel
+ enddef
+
+ def Test_disassemble_assign_null()
+ var res = execute('disass s:StoreNull')
+ assert_match('<SNR>\d*_StoreNull\_s*' ..
+ 'var ss = null_string\_s*' ..
+ '\d\+ PUSHS "\[NULL\]"\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var bb = null_blob\_s*' ..
+ '\d\+ PUSHBLOB 0z\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var dd = null_dict\_s*' ..
+ '\d\+ NEWDICT size -1\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var ll = null_list\_s*' ..
+ '\d\+ NEWLIST size -1\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var Ff = null_function\_s*' ..
+ '\d\+ PUSHFUNC "\[none\]"\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var Pp = null_partial\_s*' ..
+ '\d\+ NEWPARTIAL\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var jj = null_job\_s*' ..
+ '\d\+ PUSHJOB "no process"\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ 'var cc = null_channel\_s*' ..
+ '\d\+ PUSHCHANNEL 0\_s*' ..
+ '\d\+ STORE $\d\_s*' ..
+
+ '\d\+ RETURN void',
+ res)
+ enddef
+endif
+
+def s:ScriptFuncStoreIndex()
+ var d = {dd: {}}
+ d.dd[0] = 0
+enddef
+
+def Test_disassemble_store_index()
+ var res = execute('disass s:ScriptFuncStoreIndex')
+ assert_match('<SNR>\d*_ScriptFuncStoreIndex\_s*' ..
+ 'var d = {dd: {}}\_s*' ..
+ '\d PUSHS "dd"\_s*' ..
+ '\d NEWDICT size 0\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d SETTYPE dict<dict<unknown>>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'd.dd\[0\] = 0\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d MEMBER dd\_s*' ..
+ '\d\+ USEDICT\_s*' ..
+ '\d\+ STOREINDEX any\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:ListAssign()
+ var x: string
+ var y: string
+ var l: list<any>
+ [x, y; l] = g:stringlist
+enddef
+
+def Test_disassemble_list_assign()
+ var res = execute('disass s:ListAssign')
+ assert_match('<SNR>\d*_ListAssign\_s*' ..
+ 'var x: string\_s*' ..
+ '\d PUSHS "\[NULL\]"\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var y: string\_s*' ..
+ '\d PUSHS "\[NULL\]"\_s*' ..
+ '\d STORE $1\_s*' ..
+ 'var l: list<any>\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d STORE $2\_s*' ..
+ '\[x, y; l\] = g:stringlist\_s*' ..
+ '\d LOADG g:stringlist\_s*' ..
+ '\d CHECKTYPE list<any> stack\[-1\]\_s*' ..
+ '\d CHECKLEN >= 2\_s*' ..
+ '\d\+ ITEM 0\_s*' ..
+ '\d\+ CHECKTYPE string stack\[-1\] var 1\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+ '\d\+ ITEM 1\_s*' ..
+ '\d\+ CHECKTYPE string stack\[-1\] var 2\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ '\d\+ SLICE 2\_s*' ..
+ '\d\+ STORE $2\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:ListAssignWithOp()
+ var a = 2
+ var b = 3
+ [a, b] += [4, 5]
+enddef
+
+def Test_disassemble_list_assign_with_op()
+ var res = execute('disass s:ListAssignWithOp')
+ assert_match('<SNR>\d*_ListAssignWithOp\_s*' ..
+ 'var a = 2\_s*' ..
+ '\d STORE 2 in $0\_s*' ..
+ 'var b = 3\_s*' ..
+ '\d STORE 3 in $1\_s*' ..
+ '\[a, b\] += \[4, 5\]\_s*' ..
+ '\d\+ PUSHNR 4\_s*' ..
+ '\d\+ PUSHNR 5\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ ITEM 0 with op\_s*' ..
+ '\d\+ OPNR +\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+ '\d\+ LOAD $1\_s*' ..
+ '\d\+ ITEM 1 with op\_s*' ..
+ '\d\+ OPNR +\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:ListAdd()
+ var l: list<number> = []
+ add(l, 123)
+ add(l, g:aNumber)
+enddef
+
+def Test_disassemble_list_add()
+ var res = execute('disass s:ListAdd')
+ assert_match('<SNR>\d*_ListAdd\_s*' ..
+ 'var l: list<number> = []\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'add(l, 123)\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 123\_s*' ..
+ '\d LISTAPPEND\_s*' ..
+ '\d DROP\_s*' ..
+ 'add(l, g:aNumber)\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d\+ LOADG g:aNumber\_s*' ..
+ '\d\+ CHECKTYPE number stack\[-1\]\_s*' ..
+ '\d\+ LISTAPPEND\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:BlobAdd()
+ var b: blob = 0z
+ add(b, 123)
+ add(b, g:aNumber)
+enddef
+
+def Test_disassemble_blob_add()
+ var res = execute('disass s:BlobAdd')
+ assert_match('<SNR>\d*_BlobAdd\_s*' ..
+ 'var b: blob = 0z\_s*' ..
+ '\d PUSHBLOB 0z\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'add(b, 123)\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 123\_s*' ..
+ '\d BLOBAPPEND\_s*' ..
+ '\d DROP\_s*' ..
+ 'add(b, g:aNumber)\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d\+ LOADG g:aNumber\_s*' ..
+ '\d\+ CHECKTYPE number stack\[-1\]\_s*' ..
+ '\d\+ BLOBAPPEND\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:BlobIndexSlice()
+ var b: blob = 0z112233
+ echo b[1]
+ echo b[1 : 2]
+enddef
+
+def Test_disassemble_blob_index_slice()
+ var res = execute('disass s:BlobIndexSlice')
+ assert_match('<SNR>\d*_BlobIndexSlice\_s*' ..
+ 'var b: blob = 0z112233\_s*' ..
+ '\d PUSHBLOB 0z112233\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'echo b\[1\]\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d BLOBINDEX\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'echo b\[1 : 2\]\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d\+ PUSHNR 2\_s*' ..
+ '\d\+ BLOBSLICE\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:ScriptFuncUnlet()
+ g:somevar = "value"
+ unlet g:somevar
+ unlet! g:somevar
+ unlet $SOMEVAR
+
+ var l = [1, 2, 3]
+ unlet l[2]
+ unlet l[0 : 1]
+enddef
+
+def Test_disassemble_unlet()
+ var res = execute('disass s:ScriptFuncUnlet')
+ assert_match('<SNR>\d*_ScriptFuncUnlet\_s*' ..
+ 'g:somevar = "value"\_s*' ..
+ '\d PUSHS "value"\_s*' ..
+ '\d STOREG g:somevar\_s*' ..
+ 'unlet g:somevar\_s*' ..
+ '\d UNLET g:somevar\_s*' ..
+ 'unlet! g:somevar\_s*' ..
+ '\d UNLET! g:somevar\_s*' ..
+ 'unlet $SOMEVAR\_s*' ..
+ '\d UNLETENV $SOMEVAR\_s*' ..
+
+ 'var l = \[1, 2, 3]\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ PUSHNR 2\_s*' ..
+ '\d\+ PUSHNR 3\_s*' ..
+ '\d\+ NEWLIST size 3\_s*' ..
+ '\d\+ SETTYPE list<number>\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+
+ 'unlet l\[2]\_s*' ..
+ '\d\+ PUSHNR 2\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ UNLETINDEX\_s*' ..
+
+ 'unlet l\[0 : 1]\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ UNLETRANGE\_s*',
+ res)
+enddef
+
+def s:LockLocal()
+ var d = {a: 1}
+ lockvar d.a
+ const nr = 22
+enddef
+
+def Test_disassemble_lock_local()
+ var res = execute('disass s:LockLocal')
+ assert_match('<SNR>\d*_LockLocal\_s*' ..
+ 'var d = {a: 1}\_s*' ..
+ '\d PUSHS "a"\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d SETTYPE dict<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'lockvar d.a\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d LOCKUNLOCK lockvar 2 d.a\_s*' ..
+
+ 'const nr = 22\_s*' ..
+ '\d\+ PUSHNR 22\_s*' ..
+ '\d\+ LOCKCONST\_s*' ..
+ '\d\+ STORE $1',
+ res)
+enddef
+
+def s:ScriptFuncTry()
+ try
+ echo "yes"
+ catch /fail/
+ echo "no"
+ finally
+ throw "end"
+ endtry
+enddef
+
+def Test_disassemble_try()
+ var res = execute('disass s:ScriptFuncTry')
+ assert_match('<SNR>\d*_ScriptFuncTry\_s*' ..
+ 'try\_s*' ..
+ '\d TRY catch -> \d\+, finally -> \d\+, endtry -> \d\+\_s*' ..
+ 'echo "yes"\_s*' ..
+ '\d PUSHS "yes"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'catch /fail/\_s*' ..
+ '\d JUMP -> \d\+\_s*' ..
+ '\d PUSH v:exception\_s*' ..
+ '\d PUSHS "fail"\_s*' ..
+ '\d COMPARESTRING =\~\_s*' ..
+ '\d JUMP_IF_FALSE -> \d\+\_s*' ..
+ '\d CATCH\_s*' ..
+ 'echo "no"\_s*' ..
+ '\d\+ PUSHS "no"\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ 'finally\_s*' ..
+ '\d\+ FINALLY\_s*' ..
+ 'throw "end"\_s*' ..
+ '\d\+ PUSHS "end"\_s*' ..
+ '\d\+ THROW\_s*' ..
+ 'endtry\_s*' ..
+ '\d\+ ENDTRY',
+ res)
+enddef
+
+def s:ScriptFuncNew()
+ var ll = [1, "two", 333]
+ var dd = {one: 1, two: "val"}
+enddef
+
+def Test_disassemble_new()
+ var res = execute('disass s:ScriptFuncNew')
+ assert_match('<SNR>\d*_ScriptFuncNew\_s*' ..
+ 'var ll = \[1, "two", 333\]\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHS "two"\_s*' ..
+ '\d PUSHNR 333\_s*' ..
+ '\d NEWLIST size 3\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var dd = {one: 1, two: "val"}\_s*' ..
+ '\d PUSHS "one"\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHS "two"\_s*' ..
+ '\d PUSHS "val"\_s*' ..
+ '\d NEWDICT size 2\_s*',
+ res)
+enddef
+
+def s:FuncWithArg(arg: any)
+ echo arg
+enddef
+
+func s:UserFunc()
+ echo 'nothing'
+endfunc
+
+func s:UserFuncWithArg(arg)
+ echo a:arg
+endfunc
+
+def s:ScriptFuncCall(): string
+ changenr()
+ char2nr("abc")
+ g:Test_disassemble_new()
+ FuncWithArg(343)
+ ScriptFuncNew()
+ s:ScriptFuncNew()
+ UserFunc()
+ UserFuncWithArg("foo")
+ var FuncRef = function("UserFunc")
+ FuncRef()
+ var FuncRefWithArg = function("UserFuncWithArg")
+ FuncRefWithArg("bar")
+ return "yes"
+enddef
+
+def Test_disassemble_call()
+ var res = execute('disass s:ScriptFuncCall')
+ assert_match('<SNR>\d\+_ScriptFuncCall\_s*' ..
+ 'changenr()\_s*' ..
+ '\d BCALL changenr(argc 0)\_s*' ..
+ '\d DROP\_s*' ..
+ 'char2nr("abc")\_s*' ..
+ '\d PUSHS "abc"\_s*' ..
+ '\d BCALL char2nr(argc 1)\_s*' ..
+ '\d DROP\_s*' ..
+ 'g:Test_disassemble_new()\_s*' ..
+ '\d DCALL Test_disassemble_new(argc 0)\_s*' ..
+ '\d DROP\_s*' ..
+ 'FuncWithArg(343)\_s*' ..
+ '\d\+ PUSHNR 343\_s*' ..
+ '\d\+ DCALL <SNR>\d\+_FuncWithArg(argc 1)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'ScriptFuncNew()\_s*' ..
+ '\d\+ DCALL <SNR>\d\+_ScriptFuncNew(argc 0)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 's:ScriptFuncNew()\_s*' ..
+ '\d\+ DCALL <SNR>\d\+_ScriptFuncNew(argc 0)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'UserFunc()\_s*' ..
+ '\d\+ UCALL <80><fd>R\d\+_UserFunc(argc 0)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'UserFuncWithArg("foo")\_s*' ..
+ '\d\+ PUSHS "foo"\_s*' ..
+ '\d\+ UCALL <80><fd>R\d\+_UserFuncWithArg(argc 1)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'var FuncRef = function("UserFunc")\_s*' ..
+ '\d\+ PUSHS "UserFunc"\_s*' ..
+ '\d\+ BCALL function(argc 1)\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+ 'FuncRef()\_s*' ..
+ '\d\+ LOAD $\d\_s*' ..
+ '\d\+ PCALL (argc 0)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'var FuncRefWithArg = function("UserFuncWithArg")\_s*' ..
+ '\d\+ PUSHS "UserFuncWithArg"\_s*' ..
+ '\d\+ BCALL function(argc 1)\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ 'FuncRefWithArg("bar")\_s*' ..
+ '\d\+ PUSHS "bar"\_s*' ..
+ '\d\+ LOAD $\d\_s*' ..
+ '\d\+ PCALL (argc 1)\_s*' ..
+ '\d\+ DROP\_s*' ..
+ 'return "yes"\_s*' ..
+ '\d\+ PUSHS "yes"\_s*' ..
+ '\d\+ RETURN',
+ res)
+enddef
+
+
+def s:CreateRefs()
+ var local = 'a'
+ def Append(arg: string)
+ local ..= arg
+ enddef
+ g:Append = Append
+ def Get(): string
+ return local
+ enddef
+ g:Get = Get
+enddef
+
+def Test_disassemble_closure()
+ CreateRefs()
+ var res = execute('disass g:Append')
+ assert_match('<lambda>\d\_s*' ..
+ 'local ..= arg\_s*' ..
+ '\d LOADOUTER level 1 $0\_s*' ..
+ '\d LOAD arg\[-1\]\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d STOREOUTER level 1 $0\_s*' ..
+ '\d RETURN void',
+ res)
+
+ res = execute('disass g:Get')
+ assert_match('<lambda>\d\_s*' ..
+ 'return local\_s*' ..
+ '\d LOADOUTER level 1 $0\_s*' ..
+ '\d RETURN',
+ res)
+
+ unlet g:Append
+ unlet g:Get
+enddef
+
+def s:ClosureArg(arg: string)
+ var Ref = () => arg .. "x"
+enddef
+
+def Test_disassemble_closure_arg()
+ var res = execute('disass s:ClosureArg')
+ assert_match('<SNR>\d\+_ClosureArg\_s*' ..
+ 'var Ref = () => arg .. "x"\_s*' ..
+ '\d FUNCREF <lambda>\d\+',
+ res)
+ var lres = execute('disass ' .. matchstr(res, '<lambda>\d\+'))
+ assert_match('<lambda>\d\+\_s*' ..
+ 'return arg .. "x"\_s*' ..
+ '\d LOADOUTER level 1 arg\[-1]\_s*' ..
+ '\d PUSHS "x"\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d RETURN',
+ lres)
+enddef
+
+def s:ClosureInLoop()
+ for i in range(5)
+ var ii = i
+ continue
+ break
+ if g:val
+ return
+ endif
+ g:Ref = () => ii
+ continue
+ break
+ if g:val
+ return
+ endif
+ endfor
+enddef
+
+" Mainly check that ENDLOOP is only produced after a closure was created.
+def Test_disassemble_closure_in_loop()
+ var res = execute('disass s:ClosureInLoop')
+ assert_match('<SNR>\d\+_ClosureInLoop\_s*' ..
+ 'for i in range(5)\_s*' ..
+ '\d\+ STORE -1 in $0\_s*' ..
+ '\d\+ PUSHNR 5\_s*' ..
+ '\d\+ BCALL range(argc 1)\_s*' ..
+ '\d\+ FOR $0 -> \d\+\_s*' ..
+ '\d\+ STORE $2\_s*' ..
+
+ 'var ii = i\_s*' ..
+ '\d\+ LOAD $2\_s*' ..
+ '\d\+ STORE $3\_s*' ..
+
+ 'continue\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'break\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'if g:val\_s*' ..
+ '\d\+ LOADG g:val\_s*' ..
+ '\d\+ COND2BOOL\_s*' ..
+ '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+
+ ' return\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ RETURN\_s*' ..
+
+ 'endif\_s*' ..
+ 'g:Ref = () => ii\_s*' ..
+ '\d\+ FUNCREF <lambda>4 vars $3-$3\_s*' ..
+ '\d\+ STOREG g:Ref\_s*' ..
+
+ 'continue\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'break\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+
+ 'if g:val\_s*' ..
+ '\d\+ LOADG g:val\_s*' ..
+ '\d\+ COND2BOOL\_s*' ..
+ '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+
+ ' return\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ RETURN\_s*' ..
+
+ 'endif\_s*' ..
+ 'endfor\_s*' ..
+ '\d\+ ENDLOOP ref $1 save $3-$3 depth 0\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def EchoArg(arg: string): string
+ return arg
+enddef
+def s:RefThis(): func
+ return function('EchoArg')
+enddef
+def s:ScriptPCall()
+ RefThis()("text")
+enddef
+
+def Test_disassemble_pcall()
+ var res = execute('disass s:ScriptPCall')
+ assert_match('<SNR>\d\+_ScriptPCall\_s*' ..
+ 'RefThis()("text")\_s*' ..
+ '\d DCALL <SNR>\d\+_RefThis(argc 0)\_s*' ..
+ '\d PUSHS "text"\_s*' ..
+ '\d PCALL top (argc 1)\_s*' ..
+ '\d PCALL end\_s*' ..
+ '\d DROP\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+
+def s:FuncWithForwardCall(): string
+ return g:DefinedLater("yes")
+enddef
+
+def DefinedLater(arg: string): string
+ return arg
+enddef
+
+def Test_disassemble_update_instr()
+ var res = execute('disass s:FuncWithForwardCall')
+ assert_match('FuncWithForwardCall\_s*' ..
+ 'return g:DefinedLater("yes")\_s*' ..
+ '\d PUSHS "yes"\_s*' ..
+ '\d DCALL DefinedLater(argc 1)\_s*' ..
+ '\d RETURN',
+ res)
+
+ # Calling the function will change UCALL into the faster DCALL
+ assert_equal('yes', FuncWithForwardCall())
+
+ res = execute('disass s:FuncWithForwardCall')
+ assert_match('FuncWithForwardCall\_s*' ..
+ 'return g:DefinedLater("yes")\_s*' ..
+ '\d PUSHS "yes"\_s*' ..
+ '\d DCALL DefinedLater(argc 1)\_s*' ..
+ '\d RETURN',
+ res)
+enddef
+
+
+def FuncWithDefault(l: number, arg: string = "default", nr = 77): string
+ return arg .. nr
+enddef
+
+def Test_disassemble_call_default()
+ var res = execute('disass FuncWithDefault')
+ assert_match('FuncWithDefault\_s*' ..
+ ' arg = "default"\_s*' ..
+ '\d JUMP_IF_ARG_SET arg\[-2\] -> 3\_s*' ..
+ '\d PUSHS "default"\_s*' ..
+ '\d STORE arg\[-2]\_s*' ..
+ ' nr = 77\_s*' ..
+ '3 JUMP_IF_ARG_SET arg\[-1\] -> 6\_s*' ..
+ '\d PUSHNR 77\_s*' ..
+ '\d STORE arg\[-1]\_s*' ..
+ ' return arg .. nr\_s*' ..
+ '6 LOAD arg\[-2]\_s*' ..
+ '\d LOAD arg\[-1]\_s*' ..
+ '\d 2STRING stack\[-1]\_s*' ..
+ '\d\+ CONCAT size 2\_s*' ..
+ '\d\+ RETURN',
+ res)
+enddef
+
+
+def s:HasEval()
+ if has("eval")
+ echo "yes"
+ else
+ echo "no"
+ endif
+enddef
+
+def s:HasNothing()
+ if has("nothing")
+ echo "yes"
+ else
+ echo "no"
+ endif
+enddef
+
+def s:HasSomething()
+ if has("nothing")
+ echo "nothing"
+ elseif has("something")
+ echo "something"
+ elseif has("eval")
+ echo "eval"
+ elseif has("less")
+ echo "less"
+ endif
+enddef
+
+def s:HasGuiRunning()
+ if has("gui_running")
+ echo "yes"
+ else
+ echo "no"
+ endif
+enddef
+
+def s:LenConstant(): number
+ return len("foo") + len("fighters")
+enddef
+
+def Test_disassemble_const_expr()
+ var instr = execute('disassemble LenConstant')
+ assert_match('LenConstant\_s*' ..
+ 'return len("foo") + len("fighters")\_s*' ..
+ '\d PUSHNR 11\_s*',
+ instr)
+ assert_notmatch('BCALL len', instr)
+
+ assert_equal("\nyes", execute('HasEval()'))
+ instr = execute('disassemble HasEval')
+ assert_match('HasEval\_s*' ..
+ 'if has("eval")\_s*' ..
+ 'echo "yes"\_s*' ..
+ '\d PUSHS "yes"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'else\_s*' ..
+ 'echo "no"\_s*' ..
+ 'endif\_s*',
+ instr)
+ assert_notmatch('JUMP', instr)
+
+ assert_equal("\nno", execute('HasNothing()'))
+ instr = execute('disassemble HasNothing')
+ assert_match('HasNothing\_s*' ..
+ 'if has("nothing")\_s*' ..
+ 'echo "yes"\_s*' ..
+ 'else\_s*' ..
+ 'echo "no"\_s*' ..
+ '\d PUSHS "no"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'endif',
+ instr)
+ assert_notmatch('PUSHS "yes"', instr)
+ assert_notmatch('JUMP', instr)
+
+ assert_equal("\neval", execute('HasSomething()'))
+ instr = execute('disassemble HasSomething')
+ assert_match('HasSomething.*' ..
+ 'if has("nothing")\_s*' ..
+ 'echo "nothing"\_s*' ..
+ 'elseif has("something")\_s*' ..
+ 'echo "something"\_s*' ..
+ 'elseif has("eval")\_s*' ..
+ 'echo "eval"\_s*' ..
+ '\d PUSHS "eval"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'elseif has("less").*' ..
+ 'echo "less"\_s*' ..
+ 'endif',
+ instr)
+ assert_notmatch('PUSHS "nothing"', instr)
+ assert_notmatch('PUSHS "something"', instr)
+ assert_notmatch('PUSHS "less"', instr)
+ assert_notmatch('JUMP', instr)
+
+ var result: string
+ var instr_expected: string
+ if has('gui')
+ if has('gui_running')
+ # GUI already running, always returns "yes"
+ result = "\nyes"
+ instr_expected = 'HasGuiRunning.*' ..
+ 'if has("gui_running")\_s*' ..
+ ' echo "yes"\_s*' ..
+ '\d PUSHS "yes"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'else\_s*' ..
+ ' echo "no"\_s*' ..
+ 'endif'
+ else
+ result = "\nno"
+ if has('unix')
+ # GUI not running but can start later, call has()
+ instr_expected = 'HasGuiRunning.*' ..
+ 'if has("gui_running")\_s*' ..
+ '\d PUSHS "gui_running"\_s*' ..
+ '\d BCALL has(argc 1)\_s*' ..
+ '\d COND2BOOL\_s*' ..
+ '\d JUMP_IF_FALSE -> \d\_s*' ..
+ ' echo "yes"\_s*' ..
+ '\d PUSHS "yes"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'else\_s*' ..
+ '\d JUMP -> \d\_s*' ..
+ ' echo "no"\_s*' ..
+ '\d PUSHS "no"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'endif'
+ else
+ # GUI not running, always return "no"
+ instr_expected = 'HasGuiRunning.*' ..
+ 'if has("gui_running")\_s*' ..
+ ' echo "yes"\_s*' ..
+ 'else\_s*' ..
+ ' echo "no"\_s*' ..
+ '\d PUSHS "no"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'endif'
+ endif
+ endif
+ else
+ # GUI not supported, always return "no"
+ result = "\nno"
+ instr_expected = 'HasGuiRunning.*' ..
+ 'if has("gui_running")\_s*' ..
+ ' echo "yes"\_s*' ..
+ 'else\_s*' ..
+ ' echo "no"\_s*' ..
+ '\d PUSHS "no"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ 'endif'
+ endif
+
+ assert_equal(result, execute('HasGuiRunning()'))
+ instr = execute('disassemble HasGuiRunning')
+ assert_match(instr_expected, instr)
+enddef
+
+def ReturnInIf(): string
+ if 1 < 0
+ return "maybe"
+ endif
+ if g:cond
+ return "yes"
+ else
+ return "no"
+ endif
+enddef
+
+def Test_disassemble_return_in_if()
+ var instr = execute('disassemble ReturnInIf')
+ assert_match('ReturnInIf\_s*' ..
+ 'if 1 < 0\_s*' ..
+ ' return "maybe"\_s*' ..
+ 'endif\_s*' ..
+ 'if g:cond\_s*' ..
+ '0 LOADG g:cond\_s*' ..
+ '1 COND2BOOL\_s*' ..
+ '2 JUMP_IF_FALSE -> 5\_s*' ..
+ 'return "yes"\_s*' ..
+ '3 PUSHS "yes"\_s*' ..
+ '4 RETURN\_s*' ..
+ 'else\_s*' ..
+ ' return "no"\_s*' ..
+ '5 PUSHS "no"\_s*' ..
+ '6 RETURN$',
+ instr)
+enddef
+
+def WithFunc()
+ var Funky1: func
+ var Funky2: func = function("len")
+ var Party2: func = funcref("UserFunc")
+enddef
+
+def Test_disassemble_function()
+ var instr = execute('disassemble WithFunc')
+ assert_match('WithFunc\_s*' ..
+ 'var Funky1: func\_s*' ..
+ '0 PUSHFUNC "\[none]"\_s*' ..
+ '1 STORE $0\_s*' ..
+ 'var Funky2: func = function("len")\_s*' ..
+ '2 PUSHS "len"\_s*' ..
+ '3 BCALL function(argc 1)\_s*' ..
+ '4 STORE $1\_s*' ..
+ 'var Party2: func = funcref("UserFunc")\_s*' ..
+ '\d PUSHS "UserFunc"\_s*' ..
+ '\d BCALL funcref(argc 1)\_s*' ..
+ '\d STORE $2\_s*' ..
+ '\d RETURN void',
+ instr)
+enddef
+
+if has('channel')
+ def WithChannel()
+ var job1: job
+ var job2: job = job_start("donothing")
+ var chan1: channel
+ enddef
+endif
+
+def Test_disassemble_channel()
+ CheckFeature channel
+
+ var instr = execute('disassemble WithChannel')
+ assert_match('WithChannel\_s*' ..
+ 'var job1: job\_s*' ..
+ '\d PUSHJOB "no process"\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var job2: job = job_start("donothing")\_s*' ..
+ '\d PUSHS "donothing"\_s*' ..
+ '\d BCALL job_start(argc 1)\_s*' ..
+ '\d STORE $1\_s*' ..
+ 'var chan1: channel\_s*' ..
+ '\d PUSHCHANNEL 0\_s*' ..
+ '\d STORE $2\_s*' ..
+ '\d RETURN void',
+ instr)
+enddef
+
+def s:WithLambda(): string
+ var F = (a) => "X" .. a .. "X"
+ return F("x")
+enddef
+
+def Test_disassemble_lambda()
+ assert_equal("XxX", WithLambda())
+ var instr = execute('disassemble WithLambda')
+ assert_match('WithLambda\_s*' ..
+ 'var F = (a) => "X" .. a .. "X"\_s*' ..
+ '\d FUNCREF <lambda>\d\+\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'return F("x")\_s*' ..
+ '\d PUSHS "x"\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PCALL (argc 1)\_s*' ..
+ '\d RETURN',
+ instr)
+
+ var name = substitute(instr, '.*\(<lambda>\d\+\).*', '\1', '')
+ instr = execute('disassemble ' .. name)
+ assert_match('<lambda>\d\+\_s*' ..
+ 'return "X" .. a .. "X"\_s*' ..
+ '\d PUSHS "X"\_s*' ..
+ '\d LOAD arg\[-1\]\_s*' ..
+ '\d 2STRING_ANY stack\[-1\]\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d PUSHS "X"\_s*' ..
+ '\d CONCAT size 2\_s*' ..
+ '\d RETURN',
+ instr)
+enddef
+
+def s:LambdaWithType(): number
+ var Ref = (a: number) => a + 10
+ return Ref(g:value)
+enddef
+
+def Test_disassemble_lambda_with_type()
+ g:value = 5
+ assert_equal(15, LambdaWithType())
+ var instr = execute('disassemble LambdaWithType')
+ assert_match('LambdaWithType\_s*' ..
+ 'var Ref = (a: number) => a + 10\_s*' ..
+ '\d FUNCREF <lambda>\d\+\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'return Ref(g:value)\_s*' ..
+ '\d LOADG g:value\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d CHECKTYPE number stack\[-2\] arg 1\_s*' ..
+ '\d PCALL (argc 1)\_s*' ..
+ '\d RETURN',
+ instr)
+enddef
+
+def NestedOuter()
+ def g:Inner()
+ echomsg "inner"
+ enddef
+enddef
+
+def Test_disassemble_nested_func()
+ var instr = execute('disassemble NestedOuter')
+ assert_match('NestedOuter\_s*' ..
+ 'def g:Inner()\_s*' ..
+ 'echomsg "inner"\_s*' ..
+ 'enddef\_s*' ..
+ '\d NEWFUNC <lambda>\d\+ Inner\_s*' ..
+ '\d RETURN void',
+ instr)
+enddef
+
+def NestedDefList()
+ def
+ def Info
+ def /Info
+ def /Info/
+enddef
+
+def Test_disassemble_nested_def_list()
+ var instr = execute('disassemble NestedDefList')
+ assert_match('NestedDefList\_s*' ..
+ 'def\_s*' ..
+ '\d DEF \_s*' ..
+ 'def Info\_s*' ..
+ '\d DEF Info\_s*' ..
+ 'def /Info\_s*' ..
+ '\d DEF /Info\_s*' ..
+ 'def /Info/\_s*' ..
+ '\d DEF /Info/\_s*' ..
+ '\d RETURN void',
+ instr)
+enddef
+
+def s:AndOr(arg: any): string
+ if arg == 1 && arg != 2 || arg == 4
+ return 'yes'
+ endif
+ return 'no'
+enddef
+
+def Test_disassemble_and_or()
+ assert_equal("yes", AndOr(1))
+ assert_equal("no", AndOr(2))
+ assert_equal("yes", AndOr(4))
+ var instr = execute('disassemble AndOr')
+ assert_match('AndOr\_s*' ..
+ 'if arg == 1 && arg != 2 || arg == 4\_s*' ..
+ '\d LOAD arg\[-1]\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d COMPAREANY ==\_s*' ..
+ '\d JUMP_IF_COND_FALSE -> \d\+\_s*' ..
+ '\d LOAD arg\[-1]\_s*' ..
+ '\d PUSHNR 2\_s*' ..
+ '\d COMPAREANY !=\_s*' ..
+ '\d JUMP_IF_COND_TRUE -> \d\+\_s*' ..
+ '\d LOAD arg\[-1]\_s*' ..
+ '\d\+ PUSHNR 4\_s*' ..
+ '\d\+ COMPAREANY ==\_s*' ..
+ '\d\+ JUMP_IF_FALSE -> \d\+',
+ instr)
+enddef
+
+def s:AndConstant(arg: any): string
+ if true && arg
+ return "yes"
+ endif
+ if false && arg
+ return "never"
+ endif
+ return "no"
+enddef
+
+def Test_disassemble_and_constant()
+ assert_equal("yes", AndConstant(1))
+ assert_equal("no", AndConstant(false))
+ var instr = execute('disassemble AndConstant')
+ assert_match('AndConstant\_s*' ..
+ 'if true && arg\_s*' ..
+ '0 LOAD arg\[-1\]\_s*' ..
+ '1 COND2BOOL\_s*' ..
+ '2 JUMP_IF_FALSE -> 5\_s*' ..
+ 'return "yes"\_s*' ..
+ '3 PUSHS "yes"\_s*' ..
+ '4 RETURN\_s*' ..
+ 'endif\_s*' ..
+ 'if false && arg\_s*' ..
+ 'return "never"\_s*' ..
+ 'endif\_s*' ..
+ 'return "no"\_s*' ..
+ '5 PUSHS "no"\_s*' ..
+ '6 RETURN',
+ instr)
+enddef
+
+def s:ForLoop(): list<number>
+ var res: list<number>
+ for i in range(3)
+ res->add(i)
+ endfor
+ return res
+enddef
+
+def Test_disassemble_for_loop()
+ assert_equal([0, 1, 2], ForLoop())
+ var instr = execute('disassemble ForLoop')
+ assert_match('ForLoop\_s*' ..
+ 'var res: list<number>\_s*' ..
+ '\d NEWLIST size 0\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+
+ 'for i in range(3)\_s*' ..
+ '\d STORE -1 in $1\_s*' ..
+ '\d PUSHNR 3\_s*' ..
+ '\d BCALL range(argc 1)\_s*' ..
+ '\d FOR $1 -> \d\+\_s*' ..
+ '\d STORE $3\_s*' ..
+
+ 'res->add(i)\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d LOAD $3\_s*' ..
+ '\d\+ LISTAPPEND\_s*' ..
+ '\d\+ DROP\_s*' ..
+
+ 'endfor\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+ '\d\+ DROP',
+ instr)
+enddef
+
+def s:ForLoopEval(): string
+ var res = ""
+ for str in eval('["one", "two"]')
+ res ..= str
+ endfor
+ return res
+enddef
+
+def Test_disassemble_for_loop_eval()
+ assert_equal('onetwo', ForLoopEval())
+ var instr = execute('disassemble ForLoopEval')
+ assert_match('ForLoopEval\_s*' ..
+ 'var res = ""\_s*' ..
+ '\d PUSHS ""\_s*' ..
+ '\d STORE $0\_s*' ..
+
+ 'for str in eval(''\["one", "two"\]'')\_s*' ..
+ '\d STORE -1 in $1\_s*' ..
+ '\d PUSHS "\["one", "two"\]"\_s*' ..
+ '\d BCALL eval(argc 1)\_s*' ..
+ '\d FOR $1 -> \d\+\_s*' ..
+ '\d STORE $3\_s*' ..
+
+ 'res ..= str\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ LOAD $3\_s*' ..
+ '\d 2STRING_ANY stack\[-1\]\_s*' ..
+ '\d\+ CONCAT size 2\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+
+ 'endfor\_s*' ..
+ '\d\+ JUMP -> 5\_s*' ..
+ '\d\+ DROP\_s*' ..
+
+ 'return res\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ RETURN',
+ instr)
+enddef
+
+def s:ForLoopUnpack()
+ for [x1, x2] in [[1, 2], [3, 4]]
+ echo x1 x2
+ endfor
+enddef
+
+def Test_disassemble_for_loop_unpack()
+ var instr = execute('disassemble ForLoopUnpack')
+ assert_match('ForLoopUnpack\_s*' ..
+ 'for \[x1, x2\] in \[\[1, 2\], \[3, 4\]\]\_s*' ..
+ '\d\+ STORE -1 in $0\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ PUSHNR 2\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ PUSHNR 3\_s*' ..
+ '\d\+ PUSHNR 4\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ FOR $0 -> 16\_s*' ..
+ '\d\+ UNPACK 2\_s*' ..
+ '\d\+ STORE $2\_s*' ..
+ '\d\+ STORE $3\_s*' ..
+
+ 'echo x1 x2\_s*' ..
+ '\d\+ LOAD $2\_s*' ..
+ '\d\+ LOAD $3\_s*' ..
+ '\d\+ ECHO 2\_s*' ..
+
+ 'endfor\_s*' ..
+ '\d\+ JUMP -> 8\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ instr)
+enddef
+
+def s:ForLoopContinue()
+ for nr in [1, 2]
+ try
+ echo "ok"
+ try
+ echo "deeper"
+ catch
+ continue
+ endtry
+ catch
+ echo "not ok"
+ endtry
+ endfor
+enddef
+
+def Test_disassemble_for_loop_continue()
+ var instr = execute('disassemble ForLoopContinue')
+ assert_match('ForLoopContinue\_s*' ..
+ 'for nr in \[1, 2]\_s*' ..
+ '0 STORE -1 in $0\_s*' ..
+ '1 PUSHNR 1\_s*' ..
+ '2 PUSHNR 2\_s*' ..
+ '3 NEWLIST size 2\_s*' ..
+ '4 FOR $0 -> 22\_s*' ..
+ '5 STORE $2\_s*' ..
+
+ 'try\_s*' ..
+ '6 TRY catch -> 17, endtry -> 20\_s*' ..
+
+ 'echo "ok"\_s*' ..
+ '7 PUSHS "ok"\_s*' ..
+ '8 ECHO 1\_s*' ..
+
+ 'try\_s*' ..
+ '9 TRY catch -> 13, endtry -> 15\_s*' ..
+
+ 'echo "deeper"\_s*' ..
+ '10 PUSHS "deeper"\_s*' ..
+ '11 ECHO 1\_s*' ..
+
+ 'catch\_s*' ..
+ '12 JUMP -> 15\_s*' ..
+ '13 CATCH\_s*' ..
+
+ 'continue\_s*' ..
+ '14 TRY-CONTINUE 2 levels -> 4\_s*' ..
+
+ 'endtry\_s*' ..
+ '15 ENDTRY\_s*' ..
+
+ 'catch\_s*' ..
+ '16 JUMP -> 20\_s*' ..
+ '17 CATCH\_s*' ..
+
+ 'echo "not ok"\_s*' ..
+ '18 PUSHS "not ok"\_s*' ..
+ '19 ECHO 1\_s*' ..
+
+ 'endtry\_s*' ..
+ '20 ENDTRY\_s*' ..
+
+ 'endfor\_s*' ..
+ '21 JUMP -> 4\_s*' ..
+ '\d\+ DROP\_s*' ..
+ '\d\+ RETURN void',
+ instr)
+enddef
+
+let g:number = 42
+
+def s:TypeCast()
+ var l: list<number> = [23, <number>g:number]
+enddef
+
+def Test_disassemble_typecast()
+ var instr = execute('disassemble TypeCast')
+ assert_match('TypeCast.*' ..
+ 'var l: list<number> = \[23, <number>g:number\].*' ..
+ '\d PUSHNR 23\_s*' ..
+ '\d LOADG g:number\_s*' ..
+ '\d CHECKTYPE number stack\[-1\]\_s*' ..
+ '\d NEWLIST size 2\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ '\d RETURN void\_s*',
+ instr)
+enddef
+
+def s:Computing()
+ var nr = 3
+ var nrres = nr + 7
+ nrres = nr - 7
+ nrres = nr * 7
+ nrres = nr / 7
+ nrres = nr % 7
+
+ var anyres = g:number + 7
+ anyres = g:number - 7
+ anyres = g:number * 7
+ anyres = g:number / 7
+ anyres = g:number % 7
+
+ var fl = 3.0
+ var flres = fl + 7.0
+ flres = fl - 7.0
+ flres = fl * 7.0
+ flres = fl / 7.0
+enddef
+
+def Test_disassemble_computing()
+ var instr = execute('disassemble Computing')
+ assert_match('Computing.*' ..
+ 'var nr = 3.*' ..
+ '\d STORE 3 in $0.*' ..
+ 'var nrres = nr + 7.*' ..
+ '\d LOAD $0.*' ..
+ '\d PUSHNR 7.*' ..
+ '\d OPNR +.*' ..
+ '\d STORE $1.*' ..
+ 'nrres = nr - 7.*' ..
+ '\d OPNR -.*' ..
+ 'nrres = nr \* 7.*' ..
+ '\d OPNR \*.*' ..
+ 'nrres = nr / 7.*' ..
+ '\d OPNR /.*' ..
+ 'nrres = nr % 7.*' ..
+ '\d OPNR %.*' ..
+ 'var anyres = g:number + 7.*' ..
+ '\d LOADG g:number.*' ..
+ '\d PUSHNR 7.*' ..
+ '\d OPANY +.*' ..
+ '\d STORE $2.*' ..
+ 'anyres = g:number - 7.*' ..
+ '\d OPANY -.*' ..
+ 'anyres = g:number \* 7.*' ..
+ '\d OPANY \*.*' ..
+ 'anyres = g:number / 7.*' ..
+ '\d OPANY /.*' ..
+ 'anyres = g:number % 7.*' ..
+ '\d OPANY %.*',
+ instr)
+ assert_match('Computing.*' ..
+ 'var fl = 3.0.*' ..
+ '\d PUSHF 3.0.*' ..
+ '\d STORE $3.*' ..
+ 'var flres = fl + 7.0.*' ..
+ '\d LOAD $3.*' ..
+ '\d PUSHF 7.0.*' ..
+ '\d OPFLOAT +.*' ..
+ '\d STORE $4.*' ..
+ 'flres = fl - 7.0.*' ..
+ '\d OPFLOAT -.*' ..
+ 'flres = fl \* 7.0.*' ..
+ '\d OPFLOAT \*.*' ..
+ 'flres = fl / 7.0.*' ..
+ '\d OPFLOAT /.*',
+ instr)
+enddef
+
+def s:AddListBlob()
+ var reslist = [1, 2] + [3, 4]
+ var resblob = 0z1122 + 0z3344
+enddef
+
+def Test_disassemble_add_list_blob()
+ var instr = execute('disassemble AddListBlob')
+ assert_match('AddListBlob.*' ..
+ 'var reslist = \[1, 2] + \[3, 4].*' ..
+ '\d PUSHNR 1.*' ..
+ '\d PUSHNR 2.*' ..
+ '\d NEWLIST size 2.*' ..
+ '\d PUSHNR 3.*' ..
+ '\d PUSHNR 4.*' ..
+ '\d NEWLIST size 2.*' ..
+ '\d ADDLIST.*' ..
+ '\d STORE $.*.*' ..
+ 'var resblob = 0z1122 + 0z3344.*' ..
+ '\d PUSHBLOB 0z1122.*' ..
+ '\d PUSHBLOB 0z3344.*' ..
+ '\d ADDBLOB.*' ..
+ '\d STORE $.*',
+ instr)
+enddef
+
+let g:aa = 'aa'
+def s:ConcatString(): string
+ var res = g:aa .. "bb"
+ return res
+enddef
+
+def Test_disassemble_concat()
+ var instr = execute('disassemble ConcatString')
+ assert_match('ConcatString.*' ..
+ 'var res = g:aa .. "bb".*' ..
+ '\d LOADG g:aa.*' ..
+ '\d PUSHS "bb".*' ..
+ '\d 2STRING_ANY stack\[-2].*' ..
+ '\d CONCAT.*' ..
+ '\d STORE $.*',
+ instr)
+ assert_equal('aabb', ConcatString())
+enddef
+
+def s:StringIndex(): string
+ var s = "abcd"
+ var res = s[1]
+ return res
+enddef
+
+def Test_disassemble_string_index()
+ var instr = execute('disassemble StringIndex')
+ assert_match('StringIndex\_s*' ..
+ 'var s = "abcd"\_s*' ..
+ '\d PUSHS "abcd"\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var res = s\[1]\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d STRINDEX\_s*' ..
+ '\d STORE $1\_s*',
+ instr)
+ assert_equal('b', StringIndex())
+enddef
+
+def s:StringSlice(): string
+ var s = "abcd"
+ var res = s[1 : 8]
+ return res
+enddef
+
+def Test_disassemble_string_slice()
+ var instr = execute('disassemble StringSlice')
+ assert_match('StringSlice\_s*' ..
+ 'var s = "abcd"\_s*' ..
+ '\d PUSHS "abcd"\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var res = s\[1 : 8]\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHNR 8\_s*' ..
+ '\d STRSLICE\_s*' ..
+ '\d STORE $1\_s*',
+ instr)
+ assert_equal('bcd', StringSlice())
+enddef
+
+def s:ListIndex(): number
+ var l = [1, 2, 3]
+ var res = l[1]
+ return res
+enddef
+
+def Test_disassemble_list_index()
+ var instr = execute('disassemble ListIndex')
+ assert_match('ListIndex\_s*' ..
+ 'var l = \[1, 2, 3]\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHNR 2\_s*' ..
+ '\d PUSHNR 3\_s*' ..
+ '\d NEWLIST size 3\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var res = l\[1]\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d LISTINDEX\_s*' ..
+ '\d STORE $1\_s*',
+ instr)
+ assert_equal(2, ListIndex())
+enddef
+
+def s:ListSlice(): list<number>
+ var l = [1, 2, 3]
+ var res = l[1 : 8]
+ return res
+enddef
+
+def Test_disassemble_list_slice()
+ var instr = execute('disassemble ListSlice')
+ assert_match('ListSlice\_s*' ..
+ 'var l = \[1, 2, 3]\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHNR 2\_s*' ..
+ '\d PUSHNR 3\_s*' ..
+ '\d NEWLIST size 3\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var res = l\[1 : 8]\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHNR 8\_s*' ..
+ '\d\+ LISTSLICE\_s*' ..
+ '\d\+ SETTYPE list<number>\_s*' ..
+ '\d\+ STORE $1\_s*',
+ instr)
+ assert_equal([2, 3], ListSlice())
+enddef
+
+def s:DictMember(): number
+ var d = {item: 1}
+ var res = d.item
+ res = d["item"]
+ return res
+enddef
+
+def Test_disassemble_dict_member()
+ var instr = execute('disassemble DictMember')
+ assert_match('DictMember\_s*' ..
+ 'var d = {item: 1}\_s*' ..
+ '\d PUSHS "item"\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d SETTYPE dict<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var res = d.item\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ MEMBER item\_s*' ..
+ '\d\+ USEDICT\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ 'res = d\["item"\]\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ PUSHS "item"\_s*' ..
+ '\d\+ MEMBER\_s*' ..
+ '\d\+ USEDICT\_s*' ..
+ '\d\+ STORE $1\_s*',
+ instr)
+ assert_equal(1, DictMember())
+enddef
+
+let somelist = [1, 2, 3, 4, 5]
+def s:AnyIndex(): number
+ var res = g:somelist[2]
+ return res
+enddef
+
+def Test_disassemble_any_index()
+ var instr = execute('disassemble AnyIndex')
+ assert_match('AnyIndex\_s*' ..
+ 'var res = g:somelist\[2\]\_s*' ..
+ '\d LOADG g:somelist\_s*' ..
+ '\d PUSHNR 2\_s*' ..
+ '\d ANYINDEX\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'return res\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d CHECKTYPE number stack\[-1\]\_s*' ..
+ '\d RETURN',
+ instr)
+ assert_equal(3, AnyIndex())
+enddef
+
+def s:AnySlice(): list<number>
+ var res = g:somelist[1 : 3]
+ return res
+enddef
+
+def Test_disassemble_any_slice()
+ var instr = execute('disassemble AnySlice')
+ assert_match('AnySlice\_s*' ..
+ 'var res = g:somelist\[1 : 3\]\_s*' ..
+ '\d LOADG g:somelist\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHNR 3\_s*' ..
+ '\d ANYSLICE\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'return res\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d CHECKTYPE list<number> stack\[-1\]\_s*' ..
+ '\d RETURN',
+ instr)
+ assert_equal([2, 3, 4], AnySlice())
+enddef
+
+def s:NegateNumber(): number
+ g:nr = 9
+ var plus = +g:nr
+ var minus = -g:nr
+ return minus
+enddef
+
+def Test_disassemble_negate_number()
+ var instr = execute('disassemble NegateNumber')
+ assert_match('NegateNumber\_s*' ..
+ 'g:nr = 9\_s*' ..
+ '\d PUSHNR 9\_s*' ..
+ '\d STOREG g:nr\_s*' ..
+ 'var plus = +g:nr\_s*' ..
+ '\d LOADG g:nr\_s*' ..
+ '\d CHECKTYPE number stack\[-1\]\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var minus = -g:nr\_s*' ..
+ '\d LOADG g:nr\_s*' ..
+ '\d CHECKTYPE number stack\[-1\]\_s*' ..
+ '\d NEGATENR\_s*' ..
+ '\d STORE $1\_s*',
+ instr)
+ assert_equal(-9, NegateNumber())
+enddef
+
+def s:InvertBool(): bool
+ var flag = true
+ var invert = !flag
+ var res = !!flag
+ return res
+enddef
+
+def Test_disassemble_invert_bool()
+ var instr = execute('disassemble InvertBool')
+ assert_match('InvertBool\_s*' ..
+ 'var flag = true\_s*' ..
+ '\d PUSH true\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'var invert = !flag\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d INVERT -1 (!val)\_s*' ..
+ '\d STORE $1\_s*' ..
+ 'var res = !!flag\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d 2BOOL -1 (!!val)\_s*' ..
+ '\d STORE $2\_s*',
+ instr)
+ assert_equal(true, InvertBool())
+enddef
+
+def s:ReturnBool(): bool
+ var one = 1
+ var zero = 0
+ var none: number
+ var name: bool = one && zero || one
+ return name
+enddef
+
+def Test_disassemble_return_bool()
+ var instr = execute('disassemble ReturnBool')
+ assert_match('ReturnBool\_s*' ..
+ 'var one = 1\_s*' ..
+ '0 STORE 1 in $0\_s*' ..
+ 'var zero = 0\_s*' ..
+ 'var none: number\_s*' ..
+ 'var name: bool = one && zero || one\_s*' ..
+ '1 LOAD $0\_s*' ..
+ '2 COND2BOOL\_s*' ..
+ '3 JUMP_IF_COND_FALSE -> 6\_s*' ..
+ '4 LOAD $1\_s*' ..
+ '5 COND2BOOL\_s*' ..
+ '6 JUMP_IF_COND_TRUE -> 9\_s*' ..
+ '7 LOAD $0\_s*' ..
+ '8 COND2BOOL\_s*' ..
+ '9 STORE $3\_s*' ..
+ 'return name\_s*' ..
+ '\d\+ LOAD $3\_s*' ..
+ '\d\+ RETURN',
+ instr)
+ assert_equal(true, InvertBool())
+enddef
+
+def s:AutoInit()
+ var t: number
+ t = 1
+ t = 0
+enddef
+
+def Test_disassemble_auto_init()
+ var instr = execute('disassemble AutoInit')
+ assert_match('AutoInit\_s*' ..
+ 'var t: number\_s*' ..
+ 't = 1\_s*' ..
+ '\d STORE 1 in $0\_s*' ..
+ 't = 0\_s*' ..
+ '\d STORE 0 in $0\_s*' ..
+ '\d\+ RETURN void',
+ instr)
+enddef
+
+def Test_disassemble_compare()
+ var cases = [
+ ['true == isFalse', 'COMPAREBOOL =='],
+ ['true != isFalse', 'COMPAREBOOL !='],
+ ['v:none == isNull', 'COMPARESPECIAL =='],
+ ['v:none != isNull', 'COMPARESPECIAL !='],
+ ['"text" == isNull', 'COMPARENULL =='],
+ ['"text" != isNull', 'COMPARENULL !='],
+
+ ['111 == aNumber', 'COMPARENR =='],
+ ['111 != aNumber', 'COMPARENR !='],
+ ['111 > aNumber', 'COMPARENR >'],
+ ['111 < aNumber', 'COMPARENR <'],
+ ['111 >= aNumber', 'COMPARENR >='],
+ ['111 <= aNumber', 'COMPARENR <='],
+ ['111 =~ aNumber', 'COMPARENR =\~'],
+ ['111 !~ aNumber', 'COMPARENR !\~'],
+
+ ['"xx" != aString', 'COMPARESTRING !='],
+ ['"xx" > aString', 'COMPARESTRING >'],
+ ['"xx" < aString', 'COMPARESTRING <'],
+ ['"xx" >= aString', 'COMPARESTRING >='],
+ ['"xx" <= aString', 'COMPARESTRING <='],
+ ['"xx" =~ aString', 'COMPARESTRING =\~'],
+ ['"xx" !~ aString', 'COMPARESTRING !\~'],
+ ['"xx" is aString', 'COMPARESTRING is'],
+ ['"xx" isnot aString', 'COMPARESTRING isnot'],
+
+ ['0z11 == aBlob', 'COMPAREBLOB =='],
+ ['0z11 != aBlob', 'COMPAREBLOB !='],
+ ['0z11 is aBlob', 'COMPAREBLOB is'],
+ ['0z11 isnot aBlob', 'COMPAREBLOB isnot'],
+
+ ['[1, 2] == aList', 'COMPARELIST =='],
+ ['[1, 2] != aList', 'COMPARELIST !='],
+ ['[1, 2] is aList', 'COMPARELIST is'],
+ ['[1, 2] isnot aList', 'COMPARELIST isnot'],
+
+ ['{a: 1} == aDict', 'COMPAREDICT =='],
+ ['{a: 1} != aDict', 'COMPAREDICT !='],
+ ['{a: 1} is aDict', 'COMPAREDICT is'],
+ ['{a: 1} isnot aDict', 'COMPAREDICT isnot'],
+
+ ['(() => 33) == (() => 44)', 'COMPAREFUNC =='],
+ ['(() => 33) != (() => 44)', 'COMPAREFUNC !='],
+ ['(() => 33) is (() => 44)', 'COMPAREFUNC is'],
+ ['(() => 33) isnot (() => 44)', 'COMPAREFUNC isnot'],
+
+ ['77 == g:xx', 'COMPAREANY =='],
+ ['77 != g:xx', 'COMPAREANY !='],
+ ['77 > g:xx', 'COMPAREANY >'],
+ ['77 < g:xx', 'COMPAREANY <'],
+ ['77 >= g:xx', 'COMPAREANY >='],
+ ['77 <= g:xx', 'COMPAREANY <='],
+ ['77 =~ g:xx', 'COMPAREANY =\~'],
+ ['77 !~ g:xx', 'COMPAREANY !\~'],
+ ['77 is g:xx', 'COMPAREANY is'],
+ ['77 isnot g:xx', 'COMPAREANY isnot'],
+ ]
+ var floatDecl = ''
+ cases->extend([
+ ['1.1 == aFloat', 'COMPAREFLOAT =='],
+ ['1.1 != aFloat', 'COMPAREFLOAT !='],
+ ['1.1 > aFloat', 'COMPAREFLOAT >'],
+ ['1.1 < aFloat', 'COMPAREFLOAT <'],
+ ['1.1 >= aFloat', 'COMPAREFLOAT >='],
+ ['1.1 <= aFloat', 'COMPAREFLOAT <='],
+ ['1.1 =~ aFloat', 'COMPAREFLOAT =\~'],
+ ['1.1 !~ aFloat', 'COMPAREFLOAT !\~'],
+ ])
+ floatDecl = 'var aFloat = 2.2'
+
+ var nr = 1
+ for case in cases
+ # declare local variables to get a non-constant with the right type
+ writefile(['def TestCase' .. nr .. '()',
+ ' var isFalse = false',
+ ' var isNull = v:null',
+ ' var aNumber = 222',
+ ' var aString = "yy"',
+ ' var aBlob = 0z22',
+ ' var aList = [3, 4]',
+ ' var aDict = {x: 2}',
+ floatDecl,
+ ' if ' .. case[0],
+ ' echo 42',
+ ' endif',
+ 'enddef'], 'Xdisassemble')
+ source Xdisassemble
+ var instr = execute('disassemble TestCase' .. nr)
+ assert_match('TestCase' .. nr .. '.*' ..
+ 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' ..
+ '\d \(PUSH\|FUNCREF\).*' ..
+ '\d \(PUSH\|FUNCREF\|LOAD\).*' ..
+ '\d ' .. case[1] .. '.*' ..
+ '\d JUMP_IF_FALSE -> \d\+.*',
+ instr)
+
+ nr += 1
+ endfor
+
+ delete('Xdisassemble')
+enddef
+
+def s:FalsyOp()
+ echo g:flag ?? "yes"
+ echo [] ?? "empty list"
+ echo "" ?? "empty string"
+enddef
+
+def Test_disassemble_falsy_op()
+ var res = execute('disass s:FalsyOp')
+ assert_match('\<SNR>\d*_FalsyOp\_s*' ..
+ 'echo g:flag ?? "yes"\_s*' ..
+ '0 LOADG g:flag\_s*' ..
+ '1 JUMP_AND_KEEP_IF_TRUE -> 3\_s*' ..
+ '2 PUSHS "yes"\_s*' ..
+ '3 ECHO 1\_s*' ..
+ 'echo \[\] ?? "empty list"\_s*' ..
+ '4 NEWLIST size 0\_s*' ..
+ '5 JUMP_AND_KEEP_IF_TRUE -> 7\_s*' ..
+ '6 PUSHS "empty list"\_s*' ..
+ '7 ECHO 1\_s*' ..
+ 'echo "" ?? "empty string"\_s*' ..
+ '\d\+ PUSHS "empty string"\_s*' ..
+ '\d\+ ECHO 1\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def Test_disassemble_compare_const()
+ var cases = [
+ ['"xx" == "yy"', false],
+ ['"aa" == "aa"', true],
+ ['has("eval") ? true : false', true],
+ ['has("asdf") ? true : false', false],
+ ]
+
+ var nr = 1
+ for case in cases
+ writefile(['def TestCase' .. nr .. '()',
+ ' if ' .. case[0],
+ ' echo 42',
+ ' endif',
+ 'enddef'], 'Xdisassemble')
+ source Xdisassemble
+ var instr = execute('disassemble TestCase' .. nr)
+ if case[1]
+ # condition true, "echo 42" executed
+ assert_match('TestCase' .. nr .. '.*' ..
+ 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '.*' ..
+ '\d PUSHNR 42.*' ..
+ '\d ECHO 1.*' ..
+ '\d RETURN void',
+ instr)
+ else
+ # condition false, function just returns
+ assert_match('TestCase' .. nr .. '.*' ..
+ 'if ' .. substitute(case[0], '[[~]', '\\\0', 'g') .. '[ \n]*' ..
+ 'echo 42[ \n]*' ..
+ 'endif[ \n]*' ..
+ '\d RETURN void',
+ instr)
+ endif
+
+ nr += 1
+ endfor
+
+ delete('Xdisassemble')
+enddef
+
+def s:Execute()
+ execute 'help vim9.txt'
+ var cmd = 'help vim9.txt'
+ execute cmd
+ var tag = 'vim9.txt'
+ execute 'help ' .. tag
+enddef
+
+def Test_disassemble_execute()
+ var res = execute('disass s:Execute')
+ assert_match('\<SNR>\d*_Execute\_s*' ..
+ "execute 'help vim9.txt'\\_s*" ..
+ '\d PUSHS "help vim9.txt"\_s*' ..
+ '\d EXECUTE 1\_s*' ..
+ "var cmd = 'help vim9.txt'\\_s*" ..
+ '\d PUSHS "help vim9.txt"\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'execute cmd\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d EXECUTE 1\_s*' ..
+ "var tag = 'vim9.txt'\\_s*" ..
+ '\d PUSHS "vim9.txt"\_s*' ..
+ '\d STORE $1\_s*' ..
+ "execute 'help ' .. tag\\_s*" ..
+ '\d\+ PUSHS "help "\_s*' ..
+ '\d\+ LOAD $1\_s*' ..
+ '\d\+ CONCAT size 2\_s*' ..
+ '\d\+ EXECUTE 1\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:OnlyRange()
+ :$
+ :123
+ :'m
+enddef
+
+def Test_disassemble_range_only()
+ var res = execute('disass s:OnlyRange')
+ assert_match('\<SNR>\d*_OnlyRange\_s*' ..
+ ':$\_s*' ..
+ '\d EXECRANGE $\_s*' ..
+ ':123\_s*' ..
+ '\d EXECRANGE 123\_s*' ..
+ ':''m\_s*' ..
+ '\d EXECRANGE ''m\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:StoreRange()
+ var l = [1, 2]
+ l[0 : 1] = [7, 8]
+enddef
+
+def Test_disassemble_store_range()
+ var res = execute('disass s:StoreRange')
+ assert_match('\<SNR>\d*_StoreRange\_s*' ..
+ 'var l = \[1, 2]\_s*' ..
+ '\d PUSHNR 1\_s*' ..
+ '\d PUSHNR 2\_s*' ..
+ '\d NEWLIST size 2\_s*' ..
+ '\d SETTYPE list<number>\_s*' ..
+ '\d STORE $0\_s*' ..
+
+ 'l\[0 : 1] = \[7, 8]\_s*' ..
+ '\d\+ PUSHNR 7\_s*' ..
+ '\d\+ PUSHNR 8\_s*' ..
+ '\d\+ NEWLIST size 2\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ PUSHNR 1\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ STORERANGE\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:Echomsg()
+ echomsg 'some' 'message'
+ echoconsole 'nothing'
+ echoerr 'went' .. 'wrong'
+ var local = 'window'
+ echowin 'in' local
+ :5echowin 'five'
+enddef
+
+def Test_disassemble_echomsg()
+ var res = execute('disass s:Echomsg')
+ assert_match('\<SNR>\d*_Echomsg\_s*' ..
+ "echomsg 'some' 'message'\\_s*" ..
+ '\d PUSHS "some"\_s*' ..
+ '\d PUSHS "message"\_s*' ..
+ '\d ECHOMSG 2\_s*' ..
+ "echoconsole 'nothing'\\_s*" ..
+ '\d PUSHS "nothing"\_s*' ..
+ '\d ECHOCONSOLE 1\_s*' ..
+ "echoerr 'went' .. 'wrong'\\_s*" ..
+ '\d PUSHS "wentwrong"\_s*' ..
+ '\d ECHOERR 1\_s*' ..
+ "var local = 'window'\\_s*" ..
+ '\d\+ PUSHS "window"\_s*' ..
+ '\d\+ STORE $0\_s*' ..
+ "echowin 'in' local\\_s*" ..
+ '\d\+ PUSHS "in"\_s*' ..
+ '\d\+ LOAD $0\_s*' ..
+ '\d\+ ECHOWINDOW 2\_s*' ..
+ ":5echowin 'five'\\_s*" ..
+ '\d\+ PUSHS "five"\_s*' ..
+ '\d\+ ECHOWINDOW 1 (5 sec)\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def SomeStringArg(arg: string)
+ echo arg
+enddef
+
+def SomeAnyArg(arg: any)
+ echo arg
+enddef
+
+def SomeStringArgAndReturn(arg: string): string
+ return arg
+enddef
+
+def Test_display_func()
+ var res1 = execute('function SomeStringArg')
+ assert_match('.* def SomeStringArg(arg: string)\_s*' ..
+ '\d *echo arg.*' ..
+ ' *enddef',
+ res1)
+
+ var res2 = execute('function SomeAnyArg')
+ assert_match('.* def SomeAnyArg(arg: any)\_s*' ..
+ '\d *echo arg\_s*' ..
+ ' *enddef',
+ res2)
+
+ var res3 = execute('function SomeStringArgAndReturn')
+ assert_match('.* def SomeStringArgAndReturn(arg: string): string\_s*' ..
+ '\d *return arg\_s*' ..
+ ' *enddef',
+ res3)
+enddef
+
+def Test_vim9script_forward_func()
+ var lines =<< trim END
+ vim9script
+ def FuncOne(): string
+ return FuncTwo()
+ enddef
+ def FuncTwo(): string
+ return 'two'
+ enddef
+ g:res_FuncOne = execute('disass FuncOne')
+ END
+ writefile(lines, 'Xdisassemble', 'D')
+ source Xdisassemble
+
+ # check that the first function calls the second with DCALL
+ assert_match('\<SNR>\d*_FuncOne\_s*' ..
+ 'return FuncTwo()\_s*' ..
+ '\d DCALL <SNR>\d\+_FuncTwo(argc 0)\_s*' ..
+ '\d RETURN',
+ g:res_FuncOne)
+
+ unlet g:res_FuncOne
+enddef
+
+def s:ConcatStrings(): string
+ return 'one' .. 'two' .. 'three'
+enddef
+
+def s:ComputeConst(): number
+ return 2 + 3 * 4 / 6 + 7
+enddef
+
+def s:ComputeConstParen(): number
+ return ((2 + 4) * (8 / 2)) / (3 + 4)
+enddef
+
+def Test_simplify_const_expr()
+ var res = execute('disass s:ConcatStrings')
+ assert_match('<SNR>\d*_ConcatStrings\_s*' ..
+ "return 'one' .. 'two' .. 'three'\\_s*" ..
+ '\d PUSHS "onetwothree"\_s*' ..
+ '\d RETURN',
+ res)
+
+ res = execute('disass s:ComputeConst')
+ assert_match('<SNR>\d*_ComputeConst\_s*' ..
+ 'return 2 + 3 \* 4 / 6 + 7\_s*' ..
+ '\d PUSHNR 11\_s*' ..
+ '\d RETURN',
+ res)
+
+ res = execute('disass s:ComputeConstParen')
+ assert_match('<SNR>\d*_ComputeConstParen\_s*' ..
+ 'return ((2 + 4) \* (8 / 2)) / (3 + 4)\_s*' ..
+ '\d PUSHNR 3\>\_s*' ..
+ '\d RETURN',
+ res)
+enddef
+
+def s:CallAppend()
+ eval "some text"->append(2)
+enddef
+
+def Test_shuffle()
+ var res = execute('disass s:CallAppend')
+ assert_match('<SNR>\d*_CallAppend\_s*' ..
+ 'eval "some text"->append(2)\_s*' ..
+ '\d PUSHS "some text"\_s*' ..
+ '\d PUSHNR 2\_s*' ..
+ '\d SHUFFLE 2 up 1\_s*' ..
+ '\d BCALL append(argc 2)\_s*' ..
+ '\d DROP\_s*' ..
+ '\d RETURN void',
+ res)
+enddef
+
+
+def s:SilentMessage()
+ silent echomsg "text"
+ silent! echoerr "error"
+enddef
+
+def Test_silent()
+ var res = execute('disass s:SilentMessage')
+ assert_match('<SNR>\d*_SilentMessage\_s*' ..
+ 'silent echomsg "text"\_s*' ..
+ '\d CMDMOD silent\_s*' ..
+ '\d PUSHS "text"\_s*' ..
+ '\d ECHOMSG 1\_s*' ..
+ '\d CMDMOD_REV\_s*' ..
+ 'silent! echoerr "error"\_s*' ..
+ '\d CMDMOD silent!\_s*' ..
+ '\d PUSHS "error"\_s*' ..
+ '\d ECHOERR 1\_s*' ..
+ '\d CMDMOD_REV\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:SilentIf()
+ silent if 4 == g:five
+ silent elseif 4 == g:five
+ endif
+enddef
+
+def Test_silent_if()
+ var res = execute('disass s:SilentIf')
+ assert_match('<SNR>\d*_SilentIf\_s*' ..
+ 'silent if 4 == g:five\_s*' ..
+ '\d\+ CMDMOD silent\_s*' ..
+ '\d\+ PUSHNR 4\_s*' ..
+ '\d\+ LOADG g:five\_s*' ..
+ '\d\+ COMPAREANY ==\_s*' ..
+ '\d\+ CMDMOD_REV\_s*' ..
+ '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+ 'silent elseif 4 == g:five\_s*' ..
+ '\d\+ JUMP -> \d\+\_s*' ..
+ '\d\+ CMDMOD silent\_s*' ..
+ '\d\+ PUSHNR 4\_s*' ..
+ '\d\+ LOADG g:five\_s*' ..
+ '\d\+ COMPAREANY ==\_s*' ..
+ '\d\+ CMDMOD_REV\_s*' ..
+ '\d\+ JUMP_IF_FALSE -> \d\+\_s*' ..
+ 'endif\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+def s:SilentFor()
+ silent for i in [0]
+ endfor
+enddef
+
+def Test_silent_for()
+ var res = execute('disass s:SilentFor')
+ assert_match('<SNR>\d*_SilentFor\_s*' ..
+ 'silent for i in \[0\]\_s*' ..
+ '\d CMDMOD silent\_s*' ..
+ '\d STORE -1 in $0\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d NEWLIST size 1\_s*' ..
+ '\d CMDMOD_REV\_s*' ..
+ '5 FOR $0 -> 8\_s*' ..
+ '\d STORE $2\_s*' ..
+
+ 'endfor\_s*' ..
+ '\d JUMP -> 5\_s*' ..
+ '8 DROP\_s*' ..
+ '\d RETURN void\_s*',
+ res)
+enddef
+
+def s:SilentWhile()
+ silent while g:not
+ endwhile
+enddef
+
+def Test_silent_while()
+ var res = execute('disass s:SilentWhile')
+ assert_match('<SNR>\d*_SilentWhile\_s*' ..
+ 'silent while g:not\_s*' ..
+ '0 CMDMOD silent\_s*' ..
+ '\d LOADG g:not\_s*' ..
+ '\d COND2BOOL\_s*' ..
+ '\d CMDMOD_REV\_s*' ..
+ '\d WHILE $0 -> 6\_s*' ..
+
+ 'endwhile\_s*' ..
+ '\d JUMP -> 0\_s*' ..
+ '6 RETURN void\_s*',
+ res)
+enddef
+
+def s:SilentReturn(): string
+ silent return "done"
+enddef
+
+def Test_silent_return()
+ var res = execute('disass s:SilentReturn')
+ assert_match('<SNR>\d*_SilentReturn\_s*' ..
+ 'silent return "done"\_s*' ..
+ '\d CMDMOD silent\_s*' ..
+ '\d PUSHS "done"\_s*' ..
+ '\d CMDMOD_REV\_s*' ..
+ '\d RETURN',
+ res)
+enddef
+
+def s:Profiled(): string
+ # comment
+ echo "profiled"
+ # comment
+ var some = "some text"
+ return "done"
+enddef
+
+def Test_profiled()
+ if !has('profile')
+ MissingFeature 'profile'
+ endif
+ var res = execute('disass profile s:Profiled')
+ assert_match('<SNR>\d*_Profiled\_s*' ..
+ '# comment\_s*' ..
+ 'echo "profiled"\_s*' ..
+ '\d PROFILE START line 2\_s*' ..
+ '\d PUSHS "profiled"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ '# comment\_s*' ..
+ 'var some = "some text"\_s*' ..
+ '\d PROFILE END\_s*' ..
+ '\d PROFILE START line 4\_s*' ..
+ '\d PUSHS "some text"\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'return "done"\_s*' ..
+ '\d PROFILE END\_s*' ..
+ '\d PROFILE START line 5\_s*' ..
+ '\d PUSHS "done"\_s*' ..
+ '\d\+ RETURN\_s*' ..
+ '\d\+ PROFILE END',
+ res)
+enddef
+
+def Test_debugged()
+ var res = execute('disass debug s:Profiled')
+ assert_match('<SNR>\d*_Profiled\_s*' ..
+ '# comment\_s*' ..
+ 'echo "profiled"\_s*' ..
+ '\d DEBUG line 1-2 varcount 0\_s*' ..
+ '\d PUSHS "profiled"\_s*' ..
+ '\d ECHO 1\_s*' ..
+ '# comment\_s*' ..
+ 'var some = "some text"\_s*' ..
+ '\d DEBUG line 3-4 varcount 0\_s*' ..
+ '\d PUSHS "some text"\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'return "done"\_s*' ..
+ '\d DEBUG line 5-5 varcount 1\_s*' ..
+ '\d PUSHS "done"\_s*' ..
+ '\d RETURN\_s*',
+ res)
+enddef
+
+def s:ElseifConstant()
+ if g:value
+ echo "one"
+ elseif true
+ echo "true"
+ elseif false
+ echo "false"
+ endif
+ if 0
+ echo "yes"
+ elseif 0
+ echo "no"
+ endif
+enddef
+
+def Test_debug_elseif_constant()
+ var res = execute('disass debug s:ElseifConstant')
+ assert_match('<SNR>\d*_ElseifConstant\_s*' ..
+ 'if g:value\_s*' ..
+ '0 DEBUG line 1-1 varcount 0\_s*' ..
+ '1 LOADG g:value\_s*' ..
+ '2 COND2BOOL\_s*' ..
+ '3 JUMP_IF_FALSE -> 8\_s*' ..
+ 'echo "one"\_s*' ..
+ '4 DEBUG line 2-2 varcount 0\_s*' ..
+ '5 PUSHS "one"\_s*' ..
+ '6 ECHO 1\_s*' ..
+ 'elseif true\_s*' ..
+ '7 JUMP -> 12\_s*' ..
+ '8 DEBUG line 3-3 varcount 0\_s*' ..
+ 'echo "true"\_s*' ..
+ '9 DEBUG line 4-4 varcount 0\_s*' ..
+ '10 PUSHS "true"\_s*' ..
+ '11 ECHO 1\_s*' ..
+ 'elseif false\_s*' ..
+ 'echo "false"\_s*' ..
+ 'endif\_s*' ..
+ 'if 0\_s*' ..
+ '12 DEBUG line 8-8 varcount 0\_s*' ..
+ 'echo "yes"\_s*' ..
+ 'elseif 0\_s*' ..
+ '13 DEBUG line 11-10 varcount 0\_s*' ..
+ 'echo "no"\_s*' ..
+ 'endif\_s*' ..
+ '14 RETURN void*',
+ res)
+enddef
+
+def s:DebugElseif()
+ var b = false
+ if b
+ eval 1 + 0
+ silent elseif !b
+ eval 2 + 0
+ endif
+enddef
+
+def Test_debug_elseif()
+ var res = execute('disass debug s:DebugElseif')
+ assert_match('<SNR>\d*_DebugElseif\_s*' ..
+ 'var b = false\_s*' ..
+ '0 DEBUG line 1-1 varcount 0\_s*' ..
+ '1 PUSH false\_s*' ..
+ '2 STORE $0\_s*' ..
+
+ 'if b\_s*' ..
+ '3 DEBUG line 2-2 varcount 1\_s*' ..
+ '4 LOAD $0\_s*' ..
+ '5 JUMP_IF_FALSE -> 10\_s*' ..
+
+ 'eval 1 + 0\_s*' ..
+ '6 DEBUG line 3-3 varcount 1\_s*' ..
+ '7 PUSHNR 1\_s*' ..
+ '8 DROP\_s*' ..
+
+ 'silent elseif !b\_s*' ..
+ '9 JUMP -> 20\_s*' ..
+ '10 CMDMOD silent\_s*' ..
+ '11 DEBUG line 4-4 varcount 1\_s*' ..
+ '12 LOAD $0\_s*' ..
+ '13 INVERT -1 (!val)\_s*' ..
+ '14 CMDMOD_REV\_s*' ..
+ '15 JUMP_IF_FALSE -> 20\_s*' ..
+
+ 'eval 2 + 0\_s*' ..
+ '16 DEBUG line 5-5 varcount 1\_s*' ..
+ '17 PUSHNR 2\_s*' ..
+ '18 DROP\_s*' ..
+
+ 'endif\_s*' ..
+ '19 DEBUG line 6-6 varcount 1\_s*' ..
+ '20 RETURN void*',
+ res)
+enddef
+
+def s:DebugFor()
+ echo "hello"
+ for a in [0]
+ echo a
+ endfor
+enddef
+
+def Test_debug_for()
+ var res = execute('disass debug s:DebugFor')
+ assert_match('<SNR>\d*_DebugFor\_s*' ..
+ 'echo "hello"\_s*' ..
+ '0 DEBUG line 1-1 varcount 0\_s*' ..
+ '1 PUSHS "hello"\_s*' ..
+ '2 ECHO 1\_s*' ..
+
+ 'for a in \[0\]\_s*' ..
+ '3 DEBUG line 2-2 varcount 0\_s*' ..
+ '4 STORE -1 in $0\_s*' ..
+ '5 PUSHNR 0\_s*' ..
+ '6 NEWLIST size 1\_s*' ..
+ '7 DEBUG line 2-2 varcount 3\_s*' ..
+ '8 FOR $0 -> 15\_s*' ..
+ '9 STORE $2\_s*' ..
+
+ 'echo a\_s*' ..
+ '10 DEBUG line 3-3 varcount 3\_s*' ..
+ '11 LOAD $2\_s*' ..
+ '12 ECHO 1\_s*' ..
+
+ 'endfor\_s*' ..
+ '13 DEBUG line 4-4 varcount 3\_s*' ..
+ '14 JUMP -> 7\_s*' ..
+ '15 DROP\_s*' ..
+ '16 RETURN void*',
+ res)
+enddef
+
+def s:TryCatch()
+ try
+ echo "try"
+ catch /error/
+ echo "caught"
+ endtry
+enddef
+
+def Test_debug_try_catch()
+ var res = execute('disass debug s:TryCatch')
+ assert_match('<SNR>\d*_TryCatch\_s*' ..
+ 'try\_s*' ..
+ '0 DEBUG line 1-1 varcount 0\_s*' ..
+ '1 TRY catch -> 7, endtry -> 17\_s*' ..
+ 'echo "try"\_s*' ..
+ '2 DEBUG line 2-2 varcount 0\_s*' ..
+ '3 PUSHS "try"\_s*' ..
+ '4 ECHO 1\_s*' ..
+ 'catch /error/\_s*' ..
+ '5 DEBUG line 3-3 varcount 0\_s*' ..
+ '6 JUMP -> 17\_s*' ..
+ '7 DEBUG line 4-3 varcount 0\_s*' ..
+ '8 PUSH v:exception\_s*' ..
+ '9 PUSHS "error"\_s*' ..
+ '10 COMPARESTRING =\~\_s*' ..
+ '11 JUMP_IF_FALSE -> 17\_s*' ..
+ '12 CATCH\_s*' ..
+ 'echo "caught"\_s*' ..
+ '13 DEBUG line 4-4 varcount 0\_s*' ..
+ '14 PUSHS "caught"\_s*' ..
+ '15 ECHO 1\_s*' ..
+ 'endtry\_s*' ..
+ '16 DEBUG line 5-5 varcount 0\_s*' ..
+ '17 ENDTRY\_s*' ..
+ '\d\+ RETURN void',
+ res)
+enddef
+
+func s:Legacy() dict
+ echo 'legacy'
+endfunc
+
+def s:UseMember()
+ var d = {func: Legacy}
+ var v = d.func()
+enddef
+
+def Test_disassemble_dict_stack()
+ var res = execute('disass s:UseMember')
+ assert_match('<SNR>\d*_UseMember\_s*' ..
+ 'var d = {func: Legacy}\_s*' ..
+ '\d PUSHS "func"\_s*' ..
+ '\d PUSHFUNC "<80><fd>R\d\+_Legacy"\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d SETTYPE dict<func(...): any>\_s*' ..
+ '\d STORE $0\_s*' ..
+
+ 'var v = d.func()\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d MEMBER func\_s*' ..
+ '\d PCALL top (argc 0)\_s*' ..
+ '\d PCALL end\_s*' ..
+ '\d CLEARDICT\_s*' ..
+ '\d\+ STORE $1\_s*' ..
+ '\d\+ RETURN void*',
+ res)
+enddef
+
+def s:RetLegacy(): string
+ legacy return "yes"
+enddef
+
+def Test_disassemble_return_legacy()
+ var res = execute('disass s:RetLegacy')
+ assert_match('<SNR>\d*_RetLegacy\_s*' ..
+ 'legacy return "yes"\_s*' ..
+ '\d CMDMOD legacy\_s*' ..
+ '\d EVAL legacy "yes"\_s*' ..
+ '\d CHECKTYPE string stack\[-1]\_s*' ..
+ '\d CMDMOD_REV\_s*' ..
+ '\d RETURN',
+ res)
+enddef
+
+def s:EchoMessages()
+ echohl ErrorMsg | echom v:exception | echohl NONE
+enddef
+
+def Test_disassemble_nextcmd()
+ # splitting commands and removing trailing blanks should not change the line
+ var res = execute('disass s:EchoMessages')
+ assert_match('<SNR>\d*_EchoMessages\_s*' ..
+ 'echohl ErrorMsg | echom v:exception | echohl NONE',
+ res)
+enddef
+
+def Test_disassemble_after_reload()
+ var lines =<< trim END
+ vim9script
+ if exists('g:ThisFunc')
+ finish
+ endif
+ var name: any
+ def g:ThisFunc(): number
+ g:name = name
+ return 0
+ enddef
+ def g:ThatFunc(): number
+ name = g:name
+ return 0
+ enddef
+ END
+ lines->writefile('Xreload.vim', 'D')
+
+ source Xreload.vim
+ g:ThisFunc()
+ g:ThatFunc()
+
+ source Xreload.vim
+ var res = execute('disass g:ThisFunc')
+ assert_match('ThisFunc\_s*' ..
+ 'g:name = name\_s*' ..
+ '\d LOADSCRIPT \[deleted\] from .*/Xreload.vim\_s*' ..
+ '\d STOREG g:name\_s*' ..
+ 'return 0\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d RETURN\_s*',
+ res)
+
+ res = execute('disass g:ThatFunc')
+ assert_match('ThatFunc\_s*' ..
+ 'name = g:name\_s*' ..
+ '\d LOADG g:name\_s*' ..
+ '\d STORESCRIPT \[deleted\] in .*/Xreload.vim\_s*' ..
+ 'return 0\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d RETURN\_s*',
+ res)
+
+ delfunc g:ThisFunc
+ delfunc g:ThatFunc
+enddef
+
+def s:MakeString(x: number): string
+ return $"x={x} x^2={x * x}"
+enddef
+
+def Test_disassemble_string_interp()
+ var instr = execute('disassemble s:MakeString')
+ assert_match('MakeString\_s*' ..
+ 'return $"x={x} x^2={x \* x}"\_s*' ..
+ '0 PUSHS "x="\_s*' ..
+ '1 LOAD arg\[-1\]\_s*' ..
+ '2 2STRING stack\[-1\]\_s*' ..
+ '3 PUSHS " x^2="\_s*' ..
+ '4 LOAD arg\[-1\]\_s*' ..
+ '5 LOAD arg\[-1\]\_s*' ..
+ '6 OPNR \*\_s*' ..
+ '7 2STRING stack\[-1\]\_s*' ..
+ '8 CONCAT size 4\_s*' ..
+ '9 RETURN\_s*',
+ instr)
+enddef
+
+def BitShift()
+ var a = 1 << 2
+ var b = 8 >> 1
+ var c = a << b
+ var d = b << a
+enddef
+
+def Test_disassemble_bitshift()
+ var instr = execute('disassemble BitShift')
+ assert_match('BitShift\_s*' ..
+ 'var a = 1 << 2\_s*' ..
+ '0 STORE 4 in $0\_s*' ..
+ 'var b = 8 >> 1\_s*' ..
+ '1 STORE 4 in $1\_s*' ..
+ 'var c = a << b\_s*' ..
+ '2 LOAD $0\_s*' ..
+ '3 LOAD $1\_s*' ..
+ '4 OPNR <<\_s*' ..
+ '5 STORE $2\_s*' ..
+ 'var d = b << a\_s*' ..
+ '6 LOAD $1\_s*' ..
+ '7 LOAD $0\_s*' ..
+ '8 OPNR <<\_s*' ..
+ '9 STORE $3\_s*' ..
+ '10 RETURN void', instr)
+enddef
+
+def s:OneDefer()
+ defer delete("file")
+enddef
+
+def Test_disassemble_defer()
+ var instr = execute('disassemble s:OneDefer')
+ assert_match('OneDefer\_s*' ..
+ 'defer delete("file")\_s*' ..
+ '\d PUSHFUNC "delete"\_s*' ..
+ '\d PUSHS "file"\_s*' ..
+ '\d DEFER 1 args\_s*' ..
+ '\d RETURN\_s*',
+ instr)
+enddef
+
+def Test_disassemble_class_function()
+ var lines =<< trim END
+ vim9script
+
+ class Cl
+ static def Fc(): string
+ return "x"
+ enddef
+ endclass
+
+ g:instr = execute('disassemble Cl.Fc')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('Fc\_s*' ..
+ 'return "x"\_s*' ..
+ '\d PUSHS "x"\_s*' ..
+ '\d RETURN\_s*',
+ g:instr)
+
+ lines =<< trim END
+ vim9script
+
+ class Cl
+ def Fo(): string
+ return "y"
+ enddef
+ endclass
+
+ g:instr = execute('disassemble Cl.Fo')
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('Fo\_s*' ..
+ 'return "y"\_s*' ..
+ '\d PUSHS "y"\_s*' ..
+ '\d RETURN\_s*',
+ g:instr)
+
+ unlet g:instr
+enddef
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim
new file mode 100644
index 0000000..de6393e
--- /dev/null
+++ b/src/testdir/test_vim9_expr.vim
@@ -0,0 +1,4160 @@
+" Tests for Vim9 script expressions
+
+source check.vim
+import './vim9.vim' as v9
+
+let g:cond = v:false
+def FuncOne(arg: number): string
+ return 'yes'
+enddef
+def FuncTwo(arg: number): number
+ return 123
+enddef
+
+" test cond ? expr : expr
+def Test_expr1_ternary()
+ var lines =<< trim END
+ assert_equal('one', true ? 'one' : 'two')
+ assert_equal('one', 1 ?
+ 'one' :
+ 'two')
+ assert_equal('one', !!0.1 ? 'one' : 'two')
+ assert_equal('one', !!'x' ? 'one' : 'two')
+ assert_equal('one', !!'x'
+ ? 'one'
+ : 'two')
+ assert_equal('one', !!0z1234 ? 'one' : 'two')
+ assert_equal('one', !![0] ? 'one' : 'two')
+ assert_equal('one', !!{x: 0} ? 'one' : 'two')
+ var name = 1
+ assert_equal('one', name ? 'one' : 'two')
+
+ assert_equal('two', false ? 'one' : 'two')
+ assert_equal('two', 0 ? 'one' : 'two')
+ assert_equal('two', !!0.0 ? 'one' : 'two')
+ assert_equal('two', !!'' ? 'one' : 'two')
+ assert_equal('two', !!0z ? 'one' : 'two')
+ assert_equal('two', !![] ? 'one' : 'two')
+ assert_equal('two', !!{} ? 'one' : 'two')
+ name = 0
+ assert_equal('two', name ? 'one' : 'two')
+
+ echo ['a'] + (1 ? ['b'] : ['c']
+ )
+ echo ['a'] + (1 ? ['b'] : ['c'] # comment
+ )
+
+ # with constant condition expression is not evaluated
+ assert_equal('one', 1 ? 'one' : xxx)
+
+ var Some: func = function('len')
+ var Other: func = function('winnr')
+ var Res: func = g:atrue ? Some : Other
+ assert_equal(function('len'), Res)
+
+ var RetOne: func(string): number = function('len')
+ var RetTwo: func(string): number = function('strlen')
+ var RetThat: func = g:atrue ? RetOne : RetTwo
+ assert_equal(function('len'), RetThat)
+
+ var X = g:FuncOne
+ var Y = g:FuncTwo
+ var Z = g:cond ? g:FuncOne : g:FuncTwo
+ assert_equal(123, Z(3))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var Z = g:cond ? FuncOne : FuncTwo
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1001: Variable not found: FuncOne', 'E121: Undefined variable: FuncTwo'])
+enddef
+
+def Test_expr1_ternary_vimscript()
+ # check line continuation
+ var lines =<< trim END
+ var name = 1
+ ? 'yes'
+ : 'no'
+ assert_equal('yes', name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:false
+ ? 'yes'
+ : 'no'
+ assert_equal('no', name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:false ?
+ 'yes' :
+ 'no'
+ assert_equal('no', name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:false ? # comment
+ 'yes' :
+ # comment
+ 'no' # comment
+ assert_equal('no', name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # check white space
+ lines =<< trim END
+ var name = v:true?1:2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''?'' at "?1:2"', 1)
+
+ lines =<< trim END
+ var name = v:true? 1 : 2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ var name = v:true ?1 : 2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ var name = v:true ? 1: 2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after '':'' at ": 2"', 1)
+
+ lines =<< trim END
+ var name = v:true ? 1 :2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ var name = 'x' ? 1 : 2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1135:', 1)
+
+ lines =<< trim END
+ var name = [] ? 1 : 2
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E745:', 1)
+
+ lines =<< trim END
+ var name = {} ? 1 : 2
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E728:', 1)
+
+ # check after failure eval_flags is reset
+ lines =<< trim END
+ try
+ eval('0 ? 1: 2')
+ catch
+ endtry
+ assert_equal(v:true, eval(string(v:true)))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ try
+ eval('0 ? 1 :2')
+ catch
+ endtry
+ assert_equal(v:true, eval(string(v:true)))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+func Test_expr1_ternary_fails()
+ call v9.CheckDefAndScriptFailure(["var x = 1 ? 'one'"], "Missing ':' after '?'", 1)
+
+ let msg = "White space required before and after '?'"
+ call v9.CheckDefAndScriptFailure(["var x = 1? 'one' : 'two'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 ?'one' : 'two'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1?'one' : 'two'"], msg, 1)
+ let lines =<< trim END
+ var x = 1
+ ?'one' : 'two'
+ # comment
+ END
+ call v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''?'' at "?''one'' : ''two''"', 2)
+
+ let msg = "White space required before and after ':'"
+ call v9.CheckDefAndScriptFailure(["var x = 1 ? 'one': 'two'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 ? 'one' :'two'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 ? 'one':'two'"], msg, 1)
+ let lines =<< trim END
+ var x = 1 ? 'one'
+ :'two'
+ # Comment
+ END
+ call v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after '':'' at ":''two''"', 2)
+
+ call v9.CheckDefAndScriptFailure(["var x = 'x' ? 'one' : 'two'"], 'E1135:', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0z1234 ? 'one' : 'two'"], 'E974:', 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = [] ? 'one' : 'two'"], 'E745:', 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = {} ? 'one' : 'two'"], 'E728:', 1)
+
+ call v9.CheckDefExecFailure(["var x = false ? "], 'E1097:', 3)
+ call v9.CheckScriptFailure(['vim9script', "var x = false ? "], 'E15:', 2)
+ call v9.CheckDefExecFailure(["var x = false ? 'one' : "], 'E1097:', 3)
+ call v9.CheckScriptFailure(['vim9script', "var x = false ? 'one' : "], 'E15:', 2)
+
+ call v9.CheckDefExecAndScriptFailure(["var x = true ? xxx : 'foo'"], ['E1001:', 'E121:'], 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = false ? 'foo' : xxx"], ['E1001:', 'E121:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 0.1 ? 'one' : 'two'"], 'E805:', 1)
+
+ " missing argument detected even when common type is used
+ call v9.CheckDefAndScriptFailure([
+ \ 'var X = g:FuncOne',
+ \ 'var Y = g:FuncTwo',
+ \ 'var Z = g:cond ? g:FuncOne : g:FuncTwo',
+ \ 'Z()'], 'E119:', 4)
+endfunc
+
+def Test_expr1_falsy()
+ var lines =<< trim END
+ assert_equal(v:true, v:true ?? 456)
+ assert_equal(123, 123 ?? 456)
+ assert_equal('yes', 'yes' ?? 456)
+ assert_equal([1], [1] ?? 456)
+ assert_equal({one: 1}, {one: 1} ?? 456)
+ assert_equal(0.1, 0.1 ?? 456)
+
+ assert_equal(456, v:false ?? 456)
+ assert_equal(456, 0 ?? 456)
+ assert_equal(456, '' ?? 456)
+ assert_equal(456, [] ?? 456)
+ assert_equal(456, {} ?? 456)
+ assert_equal(456, 0.0 ?? 456)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ var msg = "White space required before and after '??'"
+ call v9.CheckDefAndScriptFailure(["var x = 1?? 'one' : 'two'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 ??'one' : 'two'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1??'one' : 'two'"], msg, 1)
+ lines =<< trim END
+ var x = 1
+ ??'one' : 'two'
+ #comment
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''??'' at "??''one'' : ''two''"', 2)
+enddef
+
+def g:Record(val: any): any
+ g:vals->add(val)
+ return val
+enddef
+
+" test ||
+def Test_expr2()
+ var lines =<< trim END
+ assert_equal(true, 1 || 0)
+ assert_equal(true, 0 ||
+ 0 ||
+ 1)
+ assert_equal(true, 0 ||
+ 0 ||
+ !!7)
+ assert_equal(false, 0 || 0)
+ assert_equal(false, 0
+ || 0)
+ assert_equal(false, 0 || false)
+
+ g:vals = []
+ assert_equal(true, g:Record(1) || g:Record(3))
+ assert_equal([1], g:vals)
+
+ g:vals = []
+ assert_equal(true, g:Record(0) || g:Record(1))
+ assert_equal([0, 1], g:vals)
+
+ g:vals = []
+ assert_equal(true, g:Record(0) || g:Record(true))
+ assert_equal([0, true], g:vals)
+
+ g:vals = []
+ assert_equal(true, g:Record(0)
+ || g:Record(1)
+ || g:Record(0))
+ assert_equal([0, 1], g:vals)
+
+ g:vals = []
+ assert_equal(true, g:Record(0)
+ || g:Record(true)
+ || g:Record(0))
+ assert_equal([0, true], g:vals)
+
+ g:vals = []
+ assert_equal(true, g:Record(true) || g:Record(false))
+ assert_equal([true], g:vals)
+
+ g:vals = []
+ assert_equal(false, g:Record(0) || g:Record(false) || g:Record(0))
+ assert_equal([0, false, 0], g:vals)
+
+ g:vals = []
+ var x = 1
+ if x || true
+ g:vals = [1]
+ endif
+ assert_equal([1], g:vals)
+
+ g:vals = []
+ x = 3
+ if true || x
+ g:vals = [1]
+ endif
+ assert_equal([1], g:vals)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr2_vimscript()
+ # check line continuation
+ var lines =<< trim END
+ var name = 0
+ || 1
+ assert_equal(true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:false
+ || v:true
+ || v:false
+ assert_equal(v:true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:false ||
+ v:true ||
+ v:false
+ assert_equal(v:true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:false || # comment
+ # comment
+ v:true ||
+ # comment
+ v:false # comment
+ assert_equal(v:true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # check white space
+ lines =<< trim END
+ var name = v:true||v:true
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1004: White space required before and after ''||'' at "||v:true"', 1)
+
+ lines =<< trim END
+ var name = v:true ||v:true
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ var name = v:true|| v:true
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+enddef
+
+def Test_expr2_fails()
+ var msg = "White space required before and after '||'"
+ v9.CheckDefAndScriptFailure(["var x = 1||0"], msg, 1)
+ v9.CheckDefAndScriptFailure(["var x = 1 ||0"], msg, 1)
+ v9.CheckDefAndScriptFailure(["var x = 1|| 0"], msg, 1)
+
+ v9.CheckDefFailure(["var x = false || "], 'E1097:', 3)
+ v9.CheckScriptFailure(['vim9script', "var x = false || "], 'E15:', 2)
+
+ # script does not fail, the second expression is skipped
+ v9.CheckDefFailure(["var x = 1 || xxx"], 'E1001:', 1)
+
+ v9.CheckDefAndScriptFailure(["var x = [] || false"], ['E1012:', 'E745:'], 1)
+
+ v9.CheckDefAndScriptFailure(["if 'yes' || 0", 'echo 0', 'endif'], ['E1012: Type mismatch; expected bool but got string', 'E1135: Using a String as a Bool'], 1)
+
+ v9.CheckDefAndScriptFailure(["var x = 3 || false"], ['E1012:', 'E1023:'], 1)
+ v9.CheckDefAndScriptFailure(["var x = false || 3"], ['E1012:', 'E1023:'], 1)
+
+ v9.CheckDefAndScriptFailure(["if 3"], 'E1023:', 1)
+ v9.CheckDefExecAndScriptFailure(['var x = 3', 'if x', 'endif'], 'E1023:', 2)
+
+ v9.CheckDefAndScriptFailure(["var x = [] || false"], ['E1012: Type mismatch; expected bool but got list<unknown>', 'E745:'], 1)
+
+ var lines =<< trim END
+ vim9script
+ echo false
+ ||true
+ # comment
+ END
+ v9.CheckScriptFailure(lines, 'E1004: White space required before and after ''||'' at "||true"', 3)
+
+ lines =<< trim END
+ var x = false
+ || false
+ || a.b
+ END
+ v9.CheckDefFailure(lines, 'E1001:', 3)
+enddef
+
+" test &&
+def Test_expr3()
+ var lines =<< trim END
+ assert_equal(false, 1 && 0)
+ assert_equal(false, 0 &&
+ 0 &&
+ 1)
+ assert_equal(true, 1
+ && true
+ && 1)
+ assert_equal(false, 0 && 0)
+ assert_equal(false, 0 && false)
+ assert_equal(true, 1 && true)
+
+ g:vals = []
+ assert_equal(true, g:Record(true) && g:Record(1))
+ assert_equal([true, 1], g:vals)
+
+ g:vals = []
+ assert_equal(true, g:Record(1) && g:Record(true))
+ assert_equal([1, true], g:vals)
+
+ g:vals = []
+ assert_equal(false, g:Record(0) && g:Record(1))
+ assert_equal([0], g:vals)
+
+ g:vals = []
+ assert_equal(false, g:Record(0) && g:Record(1) && g:Record(0))
+ assert_equal([0], g:vals)
+
+ g:vals = []
+ assert_equal(false, g:Record(0) && g:Record(4) && g:Record(0))
+ assert_equal([0], g:vals)
+
+ g:vals = []
+ assert_equal(false, g:Record(1) && g:Record(true) && g:Record(0))
+ assert_equal([1, true, 0], g:vals)
+
+ var failed = false
+ if false && g:a == g:b.c
+ failed = true
+ endif
+ assert_false(failed)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr3_vimscript()
+ # check line continuation
+ var lines =<< trim END
+ var name = 0
+ && 1
+ assert_equal(false, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:true
+ && v:true
+ && v:true
+ assert_equal(v:true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:true &&
+ v:true &&
+ v:true
+ assert_equal(v:true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = v:true && # comment
+ # comment
+ v:true &&
+ # comment
+ v:true
+ assert_equal(v:true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # check white space
+ lines =<< trim END
+ var name = v:true&&v:true
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ var name = v:true &&v:true
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''&&'' at "&&v:true"', 1)
+
+ lines =<< trim END
+ var name = v:true&& v:true
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+enddef
+
+def Test_expr3_fails()
+ var msg = "White space required before and after '&&'"
+ v9.CheckDefAndScriptFailure(["var x = 1&&0"], msg, 1)
+ v9.CheckDefAndScriptFailure(["var x = 1 &&0"], msg, 1)
+ v9.CheckDefAndScriptFailure(["var x = 1&& 0"], msg, 1)
+ var lines =<< trim END
+ var x = 1
+ &&0
+ # comment
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''&&'' at "&&0"', 2)
+
+ g:vals = []
+ v9.CheckDefAndScriptFailure(["if 'yes' && 0", 'echo 0', 'endif'], ['E1012: Type mismatch; expected bool but got string', 'E1135: Using a String as a Bool'], 1)
+
+ v9.CheckDefExecAndScriptFailure(['assert_equal(false, g:Record(1) && g:Record(4) && g:Record(0))'], 'E1023: Using a Number as a Bool: 4', 1)
+
+ lines =<< trim END
+ if 3
+ && true
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1012:', 'E1023:'], 1)
+
+ lines =<< trim END
+ if true
+ && 3
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1012:', 'E1023:'], 2)
+
+ lines =<< trim END
+ if 'yes'
+ && true
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1012:', 'E1135: Using a String as a Bool'], 1)
+
+ lines =<< trim END
+ var s = 'asdf'
+ echo true && s
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1012: Type mismatch; expected bool but got string', 'E1135: Using a String as a Bool: "asdf"'])
+enddef
+
+" global variables to use for tests with the "any" type
+let atrue = v:true
+let afalse = v:false
+let anone = v:none
+let anull = v:null
+let anint = 10
+let theone = 1
+let thefour = 4
+let afloat = 0.1
+let astring = 'asdf'
+let ablob = 0z01ab
+let alist = [2, 3, 4]
+let adict = #{aaa: 2, bbb: 8}
+
+" test == comparator
+def Test_expr4_equal()
+ var lines =<< trim END
+ var trueVar = true
+ var falseVar = false
+ assert_equal(true, true == true)
+ assert_equal(false, true ==
+ false)
+ assert_equal(true, true
+ == trueVar)
+ assert_equal(false, true == falseVar)
+ assert_equal(true, true == g:atrue)
+ assert_equal(false, g:atrue == false)
+
+ assert_equal(true, v:none == v:none)
+ assert_equal(false, v:none == v:null)
+ assert_equal(true, g:anone == v:none)
+ assert_equal(true, null == v:null)
+ assert_equal(true, null == g:anull)
+ assert_equal(false, v:none == g:anull)
+
+ var nr0 = 0
+ var nr61 = 61
+ assert_equal(false, 2 == 0)
+ assert_equal(false, 2 == nr0)
+ assert_equal(true, 61 == 61)
+ assert_equal(true, 61 == nr61)
+ assert_equal(true, g:anint == 10)
+ assert_equal(false, 61 == g:anint)
+
+ var ff = 0.3
+ assert_equal(true, ff == 0.3)
+ assert_equal(false, 0.4 == ff)
+ assert_equal(true, 0.1 == g:afloat)
+ assert_equal(false, g:afloat == 0.3)
+
+ ff = 3.0
+ assert_equal(true, ff == 3)
+ assert_equal(true, 3 == ff)
+ ff = 3.1
+ assert_equal(false, ff == 3)
+ assert_equal(false, 3 == ff)
+
+ assert_equal(true, 'abc' == 'abc')
+ assert_equal(false, 'xyz' == 'abc')
+ assert_equal(true, g:astring == 'asdf')
+ assert_equal(false, 'xyz' == g:astring)
+
+ assert_equal(false, 'abc' == 'aBc')
+ assert_equal(false, 'abc' ==# 'aBc')
+ assert_equal(true, 'abc' ==? 'aBc')
+
+ assert_equal(false, 'abc' == 'ABC')
+ set ignorecase
+ assert_equal(false, 'abc' == 'ABC')
+ assert_equal(false, 'abc' ==# 'ABC')
+ assert_equal(true, 'abc' ==? 'ABC')
+ set noignorecase
+
+ var bb = 0z3f
+ assert_equal(true, 0z3f == bb)
+ assert_equal(false, bb == 0z4f)
+ assert_equal(true, g:ablob == 0z01ab)
+ assert_equal(false, 0z3f == g:ablob)
+
+ assert_equal(true, [1, 2, 3] == [1, 2, 3])
+ assert_equal(false, [1, 2, 3] == [2, 3, 1])
+ assert_equal(true, [2, 3, 4] == g:alist)
+ assert_equal(false, g:alist == [2, 3, 1])
+ assert_equal(false, [1, 2, 3] == [])
+ assert_equal(false, [1, 2, 3] == ['1', '2', '3'])
+
+ assert_equal(true, {one: 1, two: 2} == {one: 1, two: 2})
+ assert_equal(false, {one: 1, two: 2} == {one: 2, two: 2})
+ assert_equal(false, {one: 1, two: 2} == {two: 2})
+ assert_equal(false, {one: 1, two: 2} == {})
+ assert_equal(true, g:adict == {bbb: 8, aaa: 2})
+ assert_equal(false, {ccc: 9, aaa: 2} == g:adict)
+
+ assert_equal(true, function('g:Test_expr4_equal') == function('g:Test_expr4_equal'))
+ assert_equal(false, function('g:Test_expr4_equal') == function('g:Test_expr4_is'))
+
+ assert_equal(true, function('g:Test_expr4_equal', [123]) == function('g:Test_expr4_equal', [123]))
+ assert_equal(false, function('g:Test_expr4_equal', [123]) == function('g:Test_expr4_is', [123]))
+ assert_equal(false, function('g:Test_expr4_equal', [123]) == function('g:Test_expr4_equal', [999]))
+
+ if true
+ var OneFunc: func
+ var TwoFunc: func
+ OneFunc = function('len')
+ TwoFunc = function('len')
+ assert_equal(true, OneFunc('abc') == TwoFunc('123'))
+ endif
+
+ # check this doesn't fail when skipped
+ if false
+ var OneFunc: func
+ var TwoFunc: func
+ OneFunc = function('len')
+ TwoFunc = function('len')
+ assert_equal(true, OneFunc('abc') == TwoFunc('123'))
+ endif
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var x = 'a' == xxx"], ['E1001:', 'E121:'], 1)
+ v9.CheckDefFailure(["var x = 'a' == "], 'E1097:', 3)
+ v9.CheckScriptFailure(['vim9script', "var x = 'a' == "], 'E15:', 2)
+
+ v9.CheckDefExecAndScriptFailure(['var items: any', 'eval 1 + 1', 'eval 2 + 2', 'if items == []', 'endif'], ['E691:', 'E1072:'], 4)
+
+ v9.CheckDefExecAndScriptFailure(['var x: any = "a"', 'echo x == true'], 'E1072: Cannot compare string with bool', 2)
+ v9.CheckDefExecAndScriptFailure(["var x: any = true", 'echo x == ""'], 'E1072: Cannot compare bool with string', 2)
+ v9.CheckDefExecAndScriptFailure(["var x: any = 99", 'echo x == true'], ['E1138', 'E1072:'], 2)
+ v9.CheckDefExecAndScriptFailure(["var x: any = 'a'", 'echo x == 99'], ['E1030:', 'E1072:'], 2)
+
+ lines =<< trim END
+ vim9script
+ var n: any = 2
+ def Compare()
+ eval n == '3'
+ g:notReached = false
+ enddef
+ g:notReached = true
+ Compare()
+ END
+ v9.CheckScriptFailure(lines, 'E1030: Using a String as a Number: "3"')
+ assert_true(g:notReached)
+
+ lines =<< trim END
+ vim9script
+ var n: any = 2.2
+ def Compare()
+ eval n == '3'
+ g:notReached = false
+ enddef
+ g:notReached = true
+ Compare()
+ END
+ v9.CheckScriptFailure(lines, 'E892: Using a String as a Float')
+ assert_true(g:notReached)
+
+ unlet g:notReached
+enddef
+
+def Test_expr4_compare_null()
+ g:null_dict = test_null_dict()
+ g:not_null_list = []
+ var lines =<< trim END
+ assert_false(true == null)
+ assert_false(false == null)
+ assert_false(null == true)
+ assert_false(null == false)
+ assert_true(true != null)
+ assert_true(false != null)
+ assert_true(null != true)
+ assert_true(null != false)
+
+ assert_false(123 == null)
+ assert_false(0 == null)
+ assert_false(null == 123)
+ assert_false(null == 0)
+ assert_true(123 != null)
+ assert_true(0 != null)
+ assert_true(null != 123)
+ assert_true(null != 0)
+
+ assert_false(12.3 == null)
+ assert_false(0.0 == null)
+ assert_false(null == 12.3)
+ assert_false(null == 0.0)
+ assert_true(12.3 != null)
+ assert_true(0.0 != null)
+ assert_true(null != 12.3)
+ assert_true(null != 0.0)
+
+ assert_true(test_null_blob() == v:null)
+ assert_true(null_blob == null)
+ assert_true(v:null == test_null_blob())
+ assert_true(null == null_blob)
+ assert_false(test_null_blob() != v:null)
+ assert_false(null_blob != null)
+ assert_false(v:null != test_null_blob())
+ assert_false(null != null_blob)
+
+ var nb = null_blob
+ assert_true(nb == null_blob)
+ assert_true(nb == null)
+ assert_true(null_blob == nb)
+ assert_true(null == nb)
+
+ if has('channel')
+ assert_true(test_null_channel() == v:null)
+ assert_true(null_channel == null)
+ assert_true(v:null == test_null_channel())
+ assert_true(null == null_channel)
+ assert_false(test_null_channel() != v:null)
+ assert_false(null_channel != null)
+ assert_false(v:null != test_null_channel())
+ assert_false(null != null_channel)
+
+ var nc = null_channel
+ assert_true(nc == null_channel)
+ assert_true(nc == null)
+ assert_true(null_channel == nc)
+ assert_true(null == nc)
+ endif
+
+ assert_true(test_null_dict() == v:null)
+ assert_true(null_dict == null)
+ assert_true(v:null == test_null_dict())
+ assert_true(null == null_dict)
+ assert_false(test_null_dict() != v:null)
+ assert_false(null_dict != null)
+ assert_false(v:null != test_null_dict())
+ assert_false(null != null_dict)
+
+ assert_true(g:null_dict == v:null)
+ assert_true(v:null == g:null_dict)
+ assert_false(g:null_dict != v:null)
+ assert_false(v:null != g:null_dict)
+
+ var nd = null_dict
+ assert_true(nd == null_dict)
+ assert_true(nd == null)
+ assert_true(null_dict == nd)
+ assert_true(null == nd)
+
+ assert_true(test_null_function() == v:null)
+ assert_true(null_function == null)
+ assert_true(v:null == test_null_function())
+ assert_true(null == null_function)
+ assert_false(test_null_function() != v:null)
+ assert_false(null_function != null)
+ assert_false(v:null != test_null_function())
+ assert_false(null != null_function)
+
+ var Nf = null_function
+ assert_true(Nf == null_function)
+ assert_true(Nf == null)
+ assert_true(null_function == Nf)
+ assert_true(null == Nf)
+
+ if has('job')
+ assert_true(test_null_job() == v:null)
+ assert_true(null_job == null)
+ assert_true(v:null == test_null_job())
+ assert_true(null == null_job)
+ assert_false(test_null_job() != v:null)
+ assert_false(null_job != null)
+ assert_false(v:null != test_null_job())
+ assert_false(null != null_job)
+
+ var nj = null_job
+ assert_true(nj == null_job)
+ assert_true(nj == null)
+ assert_true(null_job == nj)
+ assert_true(null == nj)
+ endif
+
+ assert_true(test_null_list() == v:null)
+ assert_true(null_list == null)
+ assert_true(v:null == test_null_list())
+ assert_true(null == null_list)
+ assert_false(test_null_list() != v:null)
+ assert_false(null_list != null)
+ assert_false(v:null != test_null_list())
+ assert_false(null != null_list)
+
+ assert_false(g:not_null_list == v:null)
+ assert_false(v:null == g:not_null_list)
+ assert_true(g:not_null_list != v:null)
+ assert_true(v:null != g:not_null_list)
+
+ var nl = null_list
+ assert_true(nl == null_list)
+ assert_true(nl == null)
+ assert_true(null_list == nl)
+ assert_true(null == nl)
+
+ assert_true(test_null_partial() == v:null)
+ assert_true(null_partial == null)
+ assert_true(v:null == test_null_partial())
+ assert_true(null == null_partial)
+ assert_false(test_null_partial() != v:null)
+ assert_false(null_partial != null)
+ assert_false(v:null != test_null_partial())
+ assert_false(null != null_partial)
+
+ var Np = null_partial
+ assert_true(Np == null_partial)
+ assert_true(Np == null)
+ assert_true(null_partial == Np)
+ assert_true(null == Np)
+
+ assert_true(test_null_string() == v:null)
+ assert_true(null_string == null)
+ assert_true(v:null == test_null_string())
+ assert_true(null == null_string)
+ assert_false(test_null_string() != v:null)
+ assert_false(null_string != null)
+ assert_false(v:null != test_null_string())
+ assert_false(null != null_string)
+
+ assert_true(null_string is test_null_string())
+ assert_false(null_string is '')
+ assert_false('' is null_string)
+ assert_false(null_string isnot test_null_string())
+ assert_true(null_string isnot '')
+ assert_true('' isnot null_string)
+
+ var ns = null_string
+ assert_true(ns == null_string)
+ assert_true(ns == null)
+ assert_true(null_string == ns)
+ assert_true(null == ns)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+ unlet g:null_dict
+ unlet g:not_null_list
+
+ # variables declared at script level used in a :def function
+ lines =<< trim END
+ vim9script
+
+ var l_decl: list<number>
+ var l_empty = []
+ var l_null = null_list
+
+ def TestList()
+ assert_false(l_decl == null)
+ assert_false(l_decl is null_list)
+ assert_false(l_empty == null)
+ assert_false(l_empty is null_list)
+ assert_true(l_null == null)
+ assert_true(l_null is null_list)
+ assert_true(l_null == null_list)
+
+ add(l_decl, 6)
+ assert_equal([6], l_decl)
+ add(l_empty, 7)
+ assert_equal([7], l_empty)
+ var caught = false
+ try
+ add(l_null, 9)
+ catch /E1130:/
+ caught = true
+ endtry
+ assert_true(caught)
+ enddef
+ TestList()
+
+ var b_decl: blob
+ var b_empty = 0z
+ var b_null = null_blob
+
+ def TestBlob()
+ assert_false(b_decl == null)
+ assert_false(b_decl is null_blob)
+ assert_false(b_empty == null)
+ assert_false(b_empty is null_blob)
+ assert_true(b_null == null)
+ assert_true(b_null is null_blob)
+ assert_true(b_null == null_blob)
+
+ add(b_decl, 6)
+ assert_equal(0z06, b_decl)
+ add(b_empty, 7)
+ assert_equal(0z07, b_empty)
+ var caught = false
+ try
+ add(b_null, 9)
+ catch /E1131:/
+ caught = true
+ endtry
+ assert_true(caught)
+ enddef
+ TestBlob()
+
+ var d_decl: dict<number>
+ var d_empty = {}
+ var d_null = null_dict
+
+ def TestDict()
+ assert_false(d_decl == null)
+ assert_false(d_decl is null_dict)
+ assert_false(d_empty == null)
+ assert_false(d_empty is null_dict)
+ assert_true(d_null == null)
+ assert_true(d_null is null_dict)
+ assert_true(d_null == null_dict)
+
+ d_decl['a'] = 6
+ assert_equal({a: 6}, d_decl)
+ d_empty['b'] = 7
+ assert_equal({b: 7}, d_empty)
+ var caught = false
+ try
+ d_null['c'] = 9
+ catch /E1103:/
+ caught = true
+ endtry
+ assert_true(caught)
+ enddef
+ TestDict()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ var d: dict<func> = {f: null_function}
+ assert_equal(null_function, d.f)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr4_compare_none()
+ var lines =<< trim END
+ assert_false('' == v:none)
+ assert_false('text' == v:none)
+ assert_true(v:none == v:none)
+ assert_false(v:none == '')
+ assert_false(v:none == 'text')
+ assert_true(v:none == v:none)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(['echo [] == v:none'], 'E1072: Cannot compare list with special')
+ v9.CheckDefAndScriptFailure(['echo 123 == v:none'], 'E1072: Cannot compare number with special')
+ v9.CheckDefAndScriptFailure(['echo 0z00 == v:none'], 'E1072: Cannot compare blob with special')
+
+ lines =<< trim END
+ echo [] == v:none
+
+ eval 0 + 0
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1072:', 1)
+enddef
+
+def Test_expr4_wrong_type()
+ for op in ['>', '>=', '<', '<=', '=~', '!~']
+ v9.CheckDefExecAndScriptFailure([
+ "var a: any = 'a'",
+ 'var b: any = true',
+ 'echo a ' .. op .. ' b'], 'E1072:', 3)
+ endfor
+ for op in ['>', '>=', '<', '<=']
+ v9.CheckDefExecAndScriptFailure([
+ "var n: any = 2",
+ 'echo n ' .. op .. ' "3"'], ['E1030:', 'E1072:'], 2)
+ endfor
+ for op in ['=~', '!~']
+ v9.CheckDefExecAndScriptFailure([
+ "var n: any = 2",
+ 'echo n ' .. op .. ' "3"'], 'E1072:', 2)
+ endfor
+
+ v9.CheckDefAndScriptFailure([
+ 'echo v:none == true'], 'E1072:', 1)
+ v9.CheckDefAndScriptFailure([
+ 'echo false >= true'], 'E1072:', 1)
+ v9.CheckDefExecAndScriptFailure([
+ "var n: any = v:none",
+ 'echo n == true'], 'E1072:', 2)
+ v9.CheckDefExecAndScriptFailure([
+ "var n: any = v:none",
+ 'echo n < true'], 'E1072:', 2)
+enddef
+
+" test != comparator
+def Test_expr4_notequal()
+ var lines =<< trim END
+ var trueVar = true
+ var falseVar = false
+ assert_equal(false, true != true)
+ assert_equal(true, true !=
+ false)
+ assert_equal(false, true
+ != trueVar)
+ assert_equal(true, true != falseVar)
+ assert_equal(false, true != g:atrue)
+ assert_equal(true, g:atrue != false)
+
+ assert_equal(false, v:none != v:none)
+ assert_equal(true, v:none != v:null)
+ assert_equal(false, g:anone != v:none)
+ assert_equal(true, v:none != g:anull)
+
+ var nr55 = 55
+ var nr0 = 55
+ assert_equal(true, 2 != 0)
+ assert_equal(true, 2 != nr0)
+ assert_equal(false, 55 != 55)
+ assert_equal(false, 55 != nr55)
+ assert_equal(false, g:anint != 10)
+ assert_equal(true, 61 != g:anint)
+
+ var ff = 0.3
+ assert_equal(false, 0.3 != ff)
+ assert_equal(true, 0.4 != ff)
+ assert_equal(false, 0.1 != g:afloat)
+ assert_equal(true, g:afloat != 0.3)
+
+ ff = 3.0
+ assert_equal(false, ff != 3)
+ assert_equal(false, 3 != ff)
+ ff = 3.1
+ assert_equal(true, ff != 3)
+ assert_equal(true, 3 != ff)
+
+ assert_equal(false, 'abc' != 'abc')
+ assert_equal(true, 'xyz' != 'abc')
+ assert_equal(false, g:astring != 'asdf')
+ assert_equal(true, 'xyz' != g:astring)
+
+ assert_equal(true, 'abc' != 'ABC')
+ set ignorecase
+ assert_equal(true, 'abc' != 'ABC')
+ assert_equal(true, 'abc' !=# 'ABC')
+ assert_equal(false, 'abc' !=? 'ABC')
+ set noignorecase
+
+ var bb = 0z3f
+ assert_equal(false, 0z3f != bb)
+ assert_equal(true, bb != 0z4f)
+ assert_equal(false, g:ablob != 0z01ab)
+ assert_equal(true, 0z3f != g:ablob)
+
+ assert_equal(false, [1, 2, 3] != [1, 2, 3])
+ assert_equal(true, [1, 2, 3] != [2, 3, 1])
+ assert_equal(false, [2, 3, 4] != g:alist)
+ assert_equal(true, g:alist != [2, 3, 1])
+ assert_equal(true, [1, 2, 3] != [])
+ assert_equal(true, [1, 2, 3] != ['1', '2', '3'])
+
+ assert_equal(false, {one: 1, two: 2} != {one: 1, two: 2})
+ assert_equal(true, {one: 1, two: 2} != {one: 2, two: 2})
+ assert_equal(true, {one: 1, two: 2} != {two: 2})
+ assert_equal(true, {one: 1, two: 2} != {})
+ assert_equal(false, g:adict != {bbb: 8, aaa: 2})
+ assert_equal(true, {ccc: 9, aaa: 2} != g:adict)
+
+ assert_equal(false, function('g:Test_expr4_equal') != function('g:Test_expr4_equal'))
+ assert_equal(true, function('g:Test_expr4_equal') != function('g:Test_expr4_is'))
+
+ assert_equal(false, function('g:Test_expr4_equal', [123]) != function('g:Test_expr4_equal', [123]))
+ assert_equal(true, function('g:Test_expr4_equal', [123]) != function('g:Test_expr4_is', [123]))
+ assert_equal(true, function('g:Test_expr4_equal', [123]) != function('g:Test_expr4_equal', [999]))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test > comparator
+def Test_expr4_greater()
+ var lines =<< trim END
+ assert_true(2 > 0)
+ assert_true(2 >
+ 1)
+ assert_false(2 > 2)
+ assert_false(2 > 3)
+ var nr2 = 2
+ assert_true(nr2 > 0)
+ assert_true(nr2 >
+ 1)
+ assert_false(nr2 > 2)
+ assert_false(nr2
+ > 3)
+ var ff = 2.0
+ assert_true(ff > 0.0)
+ assert_true(ff > 1.0)
+ assert_false(ff > 2.0)
+ assert_false(ff > 3.0)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test >= comparator
+def Test_expr4_greaterequal()
+ var lines =<< trim END
+ assert_true(2 >= 0)
+ assert_true(2 >=
+ 2)
+ assert_false(2 >= 3)
+ var nr2 = 2
+ assert_true(nr2 >= 0)
+ assert_true(nr2 >= 2)
+ assert_false(nr2 >= 3)
+ var ff = 2.0
+ assert_true(ff >= 0.0)
+ assert_true(ff >= 2.0)
+ assert_false(ff >= 3.0)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test < comparator
+def Test_expr4_smaller()
+ var lines =<< trim END
+ assert_false(2 < 0)
+ assert_false(2 <
+ 2)
+ assert_true(2
+ < 3)
+ var nr2 = 2
+ assert_false(nr2 < 0)
+ assert_false(nr2 < 2)
+ assert_true(nr2 < 3)
+ var ff = 2.0
+ assert_false(ff < 0.0)
+ assert_false(ff < 2.0)
+ assert_true(ff < 3.0)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test <= comparator
+def Test_expr4_smallerequal()
+ var lines =<< trim END
+ assert_false(2 <= 0)
+ assert_false(2 <=
+ 1)
+ assert_true(2
+ <= 2)
+ assert_true(2 <= 3)
+ var nr2 = 2
+ assert_false(nr2 <= 0)
+ assert_false(nr2 <= 1)
+ assert_true(nr2 <= 2)
+ assert_true(nr2 <= 3)
+ var ff = 2.0
+ assert_false(ff <= 0.0)
+ assert_false(ff <= 1.0)
+ assert_true(ff <= 2.0)
+ assert_true(ff <= 3.0)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test =~ comparator
+def Test_expr4_match()
+ var lines =<< trim END
+ assert_equal(false, '2' =~ '0')
+ assert_equal(false, ''
+ =~ '0')
+ assert_equal(true, '2' =~
+ '[0-9]')
+ set ignorecase
+ assert_equal(false, 'abc' =~ 'ABC')
+ assert_equal(false, 'abc' =~# 'ABC')
+ assert_equal(true, 'abc' =~? 'ABC')
+ set noignorecase
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test !~ comparator
+def Test_expr4_nomatch()
+ var lines =<< trim END
+ assert_equal(true, '2' !~ '0')
+ assert_equal(true, ''
+ !~ '0')
+ assert_equal(false, '2' !~
+ '[0-9]')
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test is comparator
+def Test_expr4_is()
+ var lines =<< trim END
+ var mylist = [2]
+ assert_false(mylist is [2])
+ var other = mylist
+ assert_true(mylist is
+ other)
+
+ var myblob = 0z1234
+ assert_false(myblob
+ is 0z1234)
+ var otherblob = myblob
+ assert_true(myblob is otherblob)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test isnot comparator
+def Test_expr4_isnot()
+ var lines =<< trim END
+ var mylist = [2]
+ assert_true('2' isnot '0')
+ assert_true(mylist isnot [2])
+ var other = mylist
+ assert_false(mylist isnot
+ other)
+
+ var myblob = 0z1234
+ assert_true(myblob
+ isnot 0z1234)
+ var otherblob = myblob
+ assert_false(myblob isnot otherblob)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def g:RetVoid()
+ var x = 1
+enddef
+
+def Test_expr4_vim9script()
+ # check line continuation
+ var lines =<< trim END
+ var name = 0
+ < 1
+ assert_equal(true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 123
+ # comment
+ != 123
+ assert_equal(false, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 123 ==
+ 123
+ assert_equal(true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var list = [1, 2, 3]
+ var name = list
+ is list
+ assert_equal(true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var list = [1, 2, 3]
+ var name = list # comment
+ # comment
+ is list
+ assert_equal(true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var myblob = 0z1234
+ var name = myblob
+ isnot 0z11
+ assert_equal(true, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # spot check mismatching types
+ lines =<< trim END
+ echo '' == 0
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1072:', 1)
+
+ lines =<< trim END
+ echo v:true > v:false
+ END
+ v9.CheckDefAndScriptFailure(lines, 'Cannot compare bool with bool', 1)
+
+ lines =<< trim END
+ echo 123 is 123
+ END
+ v9.CheckDefAndScriptFailure(lines, 'Cannot use "is" with number', 1)
+
+ # check missing white space
+ lines =<< trim END
+ echo 2>3
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''>'' at ">3"', 1)
+
+ lines =<< trim END
+ echo 2 >3
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 2> 3
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 2!=3
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 2 !=3
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''!='' at "!=3"', 1)
+
+ lines =<< trim END
+ echo 2!= 3
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ for op in ['==', '>', '>=', '<', '<=', '=~', '!~', 'is', 'isnot']
+ lines = ["echo 'aaa'", op .. "'bbb'", '# comment']
+ var msg = printf("E1004: White space required before and after '%s'", op)
+ v9.CheckDefAndScriptFailure(lines, msg, 2)
+ endfor
+
+ lines =<< trim END
+ echo len('xxx') == 3
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var line = 'abc'
+ echo line[1] =~ '\w'
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+func Test_expr4_fails()
+ let msg = "White space required before and after '>'"
+ call v9.CheckDefAndScriptFailure(["var x = 1>2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 >2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1> 2"], msg, 1)
+
+ let msg = "White space required before and after '=='"
+ call v9.CheckDefAndScriptFailure(["var x = 1==2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 ==2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1== 2"], msg, 1)
+
+ let msg = "White space required before and after 'is'"
+ call v9.CheckDefAndScriptFailure(["var x = '1'is'2'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1' is'2'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1'is '2'"], msg, 1)
+
+ let msg = "White space required before and after 'isnot'"
+ call v9.CheckDefAndScriptFailure(["var x = '1'isnot'2'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1' isnot'2'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1'isnot '2'"], msg, 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 1 is# 2"], 'E15:', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 is? 2"], 'E15:', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 isnot# 2"], 'E15:', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 isnot? 2"], 'E15:', 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 1 == '2'"], 'Cannot compare number with string', 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1' == 2"], 'Cannot compare string with number', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 == g:RetVoid()"], 'Cannot compare number with void', 1)
+ call v9.CheckDefAndScriptFailure(["var x = g:RetVoid() == 1"], 'Cannot compare void with number', 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = true > false"], 'Cannot compare bool with bool', 1)
+ call v9.CheckDefAndScriptFailure(["var x = true >= false"], 'Cannot compare bool with bool', 1)
+ call v9.CheckDefAndScriptFailure(["var x = true < false"], 'Cannot compare bool with bool', 1)
+ call v9.CheckDefAndScriptFailure(["var x = true <= false"], 'Cannot compare bool with bool', 1)
+ call v9.CheckDefAndScriptFailure(["var x = true =~ false"], 'Cannot compare bool with bool', 1)
+ call v9.CheckDefAndScriptFailure(["var x = true !~ false"], 'Cannot compare bool with bool', 1)
+ call v9.CheckDefAndScriptFailure(["var x = true is false"], 'Cannot use "is" with bool', 1)
+ call v9.CheckDefAndScriptFailure(["var x = true isnot false"], 'Cannot use "isnot" with bool', 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = v:none is v:null"], 'Cannot use "is" with special', 1)
+ call v9.CheckDefAndScriptFailure(["var x = v:none isnot v:null"], 'Cannot use "isnot" with special', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 123 is 123"], 'Cannot use "is" with number', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 123 isnot 123"], 'Cannot use "isnot" with number', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1.3 is 1.3"], 'Cannot use "is" with float', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1.3 isnot 1.3"], 'Cannot use "isnot" with float', 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 0za1 > 0z34"], 'Cannot compare blob with blob', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0za1 >= 0z34"], 'Cannot compare blob with blob', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0za1 < 0z34"], 'Cannot compare blob with blob', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0za1 <= 0z34"], 'Cannot compare blob with blob', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0za1 =~ 0z34"], 'Cannot compare blob with blob', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0za1 !~ 0z34"], 'Cannot compare blob with blob', 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = [13] > [88]"], 'Cannot compare list with list', 1)
+ call v9.CheckDefAndScriptFailure(["var x = [13] >= [88]"], 'Cannot compare list with list', 1)
+ call v9.CheckDefAndScriptFailure(["var x = [13] < [88]"], 'Cannot compare list with list', 1)
+ call v9.CheckDefAndScriptFailure(["var x = [13] <= [88]"], 'Cannot compare list with list', 1)
+ call v9.CheckDefAndScriptFailure(["var x = [13] =~ [88]"], 'Cannot compare list with list', 1)
+ call v9.CheckDefAndScriptFailure(["var x = [13] !~ [88]"], 'Cannot compare list with list', 1)
+
+ if has('job')
+ call v9.CheckDefAndScriptFailure(['var j: job', 'var chan: channel', 'var r = j == chan'], 'Cannot compare job with channel', 3)
+ call v9.CheckDefAndScriptFailure(['var j: job', 'var x: list<any>', 'var r = j == x'], 'Cannot compare job with list', 3)
+ call v9.CheckDefAndScriptFailure(['var j: job', 'var Xx: func', 'var r = j == Xx'], 'Cannot compare job with func', 3)
+ call v9.CheckDefAndScriptFailure(['var j: job', 'var Xx: func', 'var r = j == Xx'], 'Cannot compare job with func', 3)
+ endif
+endfunc
+
+" test bitwise left and right shift operators
+" The tests for this is in test_expr.vim (Test_bitwise_shift)
+" def Test_expr5()
+" enddef
+
+" test addition, subtraction, concatenation
+def Test_expr6()
+ var lines =<< trim END
+ assert_equal(66, 60 + 6)
+ assert_equal(70, 60 +
+ g:anint)
+ assert_equal(9, g:thefour
+ + 5)
+ assert_equal(14, g:thefour + g:anint)
+ assert_equal([1, 2, 3, 4], [1] + g:alist)
+
+ assert_equal(54, 60 - 6)
+ assert_equal(50, 60 -
+ g:anint)
+ assert_equal(-1, g:thefour
+ - 5)
+ assert_equal(-6, g:thefour - g:anint)
+
+ assert_equal('hello', 'hel' .. 'lo')
+ assert_equal('hello 123', 'hello ' ..
+ 123)
+ assert_equal('hello 123', 'hello '
+ .. 123)
+ assert_equal('123 hello', 123 .. ' hello')
+ assert_equal('123456', 123 .. 456)
+
+ assert_equal('atrue', 'a' .. true)
+ assert_equal('afalse', 'a' .. false)
+ assert_equal('anull', 'a' .. v:null)
+ assert_equal('av:none', 'a' .. v:none)
+ assert_equal('a0.123', 'a' .. 0.123)
+
+ assert_equal(3, 1 + [2, 3, 4][0])
+ assert_equal(5, 2 + {key: 3}['key'])
+
+ set digraph
+ assert_equal('val: true', 'val: ' .. &digraph)
+ set nodigraph
+ assert_equal('val: false', 'val: ' .. &digraph)
+
+ assert_equal([1, 2, 3, 4], [1, 2] + [3, 4])
+ assert_equal(0z11223344, 0z1122 + 0z3344)
+ assert_equal(0z112201ab, 0z1122
+ + g:ablob)
+ assert_equal(0z01ab3344, g:ablob + 0z3344)
+ assert_equal(0z01ab01ab, g:ablob + g:ablob)
+
+ # concatenate non-constant to constant
+ var save_path = &path
+ &path = 'b'
+ assert_equal('ab', 'a' .. &path)
+ &path = save_path
+
+ @b = 'b'
+ assert_equal('ab', 'a' .. @b)
+
+ $ENVVAR = 'env'
+ assert_equal('aenv', 'a' .. $ENVVAR)
+
+ assert_equal('val', '' .. {key: 'val'}['key'])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr6_vim9script()
+ # check line continuation
+ var lines =<< trim END
+ var name = 11
+ + 77
+ - 22
+ assert_equal(66, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 11 +
+ 77 -
+ 22
+ assert_equal(66, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 11 + # comment
+ 77 -
+ # comment
+ 22
+ assert_equal(66, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 'one'
+ .. 'two'
+ assert_equal('onetwo', name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ echo 'abc' is# 'abc'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:', 1)
+
+ lines =<< trim END
+ echo {} - 22
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1036:', 'E728:'], 1)
+
+ lines =<< trim END
+ echo [] - 33
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1036:', 'E745:'], 1)
+
+ lines =<< trim END
+ echo 0z1234 - 44
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1036', 'E974:'], 1)
+
+ lines =<< trim END
+ echo 'abc' is? 'abc'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:', 1)
+
+ lines =<< trim END
+ echo 'abc' isnot# 'abc'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:', 1)
+
+ lines =<< trim END
+ echo 'abc' isnot? 'abc'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:', 1)
+
+ # check white space
+ lines =<< trim END
+ echo 5+6
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+ lines =<< trim END
+ echo 5 +6
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 5+ 6
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 'a'..'b'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''..'' at "..''b''"', 1)
+
+ lines =<< trim END
+ echo 'a' ..'b'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 'a'.. 'b'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''..'' at ".. ''b''"', 1)
+
+ lines =<< trim END
+ echo 'a'
+ ..'b'
+ # comment
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004: White space required before and after ''..'' at "..''b''"', 2)
+
+ # check invalid string concatenation
+ lines =<< trim END
+ echo 'a' .. [1]
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105:', 'E730:'], 1)
+
+ lines =<< trim END
+ echo 'a' .. {a: 1}
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105:', 'E731:'], 1)
+
+ lines =<< trim END
+ echo 'a' .. test_void()
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105:', 'E908:'], 1)
+
+ lines =<< trim END
+ echo 'a' .. 0z33
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105:', 'E976:'], 1)
+
+ lines =<< trim END
+ echo 'a' .. function('len')
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105:', 'E729:'], 1)
+
+ lines =<< trim END
+ new
+ ['']->setline(1)
+ /pattern
+
+ eval 0
+ bwipe!
+ END
+ v9.CheckDefAndScriptFailure(lines, "E1004: White space required before and after '/' at \"/pattern", 3)
+
+ for op in ['+', '-']
+ lines = ['var x = 1', op .. '2', '# comment']
+ var msg = printf("E1004: White space required before and after '%s' at \"%s2\"", op, op)
+ v9.CheckDefAndScriptFailure(lines, msg, 2)
+ endfor
+enddef
+
+def Test_expr6_vim9script_channel()
+ if !has('channel')
+ MissingFeature 'channel'
+ else
+ var lines =<< trim END
+ echo 'a' .. test_null_job()
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105:', 'E908:'], 1)
+ lines =<< trim END
+ echo 'a' .. test_null_channel()
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1105:', 'E908:'], 1)
+ endif
+enddef
+
+def Test_expr6_float()
+ var lines =<< trim END
+ assert_equal(66.0, 60.0 + 6.0)
+ assert_equal(66.0, 60.0 + 6)
+ assert_equal(66.0, 60 +
+ 6.0)
+ assert_equal(5.1, g:afloat
+ + 5)
+ assert_equal(8.1, 8 + g:afloat)
+ assert_equal(10.1, g:anint + g:afloat)
+ assert_equal(10.1, g:afloat + g:anint)
+
+ assert_equal(54.0, 60.0 - 6.0)
+ assert_equal(54.0, 60.0
+ - 6)
+ assert_equal(54.0, 60 - 6.0)
+ assert_equal(-4.9, g:afloat - 5)
+ assert_equal(7.9, 8 - g:afloat)
+ assert_equal(9.9, g:anint - g:afloat)
+ assert_equal(-9.9, g:afloat - g:anint)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+func Test_expr6_fails()
+ let msg = "White space required before and after '+'"
+ call v9.CheckDefAndScriptFailure(["var x = 1+2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 +2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1+ 2"], msg, 1)
+
+ let msg = "White space required before and after '-'"
+ call v9.CheckDefAndScriptFailure(["var x = 1-2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 -2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1- 2"], msg, 1)
+
+ let msg = "White space required before and after '..'"
+ call v9.CheckDefAndScriptFailure(["var x = '1'..'2'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1' ..'2'"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1'.. '2'"], msg, 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 0z1122 + 33"], ['E1051:', 'E974:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0z1122 + [3]"], ['E1051:', 'E974:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0z1122 + 'asd'"], ['E1051:', 'E974:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 33 + 0z1122"], ['E1051:', 'E974:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = [3] + 0z1122"], ['E1051:', 'E745:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 'asdf' + 0z1122"], ['E1051:', 'E1030:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 6 + xxx"], ['E1001:', 'E121:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. [1]"], ['E1105:', 'E730:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. {a: 1}"], ['E1105:', 'E731:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. test_void()"], ['E1105:', 'E908:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. 0z32"], ['E1105:', 'E976:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. function('len')"], ['E1105:', 'E729:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. function('len', ['a'])"], ['E1105:', 'E729:'], 1)
+
+ call v9.CheckDefAndScriptFailure(['var x = 1 + v:none'], ['E1051:', 'E611:'], 1)
+ call v9.CheckDefAndScriptFailure(['var x = 1 + v:null'], ['E1051:', 'E611:'], 1)
+ call v9.CheckDefAndScriptFailure(['var x = 1 + v:true'], ['E1051:', 'E1138:'], 1)
+ call v9.CheckDefAndScriptFailure(['var x = 1 + v:false'], ['E1051:', 'E1138:'], 1)
+ call v9.CheckDefAndScriptFailure(['var x = 1 + true'], ['E1051:', 'E1138:'], 1)
+ call v9.CheckDefAndScriptFailure(['var x = 1 + false'], ['E1051:', 'E1138:'], 1)
+endfunc
+
+func Test_expr6_fails_channel()
+ CheckFeature channel
+
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. test_null_job()"], ['E1105:', 'E908:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 'a' .. test_null_channel()"], ['E1105:', 'E908:'], 1)
+endfunc
+
+def Test_expr6_list_add()
+ var lines =<< trim END
+ # concatenating two lists with same member types is OK
+ var d = {}
+ for i in ['a'] + ['b']
+ d = {[i]: 0}
+ endfor
+
+ # concatenating two lists with different member types results in "any"
+ var dany = {}
+ for i in ['a'] + [12]
+ dany[i] = i
+ endfor
+ assert_equal({a: 'a', 12: 12}, dany)
+
+ # result of glob() is "any", runtime type check
+ var sl: list<string> = glob('*.txt', false, true) + ['']
+
+ var lln: list<list<number>> = [[1] + [2]]
+ assert_equal([[1, 2]], lln)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var ln: list<number> = [0]
+ var lln: list<list<number>> = [ln + []]
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+" test multiply, divide, modulo
+def Test_expr7()
+ var lines =<< trim END
+ assert_equal(36, 6 * 6)
+ assert_equal(24, 6 *
+ g:thefour)
+ assert_equal(24, g:thefour
+ * 6)
+ assert_equal(40, g:anint * g:thefour)
+
+ assert_equal(10, 60 / 6)
+ assert_equal(6, 60 /
+ g:anint)
+ assert_equal(1, g:anint / 6)
+ assert_equal(2, g:anint
+ / g:thefour)
+
+ assert_equal(5, 11 % 6)
+ assert_equal(4, g:anint % 6)
+ assert_equal(3, 13 %
+ g:anint)
+ assert_equal(2, g:anint
+ % g:thefour)
+
+ assert_equal(4, 6 * 4 / 6)
+
+ var x = [2]
+ var y = [3]
+ assert_equal(5, x[0] + y[0])
+ assert_equal(6, x[0] * y[0])
+ var xf = [2.0]
+ var yf = [3.0]
+ assert_equal(5.0, xf[0]
+ + yf[0])
+ assert_equal(6.0, xf[0]
+ * yf[0])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var x = 6 * xxx"], ['E1001:', 'E121:'], 1)
+ v9.CheckDefFailure(["var d = 6 * "], 'E1097:', 3)
+ v9.CheckScriptFailure(['vim9script', "var d = 6 * "], 'E15:', 2)
+
+ v9.CheckDefAndScriptFailure(['echo 1 / 0'], 'E1154', 1)
+ v9.CheckDefAndScriptFailure(['echo 1 % 0'], 'E1154', 1)
+
+ g:zero = 0
+ v9.CheckDefExecFailure(['echo 123 / g:zero'], 'E1154: Divide by zero')
+ v9.CheckDefExecFailure(['echo 123 % g:zero'], 'E1154: Divide by zero')
+
+ v9.CheckDefExecAndScriptFailure([
+ 'g:one = 1.0',
+ 'g:two = 2.0',
+ 'echo g:one % g:two',
+ ], 'E804', 3)
+
+ lines =<< trim END
+ var n = 0
+ eval 1 / n
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1154', 2)
+
+ lines =<< trim END
+ var n = 0
+ eval 1 % n
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1154', 2)
+enddef
+
+def Test_expr7_vim9script()
+ # check line continuation
+ var lines =<< trim END
+ var name = 11
+ * 22
+ / 3
+ assert_equal(80, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 25
+ % 10
+ assert_equal(5, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 25
+ # comment
+
+ # comment
+ % 10
+ assert_equal(5, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var name = 11 *
+ 22 /
+ 3
+ assert_equal(80, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # check white space
+ lines =<< trim END
+ echo 5*6
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 5 *6
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+
+ lines =<< trim END
+ echo 5* 6
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1004:', 1)
+enddef
+
+def Test_expr7_float()
+ var lines =<< trim END
+ assert_equal(36.0, 6.0 * 6)
+ assert_equal(36.0, 6 *
+ 6.0)
+ assert_equal(36.0, 6.0 * 6.0)
+ assert_equal(1.0, g:afloat * g:anint)
+
+ assert_equal(10.0, 60 / 6.0)
+ assert_equal(10.0, 60.0 /
+ 6)
+ assert_equal(10.0, 60.0 / 6.0)
+ assert_equal(0.01, g:afloat / g:anint)
+
+ assert_equal(4.0, 6.0 * 4 / 6)
+ assert_equal(4.0, 6 *
+ 4.0 /
+ 6)
+ assert_equal(4.0, 6 * 4 / 6.0)
+ assert_equal(4.0, 6.0 * 4.0 / 6)
+ assert_equal(4.0, 6 * 4.0 / 6.0)
+ assert_equal(4.0, 6.0 * 4 / 6.0)
+ assert_equal(4.0, 6.0 * 4.0 / 6.0)
+
+ assert_equal(4.0, 6.0 * 4.0 / 6.0)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+func Test_expr7_fails()
+ let msg = "White space required before and after '*'"
+ call v9.CheckDefAndScriptFailure(["var x = 1*2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 *2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1* 2"], msg, 1)
+
+ let msg = "White space required before and after '/'"
+ call v9.CheckDefAndScriptFailure(["var x = 1/2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 /2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1/ 2"], msg, 1)
+
+ let msg = "White space required before and after '%'"
+ call v9.CheckDefAndScriptFailure(["var x = 1%2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1 %2"], msg, 1)
+ call v9.CheckDefAndScriptFailure(["var x = 1% 2"], msg, 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = '1' * '2'"], ['E1036:', 'E1030:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1' / '2'"], ['E1036:', 'E1030:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1' % '2'"], ['E1035:', 'E1030:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 0z01 * 0z12"], ['E1036:', 'E974:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0z01 / 0z12"], ['E1036:', 'E974:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0z01 % 0z12"], ['E1035:', 'E974:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = [1] * [2]"], ['E1036:', 'E745:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = [1] / [2]"], ['E1036:', 'E745:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = [1] % [2]"], ['E1035:', 'E745:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = {one: 1} * {two: 2}"], ['E1036:', 'E728:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = {one: 1} / {two: 2}"], ['E1036:', 'E728:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = {one: 1} % {two: 2}"], ['E1035:', 'E728:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = 0xff[1]"], ['E1107:', 'E1062:'], 1)
+ call v9.CheckDefAndScriptFailure(["var x = 0.7[1]"], ['E1107:', 'E806:'], 1)
+
+ for op in ['*', '/', '%']
+ let lines = ['var x = 1', op .. '2', '# comment']
+ let msg = printf("E1004: White space required before and after '%s' at \"%s2\"", op, op)
+ call v9.CheckDefAndScriptFailure(lines, msg, 2)
+ endfor
+endfunc
+
+func Test_expr7_float_fails()
+ call v9.CheckDefAndScriptFailure(["var x = 1.0 % 2"], ['E1035:', 'E804:'], 1)
+endfunc
+
+" define here to use old style parsing
+let g:float_zero = 0.0
+let g:float_neg = -9.8
+let g:float_big = 9.9e99
+
+let g:blob_empty = 0z
+let g:blob_one = 0z01
+let g:blob_long = 0z0102.0304
+
+let g:string_empty = ''
+let g:string_short = 'x'
+let g:string_long = 'abcdefghijklm'
+let g:string_special = "ab\ncd\ref\ekk"
+
+let g:special_true = v:true
+let g:special_false = v:false
+let g:special_null = v:null
+let g:special_none = v:none
+
+let g:list_empty = []
+let g:list_mixed = [1, 'b', v:false]
+
+let g:dict_empty = {}
+let g:dict_one = #{one: 1}
+
+let $TESTVAR = 'testvar'
+
+" type casts
+def Test_expr8()
+ var lines =<< trim END
+ var ls: list<string> = ['a', <string>g:string_empty]
+ var ln: list<number> = [<number>g:anint, <number>g:thefour]
+ var nr = <number>234
+ assert_equal(234, nr)
+ var b: bool = <bool>1
+ assert_equal(true, b)
+ var text =
+ <string>
+ 'text'
+ if false
+ text = <number>'xxx'
+ endif
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var x = <nr>123"], 'E1010:', 1)
+ v9.CheckDefFailure(["var x = <number>"], 'E1097:', 3)
+ v9.CheckDefFailure(["var x = <number>string(1)"], 'E1012:', 1)
+ v9.CheckScriptFailure(['vim9script', "var x = <number>"], 'E15:', 2)
+ v9.CheckDefAndScriptFailure(["var x = <number >123"], 'E1068:', 1)
+ v9.CheckDefAndScriptFailure(["var x = <number 123"], 'E1104:', 1)
+
+ lines =<< trim END
+ vim9script
+
+ def Sum(v: any): float
+ var sum = 0.0
+ sum += v
+ return sum
+ enddef
+
+ const kk = 1
+ echo Sum(kk)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ def Sum(v: any): float
+ var sum = 0.0
+ sum += <float>v
+ return sum
+ enddef
+
+ const kk = 1
+ Sum(kk)
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected float but got number')
+enddef
+
+" test low level expression
+def Test_expr9_number()
+ # number constant
+ var lines =<< trim END
+ assert_equal(0, 0)
+ assert_equal(654, 0654)
+
+ assert_equal(6, 0x6)
+ assert_equal(15, 0xf)
+ assert_equal(255, 0xff)
+
+ const INFTY = 1.0 / 0.0
+ def Test()
+ assert_equal(1, isinf(INFTY))
+ assert_equal(-1, isinf(-INFTY))
+ enddef
+ Test()
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_float()
+ # float constant
+ var lines =<< trim END
+ assert_equal(g:float_zero, .0)
+ assert_equal(g:float_zero, 0.0)
+ assert_equal(g:float_neg, -9.8)
+ assert_equal(g:float_big, 9.9e99)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_blob()
+ # blob constant
+ var lines =<< trim END
+ assert_equal(g:blob_empty, 0z)
+ assert_equal(g:blob_one, 0z01)
+ assert_equal(g:blob_long, 0z0102.0304)
+
+ var testblob = 0z010203
+ assert_equal(0x01, testblob[0])
+ assert_equal(0x02, testblob[1])
+ assert_equal(0x03, testblob[-1])
+ assert_equal(0x02, testblob[-2])
+
+ assert_equal(0z01, testblob[0 : 0])
+ assert_equal(0z0102, testblob[0 : 1])
+ assert_equal(0z010203, testblob[0 : 2])
+ assert_equal(0z010203, testblob[0 : ])
+ assert_equal(0z0203, testblob[1 : ])
+ assert_equal(0z0203, testblob[1 : 2])
+ assert_equal(0z0203, testblob[1 : -1])
+ assert_equal(0z03, testblob[-1 : -1])
+ assert_equal(0z02, testblob[-2 : -2])
+
+ # blob slice accepts out of range
+ assert_equal(0z, testblob[3 : 3])
+ assert_equal(0z, testblob[0 : -4])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var x = 0z123"], 'E973:', 1)
+enddef
+
+def Test_expr9_string()
+ # string constant
+ var lines =<< trim END
+ assert_equal(g:string_empty, '')
+ assert_equal(g:string_empty, "")
+ assert_equal(g:string_short, 'x')
+ assert_equal(g:string_short, "x")
+ assert_equal(g:string_long, 'abcdefghijklm')
+ assert_equal(g:string_long, "abcdefghijklm")
+ assert_equal(g:string_special, "ab\ncd\ref\ekk")
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(['var x = "abc'], 'E114:', 1)
+ v9.CheckDefAndScriptFailure(["var x = 'abc"], 'E115:', 1)
+ v9.CheckDefFailure(["if 0", "echo 'xx", "endif"], 'E115', 2)
+
+ # interpolated string
+ var val = 'val'
+ var vv = $"some {val}"
+ assert_equal('some val', vv)
+ vv = $'other {val}'
+ assert_equal('other val', vv)
+
+ v9.CheckDefAndScriptFailure(['var x = $"foo'], 'E114:', 1)
+ v9.CheckDefAndScriptFailure(['var x = $"foo{xxx}"'], ['E1001: Variable not found: xxx', 'E121: Undefined variable: xxx'], 1)
+
+ var x = 'x'
+ var vl = 'foo xxx bar xxx baz'
+ ->split($'x{x}x')
+ ->map((_, v: string) => v =~ 'bar')
+ assert_equal([false, true, false], vl)
+
+ # interpolated string in a lambda
+ lines =<< trim END
+ assert_equal(['gnome-256color', 'xterm-256color'], ['gnome', 'xterm']
+ ->map((_, term: string) => $'{term}-256color'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_vimvar()
+ v:errors = []
+ var errs: list<string> = v:errors
+ v9.CheckDefFailure(['var errs: list<number> = v:errors'], 'E1012:')
+
+ var old: list<string> = v:oldfiles
+ v9.CheckDefFailure(['var old: list<number> = v:oldfiles'], 'E1012:')
+
+ var compl: dict<string> = v:completed_item
+ v9.CheckDefFailure(['var compl: dict<number> = v:completed_item'], 'E1012:')
+
+ var args: list<string> = v:argv
+ v9.CheckDefFailure(['var args: list<number> = v:argv'], 'E1012:')
+
+ var colors: dict<string> = v:colornames
+ v9.CheckDefFailure(['var colors: dict<number> = v:colornames'], 'E1012:')
+
+ v9.CheckDefFailure(["var old: list<number> = v:oldfiles"], 'E1012: Type mismatch; expected list<number> but got list<string>', 1)
+ v9.CheckScriptFailure(['vim9script', 'v:oldfiles = ["foo"]', "var old: list<number> = v:oldfiles"], 'E1012: Type mismatch; expected list<number> but got list<string>', 3)
+ new
+ exec "normal! afoo fo\<C-N>\<Esc>"
+ v9.CheckDefExecAndScriptFailure(["var old: dict<number> = v:completed_item"], 'E1012: Type mismatch; expected dict<number> but got dict<string>', 1)
+ bwipe!
+enddef
+
+def Test_expr9_special()
+ # special constant
+ var lines =<< trim END
+ assert_equal(g:special_true, true)
+ assert_equal(g:special_false, false)
+ assert_equal(g:special_true, v:true)
+ assert_equal(g:special_false, v:false)
+ assert_equal(v:true, true)
+ assert_equal(v:false, false)
+
+ assert_equal(true, !false)
+ assert_equal(false, !true)
+ assert_equal(true, !0)
+ assert_equal(false, !1)
+ assert_equal(false, !!false)
+ assert_equal(true, !!true)
+ assert_equal(false, !!0)
+ assert_equal(true, !!1)
+
+ var t = true
+ var f = false
+ assert_equal(true, t)
+ assert_equal(false, f)
+
+ assert_equal(g:special_null, v:null)
+ assert_equal(g:special_null, null)
+ assert_equal(g:special_none, v:none)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(['v:true = true'], 'E46:', 1)
+ v9.CheckDefAndScriptFailure(['v:true = false'], 'E46:', 1)
+ v9.CheckDefAndScriptFailure(['v:false = true'], 'E46:', 1)
+ v9.CheckDefAndScriptFailure(['v:null = 11'], 'E46:', 1)
+ v9.CheckDefAndScriptFailure(['v:none = 22'], 'E46:', 1)
+enddef
+
+def Test_expr9_list()
+ # list
+ var lines =<< trim END
+ assert_equal(g:list_empty, [])
+ assert_equal(g:list_empty, [ ])
+
+ var numbers: list<number> = [1, 2, 3]
+ numbers = [1]
+ numbers = []
+
+ var strings: list<string> = ['a', 'b', 'c']
+ strings = ['x']
+ strings = []
+
+ var mixed: list<any> = [1, 'b', false,]
+ assert_equal(g:list_mixed, mixed)
+ assert_equal('b', mixed[1])
+
+ echo [1,
+ 2] [3,
+ 4]
+
+ var llstring: list<list<string>> = [['text'], []]
+ llstring = [[], ['text']]
+ llstring = [[], []]
+
+ var ls = [null_string]
+ assert_equal('list<string>', typename(ls))
+ var lb = [null_blob]
+ assert_equal('list<blob>', typename(lb))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ var rangelist: list<number> = range(3)
+ g:rangelist = range(3)
+ v9.CheckDefExecAndScriptFailure(["var x: list<string> = g:rangelist"], 'E1012: Type mismatch; expected list<string> but got list<number>', 1)
+
+ v9.CheckDefAndScriptFailure(["var x = 1234[3]"], ['E1107:', 'E1062:'], 1)
+ v9.CheckDefExecAndScriptFailure(["var x = g:anint[3]"], 'E1062:', 1)
+
+ v9.CheckDefAndScriptFailure(["var x = g:list_mixed[xxx]"], ['E1001:', 'E121:'], 1)
+
+ v9.CheckDefAndScriptFailure(["var x = [1,2,3]"], 'E1069:', 1)
+ v9.CheckDefAndScriptFailure(["var x = [1 ,2, 3]"], 'E1068:', 1)
+
+ v9.CheckDefExecAndScriptFailure(["echo 1", "var x = [][0]", "echo 3"], 'E684:', 2)
+
+ v9.CheckDefExecAndScriptFailure(["var x = g:list_mixed['xx']"], ['E1012:', 'E1030:'], 1)
+ v9.CheckDefFailure(["var x = g:list_mixed["], 'E1097:', 3)
+ v9.CheckScriptFailure(['vim9script', "var x = g:list_mixed["], 'E15:', 2)
+ v9.CheckDefFailure(["var x = g:list_mixed[0"], 'E1097:', 3)
+ v9.CheckScriptFailure(['vim9script', "var x = g:list_mixed[0"], 'E111:', 2)
+ v9.CheckDefExecAndScriptFailure(["var x = g:list_empty[3]"], 'E684:', 1)
+ v9.CheckDefExecAndScriptFailure(["var l: list<number> = [234, 'x']"], 'E1012:', 1)
+ v9.CheckDefExecAndScriptFailure(["var l: list<number> = ['x', 234]"], 'E1012:', 1)
+ v9.CheckDefExecAndScriptFailure(["var l: list<string> = [234, 'x']"], 'E1012:', 1)
+ v9.CheckDefExecAndScriptFailure(["var l: list<string> = ['x', 123]"], 'E1012:', 1)
+
+ lines =<< trim END
+ var datalist: list<string>
+ def Main()
+ datalist += ['x'.
+ enddef
+ Main()
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1127:')
+
+ lines =<< trim END
+ var numbers = [1, 2, 3, 4]
+ var a = 1
+ var b = 2
+ END
+ v9.CheckDefAndScriptFailure(lines + ['echo numbers[1:b]'],
+ 'E1004: White space required before and after '':'' at ":b]"', 4)
+ v9.CheckDefAndScriptFailure(lines + ['echo numbers[1: b]'], 'E1004:', 4)
+ v9.CheckDefAndScriptFailure(lines + ['echo numbers[a :b]'], 'E1004:', 4)
+enddef
+
+def Test_expr9_list_vim9script()
+ var lines =<< trim END
+ var l = [
+ 11,
+ 22,
+ ]
+ assert_equal([11, 22], l)
+
+ echo [1,
+ 2] [3,
+ 4]
+
+ echo [1, # comment
+ # comment
+ 2] [3,
+ # comment
+ 4]
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l = [11,
+ 22]
+ assert_equal([11, 22], l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l = [11,22]
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1069:', 1)
+
+ lines =<< trim END
+ var l = [11 , 22]
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1068:', 1)
+
+ lines =<< trim END
+ var l: list<number> = [234, 'x']
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var l: list<number> = ['x', 234]
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var l: list<string> = ['x', 234]
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var l: list<string> = [234, 'x']
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ def Failing()
+ job_stop()
+ enddef
+ var list = [Failing]
+ END
+ if has('channel')
+ v9.CheckDefAndScriptFailure(lines, 'E119:', 0)
+ else
+ v9.CheckDefAndScriptFailure(lines, 'E117:', 0)
+ endif
+enddef
+
+def LambdaWithComments(): func
+ return (x) =>
+ # some comment
+ x == 1
+ # some comment
+ ||
+ x == 2
+enddef
+
+def LambdaUsingArg(x: number): func
+ return () =>
+ # some comment
+ x == 1
+ # some comment
+ ||
+ x == 2
+enddef
+
+def Test_expr9_lambda()
+ var lines =<< trim END
+ var La = () => 'result'
+ # comment
+ assert_equal('result', La())
+ assert_equal([1, 3, 5], [1, 2, 3]->map((key, val) => key + val))
+
+ # line continuation inside lambda with "cond ? expr : expr" works
+ var ll = range(3)
+ var dll = mapnew(ll, (k, v) => v % 2 ? {
+ ['111']: 111 } : {}
+ )
+ assert_equal([{}, {111: 111}, {}], dll)
+
+ # comment halfway an expression
+ var Ref = () => 4
+ # comment
+ + 6
+ assert_equal(10, Ref())
+
+ ll = range(3)
+ map(ll, (k, v) => v == 8 || v
+ == 9
+ || v % 2 ? 111 : 222
+ )
+ assert_equal([222, 111, 222], ll)
+
+ ll = range(3)
+ map(ll, (k, v) => v != 8 && v
+ != 9
+ && v % 2 == 0 ? 111 : 222
+ )
+ assert_equal([111, 222, 111], ll)
+
+ var dl = [{key: 0}, {key: 22}]->filter(( _, v) => !!v['key'] )
+ assert_equal([{key: 22}], dl)
+
+ dl = [{key: 12}, {['foo']: 34}]
+ assert_equal([{key: 12}], filter(dl,
+ (_, v) => has_key(v, 'key') ? v['key'] == 12 : 0))
+
+ assert_equal(false, g:LambdaWithComments()(0))
+ assert_equal(true, g:LambdaWithComments()(1))
+ assert_equal(true, g:LambdaWithComments()(2))
+ assert_equal(false, g:LambdaWithComments()(3))
+
+ assert_equal(false, g:LambdaUsingArg(0)())
+ assert_equal(true, g:LambdaUsingArg(1)())
+
+ var res = map([1, 2, 3], (i: number, v: number) => i + v)
+ assert_equal([1, 3, 5], res)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var Ref = (a)=>a + 1"], 'E1004:')
+ v9.CheckDefAndScriptFailure(["var Ref = (a)=> a + 1"], 'E1004: White space required before and after ''=>'' at "=> a + 1"')
+ v9.CheckDefAndScriptFailure(["var Ref = (a) =>a + 1"], 'E1004:')
+ v9.CheckDefAndScriptFailure(["var Ref = (a) =< a + 1"], ['E1001:', 'E121:'])
+ v9.CheckDefAndScriptFailure(["var Ref = (a: int) => a + 1"], 'E1010:')
+ v9.CheckDefAndScriptFailure(["var Ref = (a): int => a + 1"], 'E1010:')
+
+ v9.CheckDefAndScriptFailure(["filter([1, 2], (k,v) => 1)"], 'E1069:', 1)
+ # error is in first line of the lambda
+ v9.CheckDefAndScriptFailure(["var L = (a) => a + b"], 'E1001:', 0)
+
+ assert_equal('xxxyyy', 'xxx'->((a, b) => a .. b)('yyy'))
+
+ v9.CheckDefExecFailure(["var s = 'asdf'->((a) => a)('x')"], 'E118:')
+ v9.CheckDefExecFailure(["var s = 'asdf'->((a) => a)('x', 'y')"], 'E118:')
+ v9.CheckDefAndScriptFailure(["echo 'asdf'->((a) => a)(x)"], ['E1001:', 'E121:'], 1)
+
+ v9.CheckDefAndScriptSuccess(['var Fx = (a) => ({k1: 0,', ' k2: 1})'])
+ v9.CheckDefAndScriptFailure(['var Fx = (a) => ({k1: 0', ' k2: 1})'], 'E722:', 2)
+ v9.CheckDefAndScriptFailure(['var Fx = (a) => ({k1: 0,', ' k2 1})'], 'E720:', 2)
+
+ v9.CheckDefAndScriptSuccess(['var Fx = (a) => [0,', ' 1]'])
+ v9.CheckDefAndScriptFailure(['var Fx = (a) => [0', ' 1]'], 'E696:', 2)
+ v9.CheckDefAndScriptFailure(['var l = [1 2]'], 'E696:', 1)
+
+ # no error for existing script variable when checking for lambda
+ lines =<< trim END
+ var name = 0
+ eval (name + 2) / 3
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_lambda_block()
+ var lines =<< trim END
+ var Func = (s: string): string => {
+ return 'hello ' .. s
+ }
+ assert_equal('hello there', Func('there'))
+
+ var ll = range(3)
+ var dll = mapnew(ll, (k, v): string => {
+ if v % 2
+ return 'yes'
+ endif
+ return 'no'
+ })
+ assert_equal(['no', 'yes', 'no'], dll)
+
+ # ignored_inline(0, (_) => {
+ # echo 'body'
+ # })
+
+ sandbox var Safe = (nr: number): number => {
+ return nr + 7
+ }
+ assert_equal(10, Safe(3))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ map([1, 2], (k, v) => { redrawt })
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E488')
+
+ lines =<< trim END
+ var Func = (nr: int) => {
+ echo nr
+ }
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1010', 1)
+
+ lines =<< trim END
+ var Func = (nr: number): int => {
+ return nr
+ }
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1010', 1)
+
+ lines =<< trim END
+ var Func = (nr: number): int => {
+ return nr
+ END
+ v9.CheckDefFailure(lines, 'E1171', 0) # line nr is function start
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E1171', 2)
+
+ lines =<< trim END
+ var Func = (nr: number): int => {
+ var ll =<< ENDIT
+ nothing
+ END
+ v9.CheckDefFailure(lines, 'E1145: Missing heredoc end marker: ENDIT', 0)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E1145: Missing heredoc end marker: ENDIT', 2)
+enddef
+
+def NewLambdaWithComments(): func
+ return (x) =>
+ # some comment
+ x == 1
+ # some comment
+ ||
+ x == 2
+enddef
+
+def NewLambdaUsingArg(x: number): func
+ return () =>
+ # some comment
+ x == 1
+ # some comment
+ ||
+ x == 2
+enddef
+
+def Test_expr9_new_lambda()
+ var lines =<< trim END
+ var La = () => 'result'
+ assert_equal('result', La())
+ assert_equal([1, 3, 5], [1, 2, 3]->map((key, val) => key + val))
+
+ # line continuation inside lambda with "cond ? expr : expr" works
+ var ll = range(3)
+ var dll = mapnew(ll, (k, v) => v % 2 ? {
+ ['111']: 111 } : {}
+ )
+ assert_equal([{}, {111: 111}, {}], dll)
+
+ ll = range(3)
+ map(ll, (k, v) => v == 8 || v
+ == 9
+ || v % 2 ? 111 : 222
+ )
+ assert_equal([222, 111, 222], ll)
+
+ ll = range(3)
+ map(ll, (k, v) => v != 8 && v
+ != 9
+ && v % 2 == 0 ? 111 : 222
+ )
+ assert_equal([111, 222, 111], ll)
+
+ var dl = [{key: 0}, {key: 22}]->filter(( _, v) => !!v['key'] )
+ assert_equal([{key: 22}], dl)
+
+ dl = [{key: 12}, {['foo']: 34}]
+ assert_equal([{key: 12}], filter(dl,
+ (_, v) => has_key(v, 'key') ? v['key'] == 12 : 0))
+
+ assert_equal(false, g:NewLambdaWithComments()(0))
+ assert_equal(true, g:NewLambdaWithComments()(1))
+ assert_equal(true, g:NewLambdaWithComments()(2))
+ assert_equal(false, g:NewLambdaWithComments()(3))
+
+ assert_equal(false, g:NewLambdaUsingArg(0)())
+ assert_equal(true, g:NewLambdaUsingArg(1)())
+
+ var res = map([1, 2, 3], (i: number, v: number) => i + v)
+ assert_equal([1, 3, 5], res)
+
+ # Lambda returning a dict
+ var Lmb = () => ({key: 42})
+ assert_equal({key: 42}, Lmb())
+
+ var RefOne: func(number): string = (a: number): string => 'x'
+ var RefTwo: func(number): any = (a: number): any => 'x'
+
+ var Fx = (a) => ({k1: 0,
+ k2: 1})
+ var Fy = (a) => [0,
+ 1]
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var Ref = (a)=>a + 1"], 'E1004:')
+ v9.CheckDefAndScriptFailure(["var Ref = (a)=> a + 1"], 'E1004:')
+ v9.CheckDefAndScriptFailure(["var Ref = (a) =>a + 1"],
+ 'E1004: White space required before and after ''=>'' at " =>a + 1"')
+
+ v9.CheckDefAndScriptFailure(["var Ref: func(number): number = (a: number): string => 'x'"], 'E1012:')
+ v9.CheckDefAndScriptFailure(["var Ref: func(number): string = (a: number): string => 99"], 'E1012:')
+
+ v9.CheckDefAndScriptFailure(["filter([1, 2], (k,v) => 1)"], 'E1069:', 1)
+ # error is in first line of the lambda
+ v9.CheckDefAndScriptFailure(["var L = (a) -> a + b"], ['E1001:', 'E121:'], 1)
+
+ assert_equal('xxxyyy', 'xxx'->((a, b) => a .. b)('yyy'))
+
+ v9.CheckDefExecFailure(["var s = 'asdf'->((a) => a)('x')"],
+ 'E118: Too many arguments for function:')
+ v9.CheckDefExecFailure(["var s = 'asdf'->((a) => a)('x', 'y')"],
+ 'E118: Too many arguments for function:')
+ v9.CheckDefFailure(["echo 'asdf'->((a) => a)(x)"], 'E1001:', 1)
+
+ v9.CheckDefAndScriptFailure(['var Fx = (a) => ({k1: 0', ' k2: 1})'], 'E722:', 2)
+ v9.CheckDefAndScriptFailure(['var Fx = (a) => ({k1: 0,', ' k2 1})'], 'E720:', 2)
+
+ v9.CheckDefAndScriptFailure(['var Fx = (a) => [0', ' 1]'], 'E696:', 2)
+enddef
+
+def Test_expr9_lambda_vim9script()
+ var lines =<< trim END
+ var v = 10->((a) =>
+ a
+ + 2
+ )()
+ assert_equal(12, v)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # nested lambda with line breaks
+ lines =<< trim END
+ search('"', 'cW', 0, 0, () =>
+ synstack('.', col('.'))
+ ->mapnew((_, v) => synIDattr(v, 'name'))->len())
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9funcref()
+ var lines =<< trim END
+ def RetNumber(): number
+ return 123
+ enddef
+ var FuncRef = RetNumber
+ assert_equal(123, FuncRef())
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ func g:GlobalFunc()
+ return 'global'
+ endfunc
+ func ScriptFunc()
+ return 'script'
+ endfunc
+ def Test()
+ var Ref = g:GlobalFunc
+ assert_equal('global', Ref())
+ Ref = g:GlobalFunc
+ assert_equal('global', Ref())
+
+ Ref = ScriptFunc
+ assert_equal('script', Ref())
+ Ref = ScriptFunc
+ assert_equal('script', Ref())
+ enddef
+ Test()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # using funcref in legacy script
+ lines =<< trim END
+ def s:Refme(): string
+ return 'yes'
+ enddef
+
+ def TestFunc()
+ var TheRef = s:Refme
+ assert_equal('yes', TheRef())
+ enddef
+
+ call TestFunc()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+let g:test_space_dict = {'': 'empty', ' ': 'space'}
+let g:test_hash_dict = #{one: 1, two: 2}
+
+def Test_expr9_dict()
+ # dictionary
+ var lines =<< trim END
+ assert_equal(g:dict_empty, {})
+ assert_equal(g:dict_empty, { })
+ assert_equal(g:dict_one, {['one']: 1})
+ var key = 'one'
+ var val = 1
+ assert_equal(g:dict_one, {[key]: val})
+
+ var numbers: dict<number> = {a: 1, b: 2, c: 3}
+ numbers = {a: 1}
+ numbers = {}
+
+ var strings: dict<string> = {a: 'a', b: 'b', c: 'c'}
+ strings = {a: 'x'}
+ strings = {}
+
+ var dash = {xx-x: 8}
+ assert_equal({['xx-x']: 8}, dash)
+
+ var dnr = {8: 8}
+ assert_equal({['8']: 8}, dnr)
+
+ var mixed: dict<any> = {a: 'a', b: 42}
+ mixed = {a: 'x'}
+ mixed = {a: 234}
+ mixed = {}
+
+ var dictlist: dict<list<string>> = {absent: [], present: ['hi']}
+ dictlist = {absent: ['hi'], present: []}
+ dictlist = {absent: [], present: []}
+
+ var dictdict: dict<dict<string>> = {one: {a: 'text'}, two: {}}
+ dictdict = {one: {}, two: {a: 'text'}}
+ dictdict = {one: {}, two: {}}
+
+ assert_equal({['']: 0}, {[matchstr('string', 'wont match')]: 0})
+
+ assert_equal(g:test_space_dict, {['']: 'empty', [' ']: 'space'})
+ assert_equal(g:test_hash_dict, {one: 1, two: 2})
+
+ assert_equal({['a a']: 1, ['b/c']: 2}, {'a a': 1, "b/c": 2})
+
+ var d = {a: () => 3, b: () => 7}
+ assert_equal(3, d.a())
+ assert_equal(7, d.b())
+
+ var cd = { # comment
+ key: 'val' # comment
+ }
+
+ # different types used for the key
+ var dkeys = {['key']: 'string',
+ [12]: 'numberexpr',
+ 34: 'number',
+ [true]: 'bool'}
+ assert_equal('string', dkeys['key'])
+ assert_equal('numberexpr', dkeys[12])
+ assert_equal('number', dkeys[34])
+ assert_equal('bool', dkeys[true])
+ dkeys = {[1.2]: 'floatexpr', [3.4]: 'float'}
+ assert_equal('floatexpr', dkeys[1.2])
+ assert_equal('float', dkeys[3.4])
+
+ # automatic conversion from number to string
+ var n = 123
+ var dictnr = {[n]: 1}
+
+ # comment to start fold is OK
+ var x1: number #{{ fold
+ var x2 = 9 #{{ fold
+
+ var ds = {k: null_string}
+ assert_equal('dict<string>', typename(ds))
+ var dl = {a: null_list}
+ assert_equal('dict<list<unknown>>', typename(dl))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # legacy syntax doesn't work
+ v9.CheckDefAndScriptFailure(["var x = #{key: 8}"], 'E1170:', 1)
+ v9.CheckDefAndScriptFailure(["var x = 'a' #{a: 1}"], 'E1170:', 1)
+ v9.CheckDefAndScriptFailure(["var x = 'a' .. #{a: 1}"], 'E1170:', 1)
+ v9.CheckDefAndScriptFailure(["var x = true ? #{a: 1}"], 'E1170:', 1)
+
+ v9.CheckDefAndScriptFailure(["var x = 'a'", " #{a: 1}"], 'E1170:', 1)
+
+ v9.CheckDefAndScriptFailure(["var x = {a:8}"], 'E1069:', 1)
+ v9.CheckDefAndScriptFailure(["var x = {a : 8}"], 'E1068:', 1)
+ v9.CheckDefAndScriptFailure(["var x = {a :8}"], 'E1068:', 1)
+ v9.CheckDefAndScriptFailure(["var x = {a: 8 , b: 9}"], 'E1068:', 1)
+ v9.CheckDefAndScriptFailure(["var x = {a: 1,b: 2}"], 'E1069:', 1)
+
+ v9.CheckDefAndScriptFailure(["var x = {xxx}"], 'E720:', 1)
+ v9.CheckDefAndScriptFailure(["var x = {xxx: 1", "var y = 2"], 'E722:', 2)
+ v9.CheckDefFailure(["var x = {xxx: 1,"], 'E723:', 2)
+ v9.CheckScriptFailure(['vim9script', "var x = {xxx: 1,"], 'E723:', 2)
+ v9.CheckDefAndScriptFailure(["var x = {['a']: xxx}"], ['E1001:', 'E121:'], 1)
+ v9.CheckDefAndScriptFailure(["var x = {a: 1, a: 2}"], 'E721:', 1)
+ g:key = 'x'
+ v9.CheckDefExecAndScriptFailure(["var x = {[g:key]: 'text', [g:key]: 'text'}"], 'E721:', 1)
+ unlet g:key
+ v9.CheckDefExecAndScriptFailure(["var x = {[notexists]: 'text'}"], ['E1001:', 'E121: Undefined variable: notexists'], 1)
+ v9.CheckDefExecAndScriptFailure(["var x = g:anint.member"], ['E715:', 'E488:'], 1)
+ v9.CheckDefExecAndScriptFailure(["var x = g:dict_empty.member"], 'E716:', 1)
+
+ v9.CheckDefExecAndScriptFailure(['var x: dict<number> = {a: 234, b: "1"}'], 'E1012:', 1)
+ v9.CheckDefExecAndScriptFailure(['var x: dict<number> = {a: "x", b: 134}'], 'E1012:', 1)
+ v9.CheckDefExecAndScriptFailure(['var x: dict<string> = {a: 234, b: "1"}'], 'E1012:', 1)
+ v9.CheckDefExecAndScriptFailure(['var x: dict<string> = {a: "x", b: 134}'], 'E1012:', 1)
+
+ # invalid types for the key
+ v9.CheckDefAndScriptFailure(["var x = {[[1, 2]]: 0}"], ['E1105:', 'E730:'], 1)
+
+ v9.CheckDefFailure(['var x = ({'], 'E723:', 2)
+ v9.CheckScriptFailure(['vim9script', 'var x = ({'], 'E723:', 2)
+ v9.CheckDefExecAndScriptFailure(['{}[getftype("file")]'], 'E716: Key not present in Dictionary: ""', 1)
+enddef
+
+def Test_expr9_dict_vim9script()
+ var lines =<< trim END
+ var d = {
+ ['one']:
+ 1,
+ ['two']: 2,
+ }
+ assert_equal({one: 1, two: 2}, d)
+
+ d = { # comment
+ ['one']:
+ # comment
+
+ 1,
+ # comment
+ # comment
+ ['two']: 2,
+ }
+ assert_equal({one: 1, two: 2}, d)
+
+ var dd = {k: 123->len()}
+ assert_equal(3, dd.k)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = { ["one"]: "one", ["two"]: "two", }
+ assert_equal({one: 'one', two: 'two'}, d)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = {one: 1,
+ two: 2,
+ }
+ assert_equal({one: 1, two: 2}, d)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = {one:1, two: 2}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1069:', 1)
+
+ lines =<< trim END
+ var d = {one: 1,two: 2}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1069:', 1)
+
+ lines =<< trim END
+ var d = {one : 1}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1068:', 1)
+
+ lines =<< trim END
+ var d = {one:1}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1069:', 1)
+
+ lines =<< trim END
+ var d = {one: 1 , two: 2}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1068:', 1)
+
+ lines =<< trim END
+ var l: dict<number> = {a: 234, b: 'x'}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var l: dict<number> = {a: 'x', b: 234}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var l: dict<string> = {a: 'x', b: 234}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var l: dict<string> = {a: 234, b: 'x'}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:', 1)
+
+ lines =<< trim END
+ var d = {['a']: 234, ['b': 'x'}
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1139:', 1)
+
+ lines =<< trim END
+ def Func()
+ var d = {['a']: 234, ['b': 'x'}
+ enddef
+ defcompile
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1139:', 0)
+
+ lines =<< trim END
+ var d = {'a':
+ END
+ v9.CheckDefFailure(lines, 'E723:', 2)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E15:', 2)
+
+ lines =<< trim END
+ def Func()
+ var d = {'a':
+ enddef
+ defcompile
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E723:', 0)
+
+ lines =<< trim END
+ def Failing()
+ job_stop()
+ enddef
+ var dict = {name: Failing}
+ END
+ if has('channel')
+ v9.CheckDefAndScriptFailure(lines, 'E119:', 0)
+ else
+ v9.CheckDefAndScriptFailure(lines, 'E117:', 0)
+ endif
+
+ lines =<< trim END
+ vim9script
+ var x = 99
+ assert_equal({x: 99}, s:)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_expr9_dict_in_block()
+ var lines =<< trim END
+ vim9script
+ command MyCommand {
+ echo {
+ k: 0, }
+ }
+ MyCommand
+
+ command YourCommand {
+ g:global = {
+ key: 'value' }
+ }
+ YourCommand
+ assert_equal({key: 'value'}, g:global)
+ unlet g:global
+ END
+ v9.CheckScriptSuccess(lines)
+
+ delcommand MyCommand
+ delcommand YourCommand
+enddef
+
+def Test_expr9_call_2bool()
+ var lines =<< trim END
+ vim9script
+
+ def BrokenCall(nr: number, mode: bool, use: string): void
+ assert_equal(3, nr)
+ assert_equal(false, mode)
+ assert_equal('ab', use)
+ enddef
+
+ def TestBrokenCall(): void
+ BrokenCall(3, 0, 'ab')
+ enddef
+
+ TestBrokenCall()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+let g:oneString = 'one'
+
+def Test_expr_member()
+ var lines =<< trim END
+ assert_equal(1, g:dict_one.one)
+ var d: dict<number> = g:dict_one
+ assert_equal(1, d['one'])
+ assert_equal(1, d[
+ 'one'
+ ])
+ assert_equal(1, d
+ .one)
+ d = {1: 1, _: 2}
+ assert_equal(1, d
+ .1)
+ assert_equal(2, d
+ ._)
+
+ # getting the one member should clear the dict after getting the item
+ assert_equal('one', {one: 'one'}.one)
+ assert_equal('one', {one: 'one'}[g:oneString])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var x = g:dict_one.#$!"], ['E1002:', 'E15:'], 1)
+ v9.CheckDefExecAndScriptFailure(["var d: dict<any>", "echo d['a']"], 'E716:', 2)
+ v9.CheckDefExecAndScriptFailure(["var d: dict<number>", "d = g:list_empty"], 'E1012: Type mismatch; expected dict<number> but got list<unknown>', 2)
+enddef
+
+def Test_expr9_any_index_slice()
+ var lines =<< trim END
+ # getting the one member should clear the list only after getting the item
+ assert_equal('bbb', ['aaa', 'bbb', 'ccc'][1])
+
+ # string is permissive, index out of range accepted
+ g:teststring = 'abcdef'
+ assert_equal('b', g:teststring[1])
+ assert_equal('f', g:teststring[-1])
+ assert_equal('', g:teststring[99])
+
+ assert_equal('b', g:teststring[1 : 1])
+ assert_equal('bcdef', g:teststring[1 :])
+ assert_equal('abcd', g:teststring[: 3])
+ assert_equal('cdef', g:teststring[-4 :])
+ assert_equal('abcdef', g:teststring[-9 :])
+ assert_equal('abcd', g:teststring[: -3])
+ assert_equal('', g:teststring[: -9])
+
+ # composing characters are included
+ g:teststring = 'aÌ€eÌuÌ‚'
+ assert_equal('à', g:teststring[0])
+ assert_equal('eÌ', g:teststring[1])
+ assert_equal('û', g:teststring[2])
+ assert_equal('', g:teststring[3])
+ assert_equal('', g:teststring[4])
+
+ assert_equal('û', g:teststring[-1])
+ assert_equal('eÌ', g:teststring[-2])
+ assert_equal('à', g:teststring[-3])
+ assert_equal('', g:teststring[-4])
+ assert_equal('', g:teststring[-5])
+
+ assert_equal('à', g:teststring[0 : 0])
+ assert_equal('eÌ', g:teststring[1 : 1])
+ assert_equal('aÌ€eÌ', g:teststring[0 : 1])
+ assert_equal('aÌ€eÌuÌ‚', g:teststring[0 : -1])
+ assert_equal('aÌ€eÌ', g:teststring[0 : -2])
+ assert_equal('à', g:teststring[0 : -3])
+ assert_equal('', g:teststring[0 : -4])
+ assert_equal('', g:teststring[0 : -5])
+ assert_equal('aÌ€eÌuÌ‚', g:teststring[ : ])
+ assert_equal('aÌ€eÌuÌ‚', g:teststring[0 : ])
+ assert_equal('eÌuÌ‚', g:teststring[1 : ])
+ assert_equal('û', g:teststring[2 : ])
+ assert_equal('', g:teststring[3 : ])
+ assert_equal('', g:teststring[4 : ])
+
+ # blob index cannot be out of range
+ g:testblob = 0z01ab
+ assert_equal(0x01, g:testblob[0])
+ assert_equal(0xab, g:testblob[1])
+ assert_equal(0xab, g:testblob[-1])
+ assert_equal(0x01, g:testblob[-2])
+
+ # blob slice accepts out of range
+ assert_equal(0z01ab, g:testblob[0 : 1])
+ assert_equal(0z01, g:testblob[0 : 0])
+ assert_equal(0z01, g:testblob[-2 : -2])
+ assert_equal(0zab, g:testblob[1 : 1])
+ assert_equal(0zab, g:testblob[-1 : -1])
+ assert_equal(0z, g:testblob[2 : 2])
+ assert_equal(0z, g:testblob[0 : -3])
+
+ # list index cannot be out of range
+ g:testlist = [0, 1, 2, 3]
+ assert_equal(0, g:testlist[0])
+ assert_equal(1, g:testlist[1])
+ assert_equal(3, g:testlist[3])
+ assert_equal(3, g:testlist[-1])
+ assert_equal(0, g:testlist[-4])
+ assert_equal(1, g:testlist[g:theone])
+
+ # list slice accepts out of range
+ assert_equal([0], g:testlist[0 : 0])
+ assert_equal([3], g:testlist[3 : 3])
+ assert_equal([0, 1], g:testlist[0 : 1])
+ assert_equal([0, 1, 2, 3], g:testlist[0 : 3])
+ assert_equal([0, 1, 2, 3], g:testlist[0 : 9])
+ assert_equal([], g:testlist[-1 : 1])
+ assert_equal([1], g:testlist[-3 : 1])
+ assert_equal([0, 1], g:testlist[-4 : 1])
+ assert_equal([0, 1], g:testlist[-9 : 1])
+ assert_equal([1, 2, 3], g:testlist[1 : -1])
+ assert_equal([1], g:testlist[1 : -3])
+ assert_equal([], g:testlist[1 : -4])
+ assert_equal([], g:testlist[1 : -9])
+
+ g:testdict = {a: 1, b: 2}
+ assert_equal(1, g:testdict['a'])
+ assert_equal(2, g:testdict['b'])
+ END
+
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ def PosIdx(s: string): string
+ return s[1]
+ enddef
+ def NegIdx(s: string): string
+ return s[-1]
+ enddef
+
+ set enc=latin1
+ assert_equal("\xe4", PosIdx("a\xe4\xe5"))
+ assert_equal("\xe5", NegIdx("a\xe4\xe5"))
+ set enc=utf-8
+ END
+ v9.CheckScriptSuccess(lines)
+
+ v9.CheckDefExecAndScriptFailure(['echo g:testblob[2]'], 'E979:', 1)
+ v9.CheckDefExecAndScriptFailure(['echo g:testblob[-3]'], 'E979:', 1)
+
+ v9.CheckDefExecAndScriptFailure(['echo g:testlist[4]'], 'E684: List index out of range: 4', 1)
+ v9.CheckDefExecAndScriptFailure(['echo g:testlist[-5]'], 'E684:', 1)
+
+ v9.CheckDefExecAndScriptFailure(['echo g:testdict["a" : "b"]'], 'E719:', 1)
+ v9.CheckDefExecAndScriptFailure(['echo g:testdict[1]'], 'E716:', 1)
+
+ unlet g:teststring
+ unlet g:testblob
+ unlet g:testlist
+enddef
+
+def s:GetList(): list<string>
+ return ['a', 'b', 'z']
+enddef
+
+def Test_slice_const_list()
+ const list = GetList()
+ final sliced = list[0 : 1]
+ # OK to change the list after slicing, it is a copy now
+ add(sliced, 'Z')
+ assert_equal(['a', 'b', 'Z'], sliced)
+enddef
+
+def Test_expr9_const_any_index_slice()
+ var lines =<< trim END
+ vim9script
+
+ export def V(): dict<any>
+ return {a: [1, 43], b: 0}
+ enddef
+ END
+ writefile(lines, 'XexportDict.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+
+ import './XexportDict.vim' as x
+
+ def Test()
+ const v = x.V()
+ assert_equal(43, v.a[1])
+ enddef
+ Test()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_expr_member_vim9script()
+ var lines =<< trim END
+ var d = {one:
+ 'one',
+ two: 'two',
+ 1: 1,
+ _: 2}
+ assert_equal('one', d.one)
+ assert_equal('one', d
+ .one)
+ assert_equal(1, d
+ .1)
+ assert_equal(2, d
+ ._)
+ assert_equal('one', d[
+ 'one'
+ ])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var l = [1,
+ 2,
+ 3, 4
+ ]
+ assert_equal(2, l[
+ 1
+ ])
+ assert_equal([2, 3], l[1 : 2])
+ assert_equal([1, 2, 3], l[
+ :
+ 2
+ ])
+ assert_equal([3, 4], l[
+ 2
+ :
+ ])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def SetSomeVar()
+ b:someVar = &fdm
+enddef
+
+def Test_expr9_option()
+ var lines =<< trim END
+ # option
+ set ts=11
+ assert_equal(11, &ts)
+ &ts = 9
+ assert_equal(9, &ts)
+ set ts=8
+ set grepprg=some\ text
+ assert_equal('some text', &grepprg)
+ &grepprg = test_null_string()
+ assert_equal('', &grepprg)
+ set grepprg&
+
+ # check matching type
+ var bval: bool = &tgc
+ var nval: number = &ts
+ var sval: string = &path
+
+ # check v_lock is cleared (requires using valgrind, doesn't always show)
+ g:SetSomeVar()
+ b:someVar = 0
+ unlet b:someVar
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_environment()
+ var lines =<< trim END
+ # environment variable
+ assert_equal('testvar', $TESTVAR)
+ assert_equal('', $ASDF_ASD_XXX)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var x = $$$"], ['E1002:', 'E15:'], 1)
+ v9.CheckDefAndScriptFailure(["$"], ['E1002:', 'E15:'], 1)
+enddef
+
+def Test_expr9_register()
+ var lines =<< trim END
+ @a = 'register a'
+ assert_equal('register a', @a)
+
+ var fname = expand('%')
+ assert_equal(fname, @%)
+
+ feedkeys(":echo 'some'\<CR>", "xt")
+ assert_equal("echo 'some'", @:)
+
+ normal axyz
+ assert_equal("xyz", @.)
+
+ @/ = 'slash'
+ assert_equal('slash', @/)
+
+ @= = 'equal'
+ assert_equal('equal', @=)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["@. = 'yes'"], ['E354:', 'E488:'], 1)
+enddef
+
+" This is slow when run under valgrind.
+def Test_expr9_namespace()
+ var lines =<< trim END
+ g:some_var = 'some'
+ assert_equal('some', get(g:, 'some_var'))
+ assert_equal('some', get(g:, 'some_var', 'xxx'))
+ assert_equal('xxx', get(g:, 'no_var', 'xxx'))
+ unlet g:some_var
+
+ b:some_var = 'some'
+ assert_equal('some', get(b:, 'some_var'))
+ assert_equal('some', get(b:, 'some_var', 'xxx'))
+ assert_equal('xxx', get(b:, 'no_var', 'xxx'))
+ unlet b:some_var
+
+ w:some_var = 'some'
+ assert_equal('some', get(w:, 'some_var'))
+ assert_equal('some', get(w:, 'some_var', 'xxx'))
+ assert_equal('xxx', get(w:, 'no_var', 'xxx'))
+ unlet w:some_var
+
+ t:some_var = 'some'
+ assert_equal('some', get(t:, 'some_var'))
+ assert_equal('some', get(t:, 'some_var', 'xxx'))
+ assert_equal('xxx', get(t:, 'no_var', 'xxx'))
+ unlet t:some_var
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_namespace_loop_def()
+ var lines =<< trim END
+ # check using g: in a for loop more than DO_NOT_FREE_CNT times
+ var exists = 0
+ var exists_not = 0
+ for i in range(100000)
+ if has_key(g:, 'does-not-exist')
+ exists += 1
+ else
+ exists_not += 1
+ endif
+ endfor
+ assert_equal(0, exists)
+ assert_equal(100000, exists_not)
+ END
+ v9.CheckDefSuccess(lines)
+enddef
+
+" NOTE: this is known to be slow. To skip use:
+" :let $TEST_SKIP_PAT = 'Test_expr9_namespace_loop_script'
+def Test_expr9_namespace_loop_script()
+ var lines =<< trim END
+ vim9script
+ # check using g: in a for loop more than DO_NOT_FREE_CNT times
+ var exists = 0
+ var exists_not = 0
+ for i in range(100000)
+ if has_key(g:, 'does-not-exist')
+ exists += 1
+ else
+ exists_not += 1
+ endif
+ endfor
+ assert_equal(0, exists)
+ assert_equal(100000, exists_not)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_expr9_parens()
+ # (expr)
+ var lines =<< trim END
+ assert_equal(4, (6 * 4) / 6)
+ assert_equal(0, 6 * ( 4 / 6 ))
+
+ assert_equal(6, +6)
+ assert_equal(-6, -6)
+ assert_equal(false, !-3)
+ assert_equal(true, !+0)
+
+ assert_equal(7, 5 + (
+ 2))
+ assert_equal(7, 5 + (
+ 2
+ ))
+ assert_equal(7, 5 + ( # comment
+ 2))
+ assert_equal(7, 5 + ( # comment
+ # comment
+ 2))
+
+ var s = (
+ 'one'
+ ..
+ 'two'
+ )
+ assert_equal('onetwo', s)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(['echo ('], ['E1097: Line incomplete', 'E15: Invalid expression: "("'])
+ v9.CheckDefAndScriptFailure(['echo (123]'], "E110: Missing ')'", 1)
+
+ # this uses up the ppconst stack
+ lines =<< eval trim END
+ vim9script
+ def F()
+ g:result = 1 + {repeat('(1 + ', 51)}1{repeat(')', 51)}
+ enddef
+ F()
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal(g:result, 53)
+ unlet g:result
+enddef
+
+def Test_expr9_negate_add()
+ var lines =<< trim END
+ assert_equal(-99, -99)
+ assert_equal(-99, - 99)
+ assert_equal(99, +99)
+
+ var nr = 88
+ assert_equal(-88, -nr)
+ assert_equal(-88, - nr)
+ assert_equal(88, + nr)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var n = 12
+ echo ++n
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:')
+ lines =<< trim END
+ var n = 12
+ echo --n
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:')
+ lines =<< trim END
+ var n = 12
+ echo +-n
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:')
+ lines =<< trim END
+ var n = 12
+ echo -+n
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:')
+ lines =<< trim END
+ var n = 12
+ echo - -n
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:')
+ lines =<< trim END
+ var n = 12
+ echo + +n
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E15:')
+enddef
+
+def LegacyReturn(): string
+ legacy return #{key: 'ok'}.key
+enddef
+
+def Test_expr9_legacy_script()
+ var lines =<< trim END
+ let s:legacy = 'legacy'
+ def GetLocal(): string
+ return legacy
+ enddef
+ def GetLocalPrefix(): string
+ return s:legacy
+ enddef
+ call assert_equal('legacy', GetLocal())
+ call assert_equal('legacy', GetLocalPrefix())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ assert_equal('ok', g:LegacyReturn())
+
+ lines =<< trim END
+ vim9script
+ def GetNumber(): number
+ legacy return notexists
+ enddef
+ echo GetNumber()
+ END
+ v9.CheckScriptFailure(lines, 'E121: Undefined variable: notexists')
+
+ lines =<< trim END
+ vim9script
+ def GetNumber(): number
+ legacy return range(3)->map('v:val + 1')
+ enddef
+ echo GetNumber()
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected number but got list<number>')
+enddef
+
+def Echo(arg: any): string
+ return arg
+enddef
+
+def s:Echo4Arg(arg: any): string
+ return arg
+enddef
+
+def Test_expr9_call()
+ var lines =<< trim END
+ assert_equal('yes', 'yes'->g:Echo())
+ assert_equal(true, !range(5)->empty())
+ assert_equal([0, 1, 2], 3->range())
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ assert_equal('yes', 'yes'
+ ->s:Echo4Arg())
+
+ v9.CheckDefAndScriptFailure(["var x = 'yes'->g:Echo"], 'E107:', 1)
+ v9.CheckDefAndScriptFailure([
+ "var x = substitute ('x', 'x', 'x', 'x')"
+ ], ['E1001:', 'E121:'], 1)
+ v9.CheckDefAndScriptFailure(["var Ref = function('len' [1, 2])"], ['E1123:', 'E116:'], 1)
+ v9.CheckDefAndScriptFailure(["echo match(['foo'] , 'foo')"], 'E1068:', 1)
+enddef
+
+def g:ExistingGlobal(): string
+ return 'existing'
+enddef
+
+def Test_expr9_call_global()
+ assert_equal('existing', g:ExistingGlobal())
+
+ def g:DefinedLater(): string
+ return 'later'
+ enddef
+ assert_equal('later', g:DefinedLater())
+
+ var lines =<< trim END
+ echo ExistingGlobal()
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E117: Unknown function: ExistingGlobal')
+enddef
+
+def Test_expr9_autoload_var()
+ var auto_lines =<< trim END
+ let autofile#var = 'found'
+ END
+ mkdir('Xruntime/autoload', 'pR')
+ writefile(auto_lines, 'Xruntime/autoload/autofile.vim')
+ var save_rtp = &rtp
+ &rtp = getcwd() .. '/Xruntime,' .. &rtp
+
+ var lines =<< trim END
+ assert_equal('found', autofile#var)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ echo autofile#other
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E121: Undefined variable: autofile#other')
+
+ &rtp = save_rtp
+enddef
+
+def Test_expr9_call_autoload()
+ var auto_lines =<< trim END
+ def g:some#func(): string
+ return 'found'
+ enddef
+ END
+ mkdir('Xruntime/autoload', 'pR')
+ writefile(auto_lines, 'Xruntime/autoload/some.vim')
+ var save_rtp = &rtp
+ &rtp = getcwd() .. '/Xruntime,' .. &rtp
+ assert_equal('found', g:some#func())
+ assert_equal('found', some#func())
+
+ &rtp = save_rtp
+enddef
+
+def Test_expr9_method_call()
+ var lines =<< trim END
+ new
+ setline(1, ['first', 'last'])
+ 'second'->append(1)
+ "third"->append(2)
+ $"fourth"->append(3)
+ $'fifth'->append(4)
+ assert_equal(['first', 'second', 'third', 'fourth', 'fifth', 'last'], getline(1, '$'))
+ bwipe!
+
+ var bufnr = bufnr()
+ var loclist = [{bufnr: bufnr, lnum: 42, col: 17, text: 'wrong'}]
+ loclist->setloclist(0)
+ assert_equal([{bufnr: bufnr,
+ lnum: 42,
+ end_lnum: 0,
+ col: 17,
+ end_col: 0,
+ text: 'wrong',
+ pattern: '',
+ valid: 1,
+ vcol: 0,
+ nr: 0,
+ type: '',
+ module: ''}
+ ], getloclist(0))
+
+ var result: bool = get({n: 0}, 'n', 0)
+ assert_equal(false, result)
+
+ assert_equal('+string+', 'string'->((s) => '+' .. s .. '+')())
+ assert_equal('-text-', 'text'->((s, c) => c .. s .. c)('-'))
+
+ var Join = (l) => join(l, 'x')
+ assert_equal('axb', ['a', 'b']->(Join)())
+
+ var sorted = [3, 1, 2]
+ -> sort()
+ assert_equal([1, 2, 3], sorted)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ def SetNumber(n: number)
+ g:number = n
+ enddef
+ const Setit = SetNumber
+ len('text')->Setit()
+ assert_equal(4, g:number)
+
+ const SetFuncref = funcref(SetNumber)
+ len('longer')->SetFuncref()
+ assert_equal(6, g:number)
+
+ const SetList = [SetNumber, SetFuncref]
+ len('xx')->SetList[0]()
+ assert_equal(2, g:number)
+ len('xxx')->SetList[1]()
+ assert_equal(3, g:number)
+
+ const SetDict = {key: SetNumber}
+ len('xxxx')->SetDict['key']()
+ assert_equal(4, g:number)
+ len('xxxxx')->SetDict.key()
+ assert_equal(5, g:number)
+
+ unlet g:number
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ def RetVoid()
+ enddef
+ RetVoid()->byteidx(3)
+ END
+ v9.CheckDefExecFailure(lines, 'E1013:')
+
+ lines =<< trim END
+ const SetList = [function('len')]
+ echo 'xx'->SetList[x]()
+ END
+ v9.CheckDefFailure(lines, 'E1001: Variable not found: x')
+
+ lines =<< trim END
+ const SetList = [function('len')]
+ echo 'xx'->SetList[0]x()
+ END
+ v9.CheckDefFailure(lines, 'E15: Invalid expression: "->SetList[0]x()"')
+enddef
+
+def Test_expr9_method_call_linebreak()
+ # this was giving an error when skipping over the expression
+ var lines =<< trim END
+ vim9script
+ def Test()
+ var a: dict<any> = {b: {}}
+ a.b->extend({f1: 1,
+ f2: 2})
+ echo a
+ enddef
+ defcompile
+ assert_equal('', v:errmsg)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # this was skipping over the expression without an error
+ lines =<< trim END
+ vim9script
+ def Test(s: string): string
+ return substitute(s, 'A', '\=toupper("x")', 'g')
+ ->tolower()
+ enddef
+ assert_equal('xbcd', Test('ABCD'))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_expr9_method_call_import()
+ var lines =<< trim END
+ vim9script
+ export def Square(items: list<number>): list<number>
+ return map(items, (_, i) => i * i)
+ enddef
+ END
+ call writefile(lines, 'Xsquare.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xsquare.vim'
+
+ def Test(): list<number>
+ return range(5)
+ ->Xsquare.Square()
+ ->map((_, i) => i * 10)
+ enddef
+
+ assert_equal([0, 10, 40, 90, 160], Test())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ import './Xsquare.vim'
+
+ echo range(5)->Xsquare.NoSuchFunc()
+ END
+ v9.CheckScriptFailure(lines, 'E1048: Item not found in script: NoSuchFunc')
+enddef
+
+
+def Test_expr9_not()
+ var lines =<< trim END
+ assert_equal(true, !'')
+ assert_equal(true, ![])
+ assert_equal(false, !'asdf')
+ assert_equal(false, ![2])
+ assert_equal(true, !!'asdf')
+ assert_equal(true, !![2])
+
+ assert_equal(true, ! false)
+ assert_equal(true, !! true)
+ assert_equal(true, ! ! true)
+ assert_equal(true, !!! false)
+ assert_equal(true, ! ! ! false)
+
+ g:true = true
+ g:false = false
+ assert_equal(true, ! g:false)
+ assert_equal(true, !! g:true)
+ assert_equal(true, ! ! g:true)
+ assert_equal(true, !!! g:false)
+ assert_equal(true, ! ! ! g:false)
+ unlet g:true
+ unlet g:false
+
+ assert_equal(true, !test_null_partial())
+ assert_equal(false, !() => 'yes')
+
+ assert_equal(true, !test_null_dict())
+ assert_equal(true, !{})
+ assert_equal(false, !{yes: 'no'})
+
+ if has('channel')
+ assert_equal(true, !test_null_job())
+ assert_equal(true, !test_null_channel())
+ endif
+
+ assert_equal(true, !test_null_blob())
+ assert_equal(true, !0z)
+ assert_equal(false, !0z01)
+
+ assert_equal(true, !test_void())
+ assert_equal(true, !test_unknown())
+
+ assert_equal(false, ![1, 2, 3]->reverse())
+ assert_equal(true, ![]->reverse())
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+let g:anumber = 42
+
+def Test_expr9_negate()
+ var lines =<< trim END
+ var nr = 1
+ assert_equal(-1, -nr)
+ assert_equal(-42, -g:anumber)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+func Test_expr9_fails()
+ call v9.CheckDefFailure(["var x = (12"], "E1097:", 3)
+ call v9.CheckScriptFailure(['vim9script', "var x = (12"], 'E110:', 2)
+
+ call v9.CheckDefAndScriptFailure(["var x = -'xx'"], "E1030:", 1)
+ call v9.CheckDefAndScriptFailure(["var x = +'xx'"], "E1030:", 1)
+ call v9.CheckDefAndScriptFailure(["var x = -0z12"], "E974:", 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = -[8]"], ["E1012:", 'E745:'], 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = -{a: 1}"], ["E1012:", 'E728:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = @"], "E1002:", 1)
+ call v9.CheckDefAndScriptFailure(["var x = @<"], "E354:", 1)
+
+ call v9.CheckDefFailure(["var x = [1, 2"], "E697:", 2)
+ call v9.CheckScriptFailure(['vim9script', "var x = [1, 2"], 'E696:', 2)
+
+ call v9.CheckDefAndScriptFailure(["var x = [notfound]"], ["E1001:", 'E121:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var X = () => 123)"], 'E488:', 1)
+ call v9.CheckDefAndScriptFailure(["var x = 123->((x) => x + 5)"], "E107:", 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = &notexist"], 'E113:', 1)
+ call v9.CheckDefAndScriptFailure(["&grepprg = [343]"], ['E1012:', 'E730:'], 1)
+
+ call v9.CheckDefExecAndScriptFailure(["echo s:doesnt_exist"], ['E121:', 'E1268:'], 1)
+ call v9.CheckDefExecAndScriptFailure(["echo g:doesnt_exist"], 'E121:', 1)
+
+ call v9.CheckDefAndScriptFailure(["echo a:somevar"], ['E1075:', 'E121:'], 1)
+ call v9.CheckDefAndScriptFailure(["echo l:somevar"], ['E1075:', 'E121:'], 1)
+ call v9.CheckDefAndScriptFailure(["echo x:somevar"], ['E1075:', 'E121:'], 1)
+
+ call v9.CheckDefExecAndScriptFailure(["var x = +g:astring"], ['E1012:', 'E1030:'], 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = +g:ablob"], ['E1012:', 'E974:'], 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = +g:alist"], ['E1012:', 'E745:'], 1)
+ call v9.CheckDefExecAndScriptFailure(["var x = +g:adict"], ['E1012:', 'E728:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["var x = ''", "var y = x.memb"], ['E1229: Expected dictionary for using key "memb", but got string', 'E488:'], 2)
+
+ call v9.CheckDefAndScriptFailure(["'yes'->", "Echo()"], ['E488: Trailing characters: ->', 'E260: Missing name after ->'], 1)
+
+ call v9.CheckDefExecFailure(["[1, 2->len()"], 'E697:', 2)
+ call v9.CheckScriptFailure(['vim9script', "[1, 2->len()"], 'E696:', 2)
+
+ call v9.CheckDefFailure(["{a: 1->len()"], 'E723:', 2)
+ call v9.CheckScriptFailure(['vim9script', "{a: 1->len()"], 'E722:', 2)
+
+ call v9.CheckDefExecFailure(["{['a']: 1->len()"], 'E723:', 2)
+ call v9.CheckScriptFailure(['vim9script', "{['a']: 1->len()"], 'E722:', 2)
+
+ call v9.CheckDefFailure(['echo #{}'], 'E1170:')
+endfunc
+
+let g:Funcrefs = [function('add')]
+
+func CallMe(arg)
+ return a:arg
+endfunc
+
+func CallMe2(one, two)
+ return a:one .. a:two
+endfunc
+
+def Test_expr9_trailing()
+ var lines =<< trim END
+ # user function call
+ assert_equal(123, g:CallMe(123))
+ assert_equal(123, g:CallMe( 123))
+ assert_equal(123, g:CallMe(123 ))
+ assert_equal('yesno', g:CallMe2('yes', 'no'))
+ assert_equal('yesno', g:CallMe2( 'yes', 'no' ))
+ assert_equal('nothing', g:CallMe('nothing'))
+
+ # partial call
+ var Part = function('g:CallMe')
+ assert_equal('yes', Part('yes'))
+
+ # funcref call, using list index
+ var l = []
+ g:Funcrefs[0](l, 2)
+ assert_equal([2], l)
+
+ # method call
+ l = [2, 5, 6]
+ l->map((k, v) => k + v)
+ assert_equal([2, 6, 8], l)
+
+ # lambda method call
+ l = [2, 5]
+ l->((ll) => add(ll, 8))()
+ assert_equal([2, 5, 8], l)
+
+ # dict member
+ var d = {key: 123}
+ assert_equal(123, d.key)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_string_subscript()
+ var lines =<< trim END
+ var text = 'abcdef'
+ assert_equal('f', text[-1])
+ assert_equal('a', text[0])
+ assert_equal('e', text[4])
+ assert_equal('f', text[5])
+ assert_equal('', text[6])
+
+ text = 'ábçdë'
+ assert_equal('ë', text[-1])
+ assert_equal('d', text[-2])
+ assert_equal('ç', text[-3])
+ assert_equal('b', text[-4])
+ assert_equal('á', text[-5])
+ assert_equal('', text[-6])
+
+ text = 'ábçdëf'
+ assert_equal('', text[-999])
+ assert_equal('f', text[-1])
+ assert_equal('á', text[0])
+ assert_equal('b', text[1])
+ assert_equal('ç', text[2])
+ assert_equal('d', text[3])
+ assert_equal('ë', text[4])
+ assert_equal('f', text[5])
+ assert_equal('', text[6])
+ assert_equal('', text[999])
+
+ assert_equal('ábçdëf', text[0 : -1])
+ assert_equal('ábçdëf', text[0 : -1])
+ assert_equal('ábçdëf', text[0 : -1])
+ assert_equal('ábçdëf', text[0 : -1])
+ assert_equal('ábçdëf', text[0
+ : -1])
+ assert_equal('ábçdëf', text[0 :
+ -1])
+ assert_equal('ábçdëf', text[0 : -1
+ ])
+ assert_equal('bçdëf', text[1 : -1])
+ assert_equal('çdëf', text[2 : -1])
+ assert_equal('dëf', text[3 : -1])
+ assert_equal('ëf', text[4 : -1])
+ assert_equal('f', text[5 : -1])
+ assert_equal('', text[6 : -1])
+ assert_equal('', text[999 : -1])
+
+ assert_equal('ábçd', text[: 3])
+ assert_equal('bçdëf', text[1 :])
+ assert_equal('ábçdëf', text[:])
+
+ assert_equal('a', g:astring[0])
+ assert_equal('sd', g:astring[1 : 2])
+ assert_equal('asdf', g:astring[:])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = 'asdf'[1 :
+ END
+ v9.CheckDefFailure(lines, 'E1097:', 3)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E15:', 2)
+
+ lines =<< trim END
+ var d = 'asdf'[1 : xxx]
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1001:', 'E121:'], 1)
+
+ lines =<< trim END
+ var d = 'asdf'[1 : 2
+ END
+ v9.CheckDefFailure(lines, 'E1097:', 3)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E111:', 2)
+
+ lines =<< trim END
+ var d = 'asdf'[1 : 2
+ echo d
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E111:', 2)
+
+ lines =<< trim END
+ var d = 'asdf'['1']
+ echo d
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1012: Type mismatch; expected number but got string', 'E1030: Using a String as a Number: "1"'], 1)
+
+ lines =<< trim END
+ var d = 'asdf'['1' : 2]
+ echo d
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1012: Type mismatch; expected number but got string', 'E1030: Using a String as a Number: "1"'], 1)
+
+ lines =<< trim END
+ var d = 'asdf'[1 : '2']
+ echo d
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1012: Type mismatch; expected number but got string', 'E1030: Using a String as a Number: "2"'], 1)
+enddef
+
+def Test_expr9_list_subscript()
+ var lines =<< trim END
+ var list = [0, 1, 2, 3, 4]
+ assert_equal(0, list[0])
+ assert_equal(4, list[4])
+ assert_equal(4, list[-1])
+ assert_equal(0, list[-5])
+
+ assert_equal([0, 1, 2, 3, 4], list[0 : 4])
+ assert_equal([0, 1, 2, 3, 4], list[:])
+ assert_equal([1, 2, 3, 4], list[1 :])
+ assert_equal([2, 3, 4], list[2 : -1])
+ assert_equal([4], list[4 : -1])
+ assert_equal([], list[5 : -1])
+ assert_equal([], list[999 : -1])
+ assert_equal([1, 2, 3, 4], list[g:theone : g:thefour])
+
+ assert_equal([0, 1, 2, 3], list[0 : 3])
+ assert_equal([0], list[0 : 0])
+ assert_equal([0, 1, 2, 3, 4], list[0 : -1])
+ assert_equal([0, 1, 2], list[0 : -3])
+ assert_equal([0], list[0 : -5])
+ assert_equal([], list[0 : -6])
+ assert_equal([], list[0 : -99])
+
+ assert_equal(2, g:alist[0])
+ assert_equal([2, 3, 4], g:alist[:])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines = ['var l = [0, 1, 2]', 'echo l[g:astring : g:theone]']
+ v9.CheckDefExecAndScriptFailure(lines, ['E1012:', 'E1030:'], 2)
+
+ lines =<< trim END
+ var ld = []
+ def Func()
+ eval ld[0].key
+ enddef
+ defcompile
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_dict_subscript()
+ var lines =<< trim END
+ var l = [{lnum: 2}, {lnum: 1}]
+ var res = l[0].lnum > l[1].lnum
+ assert_true(res)
+
+ assert_equal(2, g:adict['aaa'])
+ assert_equal(8, g:adict.bbb)
+
+ var dd = {}
+ def Func1()
+ eval dd.key1.key2
+ enddef
+ def Func2()
+ eval dd['key1'].key2
+ enddef
+ defcompile
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_blob_subscript()
+ var lines =<< trim END
+ var b = 0z112233
+ assert_equal(0x11, b[0])
+ assert_equal(0z112233, b[:])
+
+ assert_equal(0x01, g:ablob[0])
+ assert_equal(0z01ab, g:ablob[:])
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_expr9_funcref_subscript()
+ var lines =<< trim END
+ var l = function('len')("abc")
+ assert_equal(3, l)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefAndScriptFailure(["var l = function('len')(xxx)"], ['E1001: Variable not found: xxx', 'E121: Undefined variable: xxx'], 1)
+enddef
+
+def Test_expr9_subscript_linebreak()
+ var lines =<< trim END
+ var range = range(
+ 3)
+ var l = range
+ ->mapnew('string(v:key)')
+ assert_equal(['0', '1', '2'], l)
+
+ l = range
+ ->mapnew('string(v:key)')
+ assert_equal(['0', '1', '2'], l)
+
+ l = range # comment
+ ->mapnew('string(v:key)')
+ assert_equal(['0', '1', '2'], l)
+
+ l = range
+
+ ->mapnew('string(v:key)')
+ assert_equal(['0', '1', '2'], l)
+
+ l = range
+ # comment
+ ->mapnew('string(v:key)')
+ assert_equal(['0', '1', '2'], l)
+
+ assert_equal('1', l[
+ 1])
+
+ var d = {one: 33}
+ assert_equal(33, d
+ .one)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = {one: 33}
+ assert_equal(33, d.
+ one)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1127:', 'E116:'], 2)
+enddef
+
+func Test_expr9_trailing_fails()
+ call v9.CheckDefAndScriptFailure(['var l = [2]', 'l->((ll) => add(ll, 8))'], 'E107:', 2)
+ call v9.CheckDefAndScriptFailure(['var l = [2]', 'l->((ll) => add(ll, 8)) ()'], 'E274:', 2)
+endfunc
+
+func Test_expr_fails()
+ call v9.CheckDefAndScriptFailure(["var x = '1'is2"], 'E488:', 1)
+ call v9.CheckDefAndScriptFailure(["var x = '1'isnot2"], 'E488:', 1)
+
+ call v9.CheckDefAndScriptFailure(["CallMe ('yes')"], ['E476:', 'E492:'], 1)
+
+ call v9.CheckDefAndScriptFailure(["CallMe2('yes','no')"], 'E1069:', 1)
+
+ call v9.CheckDefAndScriptFailure(["v:nosuch += 3"], ['E1001:', 'E121:'], 1)
+ call v9.CheckDefAndScriptFailure(["var v:statusmsg = ''"], 'E1016: Cannot declare a v: variable:', 1)
+ call v9.CheckDefAndScriptFailure(["var asdf = v:nosuch"], ['E1001:', 'E121:'], 1)
+
+ call v9.CheckDefFailure(["echo len('asdf'"], 'E110:', 2)
+ call v9.CheckScriptFailure(['vim9script', "echo len('asdf'"], 'E116:', 2)
+
+ call v9.CheckDefAndScriptFailure(["echo Func01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789()"], ['E1011:', 'E117:'], 1)
+ call v9.CheckDefAndScriptFailure(["echo doesnotexist()"], 'E117:', 1)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_vim9_fails.vim b/src/testdir/test_vim9_fails.vim
new file mode 100644
index 0000000..9d43370
--- /dev/null
+++ b/src/testdir/test_vim9_fails.vim
@@ -0,0 +1,62 @@
+" Test for Vim9 script with failures, causing memory leaks to be reported.
+" The leaks happen after a fork() and can be ignored.
+
+source check.vim
+
+def Test_assignment()
+ if !has('channel')
+ CheckFeature channel
+ else
+ var chan1: channel
+ var job1: job
+ var job2: job = job_start('willfail')
+ endif
+enddef
+
+" Unclear why this test causes valgrind to report problems.
+def Test_job_info_return_type()
+ if !has('job')
+ CheckFeature job
+ else
+ var job: job = job_start(&shell)
+ var jobs = job_info()
+ assert_equal('list<job>', typename(jobs))
+ assert_equal('dict<any>', typename(job_info(jobs[0])))
+ job_stop(job)
+ endif
+enddef
+
+" Using "idx" from a legacy global function does not work.
+" This caused a crash when called from legacy context.
+" This creates a dict that contains a partial that refers to the dict, causing
+" valgrind to report "possibly leaked memory".
+func Test_partial_call_fails()
+ let lines =<< trim END
+ vim9script
+
+ var l = ['a', 'b', 'c']
+ def Iter(container: any): any
+ var idx = -1
+ var obj = {state: container}
+ def g:NextItem__(self: dict<any>): any
+ ++idx
+ return self.state[idx]
+ enddef
+ obj.__next__ = function('g:NextItem__', [obj])
+ return obj
+ enddef
+
+ var it = Iter(l)
+ echo it.__next__()
+ END
+ call writefile(lines, 'XpartialCall', 'D')
+ let caught = 'no'
+ try
+ source XpartialCall
+ catch /E1248:/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+ delfunc g:NextItem__
+endfunc
+
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
new file mode 100644
index 0000000..0f28ba0
--- /dev/null
+++ b/src/testdir/test_vim9_func.vim
@@ -0,0 +1,4576 @@
+" Test various aspects of the Vim9 script language.
+
+source check.vim
+source term_util.vim
+source view_util.vim
+import './vim9.vim' as v9
+source screendump.vim
+
+func Test_def_basic()
+ def SomeFunc(): string
+ return 'yes'
+ enddef
+ call SomeFunc()->assert_equal('yes')
+endfunc
+
+func Test_compiling_error()
+ " use a terminal to see the whole error message
+ CheckRunVimInTerminal
+
+ call TestCompilingError()
+ call TestCompilingErrorInTry()
+endfunc
+
+def TestCompilingError()
+ var lines =<< trim END
+ vim9script
+ def Fails()
+ echo nothing
+ enddef
+ defcompile
+ END
+ writefile(lines, 'XTest_compile_error', 'D')
+ var buf = g:RunVimInTerminal('-S XTest_compile_error',
+ {rows: 10, wait_for_ruler: 0})
+ g:WaitForAssert(() => assert_match('Error detected while compiling command line.*Fails.*Variable not found: nothing',
+ g:Term_getlines(buf, range(1, 9))))
+
+ # clean up
+ g:StopVimInTerminal(buf)
+enddef
+
+def TestCompilingErrorInTry()
+ var dir = 'Xcompdir/autoload'
+ mkdir(dir, 'pR')
+
+ var lines =<< trim END
+ vim9script
+ export def OnlyCompiled()
+ g:runtime = 'yes'
+ invalid
+ enddef
+ END
+ writefile(lines, dir .. '/script.vim')
+
+ lines =<< trim END
+ vim9script
+ todo
+ try
+ script#OnlyCompiled()
+ catch /nothing/
+ endtry
+ END
+ lines[1] = 'set rtp=' .. getcwd() .. '/Xcompdir'
+ writefile(lines, 'XTest_compile_error', 'D')
+
+ var buf = g:RunVimInTerminal('-S XTest_compile_error', {rows: 10, wait_for_ruler: 0})
+ g:WaitForAssert(() => assert_match('Error detected while compiling command line.*function script#OnlyCompiled.*Invalid command: invalid',
+ g:Term_getlines(buf, range(1, 9))))
+
+ # clean up
+ g:StopVimInTerminal(buf)
+enddef
+
+def Test_comment_error()
+ v9.CheckDefFailure(['#{ comment'], 'E1170:')
+enddef
+
+def Test_compile_error_in_called_function()
+ var lines =<< trim END
+ vim9script
+ var n: number
+ def Foo()
+ &hls = n
+ enddef
+ def Bar()
+ Foo()
+ enddef
+ silent! Foo()
+ Bar()
+ END
+ v9.CheckScriptFailureList(lines, ['E1012:', 'E1191:'])
+enddef
+
+def Test_wrong_function_name()
+ var lines =<< trim END
+ vim9script
+ func _Foo()
+ echo 'foo'
+ endfunc
+ END
+ v9.CheckScriptFailure(lines, 'E1267:')
+
+ lines =<< trim END
+ vim9script
+ def _Foo()
+ echo 'foo'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1267:')
+
+ lines =<< trim END
+ vim9script
+ var Object = {}
+ function Object.Method()
+ endfunction
+ END
+ v9.CheckScriptFailure(lines, 'E1182:')
+
+ lines =<< trim END
+ vim9script
+ var Object = {}
+ def Object.Method()
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1182:')
+
+ lines =<< trim END
+ vim9script
+ g:Object = {}
+ function g:Object.Method()
+ endfunction
+ END
+ v9.CheckScriptFailure(lines, 'E1182:')
+
+ lines =<< trim END
+ let s:Object = {}
+ def Define()
+ function s:Object.Method()
+ endfunction
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1182:')
+ delfunc g:Define
+
+ lines =<< trim END
+ let s:Object = {}
+ def Define()
+ def Object.Method()
+ enddef
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1182:')
+ delfunc g:Define
+
+ lines =<< trim END
+ let g:Object = {}
+ def Define()
+ function g:Object.Method()
+ endfunction
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1182:')
+ delfunc g:Define
+enddef
+
+def Test_break_in_skipped_block()
+ var lines =<< trim END
+ vim9script
+
+ def FixStackFrame(): string
+ for _ in [2]
+ var path = 'xxx'
+ if !!path
+ if false
+ break
+ else
+ return 'foo'
+ endif
+ endif
+ endfor
+ return 'xxx'
+ enddef
+
+ disas FixStackFrame
+
+ FixStackFrame()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_autoload_name_mismatch()
+ var dir = 'Xnamedir/autoload'
+ mkdir(dir, 'pR')
+
+ var lines =<< trim END
+ vim9script
+ export def NoFunction()
+ # comment
+ g:runtime = 'yes'
+ enddef
+ END
+ writefile(lines, dir .. '/script.vim')
+
+ var save_rtp = &rtp
+ exe 'set rtp=' .. getcwd() .. '/Xnamedir'
+ lines =<< trim END
+ call script#Function()
+ END
+ v9.CheckScriptFailure(lines, 'E117:', 1)
+
+ &rtp = save_rtp
+enddef
+
+def Test_autoload_names()
+ var dir = 'Xandir/autoload'
+ mkdir(dir, 'pR')
+
+ var lines =<< trim END
+ func foobar#function()
+ return 'yes'
+ endfunc
+ let foobar#var = 'no'
+ END
+ writefile(lines, dir .. '/foobar.vim')
+
+ var save_rtp = &rtp
+ exe 'set rtp=' .. getcwd() .. '/Xandir'
+
+ lines =<< trim END
+ assert_equal('yes', foobar#function())
+ var Function = foobar#function
+ assert_equal('yes', Function())
+
+ assert_equal('no', foobar#var)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ &rtp = save_rtp
+enddef
+
+def Test_autoload_error_in_script()
+ var dir = 'Xaedir/autoload'
+ mkdir(dir, 'pR')
+
+ var lines =<< trim END
+ func scripterror#function()
+ let g:called_function = 'yes'
+ endfunc
+ let 0 = 1
+ END
+ writefile(lines, dir .. '/scripterror.vim')
+
+ var save_rtp = &rtp
+ exe 'set rtp=' .. getcwd() .. '/Xaedir'
+
+ g:called_function = 'no'
+ # The error in the autoload script cannot be checked with assert_fails(), use
+ # CheckDefSuccess() instead of CheckDefFailure()
+ try
+ v9.CheckDefSuccess(['scripterror#function()'])
+ catch
+ assert_match('E121: Undefined variable: 0', v:exception)
+ endtry
+ assert_equal('no', g:called_function)
+
+ lines =<< trim END
+ func scriptcaught#function()
+ let g:called_function = 'yes'
+ endfunc
+ try
+ let 0 = 1
+ catch
+ let g:caught = v:exception
+ endtry
+ END
+ writefile(lines, dir .. '/scriptcaught.vim')
+
+ g:called_function = 'no'
+ v9.CheckDefSuccess(['scriptcaught#function()'])
+ assert_match('E121: Undefined variable: 0', g:caught)
+ assert_equal('yes', g:called_function)
+
+ &rtp = save_rtp
+enddef
+
+def s:CallRecursive(n: number): number
+ return CallRecursive(n + 1)
+enddef
+
+def s:CallMapRecursive(l: list<number>): number
+ return map(l, (_, v) => CallMapRecursive([v]))[0]
+enddef
+
+def Test_funcdepth_error()
+ set maxfuncdepth=10
+
+ var caught = false
+ try
+ CallRecursive(1)
+ catch /E132:/
+ caught = true
+ endtry
+ assert_true(caught)
+
+ caught = false
+ try
+ CallMapRecursive([1])
+ catch /E132:/
+ caught = true
+ endtry
+ assert_true(caught)
+
+ set maxfuncdepth&
+enddef
+
+def Test_endfunc_enddef()
+ var lines =<< trim END
+ def Test()
+ echo 'test'
+ endfunc
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1151:', 3)
+
+ lines =<< trim END
+ def Test()
+ func Nested()
+ echo 'test'
+ enddef
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1152:', 4)
+
+ lines =<< trim END
+ def Ok()
+ echo 'hello'
+ enddef | echo 'there'
+ def Bad()
+ echo 'hello'
+ enddef there
+ END
+ v9.CheckScriptFailure(lines, 'E1173: Text found after enddef: there', 6)
+enddef
+
+def Test_missing_endfunc_enddef()
+ var lines =<< trim END
+ vim9script
+ def Test()
+ echo 'test'
+ endef
+ END
+ v9.CheckScriptFailure(lines, 'E1057:', 2)
+
+ lines =<< trim END
+ vim9script
+ func Some()
+ echo 'test'
+ enfffunc
+ END
+ v9.CheckScriptFailure(lines, 'E126:', 2)
+enddef
+
+def Test_white_space_before_paren()
+ var lines =<< trim END
+ vim9script
+ def Test ()
+ echo 'test'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1068:', 2)
+
+ lines =<< trim END
+ vim9script
+ func Test ()
+ echo 'test'
+ endfunc
+ END
+ v9.CheckScriptFailure(lines, 'E1068:', 2)
+
+ lines =<< trim END
+ def Test ()
+ echo 'test'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1068:', 1)
+
+ lines =<< trim END
+ func Test ()
+ echo 'test'
+ endfunc
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_enddef_dict_key()
+ var d = {
+ enddef: 'x',
+ endfunc: 'y',
+ }
+ assert_equal({enddef: 'x', endfunc: 'y'}, d)
+enddef
+
+def ReturnString(): string
+ return 'string'
+enddef
+
+def ReturnNumber(): number
+ return 123
+enddef
+
+let g:notNumber = 'string'
+
+def ReturnGlobal(): number
+ return g:notNumber
+enddef
+
+def Test_return_something()
+ g:ReturnString()->assert_equal('string')
+ g:ReturnNumber()->assert_equal(123)
+ assert_fails('g:ReturnGlobal()', 'E1012: Type mismatch; expected number but got string', '', 1, 'ReturnGlobal')
+
+ var lines =<< trim END
+ vim9script
+
+ def Msg()
+ echomsg 'in Msg()...'
+ enddef
+
+ def Func()
+ return Msg()
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1096:')
+enddef
+
+def Test_check_argument_type()
+ var lines =<< trim END
+ vim9script
+ def Val(a: number, b: number): number
+ return 0
+ enddef
+ def Func()
+ var x: any = true
+ Val(0, x)
+ enddef
+ disass Func
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected number but got bool', 2)
+
+ lines =<< trim END
+ vim9script
+
+ def Foobar(Fn: func(any, ?string): any)
+ enddef
+
+ Foobar((t) => 0)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_missing_return()
+ v9.CheckDefFailure(['def Missing(): number',
+ ' if g:cond',
+ ' echo "no return"',
+ ' else',
+ ' return 0',
+ ' endif',
+ 'enddef'], 'E1027:')
+ v9.CheckDefFailure(['def Missing(): number',
+ ' if g:cond',
+ ' return 1',
+ ' else',
+ ' echo "no return"',
+ ' endif',
+ 'enddef'], 'E1027:')
+ v9.CheckDefFailure(['def Missing(): number',
+ ' if g:cond',
+ ' return 1',
+ ' else',
+ ' return 2',
+ ' endif',
+ ' return 3',
+ 'enddef'], 'E1095:')
+enddef
+
+def Test_return_bool()
+ var lines =<< trim END
+ vim9script
+ def MenuFilter(id: number, key: string): bool
+ return popup_filter_menu(id, key)
+ enddef
+ def YesnoFilter(id: number, key: string): bool
+ return popup_filter_yesno(id, key)
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_return_void_comment_follows()
+ var lines =<< trim END
+ vim9script
+ def ReturnCommentFollows(): void
+ return # Some comment
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+let s:nothing = 0
+def ReturnNothing()
+ s:nothing = 1
+ if true
+ return
+ endif
+ s:nothing = 2
+enddef
+
+def Test_return_nothing()
+ g:ReturnNothing()
+ s:nothing->assert_equal(1)
+enddef
+
+def Test_return_invalid()
+ var lines =<< trim END
+ vim9script
+ def Func(): invalid
+ return xxx
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1010:', 2)
+
+ lines =<< trim END
+ vim9script
+ def Test(Fun: func(number): number): list<number>
+ return map([1, 2, 3], (_, i) => Fun(i))
+ enddef
+ defcompile
+ def Inc(nr: number): nr
+ return nr + 2
+ enddef
+ echo Test(Inc)
+ END
+ # doing this twice was leaking memory
+ v9.CheckScriptFailure(lines, 'E1010:')
+ v9.CheckScriptFailure(lines, 'E1010:')
+enddef
+
+def Test_return_list_any()
+ # This used to fail but now the actual list type is checked, and since it has
+ # an item of type string it can be used as list<string>.
+ var lines =<< trim END
+ vim9script
+ def Func(): list<string>
+ var l: list<any>
+ l->add('string')
+ return l
+ enddef
+ echo Func()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Func(): list<string>
+ var l: list<any>
+ l += ['string']
+ return l
+ enddef
+ echo Func()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_return_any_two_types()
+ var lines =<< trim END
+ vim9script
+
+ def G(Fn: func(string): any)
+ g:result = Fn("hello")
+ enddef
+
+ def F(a: number, b: string): any
+ echo b
+ if a > 0
+ return 1
+ else
+ return []
+ endif
+ enddef
+
+ G(function(F, [1]))
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal(1, g:result)
+ unlet g:result
+enddef
+
+func s:Increment()
+ let g:counter += 1
+endfunc
+
+def Test_call_ufunc_count()
+ g:counter = 1
+ Increment()
+ Increment()
+ Increment()
+ # works with and without :call
+ g:counter->assert_equal(4)
+ eval g:counter->assert_equal(4)
+ unlet g:counter
+enddef
+
+def Test_call_ufunc_failure()
+ var lines =<< trim END
+ vim9script
+ def Tryit()
+ g:Global(1, 2, 3)
+ enddef
+
+ func g:Global(a, b, c)
+ echo a:a a:b a:c
+ endfunc
+
+ defcompile
+
+ func! g:Global(a, b)
+ echo a:a a:b
+ endfunc
+ Tryit()
+ END
+ v9.CheckScriptFailure(lines, 'E118: Too many arguments for function: Global')
+ delfunc g:Global
+
+ lines =<< trim END
+ vim9script
+
+ g:Ref = function('len')
+ def Tryit()
+ g:Ref('x')
+ enddef
+
+ defcompile
+
+ g:Ref = function('add')
+ Tryit()
+ END
+ v9.CheckScriptFailure(lines, 'E119: Not enough arguments for function: add')
+ unlet g:Ref
+enddef
+
+def s:MyVarargs(arg: string, ...rest: list<string>): string
+ var res = arg
+ for s in rest
+ res ..= ',' .. s
+ endfor
+ return res
+enddef
+
+def Test_call_varargs()
+ MyVarargs('one')->assert_equal('one')
+ MyVarargs('one', 'two')->assert_equal('one,two')
+ MyVarargs('one', 'two', 'three')->assert_equal('one,two,three')
+enddef
+
+def Test_call_white_space()
+ v9.CheckDefAndScriptFailure(["call Test ('text')"], ['E476:', 'E1068:'])
+enddef
+
+def MyDefaultArgs(name = 'string'): string
+ return name
+enddef
+
+def s:MyDefaultSecond(name: string, second: bool = true): string
+ return second ? name : 'none'
+enddef
+
+
+def Test_call_default_args()
+ g:MyDefaultArgs()->assert_equal('string')
+ g:MyDefaultArgs(v:none)->assert_equal('string')
+ g:MyDefaultArgs('one')->assert_equal('one')
+ assert_fails('g:MyDefaultArgs("one", "two")', 'E118:', '', 4, 'Test_call_default_args')
+
+ MyDefaultSecond('test')->assert_equal('test')
+ MyDefaultSecond('test', true)->assert_equal('test')
+ MyDefaultSecond('test', false)->assert_equal('none')
+
+ var lines =<< trim END
+ def MyDefaultThird(name: string, aa = 'aa', bb = 'bb'): string
+ return name .. aa .. bb
+ enddef
+
+ MyDefaultThird('->')->assert_equal('->aabb')
+ MyDefaultThird('->', v:none)->assert_equal('->aabb')
+ MyDefaultThird('->', 'xx')->assert_equal('->xxbb')
+ MyDefaultThird('->', v:none, v:none)->assert_equal('->aabb')
+ MyDefaultThird('->', 'xx', v:none)->assert_equal('->xxbb')
+ MyDefaultThird('->', v:none, 'yy')->assert_equal('->aayy')
+ MyDefaultThird('->', 'xx', 'yy')->assert_equal('->xxyy')
+
+ def DefArg(mandatory: any, optional = mandatory): string
+ return mandatory .. optional
+ enddef
+ DefArg(1234)->assert_equal('12341234')
+ DefArg("ok")->assert_equal('okok')
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckScriptFailure(['def Func(arg: number = asdf)', 'enddef', 'defcompile'], 'E1001:')
+ delfunc g:Func
+ v9.CheckScriptFailure(['def Func(arg: number = "text")', 'enddef', 'defcompile'], 'E1013: Argument 1: type mismatch, expected number but got string')
+ delfunc g:Func
+ v9.CheckDefFailure(['def Func(x: number = )', 'enddef'], 'E15:')
+
+ lines =<< trim END
+ vim9script
+ def Func(a = b == 0 ? 1 : 2, b = 0)
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1001: Variable not found: b')
+
+ # using script variable requires matching type or type cast when executed
+ lines =<< trim END
+ vim9script
+ var a: any
+ def Func(arg: string = a)
+ echo arg
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines + ['a = "text"', 'Func()'])
+ v9.CheckScriptFailure(lines + ['a = 123', 'Func()'], 'E1013: Argument 1: type mismatch, expected string but got number')
+
+ # using global variable does not require type cast
+ lines =<< trim END
+ vim9script
+ def Func(arg: string = g:str)
+ echo arg
+ enddef
+ g:str = 'works'
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_convert_number_to_float()
+ var lines =<< trim END
+ vim9script
+ def Foo(a: float, b: float): float
+ return a + b
+ enddef
+
+ assert_equal(5.3, Foo(3.3, 2))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:FuncWithComment( # comment
+ a: number, #comment
+ b: bool, # comment
+ c: string) #comment
+ assert_equal(4, a)
+ assert_equal(true, b)
+ assert_equal('yes', c)
+enddef
+
+def Test_func_with_comments()
+ FuncWithComment(4, true, 'yes')
+
+ var lines =<< trim END
+ def Func(# comment
+ arg: string)
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E125:', 1)
+
+ lines =<< trim END
+ def Func(
+ arg: string# comment
+ )
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E475:', 2)
+
+ lines =<< trim END
+ def Func(
+ arg: string
+ )# comment
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E488:', 3)
+enddef
+
+def Test_nested_function()
+ def NestedDef(arg: string): string
+ return 'nested ' .. arg
+ enddef
+ NestedDef(':def')->assert_equal('nested :def')
+
+ func NestedFunc(arg)
+ return 'nested ' .. a:arg
+ endfunc
+ NestedFunc(':func')->assert_equal('nested :func')
+
+ v9.CheckDefFailure(['def Nested()', 'enddef', 'Nested(66)'], 'E118:')
+ v9.CheckDefFailure(['def Nested(arg: string)', 'enddef', 'Nested()'], 'E119:')
+
+ v9.CheckDefFailure(['def s:Nested()', 'enddef'], 'E1075:')
+ v9.CheckDefFailure(['def b:Nested()', 'enddef'], 'E1075:')
+
+ var lines =<< trim END
+ def Outer()
+ def Inner()
+ # comment
+ enddef
+ def Inner()
+ enddef
+ enddef
+ END
+ v9.CheckDefFailure(lines, 'E1073:')
+
+ lines =<< trim END
+ def Outer()
+ def Inner()
+ # comment
+ enddef
+ def! Inner()
+ enddef
+ enddef
+ END
+ v9.CheckDefFailure(lines, 'E1117:')
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ def Inner()
+ g:result = 'ok'
+ enddef
+ Inner()
+ enddef
+ Outer()
+ Inner()
+ END
+ v9.CheckScriptFailure(lines, 'E117: Unknown function: Inner')
+ assert_equal('ok', g:result)
+ unlet g:result
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ def _Inner()
+ echo 'bad'
+ enddef
+ _Inner()
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1267:')
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ def g:inner()
+ echo 'bad'
+ enddef
+ g:inner()
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1267:')
+
+ lines =<< trim END
+ vim9script
+ def g:_Func()
+ echo 'bad'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1267:')
+
+ lines =<< trim END
+ vim9script
+ def _Func()
+ echo 'bad'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1267:')
+
+ # nested function inside conditional
+ lines =<< trim END
+ vim9script
+ var thecount = 0
+ if true
+ def Test(): number
+ def TheFunc(): number
+ thecount += 1
+ return thecount
+ enddef
+ return TheFunc()
+ enddef
+ endif
+ defcompile
+ assert_equal(1, Test())
+ assert_equal(2, Test())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # also works when "thecount" is inside the "if" block
+ lines =<< trim END
+ vim9script
+ if true
+ var thecount = 0
+ def Test(): number
+ def TheFunc(): number
+ thecount += 1
+ return thecount
+ enddef
+ return TheFunc()
+ enddef
+ endif
+ defcompile
+ assert_equal(1, Test())
+ assert_equal(2, Test())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # nested function with recursive call
+ lines =<< trim END
+ vim9script
+
+ def MyFunc(): number
+ def Fib(n: number): number
+ if n < 2
+ return 1
+ endif
+ return Fib(n - 2) + Fib(n - 1)
+ enddef
+
+ return Fib(5)
+ enddef
+
+ assert_equal(8, MyFunc())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ def Inner()
+ echo 'hello'
+ enddef burp
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1173: Text found after enddef: burp', 3)
+enddef
+
+def Test_nested_function_fails()
+ var lines =<< trim END
+ def T()
+ def Func(g: string):string
+ enddef
+ Func()
+ enddef
+ silent! defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1069:')
+enddef
+
+def Test_not_nested_function()
+ echo printf('%d',
+ function('len')('xxx'))
+enddef
+
+func Test_call_default_args_from_func()
+ call MyDefaultArgs()->assert_equal('string')
+ call MyDefaultArgs('one')->assert_equal('one')
+ call assert_fails('call MyDefaultArgs("one", "two")', 'E118:', '', 3, 'Test_call_default_args_from_func')
+endfunc
+
+def Test_nested_global_function()
+ var lines =<< trim END
+ vim9script
+ def Outer()
+ def g:Inner(): string
+ return 'inner'
+ enddef
+ enddef
+ defcompile
+ Outer()
+ g:Inner()->assert_equal('inner')
+ delfunc g:Inner
+ Outer()
+ g:Inner()->assert_equal('inner')
+ delfunc g:Inner
+ Outer()
+ g:Inner()->assert_equal('inner')
+ delfunc g:Inner
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ func g:Inner()
+ return 'inner'
+ endfunc
+ enddef
+ defcompile
+ Outer()
+ g:Inner()->assert_equal('inner')
+ delfunc g:Inner
+ Outer()
+ g:Inner()->assert_equal('inner')
+ delfunc g:Inner
+ Outer()
+ g:Inner()->assert_equal('inner')
+ delfunc g:Inner
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ def g:Inner(): string
+ return 'inner'
+ enddef
+ enddef
+ defcompile
+ Outer()
+ Outer()
+ END
+ v9.CheckScriptFailure(lines, "E122:")
+ delfunc g:Inner
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ def g:Inner()
+ echo map([1, 2, 3], (_, v) => v + 1)
+ enddef
+ g:Inner()
+ enddef
+ Outer()
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:Inner
+
+ lines =<< trim END
+ vim9script
+ def Func()
+ echo 'script'
+ enddef
+ def Outer()
+ def Func()
+ echo 'inner'
+ enddef
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, "E1073:", 1)
+
+ lines =<< trim END
+ vim9script
+ def Func()
+ echo 'script'
+ enddef
+ def Func()
+ echo 'script'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, "E1073:", 5)
+enddef
+
+def DefListAll()
+ def
+enddef
+
+def DefListOne()
+ def DefListOne
+enddef
+
+def DefListMatches()
+ def /DefList
+enddef
+
+def Test_nested_def_list()
+ var funcs = split(execute('call DefListAll()'), "\n")
+ assert_true(len(funcs) > 10)
+ assert_true(funcs->index('def DefListAll()') >= 0)
+
+ funcs = split(execute('call DefListOne()'), "\n")
+ assert_equal([' def DefListOne()', '1 def DefListOne', ' enddef'], funcs)
+
+ funcs = split(execute('call DefListMatches()'), "\n")
+ assert_true(len(funcs) >= 3)
+ assert_true(funcs->index('def DefListAll()') >= 0)
+ assert_true(funcs->index('def DefListOne()') >= 0)
+ assert_true(funcs->index('def DefListMatches()') >= 0)
+
+ var lines =<< trim END
+ vim9script
+ def Func()
+ def +Func+
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E476:', 1)
+enddef
+
+def Test_global_function_not_found()
+ var lines =<< trim END
+ g:Ref = 123
+ call g:Ref()
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E117:', 'E1085:'], 2)
+enddef
+
+def Test_global_local_function()
+ var lines =<< trim END
+ vim9script
+ def g:Func(): string
+ return 'global'
+ enddef
+ def Func(): string
+ return 'local'
+ enddef
+ g:Func()->assert_equal('global')
+ Func()->assert_equal('local')
+ delfunc g:Func
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def g:Funcy()
+ echo 'funcy'
+ enddef
+ Funcy()
+ END
+ v9.CheckScriptFailure(lines, 'E117:')
+enddef
+
+def Test_local_function_shadows_global()
+ var lines =<< trim END
+ vim9script
+ def g:Gfunc(): string
+ return 'global'
+ enddef
+ def AnotherFunc(): number
+ var Gfunc = function('len')
+ return Gfunc('testing')
+ enddef
+ g:Gfunc()->assert_equal('global')
+ AnotherFunc()->assert_equal(7)
+ delfunc g:Gfunc
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def g:Func(): string
+ return 'global'
+ enddef
+ def AnotherFunc()
+ g:Func = function('len')
+ enddef
+ AnotherFunc()
+ END
+ v9.CheckScriptFailure(lines, 'E705:')
+ delfunc g:Func
+
+ # global function is not found with g: prefix
+ lines =<< trim END
+ vim9script
+ def g:Func(): string
+ return 'global'
+ enddef
+ def AnotherFunc(): string
+ return Func()
+ enddef
+ assert_equal('global', AnotherFunc())
+ END
+ v9.CheckScriptFailure(lines, 'E117:')
+ delfunc g:Func
+
+ lines =<< trim END
+ vim9script
+ def g:Func(): string
+ return 'global'
+ enddef
+ assert_equal('global', g:Func())
+ delfunc g:Func
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # This does not shadow "i" which is visible only inside the for loop
+ lines =<< trim END
+ vim9script
+
+ def Foo(i: number)
+ echo i
+ enddef
+
+ for i in range(3)
+ # Foo() is compiled here
+ Foo(i)
+ endfor
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+func TakesOneArg(arg)
+ echo a:arg
+endfunc
+
+def Test_call_wrong_args()
+ v9.CheckDefFailure(['g:TakesOneArg()'], 'E119:')
+ v9.CheckDefFailure(['g:TakesOneArg(11, 22)'], 'E118:')
+ v9.CheckDefFailure(['bufnr(xxx)'], 'E1001:')
+ v9.CheckScriptFailure(['def Func(Ref: func(s: string))'], 'E475:')
+
+ var lines =<< trim END
+ vim9script
+ def Func(s: string)
+ echo s
+ enddef
+ Func([])
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got list<unknown>', 5)
+
+ # argument name declared earlier is found when declaring a function
+ lines =<< trim END
+ vim9script
+ var name = 'piet'
+ def FuncOne(name: string)
+ echo name
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1168:')
+
+ # same, inside the same block
+ lines =<< trim END
+ vim9script
+ if true
+ var name = 'piet'
+ def FuncOne(name: string)
+ echo name
+ enddef
+ endif
+ END
+ v9.CheckScriptFailure(lines, 'E1168:')
+
+ # variable in other block is OK
+ lines =<< trim END
+ vim9script
+ if true
+ var name = 'piet'
+ endif
+ def FuncOne(name: string)
+ echo name
+ enddef
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # with another variable in another block
+ lines =<< trim END
+ vim9script
+ if true
+ var name = 'piet'
+ # define a function so that the variable isn't cleared
+ def GetItem(): string
+ return item
+ enddef
+ endif
+ if true
+ var name = 'peter'
+ def FuncOne(name: string)
+ echo name
+ enddef
+ endif
+ END
+ v9.CheckScriptFailure(lines, 'E1168:')
+
+ # only variable in another block is OK
+ lines =<< trim END
+ vim9script
+ if true
+ var name = 'piet'
+ # define a function so that the variable isn't cleared
+ def GetItem(): string
+ return item
+ enddef
+ endif
+ if true
+ def FuncOne(name: string)
+ echo name
+ enddef
+ endif
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # argument name declared later is only found when compiling
+ lines =<< trim END
+ vim9script
+ def FuncOne(name: string)
+ echo nr
+ enddef
+ var name = 'piet'
+ END
+ v9.CheckScriptSuccess(lines)
+ v9.CheckScriptFailure(lines + ['defcompile'], 'E1168:')
+
+ lines =<< trim END
+ vim9script
+ def FuncOne(nr: number)
+ echo nr
+ enddef
+ def FuncTwo()
+ FuncOne()
+ enddef
+ defcompile
+ END
+ writefile(lines, 'Xscript')
+ var didCatch = false
+ try
+ source Xscript
+ catch
+ assert_match('E119: Not enough arguments for function: <SNR>\d\+_FuncOne', v:exception)
+ assert_match('Xscript\[8\]..function <SNR>\d\+_FuncTwo, line 1', v:throwpoint)
+ didCatch = true
+ endtry
+ assert_true(didCatch)
+
+ lines =<< trim END
+ vim9script
+ def FuncOne(nr: number)
+ echo nr
+ enddef
+ def FuncTwo()
+ FuncOne(1, 2)
+ enddef
+ defcompile
+ END
+ writefile(lines, 'Xscript', 'D')
+ didCatch = false
+ try
+ source Xscript
+ catch
+ assert_match('E118: Too many arguments for function: <SNR>\d\+_FuncOne', v:exception)
+ assert_match('Xscript\[8\]..function <SNR>\d\+_FuncTwo, line 1', v:throwpoint)
+ didCatch = true
+ endtry
+ assert_true(didCatch)
+enddef
+
+def Test_call_funcref_wrong_args()
+ var head =<< trim END
+ vim9script
+ def Func3(a1: string, a2: number, a3: list<number>)
+ echo a1 .. a2 .. a3[0]
+ enddef
+ def Testme()
+ var funcMap: dict<func> = {func: Func3}
+ END
+ var tail =<< trim END
+ enddef
+ Testme()
+ END
+ v9.CheckScriptSuccess(head + ["funcMap['func']('str', 123, [1, 2, 3])"] + tail)
+
+ v9.CheckScriptFailure(head + ["funcMap['func']('str', 123)"] + tail, 'E119:')
+ v9.CheckScriptFailure(head + ["funcMap['func']('str', 123, [1], 4)"] + tail, 'E118:')
+
+ var lines =<< trim END
+ vim9script
+ var Ref: func(number): any
+ Ref = (j) => !j
+ echo Ref(false)
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4)
+
+ lines =<< trim END
+ vim9script
+ var Ref: func(number): any
+ Ref = (j) => !j
+ call Ref(false)
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4)
+enddef
+
+def Test_call_lambda_args()
+ var lines =<< trim END
+ var Callback = (..._) => 'anything'
+ assert_equal('anything', Callback())
+ assert_equal('anything', Callback(1))
+ assert_equal('anything', Callback('a', 2))
+
+ assert_equal('xyz', ((a: string): string => a)('xyz'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ v9.CheckDefFailure(['echo ((i) => 0)()'],
+ 'E119: Not enough arguments for function: ((i) => 0)()')
+
+ lines =<< trim END
+ var Ref = (x: number, y: number) => x + y
+ echo Ref(1, 'x')
+ END
+ v9.CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected number but got string')
+
+ lines =<< trim END
+ var Ref: func(job, string, number)
+ Ref = (x, y) => 0
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:')
+
+ lines =<< trim END
+ var Ref: func(job, string)
+ Ref = (x, y, z) => 0
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012:')
+
+ lines =<< trim END
+ var one = 1
+ var l = [1, 2, 3]
+ echo map(l, (one) => one)
+ END
+ v9.CheckDefFailure(lines, 'E1167:')
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E1168:')
+
+ lines =<< trim END
+ var Ref: func(any, ?any): bool
+ Ref = (_, y = 1) => false
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1172:')
+
+ lines =<< trim END
+ var a = 0
+ var b = (a == 0 ? 1 : 2)
+ assert_equal(1, b)
+ var txt = 'a'
+ b = (txt =~ 'x' ? 1 : 2)
+ assert_equal(2, b)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ def ShadowLocal()
+ var one = 1
+ var l = [1, 2, 3]
+ echo map(l, (one) => one)
+ enddef
+ END
+ v9.CheckDefFailure(lines, 'E1167:')
+
+ lines =<< trim END
+ def Shadowarg(one: number)
+ var l = [1, 2, 3]
+ echo map(l, (one) => one)
+ enddef
+ END
+ v9.CheckDefFailure(lines, 'E1167:')
+
+ lines =<< trim END
+ echo ((a) => a)('aa', 'bb')
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E118:', 1)
+
+ lines =<< trim END
+ echo 'aa'->((a) => a)('bb')
+ END
+ v9.CheckDefFailure(lines, 'E118: Too many arguments for function: ->((a) => a)(''bb'')', 1)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E118: Too many arguments for function: <lambda>', 2)
+enddef
+
+def Test_lambda_line_nr()
+ var lines =<< trim END
+ vim9script
+ # comment
+ # comment
+ var id = timer_start(1'000, (_) => 0)
+ var out = execute('verbose ' .. timer_info(id)[0].callback
+ ->string()
+ ->substitute("('\\|')", ' ', 'g'))
+ assert_match('Last set from .* line 4', out)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def FilterWithCond(x: string, Cond: func(string): bool): bool
+ return Cond(x)
+enddef
+
+def Test_lambda_return_type()
+ var lines =<< trim END
+ var Ref = (): => 123
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1157:', 1)
+
+ # no space before the return type
+ lines =<< trim END
+ var Ref = (x):number => x + 1
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1069:', 1)
+
+ # this works
+ for x in ['foo', 'boo']
+ echo g:FilterWithCond(x, (v) => v =~ '^b')
+ endfor
+
+ # this fails
+ lines =<< trim END
+ echo g:FilterWithCond('foo', (v) => v .. '^b')
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected func(string): bool but got func(any): string', 1)
+
+ lines =<< trim END
+ var Lambda1 = (x) => {
+ return x
+ }
+ assert_equal('asdf', Lambda1('asdf'))
+ var Lambda2 = (x): string => {
+ return x
+ }
+ assert_equal('foo', Lambda2('foo'))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var Lambda = (x): string => {
+ return x
+ }
+ echo Lambda(['foo'])
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1012:')
+enddef
+
+def Test_lambda_uses_assigned_var()
+ v9.CheckDefSuccess([
+ 'var x: any = "aaa"',
+ 'x = filter(["bbb"], (_, v) => v =~ x)'])
+enddef
+
+def Test_lambda_invalid_block()
+ var lines =<< trim END
+ timer_start(0, (_) => { # echo
+ echo 'yes'
+ })
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ timer_start(0, (_) => { " echo
+ echo 'yes'
+ })
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E488: Trailing characters: " echo')
+
+ lines =<< trim END
+ timer_start(0, (_) => { | echo
+ echo 'yes'
+ })
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E488: Trailing characters: | echo')
+enddef
+
+def Test_lambda_with_following_cmd()
+ var lines =<< trim END
+ set ts=2
+ var Lambda = () => {
+ set ts=4
+ } | set ts=3
+ assert_equal(3, &ts)
+ Lambda()
+ assert_equal(4, &ts)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+ set ts=8
+enddef
+
+def Test_pass_legacy_lambda_to_def_func()
+ var lines =<< trim END
+ vim9script
+ func Foo()
+ eval s:Bar({x -> 0})
+ endfunc
+ def Bar(y: any)
+ enddef
+ Foo()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def g:TestFunc(F: func)
+ enddef
+ legacy call g:TestFunc({-> 0})
+ delfunc g:TestFunc
+
+ def g:TestFunc(F: func(number))
+ enddef
+ legacy call g:TestFunc({nr -> 0})
+ delfunc g:TestFunc
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_lambda_in_reduce_line_break()
+ # this was using freed memory
+ var lines =<< trim END
+ vim9script
+ const result: dict<number> =
+ ['Bob', 'Sam', 'Cat', 'Bob', 'Cat', 'Cat']
+ ->reduce((acc, val) => {
+ if has_key(acc, val)
+ acc[val] += 1
+ return acc
+ else
+ acc[val] = 1
+ return acc
+ endif
+ }, {})
+ assert_equal({Bob: 2, Sam: 1, Cat: 3}, result)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_set_opfunc_to_lambda()
+ var lines =<< trim END
+ vim9script
+ nnoremap <expr> <F4> <SID>CountSpaces() .. '_'
+ def CountSpaces(type = ''): string
+ if type == ''
+ &operatorfunc = (t) => CountSpaces(t)
+ return 'g@'
+ endif
+ normal! '[V']y
+ g:result = getreg('"')->count(' ')
+ return ''
+ enddef
+ new
+ 'a b c d e'->setline(1)
+ feedkeys("\<F4>", 'x')
+ assert_equal(4, g:result)
+ bwipe!
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_set_opfunc_to_global_function()
+ var lines =<< trim END
+ vim9script
+ def g:CountSpaces(type = ''): string
+ normal! '[V']y
+ g:result = getreg('"')->count(' ')
+ return ''
+ enddef
+ # global function works at script level
+ &operatorfunc = g:CountSpaces
+ new
+ 'a b c d e'->setline(1)
+ feedkeys("g@_", 'x')
+ assert_equal(4, g:result)
+
+ &operatorfunc = ''
+ g:result = 0
+ # global function works in :def function
+ def Func()
+ &operatorfunc = g:CountSpaces
+ enddef
+ Func()
+ feedkeys("g@_", 'x')
+ assert_equal(4, g:result)
+
+ bwipe!
+ END
+ v9.CheckScriptSuccess(lines)
+ &operatorfunc = ''
+enddef
+
+def Test_use_script_func_name_with_prefix()
+ var lines =<< trim END
+ vim9script
+ func g:Getit()
+ return 'it'
+ endfunc
+ var Fn = g:Getit
+ assert_equal('it', Fn())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_lambda_type_allocated()
+ # Check that unreferencing a partial using a lambda can use the variable type
+ # after the lambda has been freed and does not leak memory.
+ var lines =<< trim END
+ vim9script
+
+ func MyomniFunc1(val, findstart, base)
+ return a:findstart ? 0 : []
+ endfunc
+
+ var Lambda = (a, b) => MyomniFunc1(19, a, b)
+ &omnifunc = Lambda
+ Lambda = (a, b) => MyomniFunc1(20, a, b)
+ &omnifunc = string(Lambda)
+ Lambda = (a, b) => strlen(a)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_define_lambda_in_execute()
+ var lines =<< trim [CODE]
+ vim9script
+
+ def BuildFuncMultiLine(): func
+ var x =<< trim END
+ g:SomeRandomFunc = (d: dict<any>) => {
+ return d.k1 + d.k2
+ }
+ END
+ execute(x)
+ return g:SomeRandomFunc
+ enddef
+ var ResultPlus = BuildFuncMultiLine()
+ assert_equal(7, ResultPlus({k1: 3, k2: 4}))
+ [CODE]
+ v9.CheckScriptSuccess(lines)
+ unlet g:SomeRandomFunc
+enddef
+
+" Default arg and varargs
+def MyDefVarargs(one: string, two = 'foo', ...rest: list<string>): string
+ var res = one .. ',' .. two
+ for s in rest
+ res ..= ',' .. s
+ endfor
+ return res
+enddef
+
+def Test_call_def_varargs()
+ assert_fails('g:MyDefVarargs()', 'E119:', '', 1, 'Test_call_def_varargs')
+ g:MyDefVarargs('one')->assert_equal('one,foo')
+ g:MyDefVarargs('one', 'two')->assert_equal('one,two')
+ g:MyDefVarargs('one', 'two', 'three')->assert_equal('one,two,three')
+ v9.CheckDefFailure(['g:MyDefVarargs("one", 22)'],
+ 'E1013: Argument 2: type mismatch, expected string but got number')
+ v9.CheckDefFailure(['g:MyDefVarargs("one", "two", 123)'],
+ 'E1013: Argument 3: type mismatch, expected string but got number')
+
+ var lines =<< trim END
+ vim9script
+ def Func(...l: list<string>)
+ echo l
+ enddef
+ Func('a', 'b', 'c')
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Func(...l: list<string>)
+ echo l
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Func(...l: list<any>)
+ echo l
+ enddef
+ Func(0)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Func(...l: any)
+ echo l
+ enddef
+ Func(0)
+ END
+ v9.CheckScriptFailure(lines, 'E1180:', 2)
+
+ lines =<< trim END
+ vim9script
+ def Func(..._l: list<string>)
+ echo _l
+ enddef
+ Func('a', 'b', 'c')
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Func(...l: list<string>)
+ echo l
+ enddef
+ Func(1, 2, 3)
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch')
+
+ lines =<< trim END
+ vim9script
+ def Func(...l: list<string>)
+ echo l
+ enddef
+ Func('a', 9)
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 2: type mismatch')
+
+ lines =<< trim END
+ vim9script
+ def Func(...l: list<string>)
+ echo l
+ enddef
+ Func(1, 'a')
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch')
+
+ lines =<< trim END
+ vim9script
+ def Func( # some comment
+ ...l = []
+ )
+ echo l
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1160:')
+
+ lines =<< trim END
+ vim9script
+ def DoIt()
+ g:Later('')
+ enddef
+ defcompile
+ def g:Later(...l: list<number>)
+ enddef
+ DoIt()
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got string')
+enddef
+
+let s:value = ''
+
+def FuncOneDefArg(opt = 'text')
+ s:value = opt
+enddef
+
+def FuncTwoDefArg(nr = 123, opt = 'text'): string
+ return nr .. opt
+enddef
+
+def FuncVarargs(...arg: list<string>): string
+ return join(arg, ',')
+enddef
+
+def Test_func_type_varargs()
+ var RefDefArg: func(?string)
+ RefDefArg = g:FuncOneDefArg
+ RefDefArg()
+ s:value->assert_equal('text')
+ RefDefArg('some')
+ s:value->assert_equal('some')
+
+ var RefDef2Arg: func(?number, ?string): string
+ RefDef2Arg = g:FuncTwoDefArg
+ RefDef2Arg()->assert_equal('123text')
+ RefDef2Arg(99)->assert_equal('99text')
+ RefDef2Arg(77, 'some')->assert_equal('77some')
+
+ v9.CheckDefFailure(['var RefWrong: func(string?)'], 'E1010:')
+ v9.CheckDefFailure(['var RefWrong: func(?string, string)'], 'E1007:')
+
+ var RefVarargs: func(...list<string>): string
+ RefVarargs = g:FuncVarargs
+ RefVarargs()->assert_equal('')
+ RefVarargs('one')->assert_equal('one')
+ RefVarargs('one', 'two')->assert_equal('one,two')
+
+ v9.CheckDefFailure(['var RefWrong: func(...list<string>, string)'], 'E110:')
+ v9.CheckDefFailure(['var RefWrong: func(...list<string>, ?string)'], 'E110:')
+enddef
+
+" Only varargs
+def MyVarargsOnly(...args: list<string>): string
+ return join(args, ',')
+enddef
+
+def Test_call_varargs_only()
+ g:MyVarargsOnly()->assert_equal('')
+ g:MyVarargsOnly('one')->assert_equal('one')
+ g:MyVarargsOnly('one', 'two')->assert_equal('one,two')
+ v9.CheckDefFailure(['g:MyVarargsOnly(1)'], 'E1013: Argument 1: type mismatch, expected string but got number')
+ v9.CheckDefFailure(['g:MyVarargsOnly("one", 2)'], 'E1013: Argument 2: type mismatch, expected string but got number')
+enddef
+
+def Test_varargs_mismatch()
+ var lines =<< trim END
+ vim9script
+
+ def Map(Fn: func(...any): number): number
+ return Fn('12')
+ enddef
+
+ var res = Map((v) => str2nr(v))
+ assert_equal(12, res)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_using_var_as_arg()
+ var lines =<< trim END
+ def Func(x: number)
+ var x = 234
+ enddef
+ END
+ v9.CheckDefFailure(lines, 'E1006:')
+
+ lines =<< trim END
+ def Func(Ref: number)
+ def Ref()
+ enddef
+ enddef
+ END
+ v9.CheckDefFailure(lines, 'E1073:')
+enddef
+
+def s:DictArg(arg: dict<string>)
+ arg['key'] = 'value'
+enddef
+
+def s:ListArg(arg: list<string>)
+ arg[0] = 'value'
+enddef
+
+def Test_assign_to_argument()
+ # works for dict and list
+ var d: dict<string> = {}
+ DictArg(d)
+ d['key']->assert_equal('value')
+ var l: list<string> = []
+ ListArg(l)
+ l[0]->assert_equal('value')
+
+ v9.CheckScriptFailure(['def Func(arg: number)', 'arg = 3', 'enddef', 'defcompile'], 'E1090:')
+ delfunc! g:Func
+enddef
+
+" These argument names are reserved in legacy functions.
+def s:WithReservedNames(firstline: string, lastline: string): string
+ return firstline .. lastline
+enddef
+
+def Test_argument_names()
+ assert_equal('OK', WithReservedNames('O', 'K'))
+enddef
+
+def Test_call_func_defined_later()
+ g:DefinedLater('one')->assert_equal('one')
+ assert_fails('NotDefined("one")', 'E117:', '', 2, 'Test_call_func_defined_later')
+enddef
+
+func DefinedLater(arg)
+ return a:arg
+endfunc
+
+def Test_call_funcref()
+ g:SomeFunc('abc')->assert_equal(3)
+ assert_fails('NotAFunc()', 'E117:', '', 2, 'Test_call_funcref') # comment after call
+ assert_fails('g:NotAFunc()', 'E1085:', '', 3, 'Test_call_funcref')
+
+ var lines =<< trim END
+ vim9script
+ def RetNumber(): number
+ return 123
+ enddef
+ var Funcref: func: number = function('RetNumber')
+ Funcref()->assert_equal(123)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def RetNumber(): number
+ return 123
+ enddef
+ def Bar(F: func: number): number
+ return F()
+ enddef
+ var Funcref = function('RetNumber')
+ Bar(Funcref)->assert_equal(123)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def UseNumber(nr: number)
+ echo nr
+ enddef
+ var Funcref: func(number) = function('UseNumber')
+ Funcref(123)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def UseNumber(nr: number)
+ echo nr
+ enddef
+ var Funcref: func(string) = function('UseNumber')
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected func(string) but got func(number)')
+
+ lines =<< trim END
+ vim9script
+ def EchoNr(nr = 34)
+ g:echo = nr
+ enddef
+ var Funcref: func(?number) = function('EchoNr')
+ Funcref()
+ g:echo->assert_equal(34)
+ Funcref(123)
+ g:echo->assert_equal(123)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def EchoList(...l: list<number>)
+ g:echo = l
+ enddef
+ var Funcref: func(...list<number>) = function('EchoList')
+ Funcref()
+ g:echo->assert_equal([])
+ Funcref(1, 2, 3)
+ g:echo->assert_equal([1, 2, 3])
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def OptAndVar(nr: number, opt = 12, ...l: list<number>): number
+ g:optarg = opt
+ g:listarg = l
+ return nr
+ enddef
+ var Funcref: func(number, ?number, ...list<number>): number = function('OptAndVar')
+ Funcref(10)->assert_equal(10)
+ g:optarg->assert_equal(12)
+ g:listarg->assert_equal([])
+
+ Funcref(11, 22)->assert_equal(11)
+ g:optarg->assert_equal(22)
+ g:listarg->assert_equal([])
+
+ Funcref(17, 18, 1, 2, 3)->assert_equal(17)
+ g:optarg->assert_equal(18)
+ g:listarg->assert_equal([1, 2, 3])
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ function s:func(num)
+ return a:num * 2
+ endfunction
+
+ def s:CallFuncref()
+ var Funcref = function('s:func')
+ Funcref(3)->assert_equal(6)
+ enddef
+ call s:CallFuncref()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ function s:func(num)
+ return a:num * 2
+ endfunction
+
+ def s:CallFuncref()
+ var Funcref = function(s:func)
+ Funcref(3)->assert_equal(6)
+ enddef
+ call s:CallFuncref()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ function s:func(num)
+ return a:num * 2
+ endfunction
+
+ def s:CallFuncref()
+ var Funcref = s:func
+ Funcref(3)->assert_equal(6)
+ enddef
+ call s:CallFuncref()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+let SomeFunc = function('len')
+let NotAFunc = 'text'
+
+def CombineFuncrefTypes()
+ # same arguments, different return type
+ var Ref1: func(bool): string
+ var Ref2: func(bool): number
+ var Ref3: func(bool): any
+ Ref3 = g:cond ? Ref1 : Ref2
+
+ # different number of arguments
+ var Refa1: func(bool): number
+ var Refa2: func(bool, number): number
+ var Refa3: func: number
+ Refa3 = g:cond ? Refa1 : Refa2
+
+ # different argument types
+ var Refb1: func(bool, string): number
+ var Refb2: func(string, number): number
+ var Refb3: func(any, any): number
+ Refb3 = g:cond ? Refb1 : Refb2
+enddef
+
+def FuncWithForwardCall()
+ return g:DefinedEvenLater("yes")
+enddef
+
+def DefinedEvenLater(arg: string): string
+ return arg
+enddef
+
+def Test_error_in_nested_function()
+ # Error in called function requires unwinding the call stack.
+ assert_fails('g:FuncWithForwardCall()', 'E1096:', '', 1, 'FuncWithForwardCall')
+enddef
+
+def Test_nested_function_with_nextcmd()
+ var lines =<< trim END
+ vim9script
+ # Define an outer function
+ def FirstFunction()
+ # Define an inner function
+ def SecondFunction()
+ # the function has a body, a double free is detected.
+ AAAAA
+
+ # enddef followed by | or } followed by # one or more characters
+ enddef|BBBB
+ enddef
+
+ # Compile all functions
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1173: Text found after enddef: BBBB')
+enddef
+
+def Test_nested_function_with_args_split()
+ var lines =<< trim END
+ vim9script
+ def FirstFunction()
+ def SecondFunction(
+ )
+ # had a double free if the right parenthesis of the nested function is
+ # on the next line
+
+ enddef|BBBB
+ enddef
+ # Compile all functions
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1173: Text found after enddef: BBBB')
+
+ lines =<< trim END
+ vim9script
+ def FirstFunction()
+ func SecondFunction()
+ endfunc|BBBB
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1173: Text found after endfunction: BBBB')
+enddef
+
+def Test_error_in_function_args()
+ var lines =<< trim END
+ def FirstFunction()
+ def SecondFunction(J =
+ # Nois
+ # one
+
+ enddef|BBBB
+ enddef
+ # Compile all functions
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E488:')
+enddef
+
+def Test_return_type_wrong()
+ v9.CheckScriptFailure([
+ 'def Func(): number',
+ 'return "a"',
+ 'enddef',
+ 'defcompile'], 'expected number but got string')
+ delfunc! g:Func
+ v9.CheckScriptFailure([
+ 'def Func(): string',
+ 'return 1',
+ 'enddef',
+ 'defcompile'], 'expected string but got number')
+ delfunc! g:Func
+ v9.CheckScriptFailure([
+ 'def Func(): void',
+ 'return "a"',
+ 'enddef',
+ 'defcompile'],
+ 'E1096: Returning a value in a function without a return type')
+ delfunc! g:Func
+ v9.CheckScriptFailure([
+ 'def Func()',
+ 'return "a"',
+ 'enddef',
+ 'defcompile'],
+ 'E1096: Returning a value in a function without a return type')
+ delfunc! g:Func
+
+ v9.CheckScriptFailure([
+ 'def Func(): number',
+ 'return',
+ 'enddef',
+ 'defcompile'], 'E1003:')
+ delfunc! g:Func
+
+ v9.CheckScriptFailure([
+ 'def Func():number',
+ 'return 123',
+ 'enddef',
+ 'defcompile'], 'E1069:')
+ delfunc! g:Func
+
+ v9.CheckScriptFailure([
+ 'def Func() :number',
+ 'return 123',
+ 'enddef',
+ 'defcompile'], 'E1059:')
+ delfunc! g:Func
+
+ v9.CheckScriptFailure([
+ 'def Func() : number',
+ 'return 123',
+ 'enddef',
+ 'defcompile'], 'E1059:')
+ delfunc! g:Func
+
+ v9.CheckScriptFailure(['def Func(): list', 'return []', 'enddef'], 'E1008: Missing <type> after list')
+ delfunc! g:Func
+ v9.CheckScriptFailure(['def Func(): dict', 'return {}', 'enddef'], 'E1008: Missing <type> after dict')
+ delfunc! g:Func
+ v9.CheckScriptFailure(['def Func()', 'return 1'], 'E1057:')
+ delfunc! g:Func
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'def FuncB()',
+ ' return 123',
+ 'enddef',
+ 'def FuncA()',
+ ' FuncB()',
+ 'enddef',
+ 'defcompile'], 'E1096:')
+enddef
+
+def Test_arg_type_wrong()
+ v9.CheckScriptFailure(['def Func3(items: list)', 'echo "a"', 'enddef'], 'E1008: Missing <type> after list')
+ v9.CheckScriptFailure(['def Func4(...)', 'echo "a"', 'enddef'], 'E1055: Missing name after ...')
+ v9.CheckScriptFailure(['def Func5(items:string)', 'echo "a"'], 'E1069:')
+ v9.CheckScriptFailure(['def Func5(items)', 'echo "a"'], 'E1077:')
+ v9.CheckScriptFailure(['def Func6(...x:list<number>)', 'echo "a"', 'enddef'], 'E1069:')
+ v9.CheckScriptFailure(['def Func7(...x: int)', 'echo "a"', 'enddef'], 'E1010:')
+enddef
+
+def Test_white_space_before_comma()
+ var lines =<< trim END
+ vim9script
+ def Func(a: number , b: number)
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1068:')
+ call assert_fails('vim9cmd echo stridx("a" .. "b" , "a")', 'E1068:')
+enddef
+
+def Test_white_space_after_comma()
+ var lines =<< trim END
+ vim9script
+ def Func(a: number,b: number)
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1069:')
+
+ # OK in legacy function
+ lines =<< trim END
+ vim9script
+ func Func(a,b)
+ endfunc
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_vim9script_call()
+ var lines =<< trim END
+ vim9script
+ var name = ''
+ def MyFunc(arg: string)
+ name = arg
+ enddef
+ MyFunc('foobar')
+ name->assert_equal('foobar')
+
+ var str = 'barfoo'
+ str->MyFunc()
+ name->assert_equal('barfoo')
+
+ g:value = 'value'
+ g:value->MyFunc()
+ name->assert_equal('value')
+
+ var listvar = []
+ def ListFunc(arg: list<number>)
+ listvar = arg
+ enddef
+ [1, 2, 3]->ListFunc()
+ listvar->assert_equal([1, 2, 3])
+
+ var dictvar = {}
+ def DictFunc(arg: dict<number>)
+ dictvar = arg
+ enddef
+ {a: 1, b: 2}->DictFunc()
+ dictvar->assert_equal({a: 1, b: 2})
+ def CompiledDict()
+ {a: 3, b: 4}->DictFunc()
+ enddef
+ CompiledDict()
+ dictvar->assert_equal({a: 3, b: 4})
+
+ {a: 3, b: 4}->DictFunc()
+ dictvar->assert_equal({a: 3, b: 4})
+
+ ('text')->MyFunc()
+ name->assert_equal('text')
+ ("some")->MyFunc()
+ name->assert_equal('some')
+
+ # line starting with single quote is not a mark
+ # line starting with double quote can be a method call
+ 'asdfasdf'->MyFunc()
+ name->assert_equal('asdfasdf')
+ "xyz"->MyFunc()
+ name->assert_equal('xyz')
+
+ def UseString()
+ 'xyork'->MyFunc()
+ enddef
+ UseString()
+ name->assert_equal('xyork')
+
+ def UseString2()
+ "knife"->MyFunc()
+ enddef
+ UseString2()
+ name->assert_equal('knife')
+
+ # prepending a colon makes it a mark
+ new
+ setline(1, ['aaa', 'bbb', 'ccc'])
+ normal! 3Gmt1G
+ :'t
+ getcurpos()[1]->assert_equal(3)
+ bwipe!
+
+ MyFunc(
+ 'continued'
+ )
+ assert_equal('continued',
+ name
+ )
+
+ call MyFunc(
+ 'more'
+ ..
+ 'lines'
+ )
+ assert_equal(
+ 'morelines',
+ name)
+ END
+ writefile(lines, 'Xcall.vim', 'D')
+ source Xcall.vim
+enddef
+
+def Test_vim9script_call_fail_decl()
+ var lines =<< trim END
+ vim9script
+ var name = ''
+ def MyFunc(arg: string)
+ var name = 123
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1054:')
+enddef
+
+def Test_vim9script_call_fail_type()
+ var lines =<< trim END
+ vim9script
+ def MyFunc(arg: string)
+ echo arg
+ enddef
+ MyFunc(1234)
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+enddef
+
+def Test_vim9script_call_fail_const()
+ var lines =<< trim END
+ vim9script
+ const var = ''
+ def MyFunc(arg: string)
+ var = 'asdf'
+ enddef
+ defcompile
+ END
+ writefile(lines, 'Xcall_const.vim', 'D')
+ assert_fails('source Xcall_const.vim', 'E46:', '', 1, 'MyFunc')
+
+ lines =<< trim END
+ const g:Aconst = 77
+ def Change()
+ # comment
+ g:Aconst = 99
+ enddef
+ call Change()
+ unlet g:Aconst
+ END
+ v9.CheckScriptFailure(lines, 'E741: Value is locked: Aconst', 2)
+enddef
+
+" Test that inside :function a Python function can be defined, :def is not
+" recognized.
+func Test_function_python()
+ CheckFeature python3
+ let py = 'python3'
+ execute py "<< EOF"
+def do_something():
+ return 1
+EOF
+endfunc
+
+def Test_delfunc()
+ var lines =<< trim END
+ vim9script
+ def g:GoneSoon()
+ echo 'hello'
+ enddef
+
+ def CallGoneSoon()
+ g:GoneSoon()
+ enddef
+ defcompile
+
+ delfunc g:GoneSoon
+ CallGoneSoon()
+ END
+ writefile(lines, 'XToDelFunc', 'D')
+ assert_fails('so XToDelFunc', 'E933:', '', 1, 'CallGoneSoon')
+ assert_fails('so XToDelFunc', 'E933:', '', 1, 'CallGoneSoon')
+enddef
+
+func Test_free_dict_while_in_funcstack()
+ " relies on the sleep command
+ CheckUnix
+ call Run_Test_free_dict_while_in_funcstack()
+endfunc
+
+def Run_Test_free_dict_while_in_funcstack()
+ # this was freeing the TermRun() default argument dictionary while it was
+ # still referenced in a funcstack_T
+ var lines =<< trim END
+ vim9script
+
+ &updatetime = 400
+ def TermRun(_ = {})
+ def Post()
+ enddef
+ def Exec()
+ term_start('sleep 1', {
+ term_finish: 'close',
+ exit_cb: (_, _) => Post(),
+ })
+ enddef
+ Exec()
+ enddef
+ nnoremap <F4> <Cmd>call <SID>TermRun()<CR>
+ timer_start(100, (_) => feedkeys("\<F4>"))
+ timer_start(1000, (_) => feedkeys("\<F4>"))
+ sleep 1500m
+ END
+ v9.CheckScriptSuccess(lines)
+ nunmap <F4>
+ set updatetime&
+enddef
+
+def Test_redef_failure()
+ writefile(['def Func0(): string', 'return "Func0"', 'enddef'], 'Xdef')
+ so Xdef
+ writefile(['def Func1(): string', 'return "Func1"', 'enddef'], 'Xdef')
+ so Xdef
+ writefile(['def! Func0(): string', 'enddef', 'defcompile'], 'Xdef')
+ assert_fails('so Xdef', 'E1027:', '', 1, 'Func0')
+ writefile(['def Func2(): string', 'return "Func2"', 'enddef'], 'Xdef')
+ so Xdef
+ delete('Xdef')
+
+ assert_fails('g:Func0()', 'E1091:')
+ g:Func1()->assert_equal('Func1')
+ g:Func2()->assert_equal('Func2')
+
+ delfunc! Func0
+ delfunc! Func1
+ delfunc! Func2
+enddef
+
+def Test_vim9script_func()
+ var lines =<< trim END
+ vim9script
+ func Func(arg)
+ echo a:arg
+ endfunc
+ Func('text')
+ END
+ writefile(lines, 'XVim9Func', 'D')
+ so XVim9Func
+enddef
+
+let s:funcResult = 0
+
+def FuncNoArgNoRet()
+ s:funcResult = 11
+enddef
+
+def FuncNoArgRetNumber(): number
+ s:funcResult = 22
+ return 1234
+enddef
+
+def FuncNoArgRetString(): string
+ s:funcResult = 45
+ return 'text'
+enddef
+
+def FuncOneArgNoRet(arg: number)
+ s:funcResult = arg
+enddef
+
+def FuncOneArgRetNumber(arg: number): number
+ s:funcResult = arg
+ return arg
+enddef
+
+def FuncTwoArgNoRet(one: bool, two: number)
+ s:funcResult = two
+enddef
+
+def s:FuncOneArgRetString(arg: string): string
+ return arg
+enddef
+
+def s:FuncOneArgRetAny(arg: any): any
+ return arg
+enddef
+
+def Test_func_type()
+ var Ref1: func()
+ s:funcResult = 0
+ Ref1 = g:FuncNoArgNoRet
+ Ref1()
+ s:funcResult->assert_equal(11)
+
+ var Ref2: func
+ s:funcResult = 0
+ Ref2 = g:FuncNoArgNoRet
+ Ref2()
+ s:funcResult->assert_equal(11)
+
+ s:funcResult = 0
+ Ref2 = g:FuncOneArgNoRet
+ Ref2(12)
+ s:funcResult->assert_equal(12)
+
+ s:funcResult = 0
+ Ref2 = g:FuncNoArgRetNumber
+ Ref2()->assert_equal(1234)
+ s:funcResult->assert_equal(22)
+
+ s:funcResult = 0
+ Ref2 = g:FuncOneArgRetNumber
+ Ref2(13)->assert_equal(13)
+ s:funcResult->assert_equal(13)
+enddef
+
+def Test_repeat_return_type()
+ var res = 0
+ for n in repeat([1], 3)
+ res += n
+ endfor
+ res->assert_equal(3)
+
+ res = 0
+ for n in repeat(0z01, 3)->blob2list()
+ res += n
+ endfor
+ res->assert_equal(3)
+
+ res = 0
+ for n in add([1, 2], 3)
+ res += n
+ endfor
+ res->assert_equal(6)
+enddef
+
+def Test_argv_return_type()
+ next fileone filetwo
+ var res = ''
+ for name in argv()
+ res ..= name
+ endfor
+ res->assert_equal('fileonefiletwo')
+enddef
+
+def Test_func_type_part()
+ var RefVoid: func: void
+ RefVoid = g:FuncNoArgNoRet
+ RefVoid = g:FuncOneArgNoRet
+ v9.CheckDefFailure(['var RefVoid: func: void', 'RefVoid = g:FuncNoArgRetNumber'], 'E1012: Type mismatch; expected func(...) but got func(): number')
+ v9.CheckDefFailure(['var RefVoid: func: void', 'RefVoid = g:FuncNoArgRetString'], 'E1012: Type mismatch; expected func(...) but got func(): string')
+
+ var RefAny: func(): any
+ RefAny = g:FuncNoArgRetNumber
+ RefAny = g:FuncNoArgRetString
+ v9.CheckDefFailure(['var RefAny: func(): any', 'RefAny = g:FuncNoArgNoRet'], 'E1012: Type mismatch; expected func(): any but got func()')
+ v9.CheckDefFailure(['var RefAny: func(): any', 'RefAny = g:FuncOneArgNoRet'], 'E1012: Type mismatch; expected func(): any but got func(number)')
+
+ var RefAnyNoArgs: func: any = RefAny
+
+ var RefNr: func: number
+ RefNr = g:FuncNoArgRetNumber
+ RefNr = g:FuncOneArgRetNumber
+ v9.CheckDefFailure(['var RefNr: func: number', 'RefNr = g:FuncNoArgNoRet'], 'E1012: Type mismatch; expected func(...): number but got func()')
+ v9.CheckDefFailure(['var RefNr: func: number', 'RefNr = g:FuncNoArgRetString'], 'E1012: Type mismatch; expected func(...): number but got func(): string')
+
+ var RefStr: func: string
+ RefStr = g:FuncNoArgRetString
+ RefStr = FuncOneArgRetString
+ v9.CheckDefFailure(['var RefStr: func: string', 'RefStr = g:FuncNoArgNoRet'], 'E1012: Type mismatch; expected func(...): string but got func()')
+ v9.CheckDefFailure(['var RefStr: func: string', 'RefStr = g:FuncNoArgRetNumber'], 'E1012: Type mismatch; expected func(...): string but got func(): number')
+enddef
+
+def Test_func_type_fails()
+ v9.CheckDefFailure(['var ref1: func()'], 'E704:')
+
+ v9.CheckDefFailure(['var Ref1: func()', 'Ref1 = g:FuncNoArgRetNumber'], 'E1012: Type mismatch; expected func() but got func(): number')
+ v9.CheckDefFailure(['var Ref1: func()', 'Ref1 = g:FuncOneArgNoRet'], 'E1012: Type mismatch; expected func() but got func(number)')
+ v9.CheckDefFailure(['var Ref1: func()', 'Ref1 = g:FuncOneArgRetNumber'], 'E1012: Type mismatch; expected func() but got func(number): number')
+ v9.CheckDefFailure(['var Ref1: func(bool)', 'Ref1 = g:FuncTwoArgNoRet'], 'E1012: Type mismatch; expected func(bool) but got func(bool, number)')
+ v9.CheckDefFailure(['var Ref1: func(?bool)', 'Ref1 = g:FuncTwoArgNoRet'], 'E1012: Type mismatch; expected func(?bool) but got func(bool, number)')
+ v9.CheckDefFailure(['var Ref1: func(...bool)', 'Ref1 = g:FuncTwoArgNoRet'], 'E1012: Type mismatch; expected func(...bool) but got func(bool, number)')
+
+ v9.CheckDefFailure(['var RefWrong: func(string ,number)'], 'E1068:')
+ v9.CheckDefFailure(['var RefWrong: func(string,number)'], 'E1069:')
+ v9.CheckDefFailure(['var RefWrong: func(bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool)'], 'E1005:')
+ v9.CheckDefFailure(['var RefWrong: func(bool):string'], 'E1069:')
+enddef
+
+def Test_func_return_type()
+ var nr: number
+ nr = g:FuncNoArgRetNumber()
+ nr->assert_equal(1234)
+
+ nr = FuncOneArgRetAny(122)
+ nr->assert_equal(122)
+
+ var str: string
+ str = FuncOneArgRetAny('yes')
+ str->assert_equal('yes')
+
+ v9.CheckDefFailure(['var str: string', 'str = g:FuncNoArgRetNumber()'], 'E1012: Type mismatch; expected string but got number')
+enddef
+
+def Test_func_common_type()
+ def FuncOne(n: number): number
+ return n
+ enddef
+ def FuncTwo(s: string): number
+ return len(s)
+ enddef
+ def FuncThree(n: number, s: string): number
+ return n + len(s)
+ enddef
+ var list = [FuncOne, FuncTwo, FuncThree]
+ assert_equal(8, list[0](8))
+ assert_equal(4, list[1]('word'))
+ assert_equal(7, list[2](3, 'word'))
+enddef
+
+def s:MultiLine(
+ arg1: string,
+ arg2 = 1234,
+ ...rest: list<string>
+ ): string
+ return arg1 .. arg2 .. join(rest, '-')
+enddef
+
+def MultiLineComment(
+ arg1: string, # comment
+ arg2 = 1234, # comment
+ ...rest: list<string> # comment
+ ): string # comment
+ return arg1 .. arg2 .. join(rest, '-')
+enddef
+
+def Test_multiline()
+ MultiLine('text')->assert_equal('text1234')
+ MultiLine('text', 777)->assert_equal('text777')
+ MultiLine('text', 777, 'one')->assert_equal('text777one')
+ MultiLine('text', 777, 'one', 'two')->assert_equal('text777one-two')
+enddef
+
+func Test_multiline_not_vim9()
+ call s:MultiLine('text')->assert_equal('text1234')
+ call s:MultiLine('text', 777)->assert_equal('text777')
+ call s:MultiLine('text', 777, 'one')->assert_equal('text777one')
+ call s:MultiLine('text', 777, 'one', 'two')->assert_equal('text777one-two')
+endfunc
+
+
+" When using CheckScriptFailure() for the below test, E1010 is generated instead
+" of E1056.
+func Test_E1056_1059()
+ let caught_1056 = 0
+ try
+ def F():
+ return 1
+ enddef
+ catch /E1056:/
+ let caught_1056 = 1
+ endtry
+ eval caught_1056->assert_equal(1)
+
+ let caught_1059 = 0
+ try
+ def F5(items : list)
+ echo 'a'
+ enddef
+ catch /E1059:/
+ let caught_1059 = 1
+ endtry
+ eval caught_1059->assert_equal(1)
+endfunc
+
+func DelMe()
+ echo 'DelMe'
+endfunc
+
+def Test_error_reporting()
+ # comment lines at the start of the function
+ var lines =<< trim END
+ " comment
+ def Func()
+ # comment
+ # comment
+ invalid
+ enddef
+ defcompile
+ END
+ writefile(lines, 'Xdef', 'D')
+ try
+ source Xdef
+ assert_report('should have failed')
+ catch /E476:/
+ v:exception->assert_match('Invalid command: invalid')
+ v:throwpoint->assert_match(', line 3$')
+ endtry
+ delfunc! g:Func
+
+ # comment lines after the start of the function
+ lines =<< trim END
+ " comment
+ def Func()
+ var x = 1234
+ # comment
+ # comment
+ invalid
+ enddef
+ defcompile
+ END
+ writefile(lines, 'Xdef')
+ try
+ source Xdef
+ assert_report('should have failed')
+ catch /E476:/
+ v:exception->assert_match('Invalid command: invalid')
+ v:throwpoint->assert_match(', line 4$')
+ endtry
+ delfunc! g:Func
+
+ lines =<< trim END
+ vim9script
+ def Func()
+ var db = {foo: 1, bar: 2}
+ # comment
+ var x = db.asdf
+ enddef
+ defcompile
+ Func()
+ END
+ writefile(lines, 'Xdef')
+ try
+ source Xdef
+ assert_report('should have failed')
+ catch /E716:/
+ v:throwpoint->assert_match('_Func, line 3$')
+ endtry
+ delfunc! g:Func
+enddef
+
+def Test_deleted_function()
+ v9.CheckDefExecFailure([
+ 'var RefMe: func = function("g:DelMe")',
+ 'delfunc g:DelMe',
+ 'echo RefMe()'], 'E117:')
+enddef
+
+def Test_unknown_function()
+ v9.CheckDefExecFailure([
+ 'var Ref: func = function("NotExist")',
+ 'delfunc g:NotExist'], 'E700:')
+enddef
+
+def s:RefFunc(Ref: func(any): any): string
+ return Ref('more')
+enddef
+
+def Test_closure_simple()
+ var local = 'some '
+ RefFunc((s) => local .. s)->assert_equal('some more')
+enddef
+
+def s:MakeRef()
+ var local = 'some '
+ g:Ref = (s) => local .. s
+enddef
+
+def Test_closure_ref_after_return()
+ MakeRef()
+ g:Ref('thing')->assert_equal('some thing')
+ unlet g:Ref
+enddef
+
+def s:MakeTwoRefs()
+ var local = ['some']
+ g:Extend = (s) => local->add(s)
+ g:Read = () => local
+enddef
+
+def Test_closure_two_refs()
+ MakeTwoRefs()
+ join(g:Read(), ' ')->assert_equal('some')
+ g:Extend('more')
+ join(g:Read(), ' ')->assert_equal('some more')
+ g:Extend('even')
+ join(g:Read(), ' ')->assert_equal('some more even')
+
+ unlet g:Extend
+ unlet g:Read
+enddef
+
+def s:ReadRef(Ref: func(): list<string>): string
+ return join(Ref(), ' ')
+enddef
+
+def s:ExtendRef(Ref: func(string): list<string>, add: string)
+ Ref(add)
+enddef
+
+def Test_closure_two_indirect_refs()
+ MakeTwoRefs()
+ ReadRef(g:Read)->assert_equal('some')
+ ExtendRef(g:Extend, 'more')
+ ReadRef(g:Read)->assert_equal('some more')
+ ExtendRef(g:Extend, 'even')
+ ReadRef(g:Read)->assert_equal('some more even')
+
+ unlet g:Extend
+ unlet g:Read
+enddef
+
+def s:MakeArgRefs(theArg: string)
+ var local = 'loc_val'
+ g:UseArg = (s) => theArg .. '/' .. local .. '/' .. s
+enddef
+
+def s:MakeArgRefsVarargs(theArg: string, ...rest: list<string>)
+ var local = 'the_loc'
+ g:UseVararg = (s) => theArg .. '/' .. local .. '/' .. s .. '/' .. join(rest)
+enddef
+
+def Test_closure_using_argument()
+ MakeArgRefs('arg_val')
+ g:UseArg('call_val')->assert_equal('arg_val/loc_val/call_val')
+
+ MakeArgRefsVarargs('arg_val', 'one', 'two')
+ g:UseVararg('call_val')->assert_equal('arg_val/the_loc/call_val/one two')
+
+ unlet g:UseArg
+ unlet g:UseVararg
+
+ var lines =<< trim END
+ vim9script
+ def Test(Fun: func(number): number): list<number>
+ return map([1, 2, 3], (_, i) => Fun(i))
+ enddef
+ def Inc(nr: number): number
+ return nr + 2
+ enddef
+ assert_equal([3, 4, 5], Test(Inc))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:MakeGetAndAppendRefs()
+ var local = 'a'
+
+ def Append(arg: string)
+ local ..= arg
+ enddef
+ g:Append = Append
+
+ def Get(): string
+ return local
+ enddef
+ g:Get = Get
+enddef
+
+def Test_closure_append_get()
+ MakeGetAndAppendRefs()
+ g:Get()->assert_equal('a')
+ g:Append('-b')
+ g:Get()->assert_equal('a-b')
+ g:Append('-c')
+ g:Get()->assert_equal('a-b-c')
+
+ unlet g:Append
+ unlet g:Get
+enddef
+
+def Test_nested_closure()
+ var local = 'text'
+ def Closure(arg: string): string
+ return local .. arg
+ enddef
+ Closure('!!!')->assert_equal('text!!!')
+enddef
+
+func s:GetResult(Ref)
+ return a:Ref('some')
+endfunc
+
+def Test_call_closure_not_compiled()
+ var text = 'text'
+ g:Ref = (s) => s .. text
+ GetResult(g:Ref)->assert_equal('sometext')
+enddef
+
+def Test_double_closure_fails()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var name = 0
+ for i in range(2)
+ timer_start(0, () => name)
+ endfor
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_nested_closure_used()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var x = 'hello'
+ var Closure = () => x
+ g:Myclosure = () => Closure()
+ enddef
+ Func()
+ assert_equal('hello', g:Myclosure())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_nested_closure_fails()
+ var lines =<< trim END
+ vim9script
+ def FuncA()
+ FuncB(0)
+ enddef
+ def FuncB(n: number): list<string>
+ return map([0], (_, v) => n)
+ enddef
+ FuncA()
+ END
+ v9.CheckScriptFailure(lines, 'E1012:')
+enddef
+
+def Run_Test_closure_in_for_loop_fails()
+ var lines =<< trim END
+ vim9script
+ redraw
+ for n in [0]
+ # time should be enough for startup to finish
+ timer_start(200, (_) => {
+ echo n
+ })
+ endfor
+ END
+ writefile(lines, 'XTest_closure_fails', 'D')
+
+ # Check that an error shows
+ var buf = g:RunVimInTerminal('-S XTest_closure_fails', {rows: 6, wait_for_ruler: 0})
+ g:VerifyScreenDump(buf, 'Test_vim9_closure_fails', {wait: 3000})
+
+ # clean up
+ g:StopVimInTerminal(buf)
+enddef
+
+func Test_closure_in_for_loop_fails()
+ CheckScreendump
+ call Run_Test_closure_in_for_loop_fails()
+endfunc
+
+def Test_global_closure()
+ var lines =<< trim END
+ vim9script
+ def ReverseEveryNLines(n: number, line1: number, line2: number)
+ var mods = 'sil keepj keepp lockm '
+ var range = ':' .. line1 .. ',' .. line2
+ def g:Offset(): number
+ var offset = (line('.') - line1 + 1) % n
+ return offset != 0 ? offset : n
+ enddef
+ exe mods .. range .. 'g/^/exe "m .-" .. g:Offset()'
+ enddef
+
+ new
+ repeat(['aaa', 'bbb', 'ccc'], 3)->setline(1)
+ ReverseEveryNLines(3, 1, 9)
+ END
+ v9.CheckScriptSuccess(lines)
+ var expected = repeat(['ccc', 'bbb', 'aaa'], 3)
+ assert_equal(expected, getline(1, 9))
+ bwipe!
+enddef
+
+def Test_global_closure_called_directly()
+ var lines =<< trim END
+ vim9script
+ def Outer()
+ var x = 1
+ def g:Inner()
+ var y = x
+ x += 1
+ assert_equal(1, y)
+ enddef
+ g:Inner()
+ assert_equal(2, x)
+ enddef
+ Outer()
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:Inner
+enddef
+
+def Test_closure_called_from_legacy()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var outer = 'foo'
+ var F = () => {
+ outer = 'bar'
+ }
+ execute printf('call %s()', string(F))
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1248')
+enddef
+
+def Test_failure_in_called_function()
+ # this was using the frame index as the return value
+ var lines =<< trim END
+ vim9script
+ au TerminalWinOpen * eval [][0]
+ def PopupTerm(a: any)
+ # make sure typvals on stack are string
+ ['a', 'b', 'c', 'd', 'e', 'f', 'g']->join()
+ FireEvent()
+ enddef
+ def FireEvent()
+ do TerminalWinOpen
+ enddef
+ # use try/catch to make eval fail
+ try
+ call PopupTerm(0)
+ catch
+ endtry
+ au! TerminalWinOpen
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_nested_lambda()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var x = 4
+ var Lambda1 = () => 7
+ var Lambda2 = () => [Lambda1(), x]
+ var res = Lambda2()
+ assert_equal([7, 4], res)
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_double_nested_lambda()
+ var lines =<< trim END
+ vim9script
+ def F(head: string): func(string): func(string): string
+ return (sep: string): func(string): string => ((tail: string): string => {
+ return head .. sep .. tail
+ })
+ enddef
+ assert_equal('hello-there', F('hello')('-')('there'))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_nested_inline_lambda()
+ var lines =<< trim END
+ vim9script
+ def F(text: string): func(string): func(string): string
+ return (arg: string): func(string): string => ((sep: string): string => {
+ return sep .. arg .. text
+ })
+ enddef
+ assert_equal('--there++', F('++')('there')('--'))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ echo range(4)->mapnew((_, v) => {
+ return range(v) ->mapnew((_, s) => {
+ return string(s)
+ })
+ })
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ def Func()
+ range(10)
+ ->mapnew((_, _) => ({
+ key: range(10)->mapnew((_, _) => {
+ return ' '
+ }),
+ }))
+ enddef
+
+ defcomp
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Shadowed(): list<number>
+ var FuncList: list<func: number> = [() => 42]
+ return FuncList->mapnew((_, Shadowed) => Shadowed())
+enddef
+
+def Test_lambda_arg_shadows_func()
+ assert_equal([42], g:Shadowed())
+enddef
+
+def Test_compiling_referenced_func_no_shadow()
+ var lines =<< trim END
+ vim9script
+
+ def InitializeReply(lspserver: dict<any>)
+ enddef
+
+ def ProcessReply(lspserver: dict<any>)
+ var lsp_reply_handlers: dict<func> =
+ { 'initialize': InitializeReply }
+ lsp_reply_handlers['initialize'](lspserver)
+ enddef
+
+ call ProcessReply({})
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:Line_continuation_in_def(dir: string = ''): string
+ var path: string = empty(dir)
+ \ ? 'empty'
+ \ : 'full'
+ return path
+enddef
+
+def Test_line_continuation_in_def()
+ Line_continuation_in_def('.')->assert_equal('full')
+enddef
+
+def Test_script_var_in_lambda()
+ var lines =<< trim END
+ vim9script
+ var script = 'test'
+ assert_equal(['test'], map(['one'], (_, _) => script))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:Line_continuation_in_lambda(): list<string>
+ var x = range(97, 100)
+ ->mapnew((_, v) => nr2char(v)
+ ->toupper())
+ ->reverse()
+ return x
+enddef
+
+def Test_line_continuation_in_lambda()
+ Line_continuation_in_lambda()->assert_equal(['D', 'C', 'B', 'A'])
+
+ var lines =<< trim END
+ vim9script
+ var res = [{n: 1, m: 2, s: 'xxx'}]
+ ->mapnew((_, v: dict<any>): string => printf('%d:%d:%s',
+ v.n,
+ v.m,
+ substitute(v.s, '.*', 'yyy', '')
+ ))
+ assert_equal(['1:2:yyy'], res)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_list_lambda()
+ timer_start(1000, (_) => 0)
+ var body = execute(timer_info()[0].callback
+ ->string()
+ ->substitute("('", ' ', '')
+ ->substitute("')", '', '')
+ ->substitute('function\zs', ' ', ''))
+ assert_match('def <lambda>\d\+(_: any): number\n1 return 0\n enddef', body)
+enddef
+
+def Test_lambda_block_variable()
+ var lines =<< trim END
+ vim9script
+ var flist: list<func>
+ for i in range(10)
+ var inloop = i
+ flist[i] = () => inloop
+ endfor
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ if true
+ var outloop = 5
+ var flist: list<func>
+ for i in range(10)
+ flist[i] = () => outloop
+ endfor
+ endif
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ if true
+ var outloop = 5
+ endif
+ var flist: list<func>
+ for i in range(10)
+ flist[i] = () => outloop
+ endfor
+ END
+ v9.CheckScriptFailure(lines, 'E1001: Variable not found: outloop', 1)
+
+ lines =<< trim END
+ vim9script
+ for i in range(10)
+ var Ref = () => 0
+ endfor
+ assert_equal(0, ((i) => 0)(0))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_legacy_lambda()
+ legacy echo {x -> 'hello ' .. x}('foo')
+
+ var lines =<< trim END
+ echo {x -> 'hello ' .. x}('foo')
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E720:')
+
+ lines =<< trim END
+ vim9script
+ def Func()
+ echo (() => 'no error')()
+ enddef
+ legacy call s:Func()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_legacy()
+ var lines =<< trim END
+ vim9script
+ func g:LegacyFunction()
+ let g:legacyvar = 1
+ endfunc
+ def Testit()
+ legacy call g:LegacyFunction()
+ enddef
+ Testit()
+ assert_equal(1, g:legacyvar)
+ unlet g:legacyvar
+ delfunc g:LegacyFunction
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_legacy_errors()
+ for cmd in ['if', 'elseif', 'else', 'endif',
+ 'for', 'endfor', 'continue', 'break',
+ 'while', 'endwhile',
+ 'try', 'catch', 'finally', 'endtry']
+ v9.CheckDefFailure(['legacy ' .. cmd .. ' expr'], 'E1189:')
+ endfor
+enddef
+
+def Test_call_legacy_with_dict()
+ var lines =<< trim END
+ vim9script
+ func Legacy() dict
+ let g:result = self.value
+ endfunc
+ def TestDirect()
+ var d = {value: 'yes', func: Legacy}
+ d.func()
+ enddef
+ TestDirect()
+ assert_equal('yes', g:result)
+ unlet g:result
+
+ def TestIndirect()
+ var d = {value: 'foo', func: Legacy}
+ var Fi = d.func
+ Fi()
+ enddef
+ TestIndirect()
+ assert_equal('foo', g:result)
+ unlet g:result
+
+ var d = {value: 'bar', func: Legacy}
+ d.func()
+ assert_equal('bar', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:DoFilterThis(a: string): list<string>
+ # closure nested inside another closure using argument
+ var Filter = (l) => filter(l, (_, v) => stridx(v, a) == 0)
+ return ['x', 'y', 'a', 'x2', 'c']->Filter()
+enddef
+
+def Test_nested_closure_using_argument()
+ assert_equal(['x', 'x2'], DoFilterThis('x'))
+enddef
+
+def Test_triple_nested_closure()
+ var what = 'x'
+ var Match = (val: string, cmp: string): bool => stridx(val, cmp) == 0
+ var Filter = (l) => filter(l, (_, v) => Match(v, what))
+ assert_equal(['x', 'x2'], ['x', 'y', 'a', 'x2', 'c']->Filter())
+enddef
+
+func Test_silent_echo()
+ CheckScreendump
+ call Run_Test_silent_echo()
+endfunc
+
+def Run_Test_silent_echo()
+ var lines =<< trim END
+ vim9script
+ def EchoNothing()
+ silent echo ''
+ enddef
+ defcompile
+ END
+ writefile(lines, 'XTest_silent_echo', 'D')
+
+ # Check that the balloon shows up after a mouse move
+ var buf = g:RunVimInTerminal('-S XTest_silent_echo', {'rows': 6})
+ term_sendkeys(buf, ":abc")
+ g:VerifyScreenDump(buf, 'Test_vim9_silent_echo', {})
+
+ # clean up
+ g:StopVimInTerminal(buf)
+enddef
+
+def SilentlyError()
+ execute('silent! invalid')
+ g:did_it = 'yes'
+enddef
+
+func s:UserError()
+ silent! invalid
+endfunc
+
+def SilentlyUserError()
+ UserError()
+ g:did_it = 'yes'
+enddef
+
+" This can't be a :def function, because the assert would not be reached.
+func Test_ignore_silent_error()
+ let g:did_it = 'no'
+ call SilentlyError()
+ call assert_equal('yes', g:did_it)
+
+ let g:did_it = 'no'
+ call SilentlyUserError()
+ call assert_equal('yes', g:did_it)
+
+ unlet g:did_it
+endfunc
+
+def Test_ignore_silent_error_in_filter()
+ var lines =<< trim END
+ vim9script
+ def Filter(winid: number, key: string): bool
+ if key == 'o'
+ silent! eval [][0]
+ return true
+ endif
+ return popup_filter_menu(winid, key)
+ enddef
+
+ popup_create('popup', {filter: Filter})
+ feedkeys("o\r", 'xnt')
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def s:Fibonacci(n: number): number
+ if n < 2
+ return n
+ else
+ return Fibonacci(n - 1) + Fibonacci(n - 2)
+ endif
+enddef
+
+def Test_recursive_call()
+ Fibonacci(20)->assert_equal(6765)
+enddef
+
+def s:TreeWalk(dir: string): list<any>
+ return readdir(dir)->mapnew((_, val) =>
+ fnamemodify(dir .. '/' .. val, ':p')->isdirectory()
+ ? {[val]: TreeWalk(dir .. '/' .. val)}
+ : val
+ )
+enddef
+
+def Test_closure_in_map()
+ mkdir('XclosureDir/tdir', 'pR')
+ writefile(['111'], 'XclosureDir/file1')
+ writefile(['222'], 'XclosureDir/file2')
+ writefile(['333'], 'XclosureDir/tdir/file3')
+
+ TreeWalk('XclosureDir')->assert_equal(['file1', 'file2', {tdir: ['file3']}])
+enddef
+
+def Test_invalid_function_name()
+ var lines =<< trim END
+ vim9script
+ def s: list<string>
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+
+ lines =<< trim END
+ vim9script
+ def g: list<string>
+ END
+ v9.CheckScriptFailure(lines, 'E129:')
+
+ lines =<< trim END
+ vim9script
+ def <SID>: list<string>
+ END
+ v9.CheckScriptFailure(lines, 'E884:')
+
+ lines =<< trim END
+ vim9script
+ def F list<string>
+ END
+ v9.CheckScriptFailure(lines, 'E488:')
+enddef
+
+def Test_partial_call()
+ var lines =<< trim END
+ var Xsetlist: func
+ Xsetlist = function('setloclist', [0])
+ Xsetlist([], ' ', {title: 'test'})
+ getloclist(0, {title: 1})->assert_equal({title: 'test'})
+
+ Xsetlist = function('setloclist', [0, [], ' '])
+ Xsetlist({title: 'test'})
+ getloclist(0, {title: 1})->assert_equal({title: 'test'})
+
+ Xsetlist = function('setqflist')
+ Xsetlist([], ' ', {title: 'test'})
+ getqflist({title: 1})->assert_equal({title: 'test'})
+
+ Xsetlist = function('setqflist', [[], ' '])
+ Xsetlist({title: 'test'})
+ getqflist({title: 1})->assert_equal({title: 'test'})
+
+ var Len: func: number = function('len', ['word'])
+ assert_equal(4, Len())
+
+ var RepeatFunc = function('repeat', ['o'])
+ assert_equal('ooooo', RepeatFunc(5))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Foo(Parser: any)
+ enddef
+ var Expr: func(dict<any>): dict<any>
+ const Call = Foo(Expr)
+ END
+ v9.CheckScriptFailure(lines, 'E1031:')
+enddef
+
+def Test_partial_double_nested()
+ var idx = 123
+ var Get = () => idx
+ var Ref = function(Get, [])
+ var RefRef = function(Ref, [])
+ assert_equal(123, RefRef())
+enddef
+
+def Test_partial_null_function()
+ var lines =<< trim END
+ var d: dict<func> = {f: null_function}
+ var Ref = d.f
+ assert_equal('func(...): unknown', typename(Ref))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_cmd_modifier()
+ tab echo '0'
+ v9.CheckDefFailure(['5tab echo 3'], 'E16:')
+enddef
+
+def Test_restore_modifiers()
+ # check that when compiling a :def function command modifiers are not messed
+ # up.
+ var lines =<< trim END
+ vim9script
+ set eventignore=
+ autocmd QuickFixCmdPost * copen
+ def AutocmdsDisabled()
+ eval 1 + 2
+ enddef
+ func Func()
+ noautocmd call s:AutocmdsDisabled()
+ let g:ei_after = &eventignore
+ endfunc
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+ g:ei_after->assert_equal('')
+enddef
+
+def StackTop()
+ eval 1 + 2
+ eval 2 + 3
+ # call not on fourth line
+ g:StackBot()
+enddef
+
+def StackBot()
+ # throw an error
+ eval [][0]
+enddef
+
+def Test_callstack_def()
+ try
+ g:StackTop()
+ catch
+ v:throwpoint->assert_match('Test_callstack_def\[2\]..StackTop\[4\]..StackBot, line 2')
+ endtry
+enddef
+
+" Re-using spot for variable used in block
+def Test_block_scoped_var()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var x = ['a', 'b', 'c']
+ if 1
+ var y = 'x'
+ map(x, (_, _) => y)
+ endif
+ var z = x
+ assert_equal(['x', 'x', 'x'], z)
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_reset_did_emsg()
+ var lines =<< trim END
+ @s = 'blah'
+ au BufWinLeave * #
+ def Func()
+ var winid = popup_create('popup', {})
+ exe '*s'
+ popup_close(winid)
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E492:', 8)
+ delfunc! g:Func
+enddef
+
+def Test_did_emsg_reset()
+ # executing an autocommand resets did_emsg, this should not result in a
+ # builtin function considered failing
+ var lines =<< trim END
+ vim9script
+ au BufWinLeave * #
+ def Func()
+ popup_menu('', {callback: (a, b) => popup_create('', {})->popup_close()})
+ eval [][0]
+ enddef
+ nno <F3> <cmd>call <sid>Func()<cr>
+ feedkeys("\<F3>\e", 'xt')
+ END
+ writefile(lines, 'XemsgReset', 'D')
+ assert_fails('so XemsgReset', ['E684:', 'E684:'], lines, 2)
+
+ nunmap <F3>
+ au! BufWinLeave
+enddef
+
+def Test_abort_with_silent_call()
+ var lines =<< trim END
+ vim9script
+ g:result = 'none'
+ def Func()
+ g:result += 3
+ g:result = 'yes'
+ enddef
+ # error is silenced, but function aborts on error
+ silent! Func()
+ assert_equal('none', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_continues_with_silent_error()
+ var lines =<< trim END
+ vim9script
+ g:result = 'none'
+ def Func()
+ silent! g:result += 3
+ g:result = 'yes'
+ enddef
+ # error is silenced, function does not abort
+ Func()
+ assert_equal('yes', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_abort_even_with_silent()
+ var lines =<< trim END
+ vim9script
+ g:result = 'none'
+ def Func()
+ eval {-> ''}() .. '' .. {}['X']
+ g:result = 'yes'
+ enddef
+ silent! Func()
+ assert_equal('none', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_cmdmod_silent_restored()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ g:result = 'none'
+ silent! g:result += 3
+ g:result = 'none'
+ g:result += 3
+ enddef
+ Func()
+ END
+ # can't use CheckScriptFailure, it ignores the :silent!
+ var fname = 'Xdefsilent'
+ writefile(lines, fname, 'D')
+ var caught = 'no'
+ try
+ exe 'source ' .. fname
+ catch /E1030:/
+ caught = 'yes'
+ assert_match('Func, line 4', v:throwpoint)
+ endtry
+ assert_equal('yes', caught)
+enddef
+
+def Test_cmdmod_silent_nested()
+ var lines =<< trim END
+ vim9script
+ var result = ''
+
+ def Error()
+ result ..= 'Eb'
+ eval [][0]
+ result ..= 'Ea'
+ enddef
+
+ def Crash()
+ result ..= 'Cb'
+ sil! Error()
+ result ..= 'Ca'
+ enddef
+
+ Crash()
+ assert_equal('CbEbEaCa', result)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_dict_member_with_silent()
+ var lines =<< trim END
+ vim9script
+ g:result = 'none'
+ var d: dict<any>
+ def Func()
+ try
+ g:result = map([], (_, v) => ({}[v]))->join() .. d['']
+ catch
+ endtry
+ enddef
+ silent! Func()
+ assert_equal('0', g:result)
+ unlet g:result
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_skip_cmds_with_silent()
+ var lines =<< trim END
+ vim9script
+
+ def Func(b: bool)
+ Crash()
+ enddef
+
+ def Crash()
+ sil! :/not found/d _
+ sil! :/not found/put _
+ enddef
+
+ Func(true)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_opfunc()
+ nnoremap <F3> <cmd>set opfunc=g:Opfunc<cr>g@
+ def g:Opfunc(_: any): string
+ setline(1, 'ASDF')
+ return ''
+ enddef
+ new
+ setline(1, 'asdf')
+ feedkeys("\<F3>$", 'x')
+ assert_equal('ASDF', getline(1))
+
+ bwipe!
+ nunmap <F3>
+enddef
+
+func Test_opfunc_error()
+ CheckScreendump
+ call Run_Test_opfunc_error()
+endfunc
+
+def Run_Test_opfunc_error()
+ # test that the error from Opfunc() is displayed right away
+ var lines =<< trim END
+ vim9script
+
+ def Opfunc(type: string)
+ try
+ eval [][0]
+ catch /nothing/ # error not caught
+ endtry
+ enddef
+ &operatorfunc = Opfunc
+ nnoremap <expr> l <SID>L()
+ def L(): string
+ return 'l'
+ enddef
+ 'x'->repeat(10)->setline(1)
+ feedkeys('g@l', 'n')
+ feedkeys('llll')
+ END
+ call writefile(lines, 'XTest_opfunc_error', 'D')
+
+ var buf = g:RunVimInTerminal('-S XTest_opfunc_error', {rows: 6, wait_for_ruler: 0})
+ g:WaitForAssert(() => assert_match('Press ENTER', term_getline(buf, 6)))
+ g:WaitForAssert(() => assert_match('E684: List index out of range: 0', term_getline(buf, 5)))
+
+ # clean up
+ g:StopVimInTerminal(buf)
+enddef
+
+" this was crashing on exit
+def Test_nested_lambda_in_closure()
+ var lines =<< trim END
+ vim9script
+ command WriteDone writefile(['Done'], 'XnestedDone')
+ def Outer()
+ def g:Inner()
+ echo map([1, 2, 3], {_, v -> v + 1})
+ enddef
+ g:Inner()
+ enddef
+ defcompile
+ # not reached
+ END
+ if !g:RunVim([], lines, '--clean -c WriteDone -c quit')
+ return
+ endif
+ assert_equal(['Done'], readfile('XnestedDone'))
+ delete('XnestedDone')
+enddef
+
+def Test_nested_closure_funcref()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var n: number
+ def Nested()
+ ++n
+ enddef
+ Nested()
+ g:result_one = n
+ var Ref = function(Nested)
+ Ref()
+ g:result_two = n
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal(1, g:result_one)
+ assert_equal(2, g:result_two)
+ unlet g:result_one g:result_two
+enddef
+
+def Test_nested_closure_in_dict()
+ var lines =<< trim END
+ vim9script
+ def Func(): dict<any>
+ var n: number
+ def Inc(): number
+ ++n
+ return n
+ enddef
+ return {inc: function(Inc)}
+ enddef
+ disas Func
+ var d = Func()
+ assert_equal(1, d.inc())
+ assert_equal(2, d.inc())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_script_local_other_script()
+ var lines =<< trim END
+ function LegacyJob()
+ let FuncRef = function('s:close_cb')
+ endfunction
+ function s:close_cb(...)
+ endfunction
+ END
+ lines->writefile('Xlegacy.vim', 'D')
+ source Xlegacy.vim
+ g:LegacyJob()
+ g:LegacyJob()
+ g:LegacyJob()
+
+ delfunc g:LegacyJob
+enddef
+
+def Test_check_func_arg_types()
+ var lines =<< trim END
+ vim9script
+ def F1(x: string): string
+ return x
+ enddef
+
+ def F2(x: number): number
+ return x + 1
+ enddef
+
+ def G(Fg: func): dict<func>
+ return {f: Fg}
+ enddef
+
+ def H(d: dict<func>): string
+ return d.f('a')
+ enddef
+ END
+
+ v9.CheckScriptSuccess(lines + ['echo H(G(F1))'])
+ v9.CheckScriptFailure(lines + ['echo H(G(F2))'], 'E1013:')
+
+ v9.CheckScriptFailure(lines + ['def SomeFunc(ff: func)', 'enddef'], 'E704:')
+enddef
+
+def Test_call_func_with_null()
+ var lines =<< trim END
+ def Fstring(v: string)
+ assert_equal(null_string, v)
+ enddef
+ Fstring(null_string)
+ def Fblob(v: blob)
+ assert_equal(null_blob, v)
+ enddef
+ Fblob(null_blob)
+ def Flist(v: list<number>)
+ assert_equal(null_list, v)
+ enddef
+ Flist(null_list)
+ def Fdict(v: dict<number>)
+ assert_equal(null_dict, v)
+ enddef
+ Fdict(null_dict)
+ def Ffunc(Fv: func(number): number)
+ assert_equal(null_function, Fv)
+ enddef
+ Ffunc(null_function)
+ if has('channel')
+ def Fchannel(v: channel)
+ assert_equal(null_channel, v)
+ enddef
+ Fchannel(null_channel)
+ def Fjob(v: job)
+ assert_equal(null_job, v)
+ enddef
+ Fjob(null_job)
+ endif
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_null_default_argument()
+ var lines =<< trim END
+ def Fstring(v: string = null_string)
+ assert_equal(null_string, v)
+ enddef
+ Fstring()
+ def Fblob(v: blob = null_blob)
+ assert_equal(null_blob, v)
+ enddef
+ Fblob()
+ def Flist(v: list<number> = null_list)
+ assert_equal(null_list, v)
+ enddef
+ Flist()
+ def Fdict(v: dict<number> = null_dict)
+ assert_equal(null_dict, v)
+ enddef
+ Fdict()
+ def Ffunc(Fv: func(number): number = null_function)
+ assert_equal(null_function, Fv)
+ enddef
+ Ffunc()
+ if has('channel')
+ def Fchannel(v: channel = null_channel)
+ assert_equal(null_channel, v)
+ enddef
+ Fchannel()
+ def Fjob(v: job = null_job)
+ assert_equal(null_job, v)
+ enddef
+ Fjob()
+ endif
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_null_return()
+ var lines =<< trim END
+ def Fstring(): string
+ return null_string
+ enddef
+ assert_equal(null_string, Fstring())
+ def Fblob(): blob
+ return null_blob
+ enddef
+ assert_equal(null_blob, Fblob())
+ def Flist(): list<number>
+ return null_list
+ enddef
+ assert_equal(null_list, Flist())
+ def Fdict(): dict<number>
+ return null_dict
+ enddef
+ assert_equal(null_dict, Fdict())
+ def Ffunc(): func(number): number
+ return null_function
+ enddef
+ assert_equal(null_function, Ffunc())
+ if has('channel')
+ def Fchannel(): channel
+ return null_channel
+ enddef
+ assert_equal(null_channel, Fchannel())
+ def Fjob(): job
+ return null_job
+ enddef
+ assert_equal(null_job, Fjob())
+ endif
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_list_any_type_checked()
+ var lines =<< trim END
+ vim9script
+ def Foo()
+ --decl--
+ Bar(l)
+ enddef
+ def Bar(ll: list<dict<any>>)
+ enddef
+ Foo()
+ END
+ # "any" could be "dict<any>", thus OK
+ lines[2] = 'var l: list<any>'
+ v9.CheckScriptSuccess(lines)
+ lines[2] = 'var l: list<any> = []'
+ v9.CheckScriptSuccess(lines)
+
+ lines[2] = 'var l: list<any> = [11]'
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected list<dict<any>> but got list<number>', 2)
+enddef
+
+def Test_compile_error()
+ var lines =<< trim END
+ def g:Broken()
+ echo 'a' + {}
+ enddef
+ call g:Broken()
+ END
+ # First call: compilation error
+ v9.CheckScriptFailure(lines, 'E1051: Wrong argument type for +')
+
+ # Second call won't try compiling again
+ assert_fails('call g:Broken()', 'E1091: Function is not compiled: Broken')
+ delfunc g:Broken
+
+ # No error when compiling with :silent!
+ lines =<< trim END
+ def g:Broken()
+ echo 'a' + []
+ enddef
+ silent! defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # Calling the function won't try compiling again
+ assert_fails('call g:Broken()', 'E1091: Function is not compiled: Broken')
+ delfunc g:Broken
+enddef
+
+def Test_ignored_argument()
+ var lines =<< trim END
+ vim9script
+ def Ignore(_, _): string
+ return 'yes'
+ enddef
+ assert_equal('yes', Ignore(1, 2))
+
+ func Ok(_)
+ return a:_
+ endfunc
+ assert_equal('ok', Ok('ok'))
+
+ func Oktoo()
+ let _ = 'too'
+ return _
+ endfunc
+ assert_equal('too', Oktoo())
+
+ assert_equal([[1], [2], [3]], range(3)->mapnew((_, v) => [v]->map((_, w) => w + 1)))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ def Ignore(_: string): string
+ return _
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1181:', 1)
+
+ lines =<< trim END
+ var _ = 1
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1181:', 1)
+
+ lines =<< trim END
+ var x = _
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1181:', 1)
+enddef
+
+def Test_too_many_arguments()
+ var lines =<< trim END
+ echo [0, 1, 2]->map(() => 123)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E176:', 'E1106: 2 arguments too many'], 1)
+
+ lines =<< trim END
+ echo [0, 1, 2]->map((_) => 123)
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E176', 'E1106: One argument too many'], 1)
+
+ lines =<< trim END
+ vim9script
+ def OneArgument(arg: string)
+ echo arg
+ enddef
+ var Ref = OneArgument
+ Ref('a', 'b')
+ END
+ v9.CheckScriptFailure(lines, 'E118:')
+enddef
+
+def Test_funcref_with_base()
+ var lines =<< trim END
+ vim9script
+ def TwoArguments(str: string, nr: number)
+ echo str nr
+ enddef
+ var Ref = TwoArguments
+ Ref('a', 12)
+ 'b'->Ref(34)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def TwoArguments(str: string, nr: number)
+ echo str nr
+ enddef
+ var Ref = TwoArguments
+ 'a'->Ref('b')
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected number but got string', 6)
+
+ lines =<< trim END
+ vim9script
+ def TwoArguments(str: string, nr: number)
+ echo str nr
+ enddef
+ var Ref = TwoArguments
+ 123->Ref(456)
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+
+ lines =<< trim END
+ vim9script
+ def TwoArguments(nr: number, str: string)
+ echo str nr
+ enddef
+ var Ref = TwoArguments
+ 123->Ref('b')
+ def AndNowCompiled()
+ 456->Ref('x')
+ enddef
+ AndNowCompiled()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_closing_brace_at_start_of_line()
+ var lines =<< trim END
+ def Func()
+ enddef
+ Func(
+ )
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+func s:CreateMydict()
+ let g:mydict = {}
+ func g:mydict.afunc()
+ let g:result = self.key
+ endfunc
+endfunc
+
+def Test_numbered_function_reference()
+ CreateMydict()
+ var output = execute('legacy func g:mydict.afunc')
+ var funcName = 'g:' .. substitute(output, '.*function \(\d\+\).*', '\1', '')
+ execute 'function(' .. funcName .. ', [], {key: 42})()'
+ # check that the function still exists
+ assert_equal(output, execute('legacy func g:mydict.afunc'))
+ unlet g:mydict
+enddef
+
+def Test_numbered_function_call()
+ var lines =<< trim END
+ let s:legacyscript = {}
+ func s:legacyscript.Helper() abort
+ return "Success"
+ endfunc
+ let g:legacyscript = deepcopy(s:legacyscript)
+
+ let g:legacy_result = eval("g:legacyscript.Helper()")
+ vim9cmd g:vim9_result = eval("g:legacyscript.Helper()")
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal('Success', g:legacy_result)
+ assert_equal('Success', g:vim9_result)
+
+ unlet g:legacy_result
+ unlet g:vim9_result
+enddef
+
+def Test_go_beyond_end_of_cmd()
+ # this was reading the byte after the end of the line
+ var lines =<< trim END
+ def F()
+ cal
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E476:')
+enddef
+
+" Test for memory allocation failure when defining a new lambda
+func Test_lambda_allocation_failure()
+ new
+ let lines =<< trim END
+ vim9script
+ g:Xlambda = (x): number => {
+ return x + 1
+ }
+ END
+ call setline(1, lines)
+ call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ call assert_fails('source', 'E342:')
+ call assert_false(exists('g:Xlambda'))
+ bw!
+endfunc
+
+def Test_lambda_argument_type_check()
+ var lines =<< trim END
+ vim9script
+
+ def Scan(ll: list<any>): func(func(any))
+ return (Emit: func(any)) => {
+ for e in ll
+ Emit(e)
+ endfor
+ }
+ enddef
+
+ def Sum(Cont: func(func(any))): any
+ var sum = 0.0
+ Cont((v: float) => { # <== NOTE: the lambda expects a float
+ sum += v
+ })
+ return sum
+ enddef
+
+ const ml = [3.0, 2, '7']
+ echo Scan(ml)->Sum()
+ END
+ v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected float but got string')
+enddef
+
+def Test_multiple_funcref()
+ # This was using a NULL pointer
+ var lines =<< trim END
+ vim9script
+ def A(F: func, ...args: list<any>): func
+ return funcref(F, args)
+ enddef
+
+ def B(F: func): func
+ return funcref(A, [F])
+ enddef
+
+ def Test(n: number)
+ enddef
+
+ const X = B(Test)
+ X(1)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # slightly different case
+ lines =<< trim END
+ vim9script
+
+ def A(F: func, ...args: list<any>): any
+ return call(F, args)
+ enddef
+
+ def B(F: func): func
+ return funcref(A, [F])
+ enddef
+
+ def Test(n: number)
+ enddef
+
+ const X = B(Test)
+ X(1)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_cexpr_errmsg_line_number()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var qfl = {}
+ cexpr qfl
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E777', 2)
+enddef
+
+def AddDefer(s: string)
+ g:deferred->extend([s])
+enddef
+
+def DeferTwo()
+ g:deferred->extend(['in Two'])
+ for n in range(3)
+ defer g:AddDefer('two' .. n)
+ endfor
+ g:deferred->extend(['end Two'])
+enddef
+
+def DeferOne()
+ g:deferred->extend(['in One'])
+ defer g:AddDefer('one')
+ g:DeferTwo()
+ g:deferred->extend(['end One'])
+
+ writefile(['text'], 'XdeferFile')
+ defer delete('XdeferFile')
+enddef
+
+def Test_defer()
+ g:deferred = []
+ g:DeferOne()
+ assert_equal(['in One', 'in Two', 'end Two', 'two2', 'two1', 'two0', 'end One', 'one'], g:deferred)
+ unlet g:deferred
+ assert_equal('', glob('XdeferFile'))
+enddef
+
+def Test_invalid_redir()
+ var lines =<< trim END
+ def Tone()
+ if 1
+ redi =>@
+ redi END
+ endif
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E354:')
+ delfunc g:Tone
+
+ # this was reading past the end of the line
+ lines =<< trim END
+ def Ttwo()
+ if 0
+ redi =>@
+ redi END
+ endif
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E354:')
+ delfunc g:Ttwo
+enddef
+
+func Test_keytyped_in_nested_function()
+ CheckRunVimInTerminal
+
+ call Run_Test_keytyped_in_nested_function()
+endfunc
+
+def Run_Test_keytyped_in_nested_function()
+ var lines =<< trim END
+ vim9script
+ autocmd CmdlineEnter * sample#Init()
+
+ exe 'set rtp=' .. getcwd() .. '/Xrtpdir'
+ END
+ writefile(lines, 'Xkeytyped', 'D')
+
+ var dir = 'Xrtpdir/autoload'
+ mkdir(dir, 'pR')
+
+ lines =<< trim END
+ vim9script
+ export def Init(): void
+ cnoremap <expr>" <SID>Quote('"')
+ enddef
+ def Quote(str: string): string
+ def InPair(): number
+ return 0
+ enddef
+ return str
+ enddef
+ END
+ writefile(lines, dir .. '/sample.vim')
+
+ var buf = g:RunVimInTerminal('-S Xkeytyped', {rows: 6})
+
+ term_sendkeys(buf, ':"')
+ g:VerifyScreenDump(buf, 'Test_keytyped_in_nested_func', {})
+
+ # clean up
+ term_sendkeys(buf, "\<Esc>")
+ g:StopVimInTerminal(buf)
+enddef
+
+" The following messes up syntax highlight, keep near the end.
+if has('python3')
+ def Test_python3_command()
+ py3 import vim
+ py3 vim.command("g:done = 'yes'")
+ assert_equal('yes', g:done)
+ unlet g:done
+ enddef
+
+ def Test_python3_heredoc()
+ py3 << trim EOF
+ import vim
+ vim.vars['didit'] = 'yes'
+ EOF
+ assert_equal('yes', g:didit)
+
+ python3 << trim EOF
+ import vim
+ vim.vars['didit'] = 'again'
+ EOF
+ assert_equal('again', g:didit)
+ enddef
+endif
+
+if has('lua')
+ def Test_lua_heredoc()
+ g:d = {}
+ lua << trim EOF
+ x = vim.eval('g:d')
+ x['key'] = 'val'
+ EOF
+ assert_equal('val', g:d.key)
+ enddef
+
+ def Test_lua_heredoc_fails()
+ var lines = [
+ 'vim9script',
+ 'def ExeLua()',
+ 'lua << trim EOLUA',
+ "x = vim.eval('g:nodict')",
+ 'EOLUA',
+ 'enddef',
+ 'ExeLua()',
+ ]
+ v9.CheckScriptFailure(lines, 'E121: Undefined variable: g:nodict')
+ enddef
+endif
+
+if has('perl')
+ def Test_perl_heredoc_nested()
+ var lines =<< trim END
+ vim9script
+ def F(): string
+ def G(): string
+ perl << EOF
+ EOF
+ return 'done'
+ enddef
+ return G()
+ enddef
+ assert_equal('done', F())
+ END
+ v9.CheckScriptSuccess(lines)
+ enddef
+endif
+
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim
new file mode 100644
index 0000000..7d3691a
--- /dev/null
+++ b/src/testdir/test_vim9_import.vim
@@ -0,0 +1,2923 @@
+" Test import/export of the Vim9 script language.
+" Also the autoload mechanism.
+
+source check.vim
+source term_util.vim
+import './vim9.vim' as v9
+
+let s:export_script_lines =<< trim END
+ vim9script
+ var name: string = 'bob'
+ def Concat(arg: string): string
+ return name .. arg
+ enddef
+ g:result = Concat('bie')
+ g:localname = name
+
+ export const CONST = 1234
+ export var exported = 9876
+ export var exp_name = 'John'
+ export def Exported(): string
+ return 'Exported'
+ enddef
+ export def ExportedValue(): number
+ return exported
+ enddef
+ export def ExportedInc()
+ exported += 5
+ enddef
+ export final theList = [1]
+ export def AddSome(s: string): string
+ return s .. 'some'
+ enddef
+ export var AddRef = AddSome
+END
+
+def s:Undo_export_script_lines()
+ unlet g:result
+ unlet g:localname
+enddef
+
+def Test_vim9_import_export()
+ writefile(s:export_script_lines, 'Xexport.vim', 'D')
+ var import_script_lines =<< trim END
+ vim9script
+ var dir = './'
+ var ext = ".vim"
+ import dir .. 'Xexport' .. ext as expo
+
+ g:exported1 = expo.exported
+ expo.exported += 3
+ g:exported2 = expo.exported
+ g:exported3 = expo.ExportedValue()
+
+ expo.ExportedInc()
+ g:exported_i1 = expo.exported
+ g:exported_i2 = expo.ExportedValue()
+
+ expo.exported = 11
+ g:exported_s1 = expo.exported
+ g:exported_s2 = expo.ExportedValue()
+
+ g:imported_func = expo.Exported()
+
+ def GetExported(): string
+ var local_dict = {ref: expo.Exported}
+ return local_dict.ref()
+ enddef
+ g:funcref_result = GetExported()
+
+ def GetName(): string
+ return expo.exp_name .. 'son'
+ enddef
+ g:long_name = GetName()
+
+ g:imported_name = expo.exp_name
+ expo.exp_name ..= ' Doe'
+ expo.exp_name = expo.exp_name .. ' Maar'
+ g:imported_name_appended = expo.exp_name
+ g:exported_later = expo.exported
+
+ expo.theList->add(2)
+ assert_equal([1, 2], expo.theList)
+
+ assert_equal('andthensome', 'andthen'->expo.AddSome())
+ assert_equal('awesome', 'awe'->expo.AddRef())
+ END
+ writefile(import_script_lines, 'Ximport.vim', 'D')
+ source Ximport.vim
+
+ assert_equal('bobbie', g:result)
+ assert_equal('bob', g:localname)
+ assert_equal(9876, g:exported1)
+ assert_equal(9879, g:exported2)
+ assert_equal(9879, g:exported3)
+
+ assert_equal(9884, g:exported_i1)
+ assert_equal(9884, g:exported_i2)
+
+ assert_equal(11, g:exported_s1)
+ assert_equal(11, g:exported_s2)
+ assert_equal(11, g:exported_later)
+
+ assert_equal('Exported', g:imported_func)
+ assert_equal('Exported', g:funcref_result)
+ assert_equal('John', g:imported_name)
+ assert_equal('Johnson', g:long_name)
+ assert_equal('John Doe Maar', g:imported_name_appended)
+ assert_false(exists('g:name'))
+
+ Undo_export_script_lines()
+ unlet g:exported1
+ unlet g:exported2
+ unlet g:exported3
+ unlet g:exported_i1
+ unlet g:exported_i2
+ unlet g:exported_later
+ unlet g:imported_func
+ unlet g:imported_name g:long_name g:imported_name_appended
+ delete('Ximport.vim')
+
+ # similar, with line breaks
+ var import_line_break_script_lines =<< trim END
+ vim9script
+ import './Xexport.vim'
+ as expo
+ g:exported = expo.exported
+ expo.exported += 7
+ g:exported_added = expo.exported
+ g:imported_func = expo.Exported()
+ END
+ writefile(import_line_break_script_lines, 'Ximport_lbr.vim')
+ source Ximport_lbr.vim
+
+ assert_equal(11, g:exported)
+ assert_equal(18, g:exported_added)
+ assert_equal('Exported', g:imported_func)
+
+ # exported script not sourced again
+ assert_false(exists('g:result'))
+ unlet g:exported
+ unlet g:exported_added
+ unlet g:imported_func
+ delete('Ximport_lbr.vim')
+
+ var import_shadows_cmdmod_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as vim9
+ vim9.exp_name = 'Shadow'
+ assert_equal('Shadow', vim9.exp_name)
+ END
+ v9.CheckScriptSuccess(import_shadows_cmdmod_lines)
+
+ var line_break_before_dot =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ g:exported = expo
+ .exported
+ END
+ writefile(line_break_before_dot, 'Ximport_lbr_before_dot.vim')
+ assert_fails('source Ximport_lbr_before_dot.vim', 'E1060:', '', 3)
+ delete('Ximport_lbr_before_dot.vim')
+
+ var line_break_after_dot =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ g:exported = expo.
+ exported
+ END
+ writefile(line_break_after_dot, 'Ximport_lbr_after_dot.vim')
+ assert_fails('source Ximport_lbr_after_dot.vim', 'E1074:', '', 3)
+ delete('Ximport_lbr_after_dot.vim')
+
+ var import_star_as_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as Export
+ def UseExport()
+ g:exported_def = Export.exported
+ enddef
+ g:exported_script = Export.exported
+ assert_equal(1, exists('Export.exported'))
+ assert_equal(0, exists('Export.notexported'))
+ UseExport()
+ END
+ writefile(import_star_as_lines, 'Ximport.vim')
+ source Ximport.vim
+
+ assert_equal(18, g:exported_def)
+ assert_equal(18, g:exported_script)
+ unlet g:exported_def
+ unlet g:exported_script
+
+ var import_star_as_lines_no_dot =<< trim END
+ vim9script
+ import './Xexport.vim' as Export
+ def Func()
+ var dummy = 1
+ var imported = Export + dummy
+ enddef
+ defcompile
+ END
+ writefile(import_star_as_lines_no_dot, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1060:', '', 2, 'Func')
+
+ var import_star_as_lines_dot_space =<< trim END
+ vim9script
+ import './Xexport.vim' as Export
+ def Func()
+ var imported = Export . exported
+ enddef
+ defcompile
+ END
+ writefile(import_star_as_lines_dot_space, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1074:', '', 1, 'Func')
+
+ writefile(s:export_script_lines, 'Xexport2.vim')
+ var import_as_duplicated =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ import './Xexport2.vim' as expo
+ END
+ writefile(import_as_duplicated, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1073:', '', 3, 'Ximport.vim')
+ delete('Xexport2.vim')
+
+ var import_star_as_lines_script_no_dot =<< trim END
+ vim9script
+ import './Xexport.vim' as Export
+ g:imported_script = Export exported
+ END
+ writefile(import_star_as_lines_script_no_dot, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1060: Expected dot after name: Export exported')
+
+ var import_star_as_lines_script_space_after_dot =<< trim END
+ vim9script
+ import './Xexport.vim' as Export
+ g:imported_script = Export. exported
+ END
+ writefile(import_star_as_lines_script_space_after_dot, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1074:')
+
+ var import_star_as_lines_missing_name =<< trim END
+ vim9script
+ import './Xexport.vim' as Export
+ def Func()
+ var imported = Export.
+ enddef
+ defcompile
+ END
+ writefile(import_star_as_lines_missing_name, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1048:', '', 1, 'Func')
+
+ var import_star_as_lbr_lines =<< trim END
+ vim9script
+ import './Xexport.vim'
+ as Export
+ def UseExport()
+ g:exported = Export.exported
+ enddef
+ UseExport()
+ END
+ writefile(import_star_as_lbr_lines, 'Ximport.vim')
+ source Ximport.vim
+ assert_equal(18, g:exported)
+ unlet g:exported
+
+ # try to use something that exists but is not exported
+ var import_not_exported_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ echo expo.name
+ END
+ writefile(import_not_exported_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1049:', '', 3, 'Ximport.vim')
+
+ # try to import something that is already defined
+ var import_already_defined =<< trim END
+ vim9script
+ var exported = 'something'
+ import './Xexport.vim' as exported
+ END
+ writefile(import_already_defined, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1054:', '', 3, 'Ximport.vim')
+
+ # try changing an imported const
+ var import_assign_to_const =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ def Assign()
+ expo.CONST = 987
+ enddef
+ defcompile
+ END
+ writefile(import_assign_to_const, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E46:', '', 1, '_Assign')
+
+ # try changing an imported final
+ var import_assign_to_final =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ def Assign()
+ expo.theList = [2]
+ enddef
+ defcompile
+ END
+ writefile(import_assign_to_final, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E46:', '', 1, '_Assign')
+
+ var import_no_as_lines =<< trim END
+ vim9script
+ import './Xexport.vim' name
+ END
+ writefile(import_no_as_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E488:', '', 2, 'Ximport.vim')
+
+ # trailing starts with "as"
+ var import_bad_as_lines =<< trim END
+ vim9script
+ import './Xexport.vim' asname
+ END
+ writefile(import_no_as_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E488:', '', 2, 'Ximport.vim')
+
+ var import_invalid_string_lines =<< trim END
+ vim9script
+ import Xexport.vim
+ END
+ writefile(import_invalid_string_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E121:', '', 2, 'Ximport.vim')
+
+ var import_wrong_name_lines =<< trim END
+ vim9script
+ import './XnoExport.vim'
+ END
+ writefile(import_wrong_name_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1053:', '', 2, 'Ximport.vim')
+
+ var import_redefining_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as exported
+ var exported = 5
+ END
+ writefile(import_redefining_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1213: Redefining imported item "exported"', '', 3)
+
+ var import_missing_dot_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ def Test()
+ expo = 9
+ enddef
+ defcompile
+ END
+ writefile(import_missing_dot_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1258:', '', 1)
+
+ var import_missing_name_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ def Test()
+ expo.99 = 9
+ enddef
+ defcompile
+ END
+ writefile(import_missing_name_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1259:', '', 1)
+
+ var import_assign_wrong_type_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ expo.exported = 'xxx'
+ END
+ writefile(import_assign_wrong_type_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E1012: Type mismatch; expected number but got string', '', 3)
+
+ var import_assign_const_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as expo
+ expo.CONST = 4321
+ END
+ writefile(import_assign_const_lines, 'Ximport.vim')
+ assert_fails('source Ximport.vim', 'E46: Cannot change read-only variable "CONST"', '', 3)
+
+ # Check that in a Vim9 script 'cpo' is set to the Vim default.
+ # Flags added or removed are also applied to the restored value.
+ set cpo=abcd
+ var lines =<< trim END
+ vim9script
+ g:cpo_in_vim9script = &cpo
+ set cpo+=f
+ set cpo-=c
+ g:cpo_after_vim9script = &cpo
+ END
+ writefile(lines, 'Xvim9_script', 'D')
+ source Xvim9_script
+ assert_equal('fabd', &cpo)
+ set cpo&vim
+ assert_equal(&cpo, g:cpo_in_vim9script)
+ var newcpo = substitute(&cpo, 'c', '', '') .. 'f'
+ assert_equal(newcpo, g:cpo_after_vim9script)
+
+ delete('Xvim9_script')
+enddef
+
+def Test_import_very_long_name()
+ var lines =<< trim END
+ vim9script
+
+ export var verylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongname = 'asdf'
+ END
+ writefile(lines, 'Xverylong.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xverylong.vim'
+
+ g:result = Xverylong.verylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongname
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal('asdf', g:result)
+
+ unlet g:result
+enddef
+
+def Test_import_funcref()
+ var lines =<< trim END
+ vim9script
+ export def F(): number
+ return 42
+ enddef
+ export const G = F
+ END
+ writefile(lines, 'Xlib.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xlib.vim' as lib
+ const Foo = lib.G()
+ assert_equal(42, Foo)
+
+ def DoTest()
+ const Goo = lib.G()
+ assert_equal(42, Goo)
+ enddef
+ DoTest()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_export_closure()
+ # tests that the closure in block can be compiled, not the import part
+ var lines =<< trim END
+ vim9script
+ {
+ var foo = 42
+ export def Bar(): number
+ return foo
+ enddef
+ }
+ assert_equal(42, Bar())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_import_duplicate_function()
+ # Function Hover() exists in both scripts, partial should refer to the right
+ # one.
+ var lines =<< trim END
+ vim9script
+
+ def Hover(d: dict<any>): string
+ return 'found it'
+ enddef
+
+ export def NewLspServer(): dict<any>
+ var d: dict<any> = {}
+ d->extend({hover: function('Hover', [d])})
+ return d
+ enddef
+
+ NewLspServer()
+ END
+ writefile(lines, 'Xserver.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+
+ import './Xserver.vim' as server
+
+ export def Hover()
+ enddef
+
+ def AddServer()
+ var d: dict<any> = server.NewLspServer()
+ assert_equal('found it', d.hover())
+ enddef
+ AddServer()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+
+def Test_import_fails()
+ writefile([], 'Xfoo.vim', 'D')
+ var lines =<< trim END
+ import './Xfoo.vim' as foo
+ foo = 'bar'
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1094:', 'E1236: Cannot use foo itself'])
+ lines =<< trim END
+ vim9script
+ import './Xfoo.vim' as foo
+ var that = foo
+ END
+ v9.CheckScriptFailure(lines, 'E1060: Expected dot after name: foo')
+ lines =<< trim END
+ vim9script
+ import './Xfoo.vim' as foo
+ var that: any
+ that += foo
+ END
+ v9.CheckScriptFailure(lines, 'E1060: Expected dot after name: foo')
+ lines =<< trim END
+ vim9script
+ import './Xfoo.vim' as foo
+ foo += 9
+ END
+ v9.CheckScriptFailure(lines, 'E1060: Expected dot after name: foo')
+
+ lines =<< trim END
+ vim9script
+ import './Xfoo.vim' as 9foo
+ END
+ v9.CheckScriptFailure(lines, 'E1047:')
+ lines =<< trim END
+ vim9script
+ import './Xfoo.vim' as the#foo
+ END
+ v9.CheckScriptFailure(lines, 'E1047:')
+ lines =<< trim END
+ vim9script
+ import './Xfoo.vim' as g:foo
+ END
+ v9.CheckScriptFailure(lines, 'E1047:')
+
+ lines =<< trim END
+ vim9script
+ def TheFunc()
+ echo 'the func'
+ enddef
+ export var Ref = TheFunc
+ END
+ writefile([], 'Xthat.vim')
+
+ lines =<< trim END
+ import './Xthat.vim' as That
+ That()
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1094:', 'E1236: Cannot use That itself'])
+
+ lines =<< trim END
+ vim9script
+ import './Xthat.vim' as That
+ def Func()
+ echo That()
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1236: Cannot use That itself')
+
+ lines =<< trim END
+ import './Xthat.vim' as one
+ import './Xthat.vim' as two
+ END
+ v9.CheckScriptFailure(lines, 'E1262:')
+
+ delete('Xthat.vim')
+
+ lines =<< trim END
+ vim9script
+ export var item = 'hello'
+ import './Xyourself.vim'
+ END
+ writefile(lines, 'Xyourself.vim', 'D')
+ assert_fails('source Xyourself.vim', 'E1088:')
+
+ mkdir('Ximport', 'R')
+
+ writefile(['vim9script'], 'Ximport/.vim')
+ lines =<< trim END
+ vim9script
+ import './Ximport/.vim'
+ END
+ v9.CheckScriptFailure(lines, 'E1261: Cannot import .vim without using "as"')
+ lines =<< trim END
+ vim9script
+ import './Ximport/.vim' as vim
+ END
+ v9.CheckScriptSuccess(lines)
+
+ writefile(['vim9script'], 'Ximport/.vimrc')
+ lines =<< trim END
+ vim9script
+ import './Ximport/.vimrc'
+ END
+ v9.CheckScriptFailure(lines, 'E1257: Imported script must use "as" or end in .vim')
+ lines =<< trim END
+ vim9script
+ import './Ximport/.vimrc' as vimrc
+ END
+ v9.CheckScriptSuccess(lines)
+
+ new
+ setline(1, ['vim9script', 'import "" as abc'])
+ assert_fails('source', 'E1071: Invalid string for :import: "" as abc')
+ setline(2, 'import [] as abc')
+ assert_fails('source', 'E1071: Invalid string for :import: [] as abc')
+ setline(2, 'import test_null_string() as abc')
+ assert_fails('source', 'E1071: Invalid string for :import: test_null_string() as abc')
+ bw!
+ call writefile(['vim9script', "import './Xfoo.vim' ask expo"], 'Xbar.vim')
+ assert_fails('source Xbar.vim', 'E488: Trailing characters: ask expo')
+ writefile([], 'Xtemp')
+ call writefile(['vim9script', "import './Xtemp'"], 'Xbar.vim')
+ assert_fails('source Xbar.vim', 'E1257: Imported script must use "as" or end in .vim: Xtemp')
+ delete('Xtemp')
+ call writefile(['vim9script', "import './Xfoo.vim' as abc | foobar"], 'Xbar.vim')
+ assert_fails('source Xbar.vim', 'E492: Not an editor command: foobar')
+ call delete('Xbar.vim')
+enddef
+
+func g:Trigger()
+ source Ximport.vim
+ return "echo 'yes'\<CR>"
+endfunc
+
+def Test_import_export_expr_map()
+ # check that :import and :export work when buffer is locked
+ var export_lines =<< trim END
+ vim9script
+ export def That(): string
+ return 'yes'
+ enddef
+ END
+ writefile(export_lines, 'Xexport_that.vim', 'D')
+
+ var import_lines =<< trim END
+ vim9script
+ import './Xexport_that.vim' as that
+ assert_equal('yes', that.That())
+ END
+ writefile(import_lines, 'Ximport.vim', 'D')
+
+ nnoremap <expr> trigger g:Trigger()
+ feedkeys('trigger', "xt")
+
+ nunmap trigger
+enddef
+
+def Test_import_in_filetype()
+ # check that :import works when the buffer is locked
+ mkdir('ftplugin', 'pR')
+ var export_lines =<< trim END
+ vim9script
+ export var That = 'yes'
+ END
+ writefile(export_lines, 'ftplugin/Xexport_ft.vim')
+
+ var import_lines =<< trim END
+ vim9script
+ import './Xexport_ft.vim' as ft
+ assert_equal('yes', ft.That)
+ g:did_load_mytpe = 1
+ END
+ writefile(import_lines, 'ftplugin/qf.vim')
+
+ var save_rtp = &rtp
+ &rtp = getcwd() .. ',' .. &rtp
+
+ filetype plugin on
+ copen
+ assert_equal(1, g:did_load_mytpe)
+
+ quit!
+ &rtp = save_rtp
+enddef
+
+def Test_use_import_in_mapping()
+ var lines =<< trim END
+ vim9script
+ export def Funcx(nr: number)
+ g:result = nr
+ enddef
+ END
+ writefile(lines, 'XsomeExport.vim', 'D')
+ lines =<< trim END
+ vim9script
+ import './XsomeExport.vim' as some
+ var Funcy = some.Funcx
+ nnoremap <F3> :call <sid>Funcy(42)<cr>
+ nnoremap <F4> :call <sid>some.Funcx(44)<cr>
+ END
+ writefile(lines, 'Xmapscript.vim', 'D')
+
+ source Xmapscript.vim
+ feedkeys("\<F3>", "xt")
+ assert_equal(42, g:result)
+ feedkeys("\<F4>", "xt")
+ assert_equal(44, g:result)
+
+ unlet g:result
+ nunmap <F3>
+ nunmap <F4>
+enddef
+
+def Test_use_relative_autoload_import_in_mapping()
+ var lines =<< trim END
+ vim9script
+ export def Func()
+ g:result = 42
+ enddef
+ END
+ writefile(lines, 'XrelautoloadExport.vim', 'D')
+ lines =<< trim END
+ vim9script
+ import autoload './XrelautoloadExport.vim' as some
+ nnoremap <F3> :call <SID>some.Func()<CR>
+ END
+ writefile(lines, 'Xmapscript.vim', 'D')
+
+ source Xmapscript.vim
+ assert_match('\d\+ A: .*XrelautoloadExport.vim', execute('scriptnames')->split("\n")[-1])
+ var l = getscriptinfo()
+ assert_match('XrelautoloadExport.vim$', l[-1].name)
+ assert_true(l[-1].autoload)
+ feedkeys("\<F3>", "xt")
+ assert_equal(42, g:result)
+ l = getscriptinfo({name: 'XrelautoloadExport'})
+ assert_true(len(l) == 1)
+ assert_match('XrelautoloadExport.vim$', l[0].name)
+ assert_false(l[0].autoload)
+ assert_equal(999999, l[0].version)
+
+ unlet g:result
+ nunmap <F3>
+enddef
+
+def Test_autoload_import_var()
+ # variable name starts with "autoload"
+ var lines =<< trim END
+ vim9script
+ var autoloaded = "Xtest.vim"
+ import autoloaded
+ END
+ v9.CheckScriptFailure(lines, 'E1053: Could not import "Xtest.vim')
+enddef
+
+def Test_use_autoload_import_in_mapping()
+ var lines =<< trim END
+ vim9script
+ export def Func()
+ g:result = 49
+ enddef
+ END
+ mkdir('Ximpdir/autoload', 'pR')
+ writefile(lines, 'Ximpdir/autoload/XautoloadExport.vim')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Ximpdir'
+
+ lines =<< trim END
+ vim9script
+ import autoload 'XautoloadExport.vim' as some
+ nnoremap <F3> :call <SID>some.Func()<CR>
+ END
+ writefile(lines, 'Xmapscript.vim', 'D')
+
+ source Xmapscript.vim
+ assert_match('\d\+ A: .*autoload/XautoloadExport.vim', execute('scriptnames')->split("\n")[-1])
+ feedkeys("\<F3>", "xt")
+ assert_equal(49, g:result)
+
+ unlet g:result
+ nunmap <F3>
+ &rtp = save_rtp
+enddef
+
+def Test_use_import_in_command_completion()
+ var lines =<< trim END
+ vim9script
+ export def Complete(..._): list<string>
+ return ['abcd']
+ enddef
+ END
+ writefile(lines, 'Xscript.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xscript.vim'
+
+ command -nargs=1 -complete=customlist,Xscript.Complete Cmd echo 'ok'
+ feedkeys(":Cmd ab\<Tab>\<C-B>#\<CR>", 'xnt')
+ assert_equal('#Cmd abcd', @:)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ delcommand Cmd
+enddef
+
+def Test_use_import_with_funcref_in_command_completion()
+ var lines =<< trim END
+ vim9script
+ export def Complete(..._): list<string>
+ return ['abcd']
+ enddef
+ END
+ writefile(lines, 'Xscript.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xscript.vim'
+
+ var Ref = Xscript.Complete
+ exe "command -nargs=1 -complete=customlist," .. expand('<SID>') .. "Ref Cmd echo 'ok'"
+ feedkeys(":Cmd ab\<Tab>\<C-B>#\<CR>", 'xnt')
+ assert_equal('#Cmd abcd', @:)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ delcommand Cmd
+enddef
+
+def Test_use_autoload_import_in_insert_completion()
+ mkdir('Xinsdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xinsdir'
+
+ var lines =<< trim END
+ vim9script
+ export def ThesaurusFunc(findbase: bool, _): any
+ if findbase
+ return 1
+ endif
+ return [
+ 'check',
+ 'experiment',
+ 'test',
+ 'verification'
+ ]
+ enddef
+ g:completion_loaded = 'yes'
+ END
+ writefile(lines, 'Xinsdir/autoload/completion.vim')
+
+ new
+ lines =<< trim END
+ vim9script
+ g:completion_loaded = 'no'
+ import autoload 'completion.vim'
+ set thesaurusfunc=completion.ThesaurusFunc
+ assert_equal('no', g:completion_loaded)
+ feedkeys("i\<C-X>\<C-T>\<C-N>\<Esc>", 'xt')
+ assert_equal('experiment', getline(1))
+ assert_equal('yes', g:completion_loaded)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ set thesaurusfunc=
+ bwipe!
+ &rtp = save_rtp
+enddef
+
+def Test_use_autoload_import_partial_in_opfunc()
+ mkdir('Xpartdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xpartdir'
+
+ var lines =<< trim END
+ vim9script
+ export def Opfunc1(..._)
+ g:opfunc_called = 'yes'
+ enddef
+ END
+ writefile(lines, 'Xpartdir/autoload/opfunc.vim')
+
+ new
+ lines =<< trim END
+ vim9script
+ import autoload 'opfunc.vim'
+ nnoremap <expr> <F3> TheFunc()
+ def TheFunc(): string
+ &operatorfunc = function('opfunc.Opfunc1', [0])
+ return 'g@'
+ enddef
+ feedkeys("\<F3>l", 'xt')
+ assert_equal('yes', g:opfunc_called)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ set opfunc=
+ bwipe!
+ nunmap <F3>
+ &rtp = save_rtp
+enddef
+
+def Test_set_opfunc_to_autoload_func_directly()
+ mkdir('Xdirdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xdirdir'
+
+ var lines =<< trim END
+ vim9script
+ export def Opfunc2(..._)
+ g:opfunc_called = 'yes'
+ enddef
+ END
+ writefile(lines, 'Xdirdir/autoload/opfunc.vim')
+
+ new
+ lines =<< trim END
+ vim9script
+ import autoload 'opfunc.vim'
+ nnoremap <expr> <F3> TheFunc()
+ def TheFunc(): string
+ &operatorfunc = opfunc.Opfunc2
+ return 'g@'
+ enddef
+ feedkeys("\<F3>l", 'xt')
+ assert_equal('yes', g:opfunc_called)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ set opfunc=
+ bwipe!
+ nunmap <F3>
+ &rtp = save_rtp
+enddef
+
+def Test_use_autoload_import_in_fold_expression()
+ mkdir('Xfolddir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xfolddir'
+
+ var lines =<< trim END
+ vim9script
+ export def Expr(): string
+ return getline(v:lnum) =~ '^#' ? '>1' : '1'
+ enddef
+ export def Text(): string
+ return 'fold text'
+ enddef
+ g:fold_loaded = 'yes'
+ END
+ writefile(lines, 'Xfolddir/autoload/fold.vim')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'fold.vim'
+ &foldexpr = 'fold.Expr()'
+ &foldtext = 'fold.Text()'
+ &foldmethod = 'expr'
+ &debug = 'throw'
+ END
+ new
+ setline(1, ['# one', 'text', '# two', 'text'])
+ g:fold_loaded = 'no'
+ v9.CheckScriptSuccess(lines)
+ assert_equal('no', g:fold_loaded)
+ redraw
+ assert_equal('yes', g:fold_loaded)
+
+ # Check that script context of 'foldexpr' is copied to another buffer.
+ edit! otherfile
+ redraw
+
+ set foldexpr= foldtext& foldmethod& debug=
+ bwipe!
+ &rtp = save_rtp
+enddef
+
+def Test_autoload_import_relative()
+ var lines =<< trim END
+ vim9script
+
+ g:loaded = 'yes'
+ export def RelFunc(): string
+ return 'relfunc'
+ enddef
+ def NotExported()
+ echo 'not'
+ enddef
+
+ export var someText = 'some text'
+ var notexp = 'bad'
+ END
+ writefile(lines, 'XimportRel.vim', 'D')
+ writefile(lines, 'XimportRel2.vim', 'D')
+ writefile(lines, 'XimportRel3.vim', 'D')
+ writefile(lines, 'XimportRel4.vim', 'D')
+ writefile(lines, 'XimportRel5.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ g:loaded = 'no'
+ import autoload './XimportRel.vim'
+ assert_equal('no', g:loaded)
+
+ def AFunc(): string
+ var res = ''
+ res ..= XimportRel.RelFunc()
+ res ..= '/'
+ res ..= XimportRel.someText
+ XimportRel.someText = 'from AFunc'
+ return res
+ enddef
+ # script not loaded when compiling
+ defcompile
+ assert_equal('no', g:loaded)
+
+ assert_equal('relfunc/some text', AFunc())
+ assert_equal('yes', g:loaded)
+ unlet g:loaded
+
+ assert_equal('from AFunc', XimportRel.someText)
+ XimportRel.someText = 'from script'
+ assert_equal('from script', XimportRel.someText)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel.vim'
+ echo XimportRel.NotExported()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: NotExported', 3)
+
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel.vim'
+ echo XimportRel.notexp
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 3)
+
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel.vim'
+ XimportRel.notexp = 'bad'
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 3)
+
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel.vim'
+ def Func()
+ echo XimportRel.NotExported()
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: NotExported', 1)
+
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel.vim'
+ def Func()
+ echo XimportRel.notexp
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 1)
+
+ # Same, script not imported before
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel4.vim'
+ def Func()
+ echo XimportRel4.notexp
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 1)
+
+ # does not fail if the script wasn't loaded yet and only compiling
+ g:loaded = 'no'
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel2.vim'
+ def Func()
+ echo XimportRel2.notexp
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal('no', g:loaded)
+
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel.vim'
+ def Func()
+ XimportRel.notexp = 'bad'
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 1)
+
+ # fails with a not loaded import
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel3.vim'
+ def Func()
+ XimportRel3.notexp = 'bad'
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notexp', 1)
+ assert_equal('yes', g:loaded)
+ unlet g:loaded
+
+ lines =<< trim END
+ vim9script
+ import autoload './XimportRel5.vim'
+ def Func()
+ XimportRel5.nosuchvar = 'bad'
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E121: Undefined variable: nosuchvar', 1)
+ unlet g:loaded
+
+ # nasty: delete script after compiling function
+ writefile(['vim9script'], 'XimportRelDel.vim')
+ lines =<< trim END
+ vim9script
+
+ import autoload './XimportRelDel.vim'
+ def DoIt()
+ echo XimportRelDel.var
+ enddef
+ defcompile
+ delete('XimportRelDel.vim')
+ DoIt()
+ END
+ v9.CheckScriptFailure(lines, 'E484:')
+enddef
+
+def Test_autoload_import_relative_autoload_dir()
+ mkdir('autoload', 'pR')
+ var lines =<< trim END
+ vim9script
+ export def Bar()
+ g:called_bar = 'yes'
+ enddef
+ END
+ writefile(lines, 'autoload/script.vim')
+
+ lines =<< trim END
+ vim9script
+ import autoload './autoload/script.vim'
+ def Foo()
+ script.Bar()
+ enddef
+ Foo()
+ assert_equal('yes', g:called_bar)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ unlet g:called_bar
+enddef
+
+def Test_autoload_import_deleted()
+ var lines =<< trim END
+ vim9script
+ export const FOO = 1
+ END
+ writefile(lines, 'Xa.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import autoload './Xa.vim'
+
+ delete('Xa.vim')
+ var x = Xa.FOO
+ END
+ v9.CheckScriptFailure(lines, 'E484:')
+enddef
+
+def Test_autoload_import_using_const()
+ mkdir('Xdir/autoload', 'pR')
+ var lines =<< trim END
+ vim9script
+ export const FOO = 42
+ echomsg FOO
+ END
+ writefile(lines, 'Xdir/autoload/exp.vim')
+
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xdir'
+ lines =<< trim END
+ vim9script
+ import autoload 'exp.vim'
+ assert_equal(42, exp.FOO)
+ END
+ v9.CheckScriptSuccess(lines)
+ &rtp = save_rtp
+enddef
+
+func Test_import_in_diffexpr()
+ CheckExecutable diff
+
+ call Run_Test_import_in_diffexpr()
+endfunc
+
+def Run_Test_import_in_diffexpr()
+ var lines =<< trim END
+ vim9script
+
+ export def DiffExpr()
+ # Prepend some text to check diff type detection
+ writefile(['warning', ' message'], v:fname_out)
+ silent exe '!diff ' .. v:fname_in .. ' '
+ .. v:fname_new .. '>>' .. v:fname_out
+ enddef
+ END
+ writefile(lines, 'Xdiffexpr', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xdiffexpr' as diff
+
+ set diffexpr=diff.DiffExpr()
+ set diffopt=foldcolumn:0
+ END
+ v9.CheckScriptSuccess(lines)
+
+ enew!
+ call setline(1, ['one', 'two', 'three'])
+ diffthis
+
+ botright vert new
+ call setline(1, ['one', 'two', 'three.'])
+ diffthis
+ # we only check if this does not cause errors
+ redraw
+
+ diffoff!
+ set diffexpr=
+ set diffopt&
+ bwipe!
+ bwipe!
+enddef
+
+def Test_import_in_patchexpr()
+ var lines =<< trim END
+ vim9script
+ export def TPatch()
+ call writefile(['output file'], v:fname_out)
+ enddef
+ END
+ writefile(lines, 'Xpatchexpr', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xpatchexpr' as patch
+ set patchexpr=patch.TPatch()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ call writefile(['input file'], 'Xinput', 'D')
+ call writefile(['diff file'], 'Xdiff', 'D')
+ :%bwipe!
+ edit Xinput
+ diffpatch Xdiff
+ call assert_equal('output file', getline(1))
+
+ set patchexpr&
+ :%bwipe!
+enddef
+
+def Test_import_in_formatexpr()
+ var lines =<< trim END
+ vim9script
+ export def MyFormatExpr(): number
+ g:did_format = 'yes'
+ return 0
+ enddef
+ END
+ writefile(lines, 'Xformatter', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xformatter' as format
+ set formatexpr=format.MyFormatExpr()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ new
+ setline(1, ['a', 'b', 'c'])
+ normal gqG
+ assert_equal('yes', g:did_format)
+
+ bwipe!
+ unlet g:did_format
+ set formatexpr=
+enddef
+
+def Test_import_in_includeexpr()
+ writefile(['found it'], 'Xthisfile', 'D')
+ new
+
+ var lines =<< trim END
+ vim9script
+ export def DoSub(): string
+ return substitute(v:fname, 'that', 'this', '')
+ enddef
+ END
+ writefile(lines, 'Xinclude.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xinclude.vim'
+ set includeexpr=Xinclude.DoSub()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ setline(1, ['Xthatfile'])
+ exe "normal \<C-W>f"
+ assert_equal('Xthisfile', expand('%'))
+
+ bwipe!
+ bwipe!
+ set includeexpr=
+enddef
+
+def Test_import_in_indentexpr()
+ var lines =<< trim END
+ vim9script
+ export def GetIndent(): number
+ return 5
+ enddef
+ END
+ writefile(lines, 'Xindenter', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xindenter' as indent
+ set indentexpr=indent.GetIndent()
+ set debug=throw
+ END
+ v9.CheckScriptSuccess(lines)
+
+ new
+ setline(1, 'hello')
+ normal ==
+ assert_equal(' hello', getline(1))
+
+ bwipe!
+ set indentexpr= debug=
+enddef
+
+func Test_import_in_printexpr()
+ CheckFeature postscript
+ call Run_Test_import_in_printexpr()
+endfunc
+
+def Run_Test_import_in_printexpr()
+ var lines =<< trim END
+ vim9script
+ export def PrintFile(): bool
+ g:printed = 'yes'
+ delete('v:fname_in')
+ return false
+ enddef
+ END
+ writefile(lines, 'Xprint.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xprint.vim'
+ set printexpr=Xprint.PrintFile()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ help
+ hardcopy dummy args
+ assert_equal('yes', g:printed)
+
+ set printexpr=
+enddef
+
+def Test_import_in_charconvert()
+ var lines =<< trim END
+ vim9script
+ export def MakeUpper(): bool
+ var data = readfile(v:fname_in)
+ map(data, 'toupper(v:val)')
+ writefile(data, v:fname_out)
+ return false # success
+ enddef
+ END
+ writefile(lines, 'Xconvert.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xconvert.vim' as conv
+ set charconvert=conv.MakeUpper()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ writefile(['one', 'two'], 'Xiicfile', 'D')
+ new Xiicfile
+ write ++enc=ucase Xiicfile1
+ assert_equal(['ONE', 'TWO'], readfile('Xiicfile1'))
+
+ delete('Xiicfile1')
+ bwipe!
+ set charconvert&
+enddef
+
+func Test_import_in_spellsuggest_expr()
+ CheckFeature spell
+ call Run_Test_import_in_spellsuggest_expr()
+endfunc
+
+def Run_Test_import_in_spellsuggest_expr()
+ var lines =<< trim END
+ vim9script
+ export def MySuggest(): list<any>
+ return [['Fox', 8], ['Fop', 9]]
+ enddef
+ END
+ writefile(lines, 'Xsuggest.vim', 'D')
+
+ lines =<< trim END
+ vim9script
+ import './Xsuggest.vim' as sugg
+ set spell spellsuggest=expr:sugg.MySuggest()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ set verbose=1 # report errors
+ call assert_equal(['Fox', 'Fop'], spellsuggest('Fo', 2))
+
+ set nospell spellsuggest& verbose=0
+enddef
+
+def Test_import_in_lambda_method()
+ var lines =<< trim END
+ vim9script
+ export def Retarg(e: any): any
+ return e
+ enddef
+ END
+ writefile(lines, 'XexportRetarg.vim', 'D')
+ lines =<< trim END
+ vim9script
+ import './XexportRetarg.vim'
+ def Lambda(): string
+ var F = (x) => x->XexportRetarg.Retarg()
+ return F('arg')
+ enddef
+ assert_equal('arg', Lambda())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_export_shadows_global_function()
+ mkdir('Xglobdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xglobdir'
+
+ var lines =<< trim END
+ vim9script
+ export def Shadow(): string
+ return 'Shadow()'
+ enddef
+ END
+ writefile(lines, 'Xglobdir/autoload/shadow.vim')
+
+ lines =<< trim END
+ vim9script
+
+ def g:Shadow(): string
+ return 'global'
+ enddef
+
+ import autoload 'shadow.vim'
+ assert_equal('Shadow()', shadow.Shadow())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ delfunc g:Shadow
+ bwipe!
+ &rtp = save_rtp
+enddef
+
+def Test_export_fails()
+ v9.CheckScriptFailure(['export var some = 123'], 'E1042:')
+ v9.CheckScriptFailure(['vim9script', 'export var g:some'], 'E1022:')
+ v9.CheckScriptFailure(['vim9script', 'export echo 134'], 'E1043:')
+ v9.CheckScriptFailure(['vim9script', 'export function /a1b2c3'], 'E1044:')
+
+ assert_fails('export echo 1', 'E1043:')
+enddef
+
+func Test_import_fails_without_script()
+ CheckRunVimInTerminal
+
+ " call indirectly to avoid compilation error for missing functions
+ call Run_Test_import_fails_on_command_line()
+endfunc
+
+def Run_Test_import_fails_on_command_line()
+ var export =<< trim END
+ vim9script
+ export def Foo(): number
+ return 0
+ enddef
+ END
+ writefile(export, 'XexportCmd.vim', 'D')
+
+ var buf = g:RunVimInTerminal('-c "import Foo from ''./XexportCmd.vim''"', {
+ rows: 6, wait_for_ruler: 0})
+ g:WaitForAssert(() => assert_match('^E1094:', term_getline(buf, 5)))
+
+ g:StopVimInTerminal(buf)
+enddef
+
+def Test_vim9_reload_noclear()
+ var lines =<< trim END
+ vim9script
+ export var exported = 'thexport'
+
+ export def TheFunc(x = 0)
+ enddef
+ END
+ writefile(lines, 'XExportReload', 'D')
+ lines =<< trim END
+ vim9script noclear
+ g:loadCount += 1
+ var reloaded = 'init'
+ import './XExportReload' as exp
+
+ def Again(): string
+ return 'again'
+ enddef
+
+ exp.TheFunc()
+
+ if exists('loaded') | finish | endif
+ var loaded = true
+
+ var notReloaded = 'yes'
+ reloaded = 'first'
+ def g:Values(): list<string>
+ return [reloaded, notReloaded, Again(), Once(), exp.exported]
+ enddef
+
+ def Once(): string
+ return 'once'
+ enddef
+ END
+ writefile(lines, 'XReloaded', 'D')
+ g:loadCount = 0
+ source XReloaded
+ assert_equal(1, g:loadCount)
+ assert_equal(['first', 'yes', 'again', 'once', 'thexport'], g:Values())
+ source XReloaded
+ assert_equal(2, g:loadCount)
+ assert_equal(['init', 'yes', 'again', 'once', 'thexport'], g:Values())
+ source XReloaded
+ assert_equal(3, g:loadCount)
+ assert_equal(['init', 'yes', 'again', 'once', 'thexport'], g:Values())
+
+ delfunc g:Values
+ unlet g:loadCount
+
+ lines =<< trim END
+ vim9script
+ def Inner()
+ enddef
+ END
+ lines->writefile('XreloadScript.vim', 'D')
+ source XreloadScript.vim
+
+ lines =<< trim END
+ vim9script
+ def Outer()
+ def Inner()
+ enddef
+ enddef
+ defcompile
+ END
+ lines->writefile('XreloadScript.vim')
+ source XreloadScript.vim
+enddef
+
+def Test_vim_reload_noclear_arg_count()
+ var lines =<< trim END
+ vim9script noclear
+
+ if !exists('g:didload')
+ def Test(a: string, b: string)
+ echo a b
+ enddef
+ def Call()
+ Test('a', 'b')
+ enddef
+ else
+ # redefine with one argument less
+ def Test(a: string)
+ echo a
+ enddef
+ endif
+ Call()
+ g:didload = 1
+ END
+ lines->writefile('XreloadScript_1.vim', 'D')
+ source XreloadScript_1.vim
+ assert_fails('source XreloadScript_1.vim', 'E1106: One argument too many')
+ unlet g:didload
+
+ lines =<< trim END
+ vim9script noclear
+
+ if !exists('g:didload')
+ def Test(a: string, b: string, c: string)
+ echo a b
+ enddef
+ def Call()
+ Test('a', 'b', 'c')
+ enddef
+ else
+ # redefine with one argument less
+ def Test(a: string)
+ echo a
+ enddef
+ endif
+ Call()
+ g:didload = 1
+ END
+ lines->writefile('XreloadScript_2.vim', 'D')
+ source XreloadScript_2.vim
+ assert_fails('source XreloadScript_2.vim', 'E1106: 2 arguments too many')
+ unlet g:didload
+
+ lines =<< trim END
+ vim9script noclear
+
+ if !exists('g:didload')
+ def Test(a: string)
+ echo a
+ enddef
+ def Call()
+ Test('a')
+ enddef
+ else
+ # redefine with one argument extra
+ def Test(a: string, b: string)
+ echo a b
+ enddef
+ endif
+ Call()
+ g:didload = 1
+ END
+ lines->writefile('XreloadScript_3.vim', 'D')
+ source XreloadScript_3.vim
+ assert_fails('source XreloadScript_3.vim', 'E1190: One argument too few')
+ unlet g:didload
+
+ lines =<< trim END
+ vim9script noclear
+
+ if !exists('g:didload')
+ def Test(a: string)
+ echo a
+ enddef
+ def Call()
+ Test('a')
+ enddef
+ else
+ # redefine with two arguments extra
+ def Test(a: string, b: string, c: string)
+ echo a b
+ enddef
+ endif
+ Call()
+ g:didload = 1
+ END
+ lines->writefile('XreloadScript_4.vim', 'D')
+ source XreloadScript_4.vim
+ assert_fails('source XreloadScript_4.vim', 'E1190: 2 arguments too few')
+ unlet g:didload
+enddef
+
+def Test_vim9_reload_noclear_error()
+ var lines =<< trim END
+ vim9script noclear
+
+ if !exists('g:didload')
+ def Test(a: string)
+ echo a
+ enddef
+ def Call()
+ Test('a')
+ enddef
+ else
+ # redefine with a compile error
+ def Test(a: string)
+ echo ax
+ enddef
+ endif
+ Call()
+ g:didload = 1
+ END
+ lines->writefile('XreloadScriptErr.vim', 'D')
+ source XreloadScriptErr.vim
+ assert_fails('source XreloadScriptErr.vim', 'E1001: Variable not found: ax')
+
+ unlet g:didload
+enddef
+
+def Test_vim9_reload_import()
+ var lines =<< trim END
+ vim9script
+ const var = ''
+ var valone = 1234
+ def MyFunc(arg: string)
+ valone = 5678
+ enddef
+ END
+ var morelines =<< trim END
+ var valtwo = 222
+ export def GetValtwo(): number
+ return valtwo
+ enddef
+ END
+ writefile(lines + morelines, 'Xreload.vim', 'D')
+ source Xreload.vim
+ source Xreload.vim
+ source Xreload.vim
+
+ # cannot declare a var twice
+ lines =<< trim END
+ vim9script
+ var valone = 1234
+ var valone = 5678
+ END
+ writefile(lines, 'Xreload.vim')
+ assert_fails('source Xreload.vim', 'E1041:', '', 3, 'Xreload.vim')
+
+ delete('Ximport.vim')
+enddef
+
+" if a script is reloaded with a script-local variable that changed its type, a
+" compiled function using that variable must fail.
+def Test_script_reload_change_type()
+ var lines =<< trim END
+ vim9script noclear
+ var str = 'string'
+ def g:GetStr(): string
+ return str .. 'xxx'
+ enddef
+ END
+ writefile(lines, 'Xreload.vim', 'D')
+ source Xreload.vim
+ echo g:GetStr()
+
+ lines =<< trim END
+ vim9script noclear
+ var str = 1234
+ END
+ writefile(lines, 'Xreload.vim')
+ source Xreload.vim
+ assert_fails('echo g:GetStr()', 'E1150:')
+
+ delfunc g:GetStr
+enddef
+
+" Define CallFunc so that the test can be compiled
+command CallFunc echo 'nop'
+
+def Test_script_reload_from_function()
+ var lines =<< trim END
+ vim9script
+
+ if exists('g:loadedThis')
+ finish
+ endif
+ g:loadedThis = 1
+ delcommand CallFunc
+ command CallFunc Func()
+ def Func()
+ so XreloadFunc.vim
+ g:didTheFunc = 1
+ enddef
+ END
+ writefile(lines, 'XreloadFunc.vim', 'D')
+ source XreloadFunc.vim
+ CallFunc
+ assert_equal(1, g:didTheFunc)
+
+ delcommand CallFunc
+ unlet g:loadedThis
+ unlet g:didTheFunc
+enddef
+
+def s:RetSome(): string
+ return 'some'
+enddef
+
+" Not exported function that is referenced needs to be accessed by the
+" script-local name.
+def Test_vim9_funcref()
+ var sortlines =<< trim END
+ vim9script
+ def Compare(i1: number, i2: number): number
+ return i2 - i1
+ enddef
+
+ export def FastSort(): list<number>
+ return range(5)->sort(Compare)
+ enddef
+
+ export def GetString(arg: string): string
+ return arg
+ enddef
+ END
+ writefile(sortlines, 'Xsort.vim', 'D')
+
+ var lines =<< trim END
+ vim9script
+ import './Xsort.vim'
+ def Test()
+ g:result = Xsort.FastSort()
+ enddef
+ Test()
+ END
+ writefile(lines, 'Xscript.vim', 'D')
+ source Xscript.vim
+ assert_equal([4, 3, 2, 1, 0], g:result)
+ unlet g:result
+
+ lines =<< trim END
+ vim9script
+ # using a function imported with "as"
+ import './Xsort.vim' as anAlias
+ assert_equal('yes', anAlias.GetString('yes'))
+
+ # using the function from a compiled function
+ def TestMore(): string
+ var s = s:anAlias.GetString('foo')
+ return s .. anAlias.GetString('bar')
+ enddef
+ assert_equal('foobar', TestMore())
+
+ # error when using a function that isn't exported
+ assert_fails('anAlias.Compare(1, 2)', 'E1049:')
+ END
+ writefile(lines, 'Xscript.vim')
+
+ var Funcref = function('s:RetSome')
+ assert_equal('some', Funcref())
+enddef
+
+" Check that when searching for "FilterFunc" it finds the import in the
+" script where FastFilter() is called from, both as a string and as a direct
+" function reference.
+def Test_vim9_funcref_other_script()
+ var filterLines =<< trim END
+ vim9script
+ export def FilterFunc(idx: number, val: number): bool
+ return idx % 2 == 1
+ enddef
+ export def FastFilter(): list<number>
+ return range(10)->filter('FilterFunc(v:key, v:val)')
+ enddef
+ export def FastFilterDirect(): list<number>
+ return range(10)->filter(FilterFunc)
+ enddef
+ END
+ writefile(filterLines, 'Xfilter.vim', 'D')
+
+ var lines =<< trim END
+ vim9script
+ import './Xfilter.vim' as filter
+ def Test()
+ var x: list<number> = filter.FastFilter()
+ enddef
+ Test()
+ def TestDirect()
+ var x: list<number> = filter.FastFilterDirect()
+ enddef
+ TestDirect()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_import_absolute()
+ var import_lines = [
+ 'vim9script',
+ 'import "' .. escape(getcwd(), '\') .. '/Xexport_abs.vim" as abs',
+ 'def UseExported()',
+ ' g:imported_abs = abs.exported',
+ ' abs.exported = 8888',
+ ' g:imported_after = abs.exported',
+ 'enddef',
+ 'UseExported()',
+ 'g:import_disassembled = execute("disass UseExported")',
+ ]
+ writefile(import_lines, 'Ximport_abs.vim', 'D')
+ writefile(s:export_script_lines, 'Xexport_abs.vim', 'D')
+
+ source Ximport_abs.vim
+
+ assert_equal(9876, g:imported_abs)
+ assert_equal(8888, g:imported_after)
+ assert_match('<SNR>\d\+_UseExported\_s*' ..
+ 'g:imported_abs = abs.exported\_s*' ..
+ '0 LOADSCRIPT exported-2 from .*Xexport_abs.vim\_s*' ..
+ '1 STOREG g:imported_abs\_s*' ..
+ 'abs.exported = 8888\_s*' ..
+ '2 PUSHNR 8888\_s*' ..
+ '3 STORESCRIPT exported-2 in .*Xexport_abs.vim\_s*' ..
+ 'g:imported_after = abs.exported\_s*' ..
+ '4 LOADSCRIPT exported-2 from .*Xexport_abs.vim\_s*' ..
+ '5 STOREG g:imported_after',
+ g:import_disassembled)
+
+ Undo_export_script_lines()
+ unlet g:imported_abs
+ unlet g:import_disassembled
+enddef
+
+def Test_import_rtp()
+ var import_lines = [
+ 'vim9script',
+ 'import "Xexport_rtp.vim" as rtp',
+ 'g:imported_rtp = rtp.exported',
+ ]
+ writefile(import_lines, 'Ximport_rtp.vim', 'D')
+ mkdir('import', 'pR')
+ writefile(s:export_script_lines, 'import/Xexport_rtp.vim')
+
+ var save_rtp = &rtp
+ &rtp = getcwd()
+ source Ximport_rtp.vim
+ &rtp = save_rtp
+
+ assert_equal(9876, g:imported_rtp)
+
+ Undo_export_script_lines()
+ unlet g:imported_rtp
+enddef
+
+def Test_import_compile_error()
+ var export_lines = [
+ 'vim9script',
+ 'export def ExpFunc(): string',
+ ' return notDefined',
+ 'enddef',
+ ]
+ writefile(export_lines, 'Xexported.vim', 'D')
+
+ var import_lines = [
+ 'vim9script',
+ 'import "./Xexported.vim" as expo',
+ 'def ImpFunc()',
+ ' echo expo.ExpFunc()',
+ 'enddef',
+ 'defcompile',
+ ]
+ writefile(import_lines, 'Ximport.vim', 'D')
+
+ try
+ source Ximport.vim
+ catch /E1001/
+ # Error should be before the Xexported.vim file.
+ assert_match('E1001: Variable not found: notDefined', v:exception)
+ assert_match('function <SNR>\d\+_ImpFunc\[1\]..<SNR>\d\+_ExpFunc, line 1', v:throwpoint)
+ endtry
+enddef
+
+def Test_func_overrules_import_fails()
+ var export_lines =<< trim END
+ vim9script
+ export def Func()
+ echo 'imported'
+ enddef
+ END
+ writefile(export_lines, 'XexportedFunc.vim', 'D')
+
+ var lines =<< trim END
+ vim9script
+ import './XexportedFunc.vim' as Func
+ def Func()
+ echo 'local to function'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1213: Redefining imported item "Func"')
+
+ lines =<< trim END
+ vim9script
+ import './XexportedFunc.vim' as Func
+ def Outer()
+ def Func()
+ echo 'local to function'
+ enddef
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1236:')
+enddef
+
+def Test_source_vim9_from_legacy()
+ var vim9_lines =<< trim END
+ vim9script
+ var local = 'local'
+ g:global = 'global'
+ export var exported = 'exported'
+ export def GetText(): string
+ return 'text'
+ enddef
+ END
+ writefile(vim9_lines, 'Xvim9_script.vim', 'D')
+
+ var legacy_lines =<< trim END
+ source Xvim9_script.vim
+
+ call assert_false(exists('local'))
+ call assert_false(exists('exported'))
+ call assert_false(exists('s:exported'))
+ call assert_equal('global', global)
+ call assert_equal('global', g:global)
+ END
+ writefile(legacy_lines, 'Xlegacy_script.vim', 'D')
+
+ source Xlegacy_script.vim
+ assert_equal('global', g:global)
+ unlet g:global
+
+ legacy_lines =<< trim END
+ import './Xvim9_script.vim'
+ let g:global = s:Xvim9_script.GetText()
+ END
+ writefile(legacy_lines, 'Xlegacyimport.vim', 'D')
+ source Xlegacyimport.vim
+ assert_equal('text', g:global)
+ unlet g:global
+enddef
+
+def Test_import_vim9_from_legacy()
+ var vim9_lines =<< trim END
+ vim9script
+ var local = 'local'
+ g:global = 'global'
+ export var exported = 'exported'
+ export def GetText(): string
+ return 'text'
+ enddef
+ END
+ writefile(vim9_lines, 'Xvim9_export.vim', 'D')
+
+ var legacy_lines =<< trim END
+ import './Xvim9_export.vim' as vim9
+
+ call assert_false(exists('vim9'))
+ call assert_false(exists('local'))
+ call assert_false(exists('s:vim9.local'))
+ call assert_equal('global', global)
+ call assert_equal('global', g:global)
+ call assert_false(exists('exported'))
+ call assert_false(exists('s:exported'))
+ call assert_false(exists('*GetText'))
+
+ " imported symbol is script-local
+ call assert_equal('exported', s:vim9.exported)
+ call assert_equal('text', s:vim9.GetText())
+ END
+ writefile(legacy_lines, 'Xlegacy_script.vim', 'D')
+
+ source Xlegacy_script.vim
+ assert_equal('global', g:global)
+ unlet g:global
+enddef
+
+def Test_cmdline_win()
+ # if the Vim syntax highlighting uses Vim9 constructs they can be used from
+ # the command line window.
+ mkdir('rtp/syntax', 'pR')
+ var export_lines =<< trim END
+ vim9script
+ export var That = 'yes'
+ END
+ writefile(export_lines, 'rtp/syntax/Xexport.vim')
+ var import_lines =<< trim END
+ vim9script
+ import './Xexport.vim' as exp
+ echo exp.That
+ END
+ writefile(import_lines, 'rtp/syntax/vim.vim')
+ var save_rtp = &rtp
+ &rtp = getcwd() .. '/rtp' .. ',' .. &rtp
+ syntax on
+ augroup CmdWin
+ autocmd CmdwinEnter * g:got_there = 'yes'
+ augroup END
+ # this will open and also close the cmdline window
+ feedkeys('q:', 'xt')
+ assert_equal('yes', g:got_there)
+
+ augroup CmdWin
+ au!
+ augroup END
+ &rtp = save_rtp
+enddef
+
+def Test_import_gone_when_sourced_twice()
+ var exportlines =<< trim END
+ vim9script
+ if exists('g:guard')
+ finish
+ endif
+ g:guard = 1
+ export var name = 'someName'
+ END
+ writefile(exportlines, 'XexportScript.vim', 'D')
+
+ var lines =<< trim END
+ vim9script
+ import './XexportScript.vim' as expo
+ def g:GetName(): string
+ return expo.name
+ enddef
+ END
+ writefile(lines, 'XscriptImport.vim', 'D')
+ so XscriptImport.vim
+ assert_equal('someName', g:GetName())
+
+ so XexportScript.vim
+ assert_fails('call g:GetName()', 'E1149:')
+
+ delfunc g:GetName
+ unlet g:guard
+enddef
+
+" test using an auto-loaded function and variable
+def Test_vim9_autoload_full_name()
+ var lines =<< trim END
+ vim9script
+ export def Gettest(): string
+ return 'test'
+ enddef
+ g:some#name = 'name'
+ g:some#dict = {key: 'value'}
+
+ export def Varargs(a1: string, ...l: list<string>): string
+ return a1 .. l[0] .. l[1]
+ enddef
+ END
+
+ mkdir('Xfulldir/autoload', 'pR')
+ writefile(lines, 'Xfulldir/autoload/some.vim')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xfulldir'
+
+ assert_equal('test', g:some#Gettest())
+ assert_equal('name', g:some#name)
+ assert_equal('value', g:some#dict.key)
+ g:some#other = 'other'
+ assert_equal('other', g:some#other)
+
+ assert_equal('abc', some#Varargs('a', 'b', 'c'))
+
+ # upper case script name works
+ lines =<< trim END
+ vim9script
+ export def GetOther(): string
+ return 'other'
+ enddef
+ END
+ writefile(lines, 'Xfulldir/autoload/Other.vim')
+ assert_equal('other', g:Other#GetOther())
+
+ &rtp = save_rtp
+enddef
+
+def Test_vim9script_autoload()
+ mkdir('Xaldir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xaldir'
+
+ # when the path has "/autoload/" prefix is not needed
+ var lines =<< trim END
+ vim9script
+ g:prefixed_loaded += 1
+
+ export def Gettest(): string
+ return 'test'
+ enddef
+
+ export var name = 'name'
+
+ export func GetFunc()
+ return Gettest() .. 'more' .. s:name
+ endfunc
+
+ export def GetDef(): string
+ return Gettest() .. 'more' .. name
+ enddef
+
+ export final fname = 'final'
+ export const cname = 'const'
+ END
+ writefile(lines, 'Xaldir/autoload/prefixed.vim')
+
+ g:prefixed_loaded = 0
+ g:expected_loaded = 0
+ lines =<< trim END
+ vim9script
+ import autoload 'prefixed.vim'
+ assert_equal(g:expected_loaded, g:prefixed_loaded)
+ assert_equal('test', prefixed.Gettest())
+ assert_equal(1, g:prefixed_loaded)
+
+ assert_equal('testmorename', prefixed.GetFunc())
+ assert_equal('testmorename', prefixed.GetDef())
+ assert_equal('name', prefixed.name)
+ assert_equal('final', prefixed.fname)
+ assert_equal('const', prefixed.cname)
+ END
+ v9.CheckScriptSuccess(lines)
+ # can source it again, autoload script not loaded again
+ g:expected_loaded = 1
+ v9.CheckScriptSuccess(lines)
+
+ # can also get the items by autoload name
+ lines =<< trim END
+ call assert_equal('test', prefixed#Gettest())
+ call assert_equal('testmorename', prefixed#GetFunc())
+ call assert_equal('name', prefixed#name)
+ call assert_equal('final', prefixed#fname)
+ call assert_equal('const', prefixed#cname)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ unlet g:prefixed_loaded
+ unlet g:expected_loaded
+ &rtp = save_rtp
+enddef
+
+def Test_import_autoload_not_exported()
+ mkdir('Xnimdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xnimdir'
+
+ # error when using an item that is not exported from an autoload script
+ var exportLines =<< trim END
+ vim9script
+ var notExported = 123
+ def NotExport()
+ echo 'nop'
+ enddef
+ END
+ writefile(exportLines, 'Xnimdir/autoload/notExport1.vim')
+
+ var lines =<< trim END
+ vim9script
+ import autoload 'notExport1.vim'
+ echo notExport1.notFound
+ END
+ v9.CheckScriptFailure(lines, 'E1048: Item not found in script: notFound')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport1.vim'
+ echo notExport1.notExported
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: notExported')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport1.vim'
+ echo notExport1.NotFunc()
+ END
+ v9.CheckScriptFailure(lines, 'E1048: Item not found in script: NotFunc')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport1.vim'
+ echo notExport1.NotExport()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: NotExport')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport1.vim'
+ echo 'text'->notExport1.NotFunc()
+ END
+ v9.CheckScriptFailure(lines, 'E1048: Item not found in script: NotFunc')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport1.vim'
+ echo 'text'->notExport1.NotExport()
+ END
+ v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: NotExport')
+
+ # using a :def function we use a different autoload script every time so that
+ # the function is compiled without the script loaded
+ writefile(exportLines, 'Xnimdir/autoload/notExport2.vim')
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport2.vim'
+ def Testit()
+ echo notExport2.notFound
+ enddef
+ Testit()
+ END
+ v9.CheckScriptFailure(lines, 'E1048: Item not found in script: notExport2#notFound')
+
+ writefile(exportLines, 'Xnimdir/autoload/notExport3.vim')
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport3.vim'
+ def Testit()
+ echo notExport3.notExported
+ enddef
+ Testit()
+ END
+ # don't get E1049 because it is too complicated to figure out
+ v9.CheckScriptFailure(lines, 'E1048: Item not found in script: notExport3#notExported')
+
+ writefile(exportLines, 'Xnimdir/autoload/notExport4.vim')
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport4.vim'
+ def Testit()
+ echo notExport4.NotFunc()
+ enddef
+ Testit()
+ END
+ v9.CheckScriptFailure(lines, 'E117: Unknown function: notExport4#NotFunc')
+
+ writefile(exportLines, 'Xnimdir/autoload/notExport5.vim')
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport5.vim'
+ def Testit()
+ echo notExport5.NotExport()
+ enddef
+ Testit()
+ END
+ v9.CheckScriptFailure(lines, 'E117: Unknown function: notExport5#NotExport')
+
+ writefile(exportLines, 'Xnimdir/autoload/notExport6.vim')
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport6.vim'
+ def Testit()
+ echo 'text'->notExport6.NotFunc()
+ enddef
+ Testit()
+ END
+ v9.CheckScriptFailure(lines, 'E117: Unknown function: notExport6#NotFunc')
+
+ writefile(exportLines, 'Xnimdir/autoload/notExport7.vim')
+ lines =<< trim END
+ vim9script
+ import autoload 'notExport7.vim'
+ def Testit()
+ echo 'text'->notExport7.NotExport()
+ enddef
+ Testit()
+ END
+ v9.CheckScriptFailure(lines, 'E117: Unknown function: notExport7#NotExport')
+
+ &rtp = save_rtp
+enddef
+
+def Test_vim9script_autoload_call()
+ mkdir('Xcalldir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xcalldir'
+
+ var lines =<< trim END
+ vim9script
+
+ export def RetArg(arg: string): string
+ return arg
+ enddef
+
+ export def Getother()
+ g:result = 'other'
+ enddef
+ END
+ writefile(lines, 'Xcalldir/autoload/another.vim')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'another.vim'
+
+ # compile this before 'another.vim' is loaded
+ def CallAnother()
+ assert_equal('foo', 'foo'->another.RetArg())
+ enddef
+ CallAnother()
+
+ call another.Getother()
+ assert_equal('other', g:result)
+
+ assert_equal('arg', call('another.RetArg', ['arg']))
+
+ verbose function another.Getother
+ # should we disallow this?
+ verbose function another#Getother
+ END
+ v9.CheckScriptSuccess(lines)
+
+ unlet g:result
+ &rtp = save_rtp
+enddef
+
+def Test_vim9script_noclear_autoload()
+ mkdir('Xnocdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xnocdir'
+
+ var lines =<< trim END
+ vim9script
+ export def Func(): string
+ return 'called'
+ enddef
+ g:double_loaded = 'yes'
+ END
+ writefile(lines, 'Xnocdir/autoload/double.vim')
+
+ lines =<< trim END
+ vim9script noclear
+ if exists('g:script_loaded')
+ finish
+ endif
+ g:script_loaded = true
+
+ import autoload 'double.vim'
+ nnoremap <F3> <ScriptCmd>g:result = double.Func()<CR>
+ END
+ g:double_loaded = 'no'
+ writefile(lines, 'Xloaddouble', 'D')
+ source Xloaddouble
+ assert_equal('no', g:double_loaded)
+ assert_equal(true, g:script_loaded)
+ source Xloaddouble
+ feedkeys("\<F3>", 'xt')
+ assert_equal('called', g:result)
+ assert_equal('yes', g:double_loaded)
+
+ unlet g:double_loaded
+ unlet g:script_loaded
+ unlet g:result
+ &rtp = save_rtp
+enddef
+
+def Test_vim9script_autoload_duplicate()
+ mkdir('Xdupdir/autoload', 'pR')
+
+ var lines =<< trim END
+ vim9script
+
+ export def Func()
+ enddef
+
+ def Func()
+ enddef
+ END
+ writefile(lines, 'Xdupdir/autoload/dupfunc.vim')
+ assert_fails('source Xdupdir/autoload/dupfunc.vim', 'E1073:')
+
+ lines =<< trim END
+ vim9script
+
+ def Func()
+ enddef
+
+ export def Func()
+ enddef
+ END
+ writefile(lines, 'Xdupdir/autoload/dup2func.vim')
+ assert_fails('source Xdupdir/autoload/dup2func.vim', 'E1073:')
+
+ lines =<< trim END
+ vim9script
+
+ def Func()
+ enddef
+
+ export var Func = 'asdf'
+ END
+ writefile(lines, 'Xdupdir/autoload/dup3func.vim')
+ assert_fails('source Xdupdir/autoload/dup3func.vim', 'E1041: Redefining script item: "Func"')
+
+ lines =<< trim END
+ vim9script
+
+ export var Func = 'asdf'
+
+ def Func()
+ enddef
+ END
+ writefile(lines, 'Xdupdir/autoload/dup4func.vim')
+ assert_fails('source Xdupdir/autoload/dup4func.vim', 'E1041:')
+
+ lines =<< trim END
+ vim9script
+
+ var Func = 'asdf'
+
+ export def Func()
+ enddef
+ END
+ writefile(lines, 'Xdupdir/autoload/dup5func.vim')
+ assert_fails('source Xdupdir/autoload/dup5func.vim', 'E707:')
+
+ lines =<< trim END
+ vim9script
+
+ export def Func()
+ enddef
+
+ var Func = 'asdf'
+ END
+ writefile(lines, 'Xdupdir/autoload/dup6func.vim')
+ assert_fails('source Xdupdir/autoload/dup6func.vim', 'E1041: Redefining script item: "Func"')
+enddef
+
+def Test_autoload_missing_function_name()
+ mkdir('Xmisdir/autoload', 'pR')
+
+ var lines =<< trim END
+ vim9script
+
+ def loadme#()
+ enddef
+ END
+ writefile(lines, 'Xmisdir/autoload/loadme.vim')
+ assert_fails('source Xmisdir/autoload/loadme.vim', 'E129:')
+enddef
+
+def Test_autoload_name_wrong()
+ var lines =<< trim END
+ def Xscriptname#Func()
+ enddef
+ END
+ writefile(lines, 'Xscriptname.vim', 'D')
+ v9.CheckScriptFailure(lines, 'E746:')
+
+ mkdir('Xwrodir/autoload', 'pR')
+ lines =<< trim END
+ vim9script
+ def somescript#Func()
+ enddef
+ END
+ writefile(lines, 'Xwrodir/autoload/somescript.vim')
+ assert_fails('source Xwrodir/autoload/somescript.vim', 'E1263:')
+
+ delete('Xwrodir', 'rf')
+enddef
+
+def Test_import_autoload_postponed()
+ mkdir('Xpostdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xpostdir'
+
+ var lines =<< trim END
+ vim9script
+
+ g:loaded_postponed = 'true'
+ export var variable = 'bla'
+ export def Function(): string
+ return 'bla'
+ enddef
+ END
+ writefile(lines, 'Xpostdir/autoload/postponed.vim')
+
+ lines =<< trim END
+ vim9script
+
+ import autoload 'postponed.vim'
+ def Tryit()
+ echo postponed.variable
+ echo postponed.Function()
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_false(exists('g:loaded_postponed'))
+ v9.CheckScriptSuccess(lines + ['Tryit()'])
+ assert_equal('true', g:loaded_postponed)
+
+ unlet g:loaded_postponed
+ &rtp = save_rtp
+enddef
+
+def Test_import_autoload_override()
+ mkdir('Xoverdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xoverdir'
+ test_override('autoload', 1)
+
+ var lines =<< trim END
+ vim9script
+
+ g:loaded_override = 'true'
+ export var variable = 'bla'
+ export def Function(): string
+ return 'bla'
+ enddef
+ END
+ writefile(lines, 'Xoverdir/autoload/override.vim')
+
+ lines =<< trim END
+ vim9script
+
+ import autoload 'override.vim'
+ assert_equal('true', g:loaded_override)
+
+ def Tryit()
+ echo override.doesNotExist
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1048: Item not found in script: doesNotExist', 1)
+
+ test_override('autoload', 0)
+ unlet g:loaded_override
+ &rtp = save_rtp
+enddef
+
+def Test_autoload_mapping()
+ mkdir('Xmapdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xmapdir'
+
+ var lines =<< trim END
+ vim9script
+
+ g:toggle_loaded = 'yes'
+
+ export def Toggle(): string
+ return ":g:toggle_called = 'yes'\<CR>"
+ enddef
+ export def Doit()
+ g:doit_called = 'yes'
+ enddef
+ END
+ writefile(lines, 'Xmapdir/autoload/toggle.vim')
+
+ lines =<< trim END
+ vim9script
+
+ import autoload 'toggle.vim'
+
+ nnoremap <silent> <expr> tt toggle.Toggle()
+ nnoremap <silent> xx <ScriptCmd>toggle.Doit()<CR>
+ nnoremap <silent> yy <Cmd>toggle.Doit()<CR>
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_false(exists("g:toggle_loaded"))
+ assert_false(exists("g:toggle_called"))
+ assert_match('\d A: \f*[/\\]toggle.vim', execute('scriptnames'))
+
+ feedkeys("tt", 'xt')
+ assert_equal('yes', g:toggle_loaded)
+ assert_equal('yes', g:toggle_called)
+ assert_match('\d: \f*[/\\]toggle.vim', execute('scriptnames'))
+
+ feedkeys("xx", 'xt')
+ assert_equal('yes', g:doit_called)
+
+ assert_fails('call feedkeys("yy", "xt")', 'E121: Undefined variable: toggle')
+
+ nunmap tt
+ nunmap xx
+ nunmap yy
+ unlet g:toggle_loaded
+ unlet g:toggle_called
+ &rtp = save_rtp
+enddef
+
+def Test_vim9script_autoload_fails()
+ var lines =<< trim END
+ vim9script autoload
+ var n = 0
+ END
+ v9.CheckScriptFailure(lines, 'E475: Invalid argument: autoload')
+
+ lines =<< trim END
+ vim9script noclear noclear
+ var n = 0
+ END
+ v9.CheckScriptFailure(lines, 'E983: Duplicate argument: noclear')
+
+ lines =<< trim END
+ vim9script noclears
+ var n = 0
+ END
+ v9.CheckScriptFailure(lines, 'E475: Invalid argument: noclears')
+enddef
+
+def Test_import_autoload_fails()
+ var lines =<< trim END
+ vim9script
+ import autoload autoload 'prefixed.vim'
+ END
+ v9.CheckScriptFailure(lines, 'E121: Undefined variable: autoload')
+
+ lines =<< trim END
+ vim9script
+ import autoload './doesNotExist.vim'
+ END
+ v9.CheckScriptFailure(lines, 'E282:', 2)
+
+ lines =<< trim END
+ vim9script
+ import autoload '/dir/doesNotExist.vim'
+ END
+ v9.CheckScriptFailure(lines, 'E282:', 2)
+
+ lines =<< trim END
+ vim9script
+ import autoload '../testdir'
+ END
+ v9.CheckScriptFailure(lines, 'E17:', 2)
+
+ lines =<< trim END
+ vim9script
+ import autoload 'doesNotExist.vim'
+ END
+ v9.CheckScriptFailure(lines, 'E1053: Could not import "doesNotExist.vim"')
+enddef
+
+" test disassembling an auto-loaded function starting with "debug"
+def Test_vim9_autoload_disass()
+ mkdir('Xdasdir/autoload', 'pR')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xdasdir'
+
+ var lines =<< trim END
+ vim9script
+ export def Test(): string
+ return 'debug'
+ enddef
+ END
+ writefile(lines, 'Xdasdir/autoload/debugit.vim')
+
+ lines =<< trim END
+ vim9script
+ export def Test(): string
+ return 'profile'
+ enddef
+ END
+ writefile(lines, 'Xdasdir/autoload/profileit.vim')
+
+ lines =<< trim END
+ vim9script
+ assert_equal('debug', debugit#Test())
+ disass debugit#Test
+ assert_equal('profile', profileit#Test())
+ disass profileit#Test
+ END
+ v9.CheckScriptSuccess(lines)
+
+ &rtp = save_rtp
+enddef
+
+" test using a vim9script that is auto-loaded from an autocmd
+def Test_vim9_aucmd_autoload()
+ var lines =<< trim END
+ vim9script
+ export def Test()
+ echomsg getreg('"')
+ enddef
+ END
+
+ mkdir('Xauldir/autoload', 'pR')
+ writefile(lines, 'Xauldir/autoload/foo.vim')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xauldir'
+ augroup test
+ autocmd TextYankPost * call foo#Test()
+ augroup END
+
+ normal Y
+
+ augroup test
+ autocmd!
+ augroup END
+ &rtp = save_rtp
+enddef
+
+" test using a autoloaded file that is case sensitive
+def Test_vim9_autoload_case_sensitive()
+ var lines =<< trim END
+ vim9script
+ export def CaseSensitive(): string
+ return 'done'
+ enddef
+ END
+
+ mkdir('Xcasedir/autoload', 'pR')
+ writefile(lines, 'Xcasedir/autoload/CaseSensitive.vim')
+ var save_rtp = &rtp
+ exe 'set rtp^=' .. getcwd() .. '/Xcasedir'
+
+ lines =<< trim END
+ vim9script
+ import autoload 'CaseSensitive.vim'
+ assert_equal('done', CaseSensitive.CaseSensitive())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ if !has('fname_case')
+ lines =<< trim END
+ vim9script
+ import autoload 'CaseSensitive.vim'
+ import autoload 'casesensitive.vim'
+ END
+ v9.CheckScriptFailure(lines, 'E1262:')
+ endif
+
+ &rtp = save_rtp
+enddef
+
+" This was causing a crash because suppress_errthrow wasn't reset.
+def Test_vim9_autoload_error()
+ var lines =<< trim END
+ vim9script
+ def crash#func()
+ try
+ for x in List()
+ endfor
+ catch
+ endtry
+ g:ok = true
+ enddef
+ fu List()
+ invalid
+ endfu
+ try
+ alsoinvalid
+ catch /wontmatch/
+ endtry
+ END
+ call mkdir('Xruntime/autoload', 'pR')
+ call writefile(lines, 'Xruntime/autoload/crash.vim')
+
+ # run in a separate Vim to avoid the side effects of assert_fails()
+ lines =<< trim END
+ exe 'set rtp^=' .. getcwd() .. '/Xruntime'
+ call crash#func()
+ call writefile(['ok'], 'Xdidit')
+ qall!
+ END
+ writefile(lines, 'Xscript', 'D')
+ g:RunVim([], [], '-S Xscript')
+ assert_equal(['ok'], readfile('Xdidit'))
+
+ delete('Xdidit')
+
+ lines =<< trim END
+ vim9script
+ var foo#bar = 'asdf'
+ END
+ v9.CheckScriptFailure(lines, 'E461: Illegal variable name: foo#bar', 2)
+enddef
+
+def Test_vim9_import_symlink()
+ if !has('unix')
+ CheckUnix
+ else
+ mkdir('Xto/plugin', 'pR')
+ var lines =<< trim END
+ vim9script
+ import autoload 'bar.vim'
+ g:resultFunc = bar.Func()
+ g:resultValue = bar.value
+ END
+ writefile(lines, 'Xto/plugin/foo.vim')
+
+ mkdir('Xto/autoload', 'pR')
+ lines =<< trim END
+ vim9script
+ export def Func(): string
+ return 'func'
+ enddef
+ export var value = 'val'
+ END
+ writefile(lines, 'Xto/autoload/bar.vim')
+
+ var save_rtp = &rtp
+ &rtp = getcwd() .. '/Xfrom'
+ system('ln -s ' .. getcwd() .. '/Xto Xfrom')
+
+ source Xfrom/plugin/foo.vim
+ assert_equal('func', g:resultFunc)
+ assert_equal('val', g:resultValue)
+
+ var infoTo = getscriptinfo()->filter((_, v) => v.name =~ 'Xto/autoload/bar')
+ var infoFrom = getscriptinfo()->filter((_, v) => v.name =~ 'Xfrom/autoload/bar')
+ assert_equal(1, len(infoTo))
+ assert_equal(1, len(infoFrom))
+ assert_equal(infoTo[0].sid, infoFrom[0].sourced)
+ var output: string
+ redir => output
+ scriptnames
+ redir END
+ assert_match(infoFrom[0].sid .. '->' .. infoFrom[0].sourced .. '.*Xfrom', output)
+
+ unlet g:resultFunc
+ unlet g:resultValue
+ &rtp = save_rtp
+ delete('Xfrom', 'rf')
+ endif
+enddef
+
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
new file mode 100644
index 0000000..3d5148e
--- /dev/null
+++ b/src/testdir/test_vim9_script.vim
@@ -0,0 +1,4573 @@
+" Test various aspects of the Vim9 script language.
+
+source check.vim
+source term_util.vim
+import './vim9.vim' as v9
+source screendump.vim
+source shared.vim
+
+def Test_vim9script_feature()
+ # example from the help, here the feature is always present
+ var lines =<< trim END
+ " old style comment
+ if !has('vim9script')
+ " legacy commands would go here
+ finish
+ endif
+ vim9script
+ # Vim9 script commands go here
+ g:didit = true
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal(true, g:didit)
+ unlet g:didit
+enddef
+
+def Test_range_only()
+ new
+ setline(1, ['blah', 'Blah'])
+ :/Blah/
+ assert_equal(2, getcurpos()[1])
+ bwipe!
+
+ # without range commands use current line
+ new
+ setline(1, ['one', 'two', 'three'])
+ :2
+ print
+ assert_equal('two', g:Screenline(&lines))
+ :3
+ list
+ assert_equal('three$', g:Screenline(&lines))
+
+ # missing command does not print the line
+ var lines =<< trim END
+ vim9script
+ :1|
+ assert_equal('three$', g:Screenline(&lines))
+ :|
+ assert_equal('three$', g:Screenline(&lines))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ bwipe!
+
+ lines =<< trim END
+ set cpo+=-
+ :1,999
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E16:', 2)
+ set cpo&vim
+
+ v9.CheckDefExecAndScriptFailure([":'x"], 'E20:', 1)
+
+ # won't generate anything
+ if false
+ :123
+ endif
+enddef
+
+def Test_invalid_range()
+ var lines =<< trim END
+ :123 eval 1 + 2
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E481:', 1)
+
+ lines =<< trim END
+ :123 if true
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E481:', 1)
+
+ lines =<< trim END
+ :123 echo 'yes'
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E481:', 1)
+
+ lines =<< trim END
+ :123 cd there
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E481:', 1)
+enddef
+
+let g:alist = [7]
+let g:astring = 'text'
+let g:anumber = 123
+
+def Test_delfunction()
+ # Check function is defined in script namespace
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'func CheckMe()',
+ ' return 123',
+ 'endfunc',
+ 'func DoTest()',
+ ' call assert_equal(123, s:CheckMe())',
+ 'endfunc',
+ 'DoTest()',
+ ])
+
+ # Check function in script namespace cannot be deleted
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'func DeleteMe1()',
+ 'endfunc',
+ 'delfunction DeleteMe1',
+ ], 'E1084:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'func DeleteMe2()',
+ 'endfunc',
+ 'def DoThat()',
+ ' delfunction DeleteMe2',
+ 'enddef',
+ 'DoThat()',
+ ], 'E1084:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'def DeleteMe3()',
+ 'enddef',
+ 'delfunction DeleteMe3',
+ ], 'E1084:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'def DeleteMe4()',
+ 'enddef',
+ 'def DoThat()',
+ ' delfunction DeleteMe4',
+ 'enddef',
+ 'DoThat()',
+ ], 'E1084:')
+
+ # Check that global :def function can be replaced and deleted
+ var lines =<< trim END
+ vim9script
+ def g:Global(): string
+ return "yes"
+ enddef
+ assert_equal("yes", g:Global())
+ def! g:Global(): string
+ return "no"
+ enddef
+ assert_equal("no", g:Global())
+ delfunc g:Global
+ assert_false(exists('*g:Global'))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # Check that global function can be replaced by a :def function and deleted
+ lines =<< trim END
+ vim9script
+ func g:Global()
+ return "yes"
+ endfunc
+ assert_equal("yes", g:Global())
+ def! g:Global(): string
+ return "no"
+ enddef
+ assert_equal("no", g:Global())
+ delfunc g:Global
+ assert_false(exists('*g:Global'))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # Check that global :def function can be replaced by a function and deleted
+ lines =<< trim END
+ vim9script
+ def g:Global(): string
+ return "yes"
+ enddef
+ assert_equal("yes", g:Global())
+ func! g:Global()
+ return "no"
+ endfunc
+ assert_equal("no", g:Global())
+ delfunc g:Global
+ assert_false(exists('*g:Global'))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_wrong_type()
+ v9.CheckDefFailure(['var name: list<nothing>'], 'E1010:')
+ v9.CheckDefFailure(['var name: list<list<nothing>>'], 'E1010:')
+ v9.CheckDefFailure(['var name: dict<nothing>'], 'E1010:')
+ v9.CheckDefFailure(['var name: dict<dict<nothing>>'], 'E1010:')
+
+ v9.CheckDefFailure(['var name: dict<number'], 'E1009:')
+ v9.CheckDefFailure(['var name: dict<list<number>'], 'E1009:')
+
+ v9.CheckDefFailure(['var name: ally'], 'E1010:')
+ v9.CheckDefFailure(['var name: bram'], 'E1010:')
+ v9.CheckDefFailure(['var name: cathy'], 'E1010:')
+ v9.CheckDefFailure(['var name: dom'], 'E1010:')
+ v9.CheckDefFailure(['var name: freddy'], 'E1010:')
+ v9.CheckDefFailure(['var name: john'], 'E1010:')
+ v9.CheckDefFailure(['var name: larry'], 'E1010:')
+ v9.CheckDefFailure(['var name: ned'], 'E1010:')
+ v9.CheckDefFailure(['var name: pam'], 'E1010:')
+ v9.CheckDefFailure(['var name: sam'], 'E1010:')
+ v9.CheckDefFailure(['var name: vim'], 'E1010:')
+
+ v9.CheckDefFailure(['var Ref: number', 'Ref()'], 'E1085:')
+ v9.CheckDefFailure(['var Ref: string', 'var res = Ref()'], 'E1085:')
+enddef
+
+def Test_script_namespace()
+ # defining a function or variable with s: is not allowed
+ var lines =<< trim END
+ vim9script
+ def s:Function()
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+
+ for decl in ['var', 'const', 'final']
+ lines =<< trim END
+ vim9script
+ var s:var = 'var'
+ END
+ v9.CheckScriptFailure([
+ 'vim9script',
+ decl .. ' s:var = "var"',
+ ], 'E1268:')
+ endfor
+
+ # Calling a function or using a variable with s: is not allowed at script
+ # level
+ lines =<< trim END
+ vim9script
+ def Function()
+ enddef
+ s:Function()
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+ lines =<< trim END
+ vim9script
+ def Function()
+ enddef
+ call s:Function()
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+ lines =<< trim END
+ vim9script
+ var var = 'var'
+ echo s:var
+ END
+ v9.CheckScriptFailure(lines, 'E1268:')
+enddef
+
+def Test_script_wrong_type()
+ var lines =<< trim END
+ vim9script
+ var dict: dict<string>
+ dict['a'] = ['x']
+ END
+ v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got list<string>', 3)
+enddef
+
+def Test_const()
+ v9.CheckDefFailure(['final name = 234', 'name = 99'], 'E1018:')
+ v9.CheckDefFailure(['final one = 234', 'var one = 99'], 'E1017:')
+ v9.CheckDefFailure(['final list = [1, 2]', 'var list = [3, 4]'], 'E1017:')
+ v9.CheckDefFailure(['final two'], 'E1125:')
+ v9.CheckDefFailure(['final &option'], 'E996:')
+
+ var lines =<< trim END
+ final list = [1, 2, 3]
+ list[0] = 4
+ list->assert_equal([4, 2, 3])
+ const other = [5, 6, 7]
+ other->assert_equal([5, 6, 7])
+
+ var varlist = [7, 8]
+ const constlist = [1, varlist, 3]
+ varlist[0] = 77
+ constlist[1][1] = 88
+ var cl = constlist[1]
+ cl[1] = 88
+ constlist->assert_equal([1, [77, 88], 3])
+
+ var vardict = {five: 5, six: 6}
+ const constdict = {one: 1, two: vardict, three: 3}
+ vardict['five'] = 55
+ constdict['two']['six'] = 66
+ var cd = constdict['two']
+ cd['six'] = 66
+ constdict->assert_equal({one: 1, two: {five: 55, six: 66}, three: 3})
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # "any" type with const flag is recognized as "any"
+ lines =<< trim END
+ const dict: dict<any> = {foo: {bar: 42}}
+ const foo = dict.foo
+ assert_equal(v:t_number, type(foo.bar))
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # also when used as a builtin function argument
+ lines =<< trim END
+ vim9script
+
+ def SorterFunc(lhs: dict<string>, rhs: dict<string>): number
+ return lhs.name <# rhs.name ? -1 : 1
+ enddef
+
+ def Run(): void
+ var list = [{name: "3"}, {name: "2"}]
+ const Sorter = get({}, "unknown", SorterFunc)
+ sort(list, Sorter)
+ assert_equal([{name: "2"}, {name: "3"}], list)
+ enddef
+
+ Run()
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_const_bang()
+ var lines =<< trim END
+ const var = 234
+ var = 99
+ END
+ v9.CheckDefExecFailure(lines, 'E1018:', 2)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E46:', 3)
+
+ lines =<< trim END
+ const ll = [2, 3, 4]
+ ll[0] = 99
+ END
+ v9.CheckDefExecFailure(lines, 'E1119:', 2)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
+
+ lines =<< trim END
+ const ll = [2, 3, 4]
+ ll[3] = 99
+ END
+ v9.CheckDefExecFailure(lines, 'E1118:', 2)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E684:', 3)
+
+ lines =<< trim END
+ const dd = {one: 1, two: 2}
+ dd["one"] = 99
+ END
+ v9.CheckDefExecFailure(lines, 'E1121:', 2)
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
+
+ lines =<< trim END
+ const dd = {one: 1, two: 2}
+ dd["three"] = 99
+ END
+ v9.CheckDefExecFailure(lines, 'E1120:')
+ v9.CheckScriptFailure(['vim9script'] + lines, 'E741:', 3)
+enddef
+
+def Test_range_no_colon()
+ v9.CheckDefFailure(['%s/a/b/'], 'E1050:')
+ v9.CheckDefFailure(['+ s/a/b/'], 'E1050:')
+ v9.CheckDefFailure(['- s/a/b/'], 'E1050:')
+ v9.CheckDefFailure(['. s/a/b/'], 'E1050:')
+enddef
+
+
+def Test_block()
+ var outer = 1
+ {
+ var inner = 2
+ assert_equal(1, outer)
+ assert_equal(2, inner)
+ }
+ assert_equal(1, outer)
+
+ {|echo 'yes'|}
+enddef
+
+def Test_block_failure()
+ v9.CheckDefFailure(['{', 'var inner = 1', '}', 'echo inner'], 'E1001:')
+ v9.CheckDefFailure(['}'], 'E1025:')
+ v9.CheckDefFailure(['{', 'echo 1'], 'E1026:')
+enddef
+
+def Test_block_local_vars()
+ var lines =<< trim END
+ vim9script
+ v:testing = 1
+ if true
+ var text = ['hello']
+ def SayHello(): list<string>
+ return text
+ enddef
+ def SetText(v: string)
+ text = [v]
+ enddef
+ endif
+
+ if true
+ var text = ['again']
+ def SayAgain(): list<string>
+ return text
+ enddef
+ endif
+
+ # test that the "text" variables are not cleaned up
+ test_garbagecollect_now()
+
+ defcompile
+
+ assert_equal(['hello'], SayHello())
+ assert_equal(['again'], SayAgain())
+
+ SetText('foobar')
+ assert_equal(['foobar'], SayHello())
+
+ call writefile(['ok'], 'Xdidit')
+ qall!
+ END
+
+ # need to execute this with a separate Vim instance to avoid the current
+ # context gets garbage collected.
+ writefile(lines, 'Xscript', 'D')
+ g:RunVim([], [], '-S Xscript')
+ assert_equal(['ok'], readfile('Xdidit'))
+
+ delete('Xdidit')
+enddef
+
+def Test_block_local_vars_with_func()
+ var lines =<< trim END
+ vim9script
+ if true
+ var foo = 'foo'
+ if true
+ var bar = 'bar'
+ def Func(): list<string>
+ return [foo, bar]
+ enddef
+ endif
+ endif
+ # function is compiled here, after blocks have finished, can still access
+ # "foo" and "bar"
+ assert_equal(['foo', 'bar'], Func())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+" legacy func for command that's defined later
+func s:InvokeSomeCommand()
+ SomeCommand
+endfunc
+
+def Test_autocommand_block()
+ com SomeCommand {
+ g:someVar = 'some'
+ }
+ InvokeSomeCommand()
+ assert_equal('some', g:someVar)
+
+ delcommand SomeCommand
+ unlet g:someVar
+enddef
+
+def Test_command_block()
+ au BufNew *.xml {
+ g:otherVar = 'other'
+ }
+ split other.xml
+ assert_equal('other', g:otherVar)
+
+ bwipe!
+ au! BufNew *.xml
+ unlet g:otherVar
+enddef
+
+func g:NoSuchFunc()
+ echo 'none'
+endfunc
+
+def Test_try_catch_throw()
+ var l = []
+ try # comment
+ add(l, '1')
+ throw 'wrong'
+ add(l, '2')
+ catch # comment
+ add(l, v:exception)
+ finally # comment
+ add(l, '3')
+ endtry # comment
+ assert_equal(['1', 'wrong', '3'], l)
+
+ l = []
+ try
+ try
+ add(l, '1')
+ throw 'wrong'
+ add(l, '2')
+ catch /right/
+ add(l, v:exception)
+ endtry
+ catch /wrong/
+ add(l, 'caught')
+ finally
+ add(l, 'finally')
+ endtry
+ assert_equal(['1', 'caught', 'finally'], l)
+
+ var n: number
+ try
+ n = l[3]
+ catch /E684:/
+ n = 99
+ endtry
+ assert_equal(99, n)
+
+ var done = 'no'
+ if 0
+ try | catch | endtry
+ else
+ done = 'yes'
+ endif
+ assert_equal('yes', done)
+
+ done = 'no'
+ if 1
+ done = 'yes'
+ else
+ try | catch | endtry
+ done = 'never'
+ endif
+ assert_equal('yes', done)
+
+ if 1
+ else
+ try | catch /pat/ | endtry
+ try | catch /pat/
+ endtry
+ try
+ catch /pat/ | endtry
+ try
+ catch /pat/
+ endtry
+ endif
+
+ try
+ # string slice returns a string, not a number
+ n = g:astring[3]
+ catch /E1012:/
+ n = 77
+ endtry
+ assert_equal(77, n)
+
+ try
+ n = l[g:astring]
+ catch /E1012:/
+ n = 88
+ endtry
+ assert_equal(88, n)
+
+ try
+ n = s:does_not_exist
+ catch /E121:/
+ n = 111
+ endtry
+ assert_equal(111, n)
+
+ try
+ n = g:does_not_exist
+ catch /E121:/
+ n = 121
+ endtry
+ assert_equal(121, n)
+
+ var d = {one: 1}
+ try
+ n = d[g:astring]
+ catch /E716:/
+ n = 222
+ endtry
+ assert_equal(222, n)
+
+ try
+ n = -g:astring
+ catch /E1012:/
+ n = 233
+ endtry
+ assert_equal(233, n)
+
+ try
+ n = +g:astring
+ catch /E1012:/
+ n = 244
+ endtry
+ assert_equal(244, n)
+
+ try
+ n = +g:alist
+ catch /E1012:/
+ n = 255
+ endtry
+ assert_equal(255, n)
+
+ var nd: dict<any>
+ try
+ nd = {[g:alist]: 1}
+ catch /E1105:/
+ n = 266
+ endtry
+ assert_equal(266, n)
+
+ l = [1, 2, 3]
+ try
+ [n] = l
+ catch /E1093:/
+ n = 277
+ endtry
+ assert_equal(277, n)
+
+ try
+ &ts = g:astring
+ catch /E1012:/
+ n = 288
+ endtry
+ assert_equal(288, n)
+
+ try
+ &backspace = 'asdf'
+ catch /E474:/
+ n = 299
+ endtry
+ assert_equal(299, n)
+
+ l = [1]
+ try
+ l[3] = 3
+ catch /E684:/
+ n = 300
+ endtry
+ assert_equal(300, n)
+
+ try
+ unlet g:does_not_exist
+ catch /E108:/
+ n = 322
+ endtry
+ assert_equal(322, n)
+
+ try
+ d = {text: 1, [g:astring]: 2}
+ catch /E721:/
+ n = 333
+ endtry
+ assert_equal(333, n)
+
+ try
+ l = g:DeletedFunc()
+ catch /E933:/
+ n = 344
+ endtry
+ assert_equal(344, n)
+
+ try
+ echo range(1, 2, 0)
+ catch /E726:/
+ n = 355
+ endtry
+ assert_equal(355, n)
+
+ var P = function('g:NoSuchFunc')
+ delfunc g:NoSuchFunc
+ try
+ echo P()
+ catch /E117:/
+ n = 366
+ endtry
+ assert_equal(366, n)
+
+ try
+ echo g:NoSuchFunc()
+ catch /E117:/
+ n = 377
+ endtry
+ assert_equal(377, n)
+
+ try
+ echo g:alist + 4
+ catch /E745:/
+ n = 388
+ endtry
+ assert_equal(388, n)
+
+ try
+ echo 4 + g:alist
+ catch /E745:/
+ n = 399
+ endtry
+ assert_equal(399, n)
+
+ try
+ echo g:alist.member
+ catch /E715:/
+ n = 400
+ endtry
+ assert_equal(400, n)
+
+ try
+ echo d.member
+ catch /E716:/
+ n = 411
+ endtry
+ assert_equal(411, n)
+
+ var counter = 0
+ for i in range(4)
+ try
+ eval [][0]
+ catch
+ endtry
+ counter += 1
+ endfor
+ assert_equal(4, counter)
+
+ # no requirement for spaces before |
+ try|echo 0|catch|endtry
+
+ # return in try with finally
+ def ReturnInTry(): number
+ var ret = 4
+ try
+ return ret
+ catch /this/
+ return -1
+ catch /that/
+ return -1
+ finally
+ # changing ret has no effect
+ ret = 7
+ endtry
+ return -2
+ enddef
+ assert_equal(4, ReturnInTry())
+
+ # return in catch with finally
+ def ReturnInCatch(): number
+ var ret = 5
+ try
+ throw 'getout'
+ return -1
+ catch /getout/
+ # ret is evaluated here
+ return ret
+ finally
+ # changing ret later has no effect
+ ret = -3
+ endtry
+ return -2
+ enddef
+ assert_equal(5, ReturnInCatch())
+
+ # return in finally after empty catch
+ def ReturnInFinally(): number
+ try
+ finally
+ return 6
+ endtry
+ enddef
+ assert_equal(6, ReturnInFinally())
+
+ var lines =<< trim END
+ vim9script
+ try
+ acos('0.5')
+ ->setline(1)
+ catch
+ g:caught = v:exception
+ endtry
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_match('E1219: Float or Number required for argument 1', g:caught)
+ unlet g:caught
+
+ # missing catch and/or finally
+ lines =<< trim END
+ vim9script
+ try
+ echo 'something'
+ endtry
+ END
+ v9.CheckScriptFailure(lines, 'E1032:')
+
+ # skipping try-finally-endtry when try-finally-endtry is used in another block
+ lines =<< trim END
+ if v:true
+ try
+ finally
+ endtry
+ else
+ try
+ finally
+ endtry
+ endif
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_try_var_decl()
+ var lines =<< trim END
+ vim9script
+ try
+ var in_try = 1
+ assert_equal(1, get(s:, 'in_try', -1))
+ throw "getout"
+ catch
+ var in_catch = 2
+ assert_equal(-1, get(s:, 'in_try', -1))
+ assert_equal(2, get(s:, 'in_catch', -1))
+ finally
+ var in_finally = 3
+ assert_equal(-1, get(s:, 'in_try', -1))
+ assert_equal(-1, get(s:, 'in_catch', -1))
+ assert_equal(3, get(s:, 'in_finally', -1))
+ endtry
+ assert_equal(-1, get(s:, 'in_try', -1))
+ assert_equal(-1, get(s:, 'in_catch', -1))
+ assert_equal(-1, get(s:, 'in_finally', -1))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_try_ends_in_return()
+ var lines =<< trim END
+ vim9script
+ def Foo(): string
+ try
+ return 'foo'
+ catch
+ return 'caught'
+ endtry
+ enddef
+ assert_equal('foo', Foo())
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def Foo(): string
+ try
+ return 'foo'
+ catch
+ return 'caught'
+ endtry
+ echo 'notreached'
+ enddef
+ assert_equal('foo', Foo())
+ END
+ v9.CheckScriptFailure(lines, 'E1095:')
+
+ lines =<< trim END
+ vim9script
+ def Foo(): string
+ try
+ return 'foo'
+ catch /x/
+ return 'caught'
+ endtry
+ enddef
+ assert_equal('foo', Foo())
+ END
+ v9.CheckScriptFailure(lines, 'E1027:')
+
+ lines =<< trim END
+ vim9script
+ def Foo(): string
+ try
+ echo 'foo'
+ catch
+ echo 'caught'
+ finally
+ return 'done'
+ endtry
+ enddef
+ assert_equal('done', Foo())
+ END
+ v9.CheckScriptSuccess(lines)
+
+enddef
+
+def Test_try_in_catch()
+ var lines =<< trim END
+ vim9script
+ var seq = []
+ def DoIt()
+ try
+ seq->add('throw 1')
+ eval [][0]
+ seq->add('notreached')
+ catch
+ seq->add('catch')
+ try
+ seq->add('throw 2')
+ eval [][0]
+ seq->add('notreached')
+ catch /nothing/
+ seq->add('notreached')
+ endtry
+ seq->add('done')
+ endtry
+ enddef
+ DoIt()
+ assert_equal(['throw 1', 'catch', 'throw 2', 'done'], seq)
+ END
+enddef
+
+def Test_error_in_catch()
+ var lines =<< trim END
+ try
+ eval [][0]
+ catch /E684:/
+ eval [][0]
+ endtry
+ END
+ v9.CheckDefExecFailure(lines, 'E684:', 4)
+enddef
+
+" :while at the very start of a function that :continue jumps to
+def s:TryContinueFunc()
+ while g:Count < 2
+ g:sequence ..= 't'
+ try
+ echoerr 'Test'
+ catch
+ g:Count += 1
+ g:sequence ..= 'c'
+ continue
+ endtry
+ g:sequence ..= 'e'
+ g:Count += 1
+ endwhile
+enddef
+
+def Test_continue_in_try_in_while()
+ g:Count = 0
+ g:sequence = ''
+ TryContinueFunc()
+ assert_equal('tctc', g:sequence)
+ unlet g:Count
+ unlet g:sequence
+enddef
+
+def Test_break_in_try_in_for()
+ var lines =<< trim END
+ vim9script
+ def Ls(): list<string>
+ var ls: list<string>
+ for s in ['abc', 'def']
+ for _ in [123, 456]
+ try
+ eval [][0]
+ catch
+ break
+ endtry
+ endfor
+ ls += [s]
+ endfor
+ return ls
+ enddef
+ assert_equal(['abc', 'def'], Ls())
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_nocatch_return_in_try()
+ # return in try block returns normally
+ def ReturnInTry(): string
+ try
+ return '"some message"'
+ catch
+ endtry
+ return 'not reached'
+ enddef
+ exe 'echoerr ' .. ReturnInTry()
+enddef
+
+def Test_cnext_works_in_catch()
+ var lines =<< trim END
+ vim9script
+ au BufEnter * eval 1 + 2
+ writefile(['text'], 'Xcncfile1')
+ writefile(['text'], 'Xcncfile2')
+ var items = [
+ {lnum: 1, filename: 'Xcncfile1', valid: true},
+ {lnum: 1, filename: 'Xcncfile2', valid: true}
+ ]
+ setqflist([], ' ', {items: items})
+ cwindow
+
+ def CnextOrCfirst()
+ # if cnext fails, cfirst is used
+ try
+ cnext
+ catch
+ cfirst
+ endtry
+ enddef
+
+ CnextOrCfirst()
+ CnextOrCfirst()
+ writefile([getqflist({idx: 0}).idx], 'Xcncresult')
+ qall
+ END
+ writefile(lines, 'XCatchCnext', 'D')
+ g:RunVim([], [], '--clean -S XCatchCnext')
+ assert_equal(['1'], readfile('Xcncresult'))
+
+ delete('Xcncfile1')
+ delete('Xcncfile2')
+ delete('Xcncresult')
+enddef
+
+def Test_throw_skipped()
+ if 0
+ throw dontgethere
+ endif
+enddef
+
+def Test_nocatch_throw_silenced()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ throw 'error'
+ enddef
+ silent! Func()
+ END
+ writefile(lines, 'XthrowSilenced', 'D')
+ source XthrowSilenced
+enddef
+
+def DeletedFunc(): list<any>
+ return ['delete me']
+enddef
+defcompile
+delfunc DeletedFunc
+
+def s:ThrowFromDef()
+ throw "getout" # comment
+enddef
+
+func s:CatchInFunc()
+ try
+ call s:ThrowFromDef()
+ catch
+ let g:thrown_func = v:exception
+ endtry
+endfunc
+
+def s:CatchInDef()
+ try
+ ThrowFromDef()
+ catch
+ g:thrown_def = v:exception
+ endtry
+enddef
+
+def s:ReturnFinally(): string
+ try
+ return 'intry'
+ finally
+ g:in_finally = 'finally'
+ endtry
+ return 'end'
+enddef
+
+def Test_try_catch_nested()
+ CatchInFunc()
+ assert_equal('getout', g:thrown_func)
+
+ CatchInDef()
+ assert_equal('getout', g:thrown_def)
+
+ assert_equal('intry', ReturnFinally())
+ assert_equal('finally', g:in_finally)
+
+ var l = []
+ try
+ l->add('1')
+ throw 'bad'
+ l->add('x')
+ catch /bad/
+ l->add('2')
+ try
+ l->add('3')
+ throw 'one'
+ l->add('x')
+ catch /one/
+ l->add('4')
+ try
+ l->add('5')
+ throw 'more'
+ l->add('x')
+ catch /more/
+ l->add('6')
+ endtry
+ endtry
+ endtry
+ assert_equal(['1', '2', '3', '4', '5', '6'], l)
+
+ l = []
+ try
+ try
+ l->add('1')
+ throw 'foo'
+ l->add('x')
+ catch
+ l->add('2')
+ throw 'bar'
+ l->add('x')
+ finally
+ l->add('3')
+ endtry
+ l->add('x')
+ catch /bar/
+ l->add('4')
+ endtry
+ assert_equal(['1', '2', '3', '4'], l)
+enddef
+
+def s:TryOne(): number
+ try
+ return 0
+ catch
+ endtry
+ return 0
+enddef
+
+def s:TryTwo(n: number): string
+ try
+ var x = {}
+ catch
+ endtry
+ return 'text'
+enddef
+
+def Test_try_catch_twice()
+ assert_equal('text', TryOne()->TryTwo())
+enddef
+
+def Test_try_catch_match()
+ var seq = 'a'
+ try
+ throw 'something'
+ catch /nothing/
+ seq ..= 'x'
+ catch /some/
+ seq ..= 'b'
+ catch /asdf/
+ seq ..= 'x'
+ catch ?a\?sdf?
+ seq ..= 'y'
+ finally
+ seq ..= 'c'
+ endtry
+ assert_equal('abc', seq)
+enddef
+
+def Test_try_catch_fails()
+ v9.CheckDefFailure(['catch'], 'E603:')
+ v9.CheckDefFailure(['try', 'echo 0', 'catch', 'catch'], 'E1033:')
+ v9.CheckDefFailure(['try', 'echo 0', 'catch /pat'], 'E1067:')
+ v9.CheckDefFailure(['finally'], 'E606:')
+ v9.CheckDefFailure(['try', 'echo 0', 'finally', 'echo 1', 'finally'], 'E607:')
+ v9.CheckDefFailure(['endtry'], 'E602:')
+ v9.CheckDefFailure(['while 1', 'endtry'], 'E170:')
+ v9.CheckDefFailure(['for i in range(5)', 'endtry'], 'E170:')
+ v9.CheckDefFailure(['if 1', 'endtry'], 'E171:')
+ v9.CheckDefFailure(['try', 'echo 1', 'endtry'], 'E1032:')
+
+ v9.CheckDefFailure(['throw'], 'E1143:')
+ v9.CheckDefFailure(['throw xxx'], 'E1001:')
+enddef
+
+def Try_catch_skipped()
+ var l = []
+ try
+ finally
+ endtry
+
+ if 1
+ else
+ try
+ endtry
+ endif
+enddef
+
+" The skipped try/endtry was updating the wrong instruction.
+def Test_try_catch_skipped()
+ var instr = execute('disassemble Try_catch_skipped')
+ assert_match("NEWLIST size 0\n", instr)
+enddef
+
+def Test_throw_line_number()
+ def Func()
+ eval 1 + 1
+ eval 2 + 2
+ throw 'exception'
+ enddef
+ try
+ Func()
+ catch /exception/
+ assert_match('line 3', v:throwpoint)
+ endtry
+enddef
+
+
+def Test_throw_vimscript()
+ # only checks line continuation
+ var lines =<< trim END
+ vim9script
+ try
+ throw 'one'
+ .. 'two'
+ catch
+ assert_equal('onetwo', v:exception)
+ endtry
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ @r = ''
+ def Func()
+ throw @r
+ enddef
+ var result = ''
+ try
+ Func()
+ catch /E1129:/
+ result = 'caught'
+ endtry
+ assert_equal('caught', result)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_error_in_nested_function()
+ # an error in a nested :function aborts executing in the calling :def function
+ var lines =<< trim END
+ vim9script
+ def Func()
+ Error()
+ g:test_var = 1
+ enddef
+ func Error() abort
+ eval [][0]
+ endfunc
+ Func()
+ END
+ g:test_var = 0
+ v9.CheckScriptFailure(lines, 'E684:')
+ assert_equal(0, g:test_var)
+enddef
+
+def Test_abort_after_error()
+ var lines =<< trim END
+ vim9script
+ while true
+ echo notfound
+ endwhile
+ g:gotthere = true
+ END
+ g:gotthere = false
+ v9.CheckScriptFailure(lines, 'E121:')
+ assert_false(g:gotthere)
+ unlet g:gotthere
+enddef
+
+def Test_cexpr_vimscript()
+ # only checks line continuation
+ set errorformat=File\ %f\ line\ %l
+ var lines =<< trim END
+ vim9script
+ cexpr 'File'
+ .. ' someFile' ..
+ ' line 19'
+ assert_equal(19, getqflist()[0].lnum)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ def CexprFail()
+ au QuickfixCmdPre * echo g:doesnotexist
+ cexpr 'File otherFile line 99'
+ g:didContinue = 'yes'
+ enddef
+ CexprFail()
+ g:didContinue = 'also'
+ END
+ g:didContinue = 'no'
+ v9.CheckScriptFailure(lines, 'E121: Undefined variable: g:doesnotexist')
+ assert_equal('no', g:didContinue)
+ au! QuickfixCmdPre
+
+ lines =<< trim END
+ vim9script
+ def CexprFail()
+ cexpr g:aNumber
+ g:didContinue = 'yes'
+ enddef
+ CexprFail()
+ g:didContinue = 'also'
+ END
+ g:aNumber = 123
+ g:didContinue = 'no'
+ v9.CheckScriptFailure(lines, 'E777: String or List expected')
+ assert_equal('no', g:didContinue)
+ unlet g:didContinue
+
+ set errorformat&
+enddef
+
+def Test_statusline_syntax()
+ # legacy syntax is used for 'statusline'
+ var lines =<< trim END
+ vim9script
+ func g:Status()
+ return '%{"x" is# "x"}'
+ endfunc
+ set laststatus=2 statusline=%!Status()
+ redrawstatus
+ set laststatus statusline=
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_list_vimscript()
+ # checks line continuation and comments
+ var lines =<< trim END
+ vim9script
+ var mylist = [
+ 'one',
+ # comment
+ 'two', # empty line follows
+
+ 'three',
+ ]
+ assert_equal(['one', 'two', 'three'], mylist)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ # check all lines from heredoc are kept
+ lines =<< trim END
+ # comment 1
+ two
+ # comment 3
+
+ five
+ # comment 6
+ END
+ assert_equal(['# comment 1', 'two', '# comment 3', '', 'five', '# comment 6'], lines)
+
+ lines =<< trim END
+ [{
+ a: 0}]->string()->assert_equal("[{'a': 0}]")
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+if has('channel')
+ let someJob = test_null_job()
+
+ def FuncWithError()
+ echomsg g:someJob
+ enddef
+
+ func Test_convert_emsg_to_exception()
+ try
+ call FuncWithError()
+ catch
+ call assert_match('Vim:E908:', v:exception)
+ endtry
+ endfunc
+endif
+
+def Test_vim9script_mix()
+ var lines =<< trim END
+ if has(g:feature)
+ " legacy script
+ let g:legacy = 1
+ finish
+ endif
+ vim9script
+ g:legacy = 0
+ END
+ g:feature = 'eval'
+ g:legacy = -1
+ v9.CheckScriptSuccess(lines)
+ assert_equal(1, g:legacy)
+
+ g:feature = 'noteval'
+ g:legacy = -1
+ v9.CheckScriptSuccess(lines)
+ assert_equal(0, g:legacy)
+enddef
+
+def Test_vim9script_fails()
+ v9.CheckScriptFailure(['scriptversion 2', 'vim9script'], 'E1039:')
+ v9.CheckScriptFailure(['vim9script', 'scriptversion 2'], 'E1040:')
+
+ v9.CheckScriptFailure(['vim9script', 'var str: string', 'str = 1234'], 'E1012:')
+ v9.CheckScriptFailure(['vim9script', 'const str = "asdf"', 'str = "xxx"'], 'E46:')
+
+ assert_fails('vim9script', 'E1038:')
+ v9.CheckDefFailure(['vim9script'], 'E1038:')
+
+ # no error when skipping
+ if has('nothing')
+ vim9script
+ endif
+enddef
+
+def Test_script_var_shadows_function()
+ var lines =<< trim END
+ vim9script
+ def Func(): number
+ return 123
+ enddef
+ var Func = 1
+ END
+ v9.CheckScriptFailure(lines, 'E1041:', 5)
+enddef
+
+def Test_function_shadows_script_var()
+ var lines =<< trim END
+ vim9script
+ var Func = 1
+ def Func(): number
+ return 123
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1041:', 3)
+enddef
+
+def Test_script_var_shadows_command()
+ var lines =<< trim END
+ var undo = 1
+ undo = 2
+ assert_equal(2, undo)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var undo = 1
+ undo
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1207:', 2)
+enddef
+
+def Test_vim9script_call_wrong_type()
+ var lines =<< trim END
+ vim9script
+ var Time = 'localtime'
+ Time()
+ END
+ v9.CheckScriptFailure(lines, 'E1085:')
+enddef
+
+def Test_vim9script_reload_delfunc()
+ var first_lines =<< trim END
+ vim9script
+ def FuncYes(): string
+ return 'yes'
+ enddef
+ END
+ var withno_lines =<< trim END
+ def FuncNo(): string
+ return 'no'
+ enddef
+ def g:DoCheck(no_exists: bool)
+ assert_equal('yes', FuncYes())
+ assert_equal('no', FuncNo())
+ enddef
+ END
+ var nono_lines =<< trim END
+ def g:DoCheck(no_exists: bool)
+ assert_equal('yes', FuncYes())
+ assert_fails('FuncNo()', 'E117:', '', 2, 'DoCheck')
+ enddef
+ END
+
+ # FuncNo() is defined
+ writefile(first_lines + withno_lines, 'Xreloaded.vim', 'D')
+ source Xreloaded.vim
+ g:DoCheck(true)
+
+ # FuncNo() is not redefined
+ writefile(first_lines + nono_lines, 'Xreloaded.vim')
+ source Xreloaded.vim
+ g:DoCheck(false)
+
+ # FuncNo() is back
+ writefile(first_lines + withno_lines, 'Xreloaded.vim')
+ source Xreloaded.vim
+ g:DoCheck(false)
+enddef
+
+def Test_vim9script_reload_delvar()
+ # write the script with a script-local variable
+ var lines =<< trim END
+ vim9script
+ var name = 'string'
+ END
+ writefile(lines, 'XreloadVar.vim', 'D')
+ source XreloadVar.vim
+
+ # now write the script using the same variable locally - works
+ lines =<< trim END
+ vim9script
+ def Func()
+ var name = 'string'
+ enddef
+ END
+ writefile(lines, 'XreloadVar.vim')
+ source XreloadVar.vim
+enddef
+
+def Test_func_redefine_error()
+ var lines = [
+ 'vim9script',
+ 'def Func()',
+ ' eval [][0]',
+ 'enddef',
+ 'Func()',
+ ]
+ writefile(lines, 'Xtestscript.vim', 'D')
+
+ for count in range(3)
+ try
+ source Xtestscript.vim
+ catch /E684/
+ # function name should contain <SNR> every time
+ assert_match('E684: List index out of range', v:exception)
+ assert_match('function <SNR>\d\+_Func, line 1', v:throwpoint)
+ endtry
+ endfor
+enddef
+
+def Test_func_redefine_fails()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ echo 'one'
+ enddef
+ def Func()
+ echo 'two'
+ enddef
+ END
+ v9.CheckScriptFailure(lines, 'E1073:')
+
+ lines =<< trim END
+ vim9script
+ def Foo(): string
+ return 'foo'
+ enddef
+ def Func()
+ var Foo = {-> 'lambda'}
+ enddef
+ defcompile
+ END
+ v9.CheckScriptFailure(lines, 'E1073:')
+enddef
+
+def Test_lambda_split()
+ # this was using freed memory, because of the split expression
+ var lines =<< trim END
+ vim9script
+ try
+ 0
+ 0->(0
+ ->a.0(
+ ->u
+ END
+ v9.CheckScriptFailure(lines, 'E1050:')
+enddef
+
+def Test_fixed_size_list()
+ # will be allocated as one piece of memory, check that changes work
+ var l = [1, 2, 3, 4]
+ l->remove(0)
+ l->add(5)
+ l->insert(99, 1)
+ assert_equal([2, 99, 3, 4, 5], l)
+enddef
+
+def Test_no_insert_xit()
+ v9.CheckDefExecFailure(['a = 1'], 'E1100:')
+ v9.CheckDefExecFailure(['c = 1'], 'E1100:')
+ v9.CheckDefExecFailure(['i = 1'], 'E1100:')
+ v9.CheckDefExecFailure(['t = 1'], 'E1100:')
+ v9.CheckDefExecFailure(['x = 1'], 'E1100:')
+
+ v9.CheckScriptFailure(['vim9script', 'a = 1'], 'E488:')
+ v9.CheckScriptFailure(['vim9script', 'a'], 'E1100:')
+ v9.CheckScriptFailure(['vim9script', 'c = 1'], 'E488:')
+ v9.CheckScriptFailure(['vim9script', 'c'], 'E1100:')
+ v9.CheckScriptFailure(['vim9script', 'i = 1'], 'E488:')
+ v9.CheckScriptFailure(['vim9script', 'i'], 'E1100:')
+ v9.CheckScriptFailure(['vim9script', 'o = 1'], 'E1100:')
+ v9.CheckScriptFailure(['vim9script', 'o'], 'E1100:')
+ v9.CheckScriptFailure(['vim9script', 't'], 'E1100:')
+ v9.CheckScriptFailure(['vim9script', 't = 1'], 'E1100:')
+ v9.CheckScriptFailure(['vim9script', 'x = 1'], 'E1100:')
+enddef
+
+def s:IfElse(what: number): string
+ var res = ''
+ if what == 1
+ res = "one"
+ elseif what == 2
+ res = "two"
+ else
+ res = "three"
+ endif
+ return res
+enddef
+
+def Test_if_elseif_else()
+ assert_equal('one', IfElse(1))
+ assert_equal('two', IfElse(2))
+ assert_equal('three', IfElse(3))
+enddef
+
+def Test_if_elseif_else_fails()
+ v9.CheckDefFailure(['elseif true'], 'E582:')
+ v9.CheckDefFailure(['else'], 'E581:')
+ v9.CheckDefFailure(['endif'], 'E580:')
+ v9.CheckDefFailure(['if g:abool', 'elseif xxx'], 'E1001:')
+ v9.CheckDefFailure(['if true', 'echo 1'], 'E171:')
+
+ var lines =<< trim END
+ var s = ''
+ if s = ''
+ endif
+ END
+ v9.CheckDefFailure(lines, 'E488:')
+
+ lines =<< trim END
+ var s = ''
+ if s == ''
+ elseif s = ''
+ endif
+ END
+ v9.CheckDefFailure(lines, 'E488:')
+
+ lines =<< trim END
+ var cond = true
+ if cond
+ echo 'true'
+ elseif
+ echo 'false'
+ endif
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1143:', 'E15:'], 4)
+enddef
+
+def Test_if_else_func_using_var()
+ var lines =<< trim END
+ vim9script
+
+ const debug = true
+ if debug
+ var mode_chars = 'something'
+ def Bits2Ascii()
+ var x = mode_chars
+ g:where = 'in true'
+ enddef
+ else
+ def Bits2Ascii()
+ g:where = 'in false'
+ enddef
+ endif
+
+ Bits2Ascii()
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal('in true', g:where)
+ unlet g:where
+
+ lines =<< trim END
+ vim9script
+
+ const debug = false
+ if debug
+ var mode_chars = 'something'
+ def Bits2Ascii()
+ g:where = 'in true'
+ enddef
+ else
+ def Bits2Ascii()
+ var x = mode_chars
+ g:where = 'in false'
+ enddef
+ endif
+
+ Bits2Ascii()
+ END
+ v9.CheckScriptFailure(lines, 'E1001: Variable not found: mode_chars')
+enddef
+
+let g:bool_true = v:true
+let g:bool_false = v:false
+
+def Test_if_const_expr()
+ var res = false
+ if true ? true : false
+ res = true
+ endif
+ assert_equal(true, res)
+
+ g:glob = 2
+ if false
+ execute('g:glob = 3')
+ endif
+ assert_equal(2, g:glob)
+ if true
+ execute('g:glob = 3')
+ endif
+ assert_equal(3, g:glob)
+
+ res = false
+ if g:bool_true ? true : false
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if true ? g:bool_true : false
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if true ? true : g:bool_false
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if true ? false : true
+ res = true
+ endif
+ assert_equal(false, res)
+
+ res = false
+ if false ? false : true
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if false ? true : false
+ res = true
+ endif
+ assert_equal(false, res)
+
+ res = false
+ if has('xyz') ? true : false
+ res = true
+ endif
+ assert_equal(false, res)
+
+ res = false
+ if true && true
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if true && false
+ res = true
+ endif
+ assert_equal(false, res)
+
+ res = false
+ if g:bool_true && false
+ res = true
+ endif
+ assert_equal(false, res)
+
+ res = false
+ if true && g:bool_false
+ res = true
+ endif
+ assert_equal(false, res)
+
+ res = false
+ if false && false
+ res = true
+ endif
+ assert_equal(false, res)
+
+ res = false
+ if true || false
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if g:bool_true || false
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if true || g:bool_false
+ res = true
+ endif
+ assert_equal(true, res)
+
+ res = false
+ if false || false
+ res = true
+ endif
+ assert_equal(false, res)
+
+ # with constant "false" expression may be invalid so long as the syntax is OK
+ if false | eval 1 + 2 | endif
+ if false | eval burp + 234 | endif
+ if false | echo burp 234 'asd' | endif
+ if false
+ burp
+ endif
+
+ if 0
+ if 1
+ echo nothing
+ elseif 1
+ echo still nothing
+ endif
+ endif
+
+ # expression with line breaks skipped
+ if false
+ ('aaa'
+ .. 'bbb'
+ .. 'ccc'
+ )->setline(1)
+ endif
+enddef
+
+def Test_if_const_expr_fails()
+ v9.CheckDefFailure(['if "aaa" == "bbb'], 'E114:')
+ v9.CheckDefFailure(["if 'aaa' == 'bbb"], 'E115:')
+ v9.CheckDefFailure(["if has('aaa'"], 'E110:')
+ v9.CheckDefFailure(["if has('aaa') ? true false"], 'E109:')
+enddef
+
+def s:RunNested(i: number): number
+ var x: number = 0
+ if i % 2
+ if 1
+ # comment
+ else
+ # comment
+ endif
+ x += 1
+ else
+ x += 1000
+ endif
+ return x
+enddef
+
+def Test_nested_if()
+ assert_equal(1, RunNested(1))
+ assert_equal(1000, RunNested(2))
+enddef
+
+def Test_execute_cmd()
+ # missing argument is ignored
+ execute
+ execute # comment
+
+ new
+ setline(1, 'default')
+ execute 'setline(1, "execute-string")'
+ assert_equal('execute-string', getline(1))
+
+ execute "setline(1, 'execute-string')"
+ assert_equal('execute-string', getline(1))
+
+ var cmd1 = 'setline(1,'
+ var cmd2 = '"execute-var")'
+ execute cmd1 cmd2 # comment
+ assert_equal('execute-var', getline(1))
+
+ execute cmd1 cmd2 '|setline(1, "execute-var-string")'
+ assert_equal('execute-var-string', getline(1))
+
+ var cmd_first = 'call '
+ var cmd_last = 'setline(1, "execute-var-var")'
+ execute cmd_first .. cmd_last
+ assert_equal('execute-var-var', getline(1))
+ bwipe!
+
+ var n = true
+ execute 'echomsg' (n ? '"true"' : '"no"')
+ assert_match('^true$', g:Screenline(&lines))
+
+ echomsg [1, 2, 3] {a: 1, b: 2}
+ assert_match('^\[1, 2, 3\] {''a'': 1, ''b'': 2}$', g:Screenline(&lines))
+
+ v9.CheckDefFailure(['execute xxx'], 'E1001:', 1)
+ v9.CheckDefExecFailure(['execute "tabnext " .. 8'], 'E475:', 1)
+ v9.CheckDefFailure(['execute "cmd"# comment'], 'E488:', 1)
+ if has('channel')
+ v9.CheckDefExecFailure(['execute test_null_channel()'], 'E908:', 1)
+ endif
+enddef
+
+def Test_execute_cmd_vimscript()
+ # only checks line continuation
+ var lines =<< trim END
+ vim9script
+ execute 'g:someVar'
+ .. ' = ' ..
+ '28'
+ assert_equal(28, g:someVar)
+ unlet g:someVar
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_execute_finish()
+ # the empty lines are relevant here
+ var lines =<< trim END
+ vim9script
+
+ var vname = "g:hello"
+
+ if exists(vname) | finish | endif | execute vname '= "world"'
+
+ assert_equal('world', g:hello)
+
+ if exists(vname) | finish | endif | execute vname '= "world"'
+
+ assert_report('should not be reached')
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_echo_cmd()
+ echo 'some' # comment
+ echon 'thing'
+ assert_match('^something$', g:Screenline(&lines))
+
+ echo "some" # comment
+ echon "thing"
+ assert_match('^something$', g:Screenline(&lines))
+
+ var str1 = 'some'
+ var str2 = 'more'
+ echo str1 str2
+ assert_match('^some more$', g:Screenline(&lines))
+
+ echo "one\ntwo"
+ assert_match('^one$', g:Screenline(&lines - 1))
+ assert_match('^two$', g:Screenline(&lines))
+
+ v9.CheckDefFailure(['echo "xxx"# comment'], 'E488:')
+enddef
+
+def Test_echomsg_cmd()
+ echomsg 'some' 'more' # comment
+ assert_match('^some more$', g:Screenline(&lines))
+ echo 'clear'
+ :1messages
+ assert_match('^some more$', g:Screenline(&lines))
+
+ v9.CheckDefFailure(['echomsg "xxx"# comment'], 'E488:')
+enddef
+
+def Test_echomsg_cmd_vimscript()
+ # only checks line continuation
+ var lines =<< trim END
+ vim9script
+ echomsg 'here'
+ .. ' is ' ..
+ 'a message'
+ assert_match('^here is a message$', g:Screenline(&lines))
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_echoerr_cmd()
+ var local = 'local'
+ try
+ echoerr 'something' local 'wrong' # comment
+ catch
+ assert_match('something local wrong', v:exception)
+ endtry
+enddef
+
+def Test_echoerr_cmd_vimscript()
+ # only checks line continuation
+ var lines =<< trim END
+ vim9script
+ try
+ echoerr 'this'
+ .. ' is ' ..
+ 'wrong'
+ catch
+ assert_match('this is wrong', v:exception)
+ endtry
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_echoconsole_cmd()
+ var local = 'local'
+ echoconsole 'something' local # comment
+ # output goes anywhere
+enddef
+
+def Test_echowindow_cmd()
+ var local = 'local'
+ echowindow 'something' local # comment
+
+ # with modifier
+ unsilent echowin 'loud'
+
+ # output goes in message window
+ popup_clear()
+enddef
+
+def Test_for_outside_of_function()
+ var lines =<< trim END
+ vim9script
+ new
+ for var in range(0, 3)
+ append(line('$'), var)
+ endfor
+ assert_equal(['', '0', '1', '2', '3'], getline(1, '$'))
+ bwipe!
+
+ var result = ''
+ for i in [1, 2, 3]
+ var loop = ' loop ' .. i
+ result ..= loop
+ endfor
+ assert_equal(' loop 1 loop 2 loop 3', result)
+ END
+ writefile(lines, 'Xvim9for.vim', 'D')
+ source Xvim9for.vim
+enddef
+
+def Test_for_skipped_block()
+ # test skipped blocks at outside of function
+ var lines =<< trim END
+ var result = []
+ if true
+ for n in [1, 2]
+ result += [n]
+ endfor
+ else
+ for n in [3, 4]
+ result += [n]
+ endfor
+ endif
+ assert_equal([1, 2], result)
+
+ result = []
+ if false
+ for n in [1, 2]
+ result += [n]
+ endfor
+ else
+ for n in [3, 4]
+ result += [n]
+ endfor
+ endif
+ assert_equal([3, 4], result)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # test skipped blocks at inside of function
+ lines =<< trim END
+ def DefTrue()
+ var result = []
+ if true
+ for n in [1, 2]
+ result += [n]
+ endfor
+ else
+ for n in [3, 4]
+ result += [n]
+ endfor
+ endif
+ assert_equal([1, 2], result)
+ enddef
+ DefTrue()
+
+ def DefFalse()
+ var result = []
+ if false
+ for n in [1, 2]
+ result += [n]
+ endfor
+ else
+ for n in [3, 4]
+ result += [n]
+ endfor
+ endif
+ assert_equal([3, 4], result)
+ enddef
+ DefFalse()
+
+ def BuildDiagrams()
+ var diagrams: list<any>
+ if false
+ var max = 0
+ for v in diagrams
+ var l = 3
+ if max < l | max = l | endif
+ v->add(l)
+ endfor
+ endif
+ enddef
+ BuildDiagrams()
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_skipped_redir()
+ var lines =<< trim END
+ def Tredir()
+ if 0
+ redir => l[0]
+ redir END
+ endif
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:Tredir
+
+ lines =<< trim END
+ def Tredir()
+ if 0
+ redir => l[0]
+ endif
+ echo 'executed'
+ if 0
+ redir END
+ endif
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:Tredir
+
+ lines =<< trim END
+ def Tredir()
+ var l = ['']
+ if 1
+ redir => l[0]
+ endif
+ echo 'executed'
+ if 0
+ redir END
+ else
+ redir END
+ endif
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:Tredir
+
+ lines =<< trim END
+ let doit = 1
+ def Tredir()
+ var l = ['']
+ if g:doit
+ redir => l[0]
+ endif
+ echo 'executed'
+ if g:doit
+ redir END
+ endif
+ enddef
+ defcompile
+ END
+ v9.CheckScriptSuccess(lines)
+ delfunc g:Tredir
+enddef
+
+def Test_for_loop()
+ var lines =<< trim END
+ var result = ''
+ for cnt in range(7)
+ if cnt == 4
+ break
+ endif
+ if cnt == 2
+ continue
+ endif
+ result ..= cnt .. '_'
+ endfor
+ assert_equal('0_1_3_', result)
+
+ var concat = ''
+ for str in eval('["one", "two"]')
+ concat ..= str
+ endfor
+ assert_equal('onetwo', concat)
+
+ var total = 0
+ for nr in
+ [1, 2, 3]
+ total += nr
+ endfor
+ assert_equal(6, total)
+
+ total = 0
+ for nr
+ in [1, 2, 3]
+ total += nr
+ endfor
+ assert_equal(6, total)
+
+ total = 0
+ for nr
+ in
+ [1, 2, 3]
+ total += nr
+ endfor
+ assert_equal(6, total)
+
+ # with type
+ total = 0
+ for n: number in [1, 2, 3]
+ total += n
+ endfor
+ assert_equal(6, total)
+
+ total = 0
+ for b in 0z010203
+ total += b
+ endfor
+ assert_equal(6, total)
+
+ var chars = ''
+ for s: string in 'foobar'
+ chars ..= s
+ endfor
+ assert_equal('foobar', chars)
+
+ chars = ''
+ for x: string in {a: 'a', b: 'b'}->values()
+ chars ..= x
+ endfor
+ assert_equal('ab', chars)
+
+ # unpack with type
+ var res = ''
+ for [n: number, s: string] in [[1, 'a'], [2, 'b']]
+ res ..= n .. s
+ endfor
+ assert_equal('1a2b', res)
+
+ # unpack with one var
+ var reslist = []
+ for [x] in [['aaa'], ['bbb']]
+ reslist->add(x)
+ endfor
+ assert_equal(['aaa', 'bbb'], reslist)
+
+ # loop over string
+ res = ''
+ for c in 'aéc̀d'
+ res ..= c .. '-'
+ endfor
+ assert_equal('a-é-c̀-d-', res)
+
+ res = ''
+ for c in ''
+ res ..= c .. '-'
+ endfor
+ assert_equal('', res)
+
+ res = ''
+ for c in test_null_string()
+ res ..= c .. '-'
+ endfor
+ assert_equal('', res)
+
+ total = 0
+ for c in null_list
+ total += 1
+ endfor
+ assert_equal(0, total)
+
+ for c in null_blob
+ total += 1
+ endfor
+ assert_equal(0, total)
+
+ var foo: list<dict<any>> = [
+ {a: 'Cat'}
+ ]
+ for dd in foo
+ dd.counter = 12
+ endfor
+ assert_equal([{a: 'Cat', counter: 12}], foo)
+
+ reslist = []
+ for _ in range(3)
+ reslist->add('x')
+ endfor
+ assert_equal(['x', 'x', 'x'], reslist)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_for_loop_list_of_lists()
+ # loop variable is final, not const
+ var lines =<< trim END
+ # Filter out all odd numbers in each sublist
+ var list: list<list<number>> = [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]
+ for i in list
+ filter(i, (_, n: number): bool => n % 2 == 0)
+ endfor
+
+ assert_equal([[], [2], [2], [2, 4]], list)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_for_loop_with_closure()
+ # using the loop variable in a closure results in the last used value
+ var lines =<< trim END
+ var flist: list<func>
+ for i in range(5)
+ flist[i] = () => i
+ endfor
+ for i in range(5)
+ assert_equal(4, flist[i]())
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # also works when the loop variable is used only once halfway the loops
+ lines =<< trim END
+ var Clo: func
+ for i in range(5)
+ if i == 3
+ Clo = () => i
+ endif
+ endfor
+ assert_equal(4, Clo())
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # using a local variable set to the loop variable in a closure results in the
+ # value at that moment
+ lines =<< trim END
+ var flist: list<func>
+ for i in range(5)
+ var inloop = i
+ flist[i] = () => inloop
+ endfor
+ for i in range(5)
+ assert_equal(i, flist[i]())
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # also with an extra block level
+ lines =<< trim END
+ var flist: list<func>
+ for i in range(5)
+ {
+ var inloop = i
+ flist[i] = () => inloop
+ }
+ endfor
+ for i in range(5)
+ assert_equal(i, flist[i]())
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # and declaration in higher block
+ lines =<< trim END
+ var flist: list<func>
+ for i in range(5)
+ var inloop = i
+ {
+ flist[i] = () => inloop
+ }
+ endfor
+ for i in range(5)
+ assert_equal(i, flist[i]())
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ var flist: list<func>
+ for i in range(5)
+ var inloop = i
+ flist[i] = () => {
+ return inloop
+ }
+ endfor
+ for i in range(5)
+ assert_equal(i, flist[i]())
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # Also works for a nested loop
+ lines =<< trim END
+ var flist: list<func>
+ var n = 0
+ for i in range(3)
+ var ii = i
+ for a in ['a', 'b', 'c']
+ var aa = a
+ flist[n] = () => ii .. aa
+ ++n
+ endfor
+ endfor
+
+ n = 0
+ for i in range(3)
+ for a in ['a', 'b', 'c']
+ assert_equal(i .. a, flist[n]())
+ ++n
+ endfor
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # using two loop variables
+ lines =<< trim END
+ var lv_list: list<func>
+ var copy_list: list<func>
+ for [idx, c] in items('word')
+ var lidx = idx
+ var lc = c
+ lv_list[idx] = () => {
+ return idx .. c
+ }
+ copy_list[idx] = () => {
+ return lidx .. lc
+ }
+ endfor
+ for [i, c] in items('word')
+ assert_equal(3 .. 'd', lv_list[i]())
+ assert_equal(i .. c, copy_list[i]())
+ endfor
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_define_global_closure_in_loops()
+ var lines =<< trim END
+ vim9script
+
+ def Func()
+ for i in range(3)
+ var ii = i
+ for a in ['a', 'b', 'c']
+ var aa = a
+ if ii == 0 && aa == 'a'
+ def g:Global_0a(): string
+ return ii .. aa
+ enddef
+ endif
+ if ii == 1 && aa == 'b'
+ def g:Global_1b(): string
+ return ii .. aa
+ enddef
+ endif
+ if ii == 2 && aa == 'c'
+ def g:Global_2c(): string
+ return ii .. aa
+ enddef
+ endif
+ endfor
+ endfor
+ enddef
+ Func()
+ END
+ v9.CheckScriptSuccess(lines)
+ assert_equal("0a", g:Global_0a())
+ assert_equal("1b", g:Global_1b())
+ assert_equal("2c", g:Global_2c())
+
+ delfunc g:Global_0a
+ delfunc g:Global_1b
+ delfunc g:Global_2c
+enddef
+
+def Test_for_loop_fails()
+ v9.CheckDefAndScriptFailure(['for '], ['E1097:', 'E690:'])
+ v9.CheckDefAndScriptFailure(['for x'], ['E1097:', 'E690:'])
+ v9.CheckDefAndScriptFailure(['for x in'], ['E1097:', 'E15:'])
+ v9.CheckDefAndScriptFailure(['for # in range(5)'], 'E690:')
+ v9.CheckDefAndScriptFailure(['for i In range(5)'], 'E690:')
+ v9.CheckDefAndScriptFailure(['var x = 5', 'for x in range(5)', 'endfor'], ['E1017:', 'E1041:'])
+ v9.CheckScriptFailure(['vim9script', 'var x = 5', 'for x in range(5)', '# comment', 'endfor'], 'E1041:', 3)
+ v9.CheckScriptFailure(['def Func(arg: any)', 'for arg in range(5)', 'enddef', 'defcompile'], 'E1006:')
+ delfunc! g:Func
+ v9.CheckDefFailure(['for i in xxx'], 'E1001:')
+ v9.CheckDefFailure(['endfor'], 'E588:')
+ v9.CheckDefFailure(['for i in range(3)', 'echo 3'], 'E170:')
+
+ # wrong type detected at compile time
+ v9.CheckDefFailure(['for i in {a: 1}', 'echo 3', 'endfor'], 'E1177: For loop on dict not supported')
+
+ # wrong type detected at runtime
+ g:adict = {a: 1}
+ v9.CheckDefExecFailure(['for i in g:adict', 'echo 3', 'endfor'], 'E1177: For loop on dict not supported')
+ unlet g:adict
+
+ var lines =<< trim END
+ var d: list<dict<any>> = [{a: 0}]
+ for e in d
+ e = {a: 0, b: ''}
+ endfor
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E1018:', 'E46:'], 3)
+
+ lines =<< trim END
+ for nr: number in ['foo']
+ endfor
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1012: Type mismatch; expected number but got string', 1)
+
+ lines =<< trim END
+ for n : number in [1, 2]
+ echo n
+ endfor
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1059:', 1)
+
+ lines =<< trim END
+ var d: dict<number> = {a: 1, b: 2}
+ for [k: job, v: job] in d->items()
+ echo k v
+ endfor
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1163: Variable 1: type mismatch, expected job but got string', 'E1012: Type mismatch; expected job but got string'], 2)
+
+ lines =<< trim END
+ var i = 0
+ for i in [1, 2, 3]
+ echo i
+ endfor
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E1017:', 'E1041:'])
+
+ lines =<< trim END
+ var l = [0]
+ for l[0] in [1, 2, 3]
+ echo l[0]
+ endfor
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E461:', 'E1017:'])
+
+ lines =<< trim END
+ var d = {x: 0}
+ for d.x in [1, 2, 3]
+ echo d.x
+ endfor
+ END
+ v9.CheckDefExecAndScriptFailure(lines, ['E461:', 'E1017:'])
+
+ lines =<< trim END
+ var l: list<dict<any>> = [{a: 1, b: 'x'}]
+ for item: dict<number> in l
+ echo item
+ endfor
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1012: Type mismatch; expected dict<number> but got dict<any>')
+
+ lines =<< trim END
+ var l: list<dict<any>> = [{n: 1}]
+ for item: dict<number> in l
+ var d = {s: ''}
+ d->extend(item)
+ endfor
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1013: Argument 2: type mismatch, expected dict<string> but got dict<number>')
+
+ lines =<< trim END
+ for a in range(3)
+ while a > 3
+ for b in range(2)
+ while b < 0
+ for c in range(5)
+ while c > 6
+ while c < 0
+ for d in range(1)
+ for e in range(3)
+ while e > 3
+ endwhile
+ endfor
+ endfor
+ endwhile
+ endwhile
+ endfor
+ endwhile
+ endfor
+ endwhile
+ endfor
+ END
+ v9.CheckDefSuccess(lines)
+
+ v9.CheckDefFailure(['for x in range(3)'] + lines + ['endfor'], 'E1306:')
+enddef
+
+def Test_for_loop_script_var()
+ # cannot use s:var in a :def function
+ v9.CheckDefFailure(['for s:var in range(3)', 'echo 3'], 'E1254:')
+
+ # can use s:var in Vim9 script, with or without s:
+ var lines =<< trim END
+ vim9script
+ var total = 0
+ for s:var in [1, 2, 3]
+ total += s:var
+ endfor
+ assert_equal(6, total)
+
+ total = 0
+ for var in [1, 2, 3]
+ total += var
+ endfor
+ assert_equal(6, total)
+ END
+enddef
+
+def Test_for_loop_unpack()
+ var lines =<< trim END
+ var result = []
+ for [v1, v2] in [[1, 2], [3, 4]]
+ result->add(v1)
+ result->add(v2)
+ endfor
+ assert_equal([1, 2, 3, 4], result)
+
+ result = []
+ for [v1, v2; v3] in [[1, 2], [3, 4, 5, 6]]
+ result->add(v1)
+ result->add(v2)
+ result->add(v3)
+ endfor
+ assert_equal([1, 2, [], 3, 4, [5, 6]], result)
+
+ result = []
+ for [&ts, &sw] in [[1, 2], [3, 4]]
+ result->add(&ts)
+ result->add(&sw)
+ endfor
+ assert_equal([1, 2, 3, 4], result)
+
+ var slist: list<string>
+ for [$LOOPVAR, @r, v:errmsg] in [['a', 'b', 'c'], ['d', 'e', 'f']]
+ slist->add($LOOPVAR)
+ slist->add(@r)
+ slist->add(v:errmsg)
+ endfor
+ assert_equal(['a', 'b', 'c', 'd', 'e', 'f'], slist)
+
+ slist = []
+ for [g:globalvar, b:bufvar, w:winvar, t:tabvar] in [['global', 'buf', 'win', 'tab'], ['1', '2', '3', '4']]
+ slist->add(g:globalvar)
+ slist->add(b:bufvar)
+ slist->add(w:winvar)
+ slist->add(t:tabvar)
+ endfor
+ assert_equal(['global', 'buf', 'win', 'tab', '1', '2', '3', '4'], slist)
+ unlet! g:globalvar b:bufvar w:winvar t:tabvar
+
+ var res = []
+ for [_, n, _] in [[1, 2, 3], [4, 5, 6]]
+ res->add(n)
+ endfor
+ assert_equal([2, 5], res)
+
+ var text: list<string> = ["hello there", "goodbye now"]
+ var splitted = ''
+ for [first; next] in mapnew(text, (i, v) => split(v))
+ splitted ..= string(first) .. string(next) .. '/'
+ endfor
+ assert_equal("'hello'['there']/'goodbye'['now']/", splitted)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ lines =<< trim END
+ for [v1, v2] in [[1, 2, 3], [3, 4]]
+ echo v1 v2
+ endfor
+ END
+ v9.CheckDefExecFailure(lines, 'E710:', 1)
+
+ lines =<< trim END
+ for [v1, v2] in [[1], [3, 4]]
+ echo v1 v2
+ endfor
+ END
+ v9.CheckDefExecFailure(lines, 'E711:', 1)
+
+ lines =<< trim END
+ for [v1, v1] in [[1, 2], [3, 4]]
+ echo v1
+ endfor
+ END
+ v9.CheckDefExecFailure(lines, 'E1017:', 1)
+
+ lines =<< trim END
+ for [a, b] in g:listlist
+ echo a
+ endfor
+ END
+ g:listlist = [1, 2, 3]
+ v9.CheckDefExecFailure(lines, 'E1140:', 1)
+enddef
+
+def Test_for_loop_with_try_continue()
+ var lines =<< trim END
+ var looped = 0
+ var cleanup = 0
+ for i in range(3)
+ looped += 1
+ try
+ eval [][0]
+ catch
+ continue
+ finally
+ cleanup += 1
+ endtry
+ endfor
+ assert_equal(3, looped)
+ assert_equal(3, cleanup)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_while_skipped_block()
+ # test skipped blocks at outside of function
+ var lines =<< trim END
+ var result = []
+ var n = 0
+ if true
+ n = 1
+ while n < 3
+ result += [n]
+ n += 1
+ endwhile
+ else
+ n = 3
+ while n < 5
+ result += [n]
+ n += 1
+ endwhile
+ endif
+ assert_equal([1, 2], result)
+
+ result = []
+ if false
+ n = 1
+ while n < 3
+ result += [n]
+ n += 1
+ endwhile
+ else
+ n = 3
+ while n < 5
+ result += [n]
+ n += 1
+ endwhile
+ endif
+ assert_equal([3, 4], result)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+
+ # test skipped blocks at inside of function
+ lines =<< trim END
+ def DefTrue()
+ var result = []
+ var n = 0
+ if true
+ n = 1
+ while n < 3
+ result += [n]
+ n += 1
+ endwhile
+ else
+ n = 3
+ while n < 5
+ result += [n]
+ n += 1
+ endwhile
+ endif
+ assert_equal([1, 2], result)
+ enddef
+ DefTrue()
+
+ def DefFalse()
+ var result = []
+ var n = 0
+ if false
+ n = 1
+ while n < 3
+ result += [n]
+ n += 1
+ endwhile
+ else
+ n = 3
+ while n < 5
+ result += [n]
+ n += 1
+ endwhile
+ endif
+ assert_equal([3, 4], result)
+ enddef
+ DefFalse()
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+def Test_while_loop()
+ var result = ''
+ var cnt = 0
+ while cnt < 555
+ if cnt == 3
+ break
+ endif
+ cnt += 1
+ if cnt == 2
+ continue
+ endif
+ result ..= cnt .. '_'
+ endwhile
+ assert_equal('1_3_', result)
+
+ var s = ''
+ while s == 'x' # {comment}
+ endwhile
+enddef
+
+def Test_while_loop_in_script()
+ var lines =<< trim END
+ vim9script
+ var result = ''
+ var cnt = 0
+ while cnt < 3
+ var s = 'v' .. cnt
+ result ..= s
+ cnt += 1
+ endwhile
+ assert_equal('v0v1v2', result)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_while_loop_fails()
+ v9.CheckDefFailure(['while xxx'], 'E1001:')
+ v9.CheckDefFailure(['endwhile'], 'E588:')
+ v9.CheckDefFailure(['continue'], 'E586:')
+ v9.CheckDefFailure(['if true', 'continue'], 'E586:')
+ v9.CheckDefFailure(['break'], 'E587:')
+ v9.CheckDefFailure(['if true', 'break'], 'E587:')
+ v9.CheckDefFailure(['while 1', 'echo 3'], 'E170:')
+
+ var lines =<< trim END
+ var s = ''
+ while s = ''
+ endwhile
+ END
+ v9.CheckDefFailure(lines, 'E488:')
+enddef
+
+def Test_interrupt_loop()
+ var caught = false
+ var x = 0
+ try
+ while 1
+ x += 1
+ if x == 100
+ feedkeys("\<C-C>", 'Lt')
+ endif
+ endwhile
+ catch
+ caught = true
+ assert_equal(100, x)
+ endtry
+ assert_true(caught, 'should have caught an exception')
+ # consume the CTRL-C
+ getchar(0)
+enddef
+
+def Test_automatic_line_continuation()
+ var mylist = [
+ 'one',
+ 'two',
+ 'three',
+ ] # comment
+ assert_equal(['one', 'two', 'three'], mylist)
+
+ var mydict = {
+ ['one']: 1,
+ ['two']: 2,
+ ['three']:
+ 3,
+ } # comment
+ assert_equal({one: 1, two: 2, three: 3}, mydict)
+ mydict = {
+ one: 1, # comment
+ two: # comment
+ 2, # comment
+ three: 3 # comment
+ }
+ assert_equal({one: 1, two: 2, three: 3}, mydict)
+ mydict = {
+ one: 1,
+ two:
+ 2,
+ three: 3
+ }
+ assert_equal({one: 1, two: 2, three: 3}, mydict)
+
+ assert_equal(
+ ['one', 'two', 'three'],
+ split('one two three')
+ )
+enddef
+
+def Test_vim9_comment()
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ '# something',
+ '#something',
+ '#{{something',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ '#{something',
+ ], 'E1170:')
+
+ split Xv9cfile
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'edit #something',
+ ])
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'edit #{something',
+ ])
+ close
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ ':# something',
+ ], 'E488:')
+ v9.CheckScriptFailure([
+ '# something',
+ ], 'E488:')
+ v9.CheckScriptFailure([
+ ':# something',
+ ], 'E488:')
+
+ { # block start
+ } # block end
+ v9.CheckDefFailure([
+ '{# comment',
+ ], 'E488:')
+ v9.CheckDefFailure([
+ '{',
+ '}# comment',
+ ], 'E488:')
+
+ echo "yes" # comment
+ v9.CheckDefFailure([
+ 'echo "yes"# comment',
+ ], 'E488:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'echo "yes" # something',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'echo "yes"# something',
+ ], 'E121:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'echo# something',
+ ], 'E1144:')
+ v9.CheckScriptFailure([
+ 'echo "yes" # something',
+ ], 'E121:')
+
+ exe "echo" # comment
+ v9.CheckDefFailure([
+ 'exe "echo"# comment',
+ ], 'E488:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'exe "echo" # something',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'exe "echo"# something',
+ ], 'E121:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'exe# something',
+ ], 'E1144:')
+ v9.CheckScriptFailure([
+ 'exe "echo" # something',
+ ], 'E121:')
+
+ v9.CheckDefFailure([
+ 'try# comment',
+ ' echo "yes"',
+ 'catch',
+ 'endtry',
+ ], 'E1144:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'try# comment',
+ 'echo "yes"',
+ ], 'E1144:')
+ v9.CheckDefFailure([
+ 'try',
+ ' throw#comment',
+ 'catch',
+ 'endtry',
+ ], 'E1144:')
+ v9.CheckDefFailure([
+ 'try',
+ ' throw "yes"#comment',
+ 'catch',
+ 'endtry',
+ ], 'E488:')
+ v9.CheckDefFailure([
+ 'try',
+ ' echo "yes"',
+ 'catch# comment',
+ 'endtry',
+ ], 'E1144:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'try',
+ ' echo "yes"',
+ 'catch# comment',
+ 'endtry',
+ ], 'E1144:')
+ v9.CheckDefFailure([
+ 'try',
+ ' echo "yes"',
+ 'catch /pat/# comment',
+ 'endtry',
+ ], 'E488:')
+ v9.CheckDefFailure([
+ 'try',
+ 'echo "yes"',
+ 'catch',
+ 'endtry# comment',
+ ], 'E1144:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'try',
+ ' echo "yes"',
+ 'catch',
+ 'endtry# comment',
+ ], 'E1144:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'hi # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'hi# comment',
+ ], 'E1144:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'hi Search # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'hi Search# comment',
+ ], 'E416:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'hi link This Search # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'hi link This That# comment',
+ ], 'E413:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'hi clear This # comment',
+ 'hi clear # comment',
+ ])
+ # not tested, because it doesn't give an error but a warning:
+ # hi clear This# comment',
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'hi clear# comment',
+ ], 'E416:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'hi Group term=bold',
+ 'match Group /todo/ # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'hi Group term=bold',
+ 'match Group /todo/# comment',
+ ], 'E488:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'match # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'match# comment',
+ ], 'E1144:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'match none # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'match none# comment',
+ ], 'E475:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'menutrans clear # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'menutrans clear# comment text',
+ ], 'E474:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax clear # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax clear# comment text',
+ ], 'E28:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax keyword Word some',
+ 'syntax clear Word # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax keyword Word some',
+ 'syntax clear Word# comment text',
+ ], 'E28:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax list # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax list# comment text',
+ ], 'E28:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax match Word /pat/ oneline # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax match Word /pat/ oneline# comment',
+ ], 'E475:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax keyword Word word # comm[ent',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax keyword Word word# comm[ent',
+ ], 'E789:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax match Word /pat/ # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax match Word /pat/# comment',
+ ], 'E402:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax match Word /pat/ contains=Something # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax match Word /pat/ contains=Something# comment',
+ ], 'E475:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax match Word /pat/ contains= # comment',
+ ], 'E406:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax match Word /pat/ contains=# comment',
+ ], 'E475:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax region Word start=/pat/ end=/pat/ # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax region Word start=/pat/ end=/pat/# comment',
+ ], 'E402:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax sync # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax sync# comment',
+ ], 'E404:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax sync ccomment # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax sync ccomment# comment',
+ ], 'E404:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'syntax cluster Some contains=Word # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'syntax cluster Some contains=Word# comment',
+ ], 'E475:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'command Echo echo # comment',
+ 'command Echo # comment',
+ 'delcommand Echo',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'command Echo echo# comment',
+ 'Echo',
+ ], 'E1144:')
+ delcommand Echo
+
+ var curdir = getcwd()
+ v9.CheckScriptSuccess([
+ 'command Echo cd " comment',
+ 'Echo',
+ 'delcommand Echo',
+ ])
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'command Echo cd # comment',
+ 'Echo',
+ 'delcommand Echo',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'command Echo cd " comment',
+ 'Echo',
+ ], 'E344:')
+ delcommand Echo
+ chdir(curdir)
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'command Echo# comment',
+ ], 'E182:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'command Echo echo',
+ 'command Echo# comment',
+ ], 'E182:')
+ delcommand Echo
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'function # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'function " comment',
+ ], 'E129:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'function# comment',
+ ], 'E1144:')
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'import "./vim9.vim" as v9',
+ 'function v9.CheckScriptSuccess # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'import "./vim9.vim" as v9',
+ 'function v9.CheckScriptSuccess# comment',
+ ], 'E1048: Item not found in script: CheckScriptSuccess#')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'func g:DeleteMeA()',
+ 'endfunc',
+ 'delfunction g:DeleteMeA # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'func g:DeleteMeB()',
+ 'endfunc',
+ 'delfunction g:DeleteMeB# comment',
+ ], 'E488:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'call execute("ls") # comment',
+ ])
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'call execute("ls")# comment',
+ ], 'E488:')
+
+ v9.CheckScriptFailure([
+ 'def Test() " comment',
+ 'enddef',
+ ], 'E488:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'def Test() " comment',
+ 'enddef',
+ ], 'E488:')
+
+ v9.CheckScriptSuccess([
+ 'func Test() " comment',
+ 'endfunc',
+ 'delfunc Test',
+ ])
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'func Test() " comment',
+ 'endfunc',
+ ])
+
+ v9.CheckScriptSuccess([
+ 'def Test() # comment',
+ 'enddef',
+ ])
+ v9.CheckScriptFailure([
+ 'func Test() # comment',
+ 'endfunc',
+ ], 'E488:')
+
+ var lines =<< trim END
+ vim9script
+ syn region Text
+ \ start='foo'
+ #\ comment
+ \ end='bar'
+ syn region Text start='foo'
+ #\ comment
+ \ end='bar'
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ syn region Text
+ \ start='foo'
+ "\ comment
+ \ end='bar'
+ END
+ v9.CheckScriptFailure(lines, 'E399:')
+enddef
+
+def Test_vim9_comment_gui()
+ CheckCanRunGui
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'gui#comment'
+ ], 'E1144:')
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'gui -f#comment'
+ ], 'E194:')
+enddef
+
+def Test_vim9_comment_not_compiled()
+ au TabEnter *.vim g:entered = 1
+ au TabEnter *.x g:entered = 2
+
+ edit test.vim
+ doautocmd TabEnter #comment
+ assert_equal(1, g:entered)
+
+ doautocmd TabEnter f.x
+ assert_equal(2, g:entered)
+
+ g:entered = 0
+ doautocmd TabEnter f.x #comment
+ assert_equal(2, g:entered)
+
+ assert_fails('doautocmd Syntax#comment', 'E216:')
+
+ au! TabEnter
+ unlet g:entered
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'g:var = 123',
+ 'b:var = 456',
+ 'w:var = 777',
+ 't:var = 888',
+ 'unlet g:var w:var # something',
+ ])
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'let var = 123',
+ ], 'E1126: Cannot use :let in Vim9 script')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var g:var = 123',
+ ], 'E1016: Cannot declare a global variable:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var b:var = 123',
+ ], 'E1016: Cannot declare a buffer variable:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var w:var = 123',
+ ], 'E1016: Cannot declare a window variable:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var t:var = 123',
+ ], 'E1016: Cannot declare a tab variable:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var v:version = 123',
+ ], 'E1016: Cannot declare a v: variable:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var $VARIABLE = "text"',
+ ], 'E1016: Cannot declare an environment variable:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'g:var = 123',
+ 'unlet g:var# comment1',
+ ], 'E108:')
+
+ v9.CheckScriptFailure([
+ 'let g:var = 123',
+ 'unlet g:var # something',
+ ], 'E488:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'if 1 # comment2',
+ ' echo "yes"',
+ 'elseif 2 #comment',
+ ' echo "no"',
+ 'endif',
+ ])
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'if 1# comment3',
+ ' echo "yes"',
+ 'endif',
+ ], 'E488:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'if 0 # comment4',
+ ' echo "yes"',
+ 'elseif 2#comment',
+ ' echo "no"',
+ 'endif',
+ ], 'E488:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'var v = 1 # comment5',
+ ])
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'var v = 1# comment6',
+ ], 'E488:')
+
+ v9.CheckScriptSuccess([
+ 'vim9script',
+ 'new',
+ 'setline(1, ["# define pat", "last"])',
+ ':$',
+ 'dsearch /pat/ #comment',
+ 'bwipe!',
+ ])
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'new',
+ 'setline(1, ["# define pat", "last"])',
+ ':$',
+ 'dsearch /pat/#comment',
+ 'bwipe!',
+ ], 'E488:')
+
+ v9.CheckScriptFailure([
+ 'vim9script',
+ 'func! SomeFunc()',
+ ], 'E477:')
+enddef
+
+def Test_finish()
+ var lines =<< trim END
+ vim9script
+ g:res = 'one'
+ if v:false | finish | endif
+ g:res = 'two'
+ finish
+ g:res = 'three'
+ END
+ writefile(lines, 'Xfinished', 'D')
+ source Xfinished
+ assert_equal('two', g:res)
+
+ unlet g:res
+enddef
+
+def Test_forward_declaration()
+ var lines =<< trim END
+ vim9script
+ def GetValue(): string
+ return theVal
+ enddef
+ var theVal = 'something'
+ g:initVal = GetValue()
+ theVal = 'else'
+ g:laterVal = GetValue()
+ END
+ writefile(lines, 'Xforward', 'D')
+ source Xforward
+ assert_equal('something', g:initVal)
+ assert_equal('else', g:laterVal)
+
+ unlet g:initVal
+ unlet g:laterVal
+enddef
+
+def Test_declare_script_var_in_func()
+ var lines =<< trim END
+ vim9script
+ func Declare()
+ let s:local = 123
+ endfunc
+ Declare()
+ END
+ v9.CheckScriptFailure(lines, 'E1269:')
+enddef
+
+def Test_lock_script_var()
+ var lines =<< trim END
+ vim9script
+ var local = 123
+ assert_equal(123, local)
+
+ var error: string
+ try
+ local = 'asdf'
+ catch
+ error = v:exception
+ endtry
+ assert_match('E1012: Type mismatch; expected number but got string', error)
+
+ lockvar local
+ try
+ local = 999
+ catch
+ error = v:exception
+ endtry
+ assert_match('E741: Value is locked: local', error)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+
+func Test_vim9script_not_global()
+ " check that items defined in Vim9 script are script-local, not global
+ let vim9lines =<< trim END
+ vim9script
+ var name = 'local'
+ func TheFunc()
+ echo 'local'
+ endfunc
+ def DefFunc()
+ echo 'local'
+ enddef
+ END
+ call writefile(vim9lines, 'Xvim9script.vim', 'D')
+ source Xvim9script.vim
+ try
+ echo g:var
+ assert_report('did not fail')
+ catch /E121:/
+ " caught
+ endtry
+ try
+ call TheFunc()
+ assert_report('did not fail')
+ catch /E117:/
+ " caught
+ endtry
+ try
+ call DefFunc()
+ assert_report('did not fail')
+ catch /E117:/
+ " caught
+ endtry
+endfunc
+
+def Test_vim9_copen()
+ # this was giving an error for setting w:quickfix_title
+ copen
+ quit
+enddef
+
+def Test_script_var_in_autocmd()
+ # using a script variable from an autocommand, defined in a :def function in a
+ # legacy Vim script, cannot check the variable type.
+ var lines =<< trim END
+ let s:counter = 1
+ def s:Func()
+ au! CursorHold
+ au CursorHold * s:counter += 1
+ enddef
+ call s:Func()
+ doau CursorHold
+ call assert_equal(2, s:counter)
+ au! CursorHold
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+def Test_error_in_autoload_script()
+ var save_rtp = &rtp
+ var dir = getcwd() .. '/Xruntime'
+ &rtp = dir
+ mkdir(dir .. '/autoload', 'pR')
+
+ var lines =<< trim END
+ vim9script noclear
+ export def Autoloaded()
+ enddef
+ def Broken()
+ var x: any = ''
+ eval x != 0
+ enddef
+ Broken()
+ END
+ writefile(lines, dir .. '/autoload/script.vim')
+
+ lines =<< trim END
+ vim9script
+ def CallAutoloaded()
+ script#Autoloaded()
+ enddef
+
+ function Legacy()
+ try
+ call s:CallAutoloaded()
+ catch
+ call assert_match('E1030: Using a String as a Number', v:exception)
+ endtry
+ endfunction
+
+ Legacy()
+ END
+ v9.CheckScriptSuccess(lines)
+
+ &rtp = save_rtp
+enddef
+
+def Test_error_in_autoload_script_foldexpr()
+ var save_rtp = &rtp
+ mkdir('Xvim/autoload', 'pR')
+ &runtimepath = 'Xvim'
+
+ var lines =<< trim END
+ vim9script
+ eval [][0]
+ echomsg 'no error'
+ END
+ lines->writefile('Xvim/autoload/script.vim')
+
+ lines =<< trim END
+ vim9script
+ import autoload 'script.vim'
+ &foldmethod = 'expr'
+ &foldexpr = 'script.Func()'
+ redraw
+ END
+ v9.CheckScriptFailure(lines, 'E684: List index out of range: 0')
+enddef
+
+def Test_invalid_sid()
+ assert_fails('func <SNR>1234_func', 'E123:')
+
+ if g:RunVim([], ['wq! Xdidit'], '+"func <SNR>1_func"')
+ assert_equal([], readfile('Xdidit'))
+ endif
+ delete('Xdidit')
+enddef
+
+def Test_restoring_cpo()
+ writefile(['vim9script', 'set nocp'], 'Xsourced', 'D')
+ writefile(['call writefile(["done"], "Xdone")', 'quit!'], 'Xclose', 'D')
+ if g:RunVim([], [], '-u NONE +"set cpo+=a" -S Xsourced -S Xclose')
+ assert_equal(['done'], readfile('Xdone'))
+ endif
+ delete('Xdone')
+
+ writefile(['vim9script', 'g:cpoval = &cpo'], 'XanotherScript', 'D')
+ set cpo=aABceFsMny>
+ edit XanotherScript
+ so %
+ assert_equal('aABceFsMny>', &cpo)
+ assert_equal('aABceFs', g:cpoval)
+ :1del
+ setline(1, 'let g:cpoval = &cpo')
+ w
+ so %
+ assert_equal('aABceFsMny>', &cpo)
+ assert_equal('aABceFsMny>', g:cpoval)
+
+ set cpo&vim
+ unlet g:cpoval
+
+ if has('unix')
+ # 'cpo' is not restored in main vimrc
+ var save_HOME = $HOME
+ $HOME = getcwd() .. '/Xhome'
+ mkdir('Xhome', 'R')
+ var lines =<< trim END
+ vim9script
+ writefile(['before: ' .. &cpo], 'Xrporesult')
+ set cpo+=M
+ writefile(['after: ' .. &cpo], 'Xrporesult', 'a')
+ END
+ writefile(lines, 'Xhome/.vimrc')
+
+ lines =<< trim END
+ call writefile(['later: ' .. &cpo], 'Xrporesult', 'a')
+ END
+ writefile(lines, 'Xlegacy', 'D')
+
+ lines =<< trim END
+ vim9script
+ call writefile(['vim9: ' .. &cpo], 'Xrporesult', 'a')
+ qa
+ END
+ writefile(lines, 'Xvim9', 'D')
+
+ var cmd = g:GetVimCommand() .. " -S Xlegacy -S Xvim9"
+ cmd = substitute(cmd, '-u NONE', '', '')
+ exe "silent !" .. cmd
+
+ assert_equal([
+ 'before: aABceFs',
+ 'after: aABceFsM',
+ 'later: aABceFsM',
+ 'vim9: aABceFs'], readfile('Xrporesult'))
+
+ $HOME = save_HOME
+ delete('Xrporesult')
+ endif
+enddef
+
+" Use :function so we can use Check commands
+func Test_no_redraw_when_restoring_cpo()
+ CheckScreendump
+ CheckFeature timers
+ call Run_test_no_redraw_when_restoring_cpo()
+endfunc
+
+def Run_test_no_redraw_when_restoring_cpo()
+ var lines =<< trim END
+ vim9script
+ export def Func()
+ enddef
+ END
+ mkdir('Xnordir/autoload', 'pR')
+ writefile(lines, 'Xnordir/autoload/script.vim')
+
+ lines =<< trim END
+ vim9script
+ set cpo+=M
+ exe 'set rtp^=' .. getcwd() .. '/Xnordir'
+ au CmdlineEnter : ++once timer_start(0, (_) => script#Func())
+ setline(1, 'some text')
+ END
+ writefile(lines, 'XTest_redraw_cpo', 'D')
+ var buf = g:RunVimInTerminal('-S XTest_redraw_cpo', {'rows': 6})
+ term_sendkeys(buf, "V:")
+ g:VerifyScreenDump(buf, 'Test_vim9_no_redraw', {})
+
+ # clean up
+ term_sendkeys(buf, "\<Esc>u")
+ g:StopVimInTerminal(buf)
+enddef
+
+func Test_reject_declaration()
+ CheckScreendump
+ call Run_test_reject_declaration()
+endfunc
+
+def Run_test_reject_declaration()
+ var buf = g:RunVimInTerminal('', {'rows': 6})
+ term_sendkeys(buf, ":vim9cmd var x: number\<CR>")
+ g:VerifyScreenDump(buf, 'Test_vim9_reject_declaration_1', {})
+ term_sendkeys(buf, ":\<CR>")
+ term_sendkeys(buf, ":vim9cmd g:foo = 123 | echo g:foo\<CR>")
+ g:VerifyScreenDump(buf, 'Test_vim9_reject_declaration_2', {})
+
+ # clean up
+ g:StopVimInTerminal(buf)
+enddef
+
+def Test_minimal_command_name_length()
+ var names = [
+ 'cons',
+ 'brea',
+ 'cat',
+ 'catc',
+ 'con',
+ 'cont',
+ 'conti',
+ 'contin',
+ 'continu',
+ 'el',
+ 'els',
+ 'elsei',
+ 'endfo',
+ 'en',
+ 'end',
+ 'endi',
+ 'endw',
+ 'endt',
+ 'endtr',
+ 'exp',
+ 'expo',
+ 'expor',
+ 'fina',
+ 'finall',
+ 'fini',
+ 'finis',
+ 'imp',
+ 'impo',
+ 'impor',
+ 'retu',
+ 'retur',
+ 'th',
+ 'thr',
+ 'thro',
+ 'wh',
+ 'whi',
+ 'whil',
+ ]
+ for name in names
+ v9.CheckDefAndScriptFailure([name .. ' '], 'E1065:')
+ endfor
+
+ var lines =<< trim END
+ vim9script
+ def SomeFunc()
+ endd
+ END
+ v9.CheckScriptFailure(lines, 'E1065:')
+ lines =<< trim END
+ vim9script
+ def SomeFunc()
+ endde
+ END
+ v9.CheckScriptFailure(lines, 'E1065:')
+enddef
+
+def Test_unset_any_variable()
+ var lines =<< trim END
+ var name: any
+ assert_equal(0, name)
+ END
+ v9.CheckDefAndScriptSuccess(lines)
+enddef
+
+func Test_define_func_at_command_line()
+ CheckRunVimInTerminal
+
+ " call indirectly to avoid compilation error for missing functions
+ call Run_Test_define_func_at_command_line()
+endfunc
+
+def Run_Test_define_func_at_command_line()
+ # run in a separate Vim instance to avoid the script context
+ var lines =<< trim END
+ func CheckAndQuit()
+ call assert_fails('call Afunc()', 'E117: Unknown function: Bfunc')
+ call writefile(['errors: ' .. string(v:errors)], 'Xdidcmd')
+ endfunc
+ END
+ writefile([''], 'Xdidcmd', 'D')
+ writefile(lines, 'XcallFunc', 'D')
+ var buf = g:RunVimInTerminal('-S XcallFunc', {rows: 6})
+ # define Afunc() on the command line
+ term_sendkeys(buf, ":def Afunc()\<CR>Bfunc()\<CR>enddef\<CR>")
+ term_sendkeys(buf, ":call CheckAndQuit()\<CR>")
+ g:WaitForAssert(() => assert_equal(['errors: []'], readfile('Xdidcmd')))
+
+ call g:StopVimInTerminal(buf)
+enddef
+
+def Test_script_var_scope()
+ var lines =<< trim END
+ vim9script
+ if true
+ if true
+ var one = 'one'
+ echo one
+ endif
+ echo one
+ endif
+ END
+ v9.CheckScriptFailure(lines, 'E121:', 7)
+
+ lines =<< trim END
+ vim9script
+ if true
+ if false
+ var one = 'one'
+ echo one
+ else
+ var one = 'one'
+ echo one
+ endif
+ echo one
+ endif
+ END
+ v9.CheckScriptFailure(lines, 'E121:', 10)
+
+ lines =<< trim END
+ vim9script
+ while true
+ var one = 'one'
+ echo one
+ break
+ endwhile
+ echo one
+ END
+ v9.CheckScriptFailure(lines, 'E121:', 7)
+
+ lines =<< trim END
+ vim9script
+ for i in range(1)
+ var one = 'one'
+ echo one
+ endfor
+ echo one
+ END
+ v9.CheckScriptFailure(lines, 'E121:', 6)
+
+ lines =<< trim END
+ vim9script
+ {
+ var one = 'one'
+ assert_equal('one', one)
+ }
+ assert_false(exists('one'))
+ assert_false(exists('s:one'))
+ END
+ v9.CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+ {
+ var one = 'one'
+ echo one
+ }
+ echo one
+ END
+ v9.CheckScriptFailure(lines, 'E121:', 6)
+enddef
+
+def Test_catch_exception_in_callback()
+ var lines =<< trim END
+ vim9script
+ def Callback(...l: list<any>)
+ try
+ var x: string
+ var y: string
+ # this error should be caught with CHECKLEN
+ var sl = ['']
+ [x, y] = sl
+ catch
+ g:caught = 'yes'
+ endtry
+ enddef
+ popup_menu('popup', {callback: Callback})
+ feedkeys("\r", 'xt')
+ END
+ v9.CheckScriptSuccess(lines)
+
+ unlet g:caught
+enddef
+
+def Test_no_unknown_error_after_error()
+ if !has('unix') || !has('job')
+ throw 'Skipped: not unix of missing +job feature'
+ endif
+ # FIXME: this check should not be needed
+ if has('win32')
+ throw 'Skipped: does not work on MS-Windows'
+ endif
+ var lines =<< trim END
+ vim9script
+ var source: list<number>
+ def Out_cb(...l: list<any>)
+ eval [][0]
+ enddef
+ def Exit_cb(...l: list<any>)
+ sleep 1m
+ g:did_call_exit_cb = true
+ source += l
+ enddef
+ var myjob = job_start('echo burp', {out_cb: Out_cb, exit_cb: Exit_cb, mode: 'raw'})
+ while job_status(myjob) == 'run'
+ sleep 10m
+ endwhile
+ # wait for Exit_cb() to be called
+ for x in range(100)
+ if exists('g:did_call_exit_cb')
+ unlet g:did_call_exit_cb
+ break
+ endif
+ sleep 10m
+ endfor
+ END
+ writefile(lines, 'Xdef', 'D')
+ # Either the exit or out callback is called first, accept them in any order
+ assert_fails('so Xdef', ['E684:\|E1012:', 'E1012:\|E684:'])
+enddef
+
+def InvokeNormal()
+ exe "norm! :m+1\r"
+enddef
+
+def Test_invoke_normal_in_visual_mode()
+ xnoremap <F3> <Cmd>call <SID>InvokeNormal()<CR>
+ new
+ setline(1, ['aaa', 'bbb'])
+ feedkeys("V\<F3>", 'xt')
+ assert_equal(['bbb', 'aaa'], getline(1, 2))
+ xunmap <F3>
+enddef
+
+def Test_white_space_after_command()
+ var lines =<< trim END
+ exit_cb: Func})
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1144:', 1)
+
+ lines =<< trim END
+ e#
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1144:', 1)
+enddef
+
+def Test_script_var_gone_when_sourced_twice()
+ var lines =<< trim END
+ vim9script
+ if exists('g:guard')
+ finish
+ endif
+ g:guard = 1
+ var name = 'thename'
+ def g:GetName(): string
+ return name
+ enddef
+ def g:SetName(arg: string)
+ name = arg
+ enddef
+ END
+ writefile(lines, 'XscriptTwice.vim', 'D')
+ so XscriptTwice.vim
+ assert_equal('thename', g:GetName())
+ g:SetName('newname')
+ assert_equal('newname', g:GetName())
+ so XscriptTwice.vim
+ assert_fails('call g:GetName()', 'E1149:')
+ assert_fails('call g:SetName("x")', 'E1149:')
+
+ delfunc g:GetName
+ delfunc g:SetName
+ unlet g:guard
+enddef
+
+def Test_unsupported_commands()
+ var lines =<< trim END
+ ka
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E476:', 'E492:'])
+
+ lines =<< trim END
+ :1ka
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E476:', 'E492:'])
+
+ lines =<< trim END
+ :k a
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1100:')
+
+ lines =<< trim END
+ :1k a
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E481:')
+
+ lines =<< trim END
+ t
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1100:')
+
+ lines =<< trim END
+ x
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1100:')
+
+ lines =<< trim END
+ xit
+ END
+ v9.CheckDefAndScriptFailure(lines, 'E1100:')
+
+ lines =<< trim END
+ Print
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E476: Invalid command: Print', 'E492: Not an editor command: Print'])
+
+ lines =<< trim END
+ mode 4
+ END
+ v9.CheckDefAndScriptFailure(lines, ['E476: Invalid command: mode 4', 'E492: Not an editor command: mode 4'])
+enddef
+
+def Test_mapping_line_number()
+ var lines =<< trim END
+ vim9script
+ def g:FuncA()
+ # Some comment
+ FuncB(0)
+ enddef
+ # Some comment
+ def FuncB(
+ # Some comment
+ n: number
+ )
+ exe 'nno '
+ # Some comment
+ .. '<F3> a'
+ .. 'b'
+ .. 'c'
+ enddef
+ END
+ v9.CheckScriptSuccess(lines)
+ var res = execute('verbose nmap <F3>')
+ assert_match('No mapping found', res)
+
+ g:FuncA()
+ res = execute('verbose nmap <F3>')
+ assert_match(' <F3> .* abc.*Last set from .*XScriptSuccess\d\+ line 11', res)
+
+ nunmap <F3>
+ delfunc g:FuncA
+enddef
+
+def Test_option_set()
+ # legacy script allows for white space
+ var lines =<< trim END
+ set foldlevel =11
+ call assert_equal(11, &foldlevel)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ set foldlevel
+ set foldlevel=12
+ assert_equal(12, &foldlevel)
+ set foldlevel+=2
+ assert_equal(14, &foldlevel)
+ set foldlevel-=3
+ assert_equal(11, &foldlevel)
+
+ lines =<< trim END
+ set foldlevel =1
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1205: No white space allowed between option and: =1')
+
+ lines =<< trim END
+ set foldlevel +=1
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1205: No white space allowed between option and: +=1')
+
+ lines =<< trim END
+ set foldlevel ^=1
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1205: No white space allowed between option and: ^=1')
+
+ lines =<< trim END
+ set foldlevel -=1
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1205: No white space allowed between option and: -=1')
+
+ set foldlevel&
+enddef
+
+def Test_option_modifier()
+ # legacy script allows for white space
+ var lines =<< trim END
+ set hlsearch & hlsearch !
+ call assert_equal(1, &hlsearch)
+ END
+ v9.CheckScriptSuccess(lines)
+
+ set hlsearch
+ set hlsearch!
+ assert_equal(false, &hlsearch)
+
+ set hlsearch
+ set hlsearch&
+ assert_equal(false, &hlsearch)
+
+ lines =<< trim END
+ set hlsearch &
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1205: No white space allowed between option and: &')
+
+ lines =<< trim END
+ set hlsearch !
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'E1205: No white space allowed between option and: !')
+
+ set hlsearch&
+enddef
+
+" This must be called last, it may cause following :def functions to fail
+def Test_xxx_echoerr_line_number()
+ var lines =<< trim END
+ echoerr 'some'
+ .. ' error'
+ .. ' continued'
+ END
+ v9.CheckDefExecAndScriptFailure(lines, 'some error continued', 1)
+enddef
+
+func Test_debug_with_lambda()
+ CheckRunVimInTerminal
+
+ " call indirectly to avoid compilation error for missing functions
+ call Run_Test_debug_with_lambda()
+endfunc
+
+def Run_Test_debug_with_lambda()
+ var lines =<< trim END
+ vim9script
+ def Func()
+ var n = 0
+ echo [0]->filter((_, v) => v == n)
+ enddef
+ breakadd func Func
+ Func()
+ END
+ writefile(lines, 'XdebugFunc', 'D')
+ var buf = g:RunVimInTerminal('-S XdebugFunc', {rows: 6, wait_for_ruler: 0})
+ g:WaitForAssert(() => assert_match('^>', term_getline(buf, 6)))
+
+ term_sendkeys(buf, "cont\<CR>")
+ g:WaitForAssert(() => assert_match('\[0\]', term_getline(buf, 5)))
+
+ g:StopVimInTerminal(buf)
+enddef
+
+func Test_debug_running_out_of_lines()
+ CheckRunVimInTerminal
+
+ " call indirectly to avoid compilation error for missing functions
+ call Run_Test_debug_running_out_of_lines()
+endfunc
+
+def Run_Test_debug_running_out_of_lines()
+ var lines =<< trim END
+ vim9script
+ def Crash()
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ if true
+ #
+ endif
+ enddef
+ breakadd func Crash
+ Crash()
+ END
+ writefile(lines, 'XdebugFunc', 'D')
+ var buf = g:RunVimInTerminal('-S XdebugFunc', {rows: 6, wait_for_ruler: 0})
+ g:WaitForAssert(() => assert_match('^>', term_getline(buf, 6)))
+
+ term_sendkeys(buf, "next\<CR>")
+ g:TermWait(buf)
+ g:WaitForAssert(() => assert_match('^>', term_getline(buf, 6)))
+
+ term_sendkeys(buf, "cont\<CR>")
+ g:TermWait(buf)
+
+ g:StopVimInTerminal(buf)
+enddef
+
+def Test_ambiguous_command_error()
+ var lines =<< trim END
+ vim9script
+ command CmdA echomsg 'CmdA'
+ command CmdB echomsg 'CmdB'
+ Cmd
+ END
+ v9.CheckScriptFailure(lines, 'E464: Ambiguous use of user-defined command: Cmd', 4)
+
+ lines =<< trim END
+ vim9script
+ def Func()
+ Cmd
+ enddef
+ Func()
+ END
+ v9.CheckScriptFailure(lines, 'E464: Ambiguous use of user-defined command: Cmd', 1)
+
+ lines =<< trim END
+ vim9script
+ nnoremap <F3> <ScriptCmd>Cmd<CR>
+ feedkeys("\<F3>", 'xt')
+ END
+ v9.CheckScriptFailure(lines, 'E464: Ambiguous use of user-defined command: Cmd', 3)
+
+ delcommand CmdA
+ delcommand CmdB
+ nunmap <F3>
+enddef
+
+" Execute this near the end, profiling doesn't stop until Vim exits.
+" This only tests that it works, not the profiling output.
+def Test_profile_with_lambda()
+ CheckFeature profile
+
+ var lines =<< trim END
+ vim9script
+
+ def ProfiledWithLambda()
+ var n = 3
+ echo [[1, 2], [3, 4]]->filter((_, l) => l[0] == n)
+ enddef
+
+ def ProfiledNested()
+ var x = 0
+ def Nested(): any
+ return x
+ enddef
+ Nested()
+ enddef
+
+ def g:ProfiledNestedProfiled()
+ var x = 0
+ def Nested(): any
+ return x
+ enddef
+ Nested()
+ enddef
+
+ def Profile()
+ ProfiledWithLambda()
+ ProfiledNested()
+
+ # Also profile the nested function. Use a different function, although
+ # the contents is the same, to make sure it was not already compiled.
+ profile func *
+ g:ProfiledNestedProfiled()
+
+ profdel func *
+ profile pause
+ enddef
+
+ var result = 'done'
+ try
+ # mark functions for profiling now to avoid E1271
+ profile start Xprofile.log
+ profile func ProfiledWithLambda
+ profile func ProfiledNested
+
+ Profile()
+ catch
+ result = 'failed: ' .. v:exception
+ finally
+ writefile([result], 'Xdidprofile')
+ endtry
+ END
+ writefile(lines, 'Xprofile.vim', 'D')
+ call system(g:GetVimCommand()
+ .. ' --clean'
+ .. ' -c "so Xprofile.vim"'
+ .. ' -c "qall!"')
+ call assert_equal(0, v:shell_error)
+
+ assert_equal(['done'], readfile('Xdidprofile'))
+ assert_true(filereadable('Xprofile.log'))
+ delete('Xdidprofile')
+ delete('Xprofile.log')
+enddef
+
+func Test_misplaced_type()
+ CheckRunVimInTerminal
+ call Run_Test_misplaced_type()
+endfunc
+
+def Run_Test_misplaced_type()
+ writefile(['let g:somevar = "asdf"'], 'XTest_misplaced_type', 'D')
+ var buf = g:RunVimInTerminal('-S XTest_misplaced_type', {'rows': 6})
+ term_sendkeys(buf, ":vim9cmd echo islocked('somevar: string')\<CR>")
+ g:VerifyScreenDump(buf, 'Test_misplaced_type', {})
+
+ g:StopVimInTerminal(buf)
+enddef
+
+" Ensure echo doesn't crash when stringifying empty variables.
+def Test_echo_uninit_variables()
+ var res: string
+
+ var var_bool: bool
+ var var_num: number
+ var var_float: float
+ var Var_func: func
+ var var_string: string
+ var var_blob: blob
+ var var_list: list<any>
+ var var_dict: dict<any>
+
+ redir => res
+ echo var_bool
+ echo var_num
+ echo var_float
+ echo Var_func
+ echo var_string
+ echo var_blob
+ echo var_list
+ echo var_dict
+ redir END
+
+ assert_equal(['false', '0', '0.0', 'function()', '', '0z', '[]', '{}'], res->split('\n'))
+
+ if has('job')
+ var var_job: job
+ var var_channel: channel
+
+ redir => res
+ echo var_job
+ echo var_channel
+ redir END
+
+ assert_equal(['no process', 'channel fail'], res->split('\n'))
+ endif
+enddef
+
+def Test_free_type_before_use()
+ # this rather complicated script was freeing a type before using it
+ var lines =<< trim END
+ vim9script
+
+ def Scan(rel: list<dict<any>>): func(func(dict<any>))
+ return (Emit: func(dict<any>)) => {
+ for t in rel
+ Emit(t)
+ endfor
+ }
+ enddef
+
+ def Build(Cont: func(func(dict<any>))): list<dict<any>>
+ var rel: list<dict<any>> = []
+ Cont((t) => {
+ add(rel, t)
+ })
+ return rel
+ enddef
+
+ var R = [{A: 0}]
+ var result = Scan(R)->Build()
+ result = Scan(R)->Build()
+
+ assert_equal(R, result)
+ END
+ v9.CheckScriptSuccess(lines)
+enddef
+
+" Keep this last, it messes up highlighting.
+def Test_substitute_cmd()
+ new
+ setline(1, 'something')
+ :substitute(some(other(
+ assert_equal('otherthing', getline(1))
+ bwipe!
+
+ # also when the context is Vim9 script
+ var lines =<< trim END
+ vim9script
+ new
+ setline(1, 'something')
+ :substitute(some(other(
+ assert_equal('otherthing', getline(1))
+ bwipe!
+ END
+ writefile(lines, 'Xvim9lines', 'D')
+ source Xvim9lines
+enddef
+
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_viminfo.vim b/src/testdir/test_viminfo.vim
new file mode 100644
index 0000000..0551ea1
--- /dev/null
+++ b/src/testdir/test_viminfo.vim
@@ -0,0 +1,1282 @@
+" Test for reading and writing .viminfo
+
+source check.vim
+source term_util.vim
+source shared.vim
+
+func Test_viminfo_read_and_write()
+ " First clear 'history', so that "hislen" is zero. Then set it again,
+ " simulating Vim starting up.
+ set history=0
+ wviminfo Xviminfo
+ set history=1000
+
+ call histdel(':')
+ let @/=''
+ let lines = [
+ \ '# comment line',
+ \ '*encoding=utf-8',
+ \ '~MSle0~/asdf',
+ \ '|copied as-is',
+ \ '|and one more',
+ \ ]
+ call writefile(lines, 'Xviminfo', 'D')
+ rviminfo Xviminfo
+ call assert_equal('asdf', @/)
+
+ wviminfo Xviminfo
+ let lines = readfile('Xviminfo')
+ let done = 0
+ for line in lines
+ if line[0] == '|' && line !~ '^|[234],' && line !~ '^|<'
+ if done == 0
+ call assert_equal('|1,4', line)
+ elseif done == 1
+ call assert_equal('|copied as-is', line)
+ elseif done == 2
+ call assert_equal('|and one more', line)
+ endif
+ let done += 1
+ endif
+ endfor
+ call assert_equal(3, done)
+endfunc
+
+func Test_global_vars()
+ let g:MY_GLOBAL_STRING = "Vim Editor"
+ let g:MY_GLOBAL_NUM = 345
+ let g:MY_GLOBAL_FLOAT = 3.14
+ let test_dict = {'foo': 1, 'bar': 0, 'longvarible': 1000}
+ let g:MY_GLOBAL_DICT = test_dict
+ " store a really long list, so line wrapping will occur in viminfo file
+ let test_list = range(1,100)
+ let g:MY_GLOBAL_LIST = test_list
+ let test_blob = 0z00112233445566778899aabbccddeeff
+ let g:MY_GLOBAL_BLOB = test_blob
+ let test_false = v:false
+ let g:MY_GLOBAL_FALSE = test_false
+ let test_true = v:true
+ let g:MY_GLOBAL_TRUE = test_true
+ let test_null = v:null
+ let g:MY_GLOBAL_NULL = test_null
+ let test_none = v:none
+ let g:MY_GLOBAL_NONE = test_none
+ let g:MY_GLOBAL_FUNCREF = function('min')
+
+ set viminfo='100,<50,s10,h,!,nviminfo
+ wv! Xviminfo
+
+ unlet g:MY_GLOBAL_STRING
+ unlet g:MY_GLOBAL_NUM
+ unlet g:MY_GLOBAL_FLOAT
+ unlet g:MY_GLOBAL_DICT
+ unlet g:MY_GLOBAL_LIST
+ unlet g:MY_GLOBAL_BLOB
+ unlet g:MY_GLOBAL_FALSE
+ unlet g:MY_GLOBAL_TRUE
+ unlet g:MY_GLOBAL_NULL
+ unlet g:MY_GLOBAL_NONE
+ unlet g:MY_GLOBAL_FUNCREF
+
+ rv! Xviminfo
+ call assert_equal("Vim Editor", g:MY_GLOBAL_STRING)
+ call assert_equal(345, g:MY_GLOBAL_NUM)
+ call assert_equal(3.14, g:MY_GLOBAL_FLOAT)
+ call assert_equal(test_dict, g:MY_GLOBAL_DICT)
+ call assert_equal(test_list, g:MY_GLOBAL_LIST)
+ call assert_equal(test_blob, g:MY_GLOBAL_BLOB)
+ call assert_equal(test_false, g:MY_GLOBAL_FALSE)
+ call assert_equal(test_true, g:MY_GLOBAL_TRUE)
+ call assert_equal(test_null, g:MY_GLOBAL_NULL)
+ call assert_equal(test_none, g:MY_GLOBAL_NONE)
+ call assert_false(exists("g:MY_GLOBAL_FUNCREF"))
+
+ " When reading global variables from viminfo, if a variable cannot be
+ " modified, then the value should not be changed.
+ unlet g:MY_GLOBAL_STRING
+ unlet g:MY_GLOBAL_NUM
+ unlet g:MY_GLOBAL_FLOAT
+ unlet g:MY_GLOBAL_DICT
+ unlet g:MY_GLOBAL_LIST
+ unlet g:MY_GLOBAL_BLOB
+
+ const g:MY_GLOBAL_STRING = 'New Value'
+ const g:MY_GLOBAL_NUM = 987
+ const g:MY_GLOBAL_FLOAT = 1.16
+ const g:MY_GLOBAL_DICT = {'editor': 'vim'}
+ const g:MY_GLOBAL_LIST = [5, 7, 13]
+ const g:MY_GLOBAL_BLOB = 0zDEADBEEF
+ call assert_fails('rv! Xviminfo', 'E741:')
+ call assert_equal('New Value', g:MY_GLOBAL_STRING)
+ call assert_equal(987, g:MY_GLOBAL_NUM)
+ call assert_equal(1.16, g:MY_GLOBAL_FLOAT)
+ call assert_equal({'editor': 'vim'}, g:MY_GLOBAL_DICT)
+ call assert_equal([5, 7 , 13], g:MY_GLOBAL_LIST)
+ call assert_equal(0zDEADBEEF, g:MY_GLOBAL_BLOB)
+
+ unlet g:MY_GLOBAL_STRING
+ unlet g:MY_GLOBAL_NUM
+ unlet g:MY_GLOBAL_FLOAT
+ unlet g:MY_GLOBAL_DICT
+ unlet g:MY_GLOBAL_LIST
+ unlet g:MY_GLOBAL_BLOB
+
+ " Test for invalid values for a blob, list, dict in a viminfo file
+ call writefile([
+ \ "!GLOB_BLOB_1\tBLO\t123",
+ \ "!GLOB_BLOB_2\tBLO\t012",
+ \ "!GLOB_BLOB_3\tBLO\t0z1x",
+ \ "!GLOB_BLOB_4\tBLO\t0z12 ab",
+ \ "!GLOB_LIST_1\tLIS\t1 2",
+ \ "!GLOB_DICT_1\tDIC\t1 2"], 'Xviminfo', 'D')
+ call assert_fails('rv! Xviminfo', 'E488:')
+ call assert_equal('123', g:GLOB_BLOB_1)
+ call assert_equal(1, type(g:GLOB_BLOB_1))
+ call assert_equal('012', g:GLOB_BLOB_2)
+ call assert_equal(1, type(g:GLOB_BLOB_2))
+ call assert_equal('0z1x', g:GLOB_BLOB_3)
+ call assert_equal(1, type(g:GLOB_BLOB_3))
+ call assert_equal('0z12 ab', g:GLOB_BLOB_4)
+ call assert_equal(1, type(g:GLOB_BLOB_4))
+ call assert_equal('1 2', g:GLOB_LIST_1)
+ call assert_equal(1, type(g:GLOB_LIST_1))
+ call assert_equal('1 2', g:GLOB_DICT_1)
+ call assert_equal(1, type(g:GLOB_DICT_1))
+
+ set viminfo-=!
+endfunc
+
+func Test_global_vars_with_circular_reference()
+ let g:MY_GLOBAL_LIST = []
+ call add(g:MY_GLOBAL_LIST, g:MY_GLOBAL_LIST)
+ let g:MY_GLOBAL_DICT = {}
+ let g:MY_GLOBAL_DICT['self'] = g:MY_GLOBAL_DICT
+
+ set viminfo='100,<50,s10,h,!,nviminfo
+ wv! Xviminfo
+ call assert_equal(v:errmsg, '')
+
+ unlet g:MY_GLOBAL_LIST
+ unlet g:MY_GLOBAL_DICT
+
+ rv! Xviminfo
+ call assert_equal(v:errmsg, '')
+ call assert_true(!exists('g:MY_GLOBAL_LIST'))
+ call assert_true(!exists('g:MY_GLOBAL_DICT'))
+
+ call delete('Xviminfo')
+ set viminfo-=!
+endfunc
+
+func Test_cmdline_history()
+ call histdel(':')
+ call test_settime(11)
+ call histadd(':', "echo 'one'")
+ call test_settime(12)
+ " split into two lines
+ let long800 = repeat(" 'eight'", 100)
+ call histadd(':', "echo " . long800)
+ call test_settime(13)
+ " split into three lines
+ let long1400 = repeat(" 'fourteeeeen'", 100)
+ call histadd(':', "echo " . long1400)
+ wviminfo Xviminfo
+ let lines = readfile('Xviminfo')
+ let done_colon = 0
+ let done_bar = 0
+ let lnum = 0
+ while lnum < len(lines)
+ let line = lines[lnum] | let lnum += 1
+ if line[0] == ':'
+ if done_colon == 0
+ call assert_equal(":\x161408", line)
+ let line = lines[lnum] | let lnum += 1
+ call assert_equal('<echo ' . long1400, line)
+ elseif done_colon == 1
+ call assert_equal(":\x16808", line)
+ let line = lines[lnum] | let lnum += 1
+ call assert_equal("<echo " . long800, line)
+ elseif done_colon == 2
+ call assert_equal(":echo 'one'", line)
+ endif
+ let done_colon += 1
+ elseif line[0:4] == '|2,0,'
+ if done_bar == 0
+ call assert_equal("|2,0,13,,>1407", line)
+ let line = lines[lnum] | let lnum += 1
+ call assert_equal('|<"echo ' . long1400[0:484], line)
+ let line = lines[lnum] | let lnum += 1
+ call assert_equal('|<' . long1400[485:974], line)
+ let line = lines[lnum] | let lnum += 1
+ call assert_equal('|<' . long1400[975:] . '"', line)
+ elseif done_bar == 1
+ call assert_equal('|2,0,12,,>807', line)
+ let line = lines[lnum] | let lnum += 1
+ call assert_equal('|<"echo ' . long800[0:484], line)
+ let line = lines[lnum] | let lnum += 1
+ call assert_equal('|<' . long800[485:] . '"', line)
+ elseif done_bar == 2
+ call assert_equal("|2,0,11,,\"echo 'one'\"", line)
+ endif
+ let done_bar += 1
+ endif
+ endwhile
+ call assert_equal(3, done_colon)
+ call assert_equal(3, done_bar)
+
+ call histdel(':')
+ rviminfo Xviminfo
+ call assert_equal("echo " . long1400, histget(':', -1))
+ call assert_equal("echo " . long800, histget(':', -2))
+ call assert_equal("echo 'one'", histget(':', -3))
+
+ " If the value for the '/' or ':' or '@' field in 'viminfo' is zero, then
+ " the corresponding history entries are not saved.
+ set viminfo='100,/0,:0,@0,<50,s10,h,!,nviminfo
+ call histdel('/')
+ call histdel(':')
+ call histdel('@')
+ call histadd('/', 'foo')
+ call histadd(':', 'bar')
+ call histadd('@', 'baz')
+ wviminfo! Xviminfo
+ call histdel('/')
+ call histdel(':')
+ call histdel('@')
+ rviminfo! Xviminfo
+ call assert_equal('', histget('/'))
+ call assert_equal('', histget(':'))
+ call assert_equal('', histget('@'))
+
+ call delete('Xviminfo')
+ set viminfo&vim
+endfunc
+
+func Test_cmdline_history_order()
+ call histdel(':')
+ call test_settime(11)
+ call histadd(':', "echo '11'")
+ call test_settime(22)
+ call histadd(':', "echo '22'")
+ call test_settime(33)
+ call histadd(':', "echo '33'")
+ wviminfo Xviminfo
+
+ call histdel(':')
+ " items go in between
+ call test_settime(15)
+ call histadd(':', "echo '15'")
+ call test_settime(27)
+ call histadd(':', "echo '27'")
+
+ rviminfo Xviminfo
+ call assert_equal("echo '33'", histget(':', -1))
+ call assert_equal("echo '27'", histget(':', -2))
+ call assert_equal("echo '22'", histget(':', -3))
+ call assert_equal("echo '15'", histget(':', -4))
+ call assert_equal("echo '11'", histget(':', -5))
+
+ call histdel(':')
+ " items go before and after
+ eval 8->test_settime()
+ call histadd(':', "echo '8'")
+ call test_settime(39)
+ call histadd(':', "echo '39'")
+
+ rviminfo Xviminfo
+ call assert_equal("echo '39'", histget(':', -1))
+ call assert_equal("echo '33'", histget(':', -2))
+ call assert_equal("echo '22'", histget(':', -3))
+ call assert_equal("echo '11'", histget(':', -4))
+ call assert_equal("echo '8'", histget(':', -5))
+
+ " Check sorting works when writing with merge.
+ call histdel(':')
+ call test_settime(8)
+ call histadd(':', "echo '8'")
+ call test_settime(15)
+ call histadd(':', "echo '15'")
+ call test_settime(27)
+ call histadd(':', "echo '27'")
+ call test_settime(39)
+ call histadd(':', "echo '39'")
+ wviminfo Xviminfo
+
+ call histdel(':')
+ rviminfo Xviminfo
+ call assert_equal("echo '39'", histget(':', -1))
+ call assert_equal("echo '33'", histget(':', -2))
+ call assert_equal("echo '27'", histget(':', -3))
+ call assert_equal("echo '22'", histget(':', -4))
+ call assert_equal("echo '15'", histget(':', -5))
+ call assert_equal("echo '11'", histget(':', -6))
+ call assert_equal("echo '8'", histget(':', -7))
+
+ call delete('Xviminfo')
+endfunc
+
+func Test_viminfo_registers()
+ call test_settime(8)
+ call setreg('a', "eight", 'c')
+ call test_settime(20)
+ call setreg('b', ["twenty", "again"], 'l')
+ call test_settime(40)
+ call setreg('c', ["four", "agai"], 'b4')
+ let l = []
+ set viminfo='100,<600,s10,h,!,nviminfo
+ for i in range(500)
+ call add(l, 'something')
+ endfor
+ call setreg('d', l, 'l')
+ call setreg('e', "abc\<C-V>xyz")
+ wviminfo Xviminfo
+
+ call test_settime(10)
+ call setreg('a', '', 'b10')
+ call test_settime(15)
+ call setreg('b', 'drop')
+ call test_settime(50)
+ call setreg('c', 'keep', 'l')
+ call test_settime(30)
+ call setreg('d', 'drop', 'l')
+ call setreg('e', 'drop')
+ rviminfo Xviminfo
+
+ call assert_equal("", getreg('a'))
+ call assert_equal("\<C-V>10", getregtype('a'))
+ call assert_equal("twenty\nagain\n", getreg('b'))
+ call assert_equal("V", getregtype('b'))
+ call assert_equal("keep\n", getreg('c'))
+ call assert_equal("V", getregtype('c'))
+ call assert_equal(l, getreg('d', 1, 1))
+ call assert_equal("V", getregtype('d'))
+ call assert_equal("abc\<C-V>xyz", getreg('e'))
+
+ " Length around 440 switches to line continuation.
+ let len = 434
+ while len < 445
+ let s = repeat('a', len)
+ call setreg('"', s)
+ wviminfo Xviminfo
+ call setreg('"', '')
+ rviminfo Xviminfo
+ call assert_equal(s, getreg('"'), 'wrong register at length: ' . len)
+
+ let len += 1
+ endwhile
+
+ " If the maximum number of lines saved for a register ('<' in 'viminfo') is
+ " zero, then register values should not be saved.
+ let @a = 'abc'
+ set viminfo='100,<0,s10,h,!,nviminfo
+ wviminfo Xviminfo
+ let @a = 'xyz'
+ rviminfo! Xviminfo
+ call assert_equal('xyz', @a)
+ " repeat the test with '"' instead of '<'
+ let @b = 'def'
+ set viminfo='100,\"0,s10,h,!,nviminfo
+ wviminfo Xviminfo
+ let @b = 'rst'
+ rviminfo! Xviminfo
+ call assert_equal('rst', @b)
+
+ " If the maximum size of an item ('s' in 'viminfo') is zero, then register
+ " values should not be saved.
+ let @c = '123'
+ set viminfo='100,<20,s0,h,!,nviminfo
+ wviminfo Xviminfo
+ let @c = '456'
+ rviminfo! Xviminfo
+ call assert_equal('456', @c)
+
+ call delete('Xviminfo')
+ set viminfo&vim
+endfunc
+
+func Test_viminfo_marks()
+ sp bufa
+ let bufa = bufnr('%')
+ sp bufb
+ let bufb = bufnr('%')
+
+ call test_settime(8)
+ call setpos("'A", [bufa, 1, 1, 0])
+ call test_settime(20)
+ call setpos("'B", [bufb, 9, 1, 0])
+ call setpos("'C", [bufa, 7, 1, 0])
+
+ delmark 0-9
+ call test_settime(25)
+ call setpos("'1", [bufb, 12, 1, 0])
+ call test_settime(35)
+ call setpos("'0", [bufa, 11, 1, 0])
+
+ call test_settime(45)
+ wviminfo Xviminfo
+
+ " Writing viminfo inserts the '0 mark.
+ call assert_equal([bufb, 1, 1, 0], getpos("'0"))
+ call assert_equal([bufa, 11, 1, 0], getpos("'1"))
+ call assert_equal([bufb, 12, 1, 0], getpos("'2"))
+
+ call test_settime(4)
+ call setpos("'A", [bufa, 9, 1, 0])
+ call test_settime(30)
+ call setpos("'B", [bufb, 2, 3, 0])
+ delmark C
+
+ delmark 0-9
+ call test_settime(30)
+ call setpos("'1", [bufb, 22, 1, 0])
+ call test_settime(55)
+ call setpos("'0", [bufa, 21, 1, 0])
+
+ rviminfo Xviminfo
+
+ call assert_equal([bufa, 1, 1, 0], getpos("'A"))
+ call assert_equal([bufb, 2, 3, 0], getpos("'B"))
+ call assert_equal([bufa, 7, 1, 0], getpos("'C"))
+
+ " numbered marks are merged
+ call assert_equal([bufa, 21, 1, 0], getpos("'0")) " time 55
+ call assert_equal([bufb, 1, 1, 0], getpos("'1")) " time 45
+ call assert_equal([bufa, 11, 1, 0], getpos("'2")) " time 35
+ call assert_equal([bufb, 22, 1, 0], getpos("'3")) " time 30
+ call assert_equal([bufb, 12, 1, 0], getpos("'4")) " time 25
+
+ " deleted file marks are removed from viminfo
+ delmark C
+ wviminfo Xviminfo
+ rviminfo Xviminfo
+ call assert_equal([0, 0, 0, 0], getpos("'C"))
+
+ " deleted file marks stay in viminfo if defined in another vim later
+ call test_settime(70)
+ call setpos("'D", [bufb, 8, 1, 0])
+ wviminfo Xviminfo
+ call test_settime(65)
+ delmark D
+ call assert_equal([0, 0, 0, 0], getpos("'D"))
+ call test_settime(75)
+ rviminfo Xviminfo
+ call assert_equal([bufb, 8, 1, 0], getpos("'D"))
+
+ call delete('Xviminfo')
+ exe 'bwipe ' . bufa
+ exe 'bwipe ' . bufb
+endfunc
+
+func Test_viminfo_jumplist()
+ split testbuf
+ clearjumps
+ call setline(1, ['time 05', 'time 10', 'time 15', 'time 20', 'time 30', 'last pos'])
+ call cursor(2, 1)
+ call test_settime(10)
+ exe "normal /20\r"
+ call test_settime(20)
+ exe "normal /30\r"
+ call test_settime(30)
+ exe "normal /last pos\r"
+ wviminfo Xviminfo
+
+ clearjumps
+ call cursor(1, 1)
+ call test_settime(5)
+ exe "normal /15\r"
+ call test_settime(15)
+ exe "normal /last pos\r"
+ call test_settime(40)
+ exe "normal ?30\r"
+ rviminfo Xviminfo
+
+ call assert_equal('time 30', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('last pos', getline('.'))
+ exe "normal \<C-O>"
+ " duplicate for 'time 30' was removed
+ call assert_equal('time 20', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('time 15', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('time 10', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('time 05', getline('.'))
+
+ clearjumps
+ call cursor(1, 1)
+ call test_settime(5)
+ exe "normal /15\r"
+ call test_settime(15)
+ exe "normal /last pos\r"
+ call test_settime(40)
+ exe "normal ?30\r"
+ " Test merge when writing
+ wviminfo Xviminfo
+ clearjumps
+ rviminfo Xviminfo
+
+ let last_line = line('.')
+ exe "normal \<C-O>"
+ call assert_equal('time 30', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('last pos', getline('.'))
+ exe "normal \<C-O>"
+ " duplicate for 'time 30' was removed
+ call assert_equal('time 20', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('time 15', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('time 10', getline('.'))
+ exe "normal \<C-O>"
+ call assert_equal('time 05', getline('.'))
+
+ " Test with jumplist full.
+ clearjumps
+ call setline(1, repeat(['match here'], 101))
+ call cursor(1, 1)
+ call test_settime(10)
+ for i in range(100)
+ exe "normal /here\r"
+ endfor
+ rviminfo Xviminfo
+
+ " must be newest mark that comes from viminfo.
+ exe "normal \<C-O>"
+ call assert_equal(last_line, line('.'))
+
+ bwipe!
+ call delete('Xviminfo')
+endfunc
+
+func Test_viminfo_encoding()
+ set enc=latin1
+ call histdel(':')
+ call histadd(':', "echo '\xe9'")
+ wviminfo Xviminfo
+
+ set fencs=utf-8,latin1
+ set enc=utf-8
+ sp Xviminfo
+ call assert_equal('latin1', &fenc)
+ close
+
+ call histdel(':')
+ rviminfo Xviminfo
+ call assert_equal("echo 'é'", histget(':', -1))
+
+ call delete('Xviminfo')
+endfunc
+
+func Test_viminfo_bad_syntax()
+ let lines = []
+ call add(lines, '|<') " empty continuation line
+ call add(lines, '|234234234234234324,nothing')
+ call add(lines, '|1+"no comma"')
+ call add(lines, '|1,2,3,4,5,6,7') " too many items
+ call add(lines, '|1,"string version"')
+ call add(lines, '|1,>x') " bad continuation line
+ call add(lines, '|1,"x') " missing quote
+ call add(lines, '|1,"x\') " trailing backslash
+ call add(lines, '|1,,,,') "trailing comma
+ call add(lines, '|1,>234') " trailing continuation line
+ call writefile(lines, 'Xviminfo', 'D')
+ rviminfo Xviminfo
+
+ call delete('Xviminfo')
+endfunc
+
+func Test_viminfo_bad_syntax2()
+ let lines = []
+ call add(lines, '|1,4')
+
+ " bad viminfo syntax for history barline
+ call add(lines, '|2') " invalid number of fields in a history barline
+ call add(lines, '|2,9,1,1,"x"') " invalid value for the history type
+ call add(lines, '|2,0,,1,"x"') " no timestamp
+ call add(lines, '|2,0,1,1,10') " non-string text
+
+ " bad viminfo syntax for register barline
+ call add(lines, '|3') " invalid number of fields in a register barline
+ call add(lines, '|3,1,1,1,1,,1,"x"') " missing width field
+ call add(lines, '|3,0,80,1,1,1,1,"x"') " invalid register number
+ call add(lines, '|3,0,10,5,1,1,1,"x"') " invalid register type
+ call add(lines, '|3,0,10,1,20,1,1,"x"') " invalid line count
+ call add(lines, '|3,0,10,1,0,1,1') " zero line count
+
+ " bad viminfo syntax for mark barline
+ call add(lines, '|4') " invalid number of fields in a mark barline
+ call add(lines, '|4,1,1,1,1,1') " invalid value for file name
+ call add(lines, '|4,20,1,1,1,"x"') " invalid value for file name
+ call add(lines, '|4,49,0,1,1,"x"') " invalid value for line number
+
+ call writefile(lines, 'Xviminfo', 'D')
+ rviminfo Xviminfo
+endfunc
+
+func Test_viminfo_file_marks()
+ silent! bwipe test_viminfo.vim
+ silent! bwipe Xviminfo
+
+ call test_settime(10)
+ edit ten
+ call test_settime(25)
+ edit again
+ call test_settime(30)
+ edit thirty
+ wviminfo Xviminfo
+
+ call test_settime(20)
+ edit twenty
+ call test_settime(35)
+ edit again
+ call test_settime(40)
+ edit forty
+ wviminfo Xviminfo
+
+ sp Xviminfo
+ 1
+ for name in ['forty', 'again', 'thirty', 'twenty', 'ten']
+ /^>
+ call assert_equal(name, substitute(getline('.'), '.*/', '', ''))
+ endfor
+ close
+
+ call delete('Xviminfo')
+endfunc
+
+func Test_viminfo_file_mark_tabclose()
+ tabnew Xtestfileintab
+ call setline(1, ['a','b','c','d','e'])
+ 4
+ q!
+ wviminfo Xviminfo
+ sp Xviminfo
+ /^> .*Xtestfileintab
+ let lnum = line('.')
+ while 1
+ if lnum == line('$')
+ call assert_report('mark not found in Xtestfileintab')
+ break
+ endif
+ let lnum += 1
+ let line = getline(lnum)
+ if line == ''
+ call assert_report('mark not found in Xtestfileintab')
+ break
+ endif
+ if line =~ "^\t\""
+ call assert_equal('4', substitute(line, ".*\"\t\\(\\d\\).*", '\1', ''))
+ break
+ endif
+ endwhile
+
+ call delete('Xviminfo')
+ silent! bwipe Xtestfileintab
+endfunc
+
+func Test_viminfo_file_mark_zero_time()
+ let lines = [
+ \ '# Viminfo version',
+ \ '|1,4',
+ \ '',
+ \ '*encoding=utf-8',
+ \ '',
+ \ '# File marks:',
+ \ "'B 1 0 /tmp/nothing",
+ \ '|4,66,1,0,0,"/tmp/nothing"',
+ \ "",
+ \ ]
+ call writefile(lines, 'Xviminfo', 'D')
+ delmark B
+ rviminfo Xviminfo
+ call assert_equal(1, line("'B"))
+ delmark B
+endfunc
+
+" Test for saving and restoring file marks in unloaded buffers
+func Test_viminfo_file_mark_unloaded_buf()
+ let save_viminfo = &viminfo
+ set viminfo&vim
+ call writefile(repeat(['vim'], 10), 'Xfile1', 'D')
+ %bwipe
+ edit! Xfile1
+ call setpos("'u", [0, 3, 1, 0])
+ call setpos("'v", [0, 5, 1, 0])
+ enew
+ wviminfo Xviminfo
+ %bwipe
+ edit Xfile1
+ rviminfo! Xviminfo
+ call assert_equal([0, 3, 1, 0], getpos("'u"))
+ call assert_equal([0, 5, 1, 0], getpos("'v"))
+ %bwipe
+ call delete('Xviminfo')
+ let &viminfo = save_viminfo
+endfunc
+
+func Test_viminfo_oldfiles()
+ set noswapfile
+ let v:oldfiles = []
+ let lines = [
+ \ '# comment line',
+ \ '*encoding=utf-8',
+ \ '',
+ \ ':h viminfo',
+ \ '?/session',
+ \ '=myvar',
+ \ '@123',
+ \ '',
+ \ "'E 2 0 /tmp/nothing",
+ \ '',
+ \ "> /tmp/file_one.txt",
+ \ "\t\"\t11\t0",
+ \ "",
+ \ "> /tmp/file_two.txt",
+ \ "\t\"\t11\t0",
+ \ "",
+ \ "> /tmp/another.txt",
+ \ "\t\"\t11\t0",
+ \ "",
+ \ ]
+ call writefile(lines, 'Xviminfo', 'D')
+ delmark E
+ edit /tmp/file_two.txt
+ rviminfo! Xviminfo
+
+ call assert_equal('h viminfo', histget(':'))
+ call assert_equal('session', histget('/'))
+ call assert_equal('myvar', histget('='))
+ call assert_equal('123', histget('@'))
+ call assert_equal(2, line("'E"))
+ call assert_equal(['1: /tmp/file_one.txt', '2: /tmp/file_two.txt', '3: /tmp/another.txt'], filter(split(execute('oldfiles'), "\n"), {i, v -> v =~ '/tmp/'}))
+ call assert_equal(['1: /tmp/file_one.txt', '2: /tmp/file_two.txt'], filter(split(execute('filter file_ oldfiles'), "\n"), {i, v -> v =~ '/tmp/'}))
+ call assert_equal(['3: /tmp/another.txt'], filter(split(execute('filter /another/ oldfiles'), "\n"), {i, v -> v =~ '/tmp/'}))
+
+ new
+ call feedkeys("3\<CR>", 't')
+ browse oldfiles
+ call assert_equal("/tmp/another.txt", expand("%"))
+ bwipe
+ delmark E
+ set swapfile&
+endfunc
+
+" Test for storing and restoring buffer list in 'viminfo'
+func Test_viminfo_bufferlist()
+ " If there are arguments, then :rviminfo doesn't read the buffer list.
+ " Need to delete all the arguments for :rviminfo to work.
+ %argdelete
+ set viminfo&vim
+
+ edit Xfile1
+ edit Xfile2
+ set viminfo-=%
+ wviminfo Xviminfo
+ %bwipe
+ rviminfo Xviminfo
+ call assert_equal(1, len(getbufinfo()))
+
+ edit Xfile1
+ edit Xfile2
+ set viminfo^=%
+ wviminfo Xviminfo
+ %bwipe
+ rviminfo Xviminfo
+ let l = getbufinfo()
+ call assert_equal(3, len(l))
+ call assert_equal('Xfile1', bufname(l[1].bufnr))
+ call assert_equal('Xfile2', bufname(l[2].bufnr))
+
+ " The quickfix, terminal, unlisted, unnamed buffers are not stored in the
+ " viminfo file
+ %bw!
+ edit Xfile1
+ new
+ setlocal nobuflisted
+ new
+ copen
+ if has('terminal')
+ terminal
+ endif
+ wviminfo! Xviminfo
+ %bwipe!
+ rviminfo Xviminfo
+ let l = getbufinfo()
+ call assert_equal(2, len(l))
+ call assert_true(bufexists('Xfile1'))
+
+ " If a count is specified for '%', then only that many buffers should be
+ " stored in the viminfo file.
+ %bw!
+ set viminfo&vim
+ new Xbuf1
+ new Xbuf2
+ set viminfo+=%1
+ wviminfo! Xviminfo
+ %bwipe!
+ rviminfo! Xviminfo
+ let l = getbufinfo()
+ call assert_equal(2, len(l))
+ call assert_true(bufexists('Xbuf1'))
+ call assert_false(bufexists('Xbuf2'))
+
+ call delete('Xviminfo')
+ %bwipe
+ set viminfo&vim
+endfunc
+
+" Test for errors in a viminfo file
+func Test_viminfo_error()
+ " Non-existing viminfo files
+ call assert_fails('rviminfo xyz', 'E195:')
+
+ " Illegal starting character
+ call writefile(["a 123"], 'Xviminfo', 'D')
+ call assert_fails('rv Xviminfo', 'E575:')
+
+ " Illegal register name in the viminfo file
+ call writefile(['"@ LINE 0'], 'Xviminfo')
+ call assert_fails('rv Xviminfo', 'E577:')
+
+ " Invalid file mark line
+ call writefile(['>', '@'], 'Xviminfo')
+ call assert_fails('rv Xviminfo', 'E576:')
+
+ " Too many errors in viminfo file
+ call writefile(repeat(["a 123"], 15), 'Xviminfo')
+ call assert_fails('rv Xviminfo', 'E575:')
+
+ call writefile(['>'] + repeat(['@'], 10), 'Xviminfo')
+ call assert_fails('rv Xviminfo', 'E576:')
+
+ call writefile(repeat(['"@'], 15), 'Xviminfo')
+ call assert_fails('rv Xviminfo', 'E577:')
+endfunc
+
+" Test for saving and restoring last substitute string in viminfo
+func Test_viminfo_lastsub()
+ enew
+ call append(0, "blue blue blue")
+ call cursor(1, 1)
+ s/blue/green/
+ wviminfo Xviminfo
+ s/blue/yellow/
+ rviminfo! Xviminfo
+ &
+ call assert_equal("green yellow green", getline(1))
+ enew!
+ call delete('Xviminfo')
+endfunc
+
+" Test saving and restoring the register values using the older method
+func Test_viminfo_registers_old()
+ let lines = [
+ \ '# Viminfo version',
+ \ '|1,1',
+ \ '',
+ \ '*encoding=utf-8',
+ \ '',
+ \ '# Registers:',
+ \ '""0 CHAR 0',
+ \ ' Vim',
+ \ '"a CHAR 0',
+ \ ' red',
+ \ '"c BLOCK 0',
+ \ ' a',
+ \ ' d',
+ \ '"d LINE 0',
+ \ ' abc',
+ \ ' def',
+ \ '"m@ CHAR 0',
+ \ " :echo 'Hello'\<CR>",
+ \ "",
+ \ ]
+ call writefile(lines, 'Xviminfo', 'D')
+ let @a = 'one'
+ let @b = 'two'
+ let @m = 'three'
+ let @" = 'four'
+ let @t = ":echo 'Unix'\<CR>"
+ silent! normal @t
+ rviminfo! Xviminfo
+ call assert_equal('red', getreg('a'))
+ call assert_equal("v", getregtype('a'))
+ call assert_equal('two', getreg('b'))
+ call assert_equal("a\nd", getreg('c'))
+ call assert_equal("\<C-V>1", getregtype('c'))
+ call assert_equal("abc\ndef\n", getreg('d'))
+ call assert_equal("V", getregtype('d'))
+ call assert_equal(":echo 'Hello'\<CR>", getreg('m'))
+ call assert_equal('Vim', getreg('"'))
+ call assert_equal("\nHello", execute('normal @@'))
+
+ let @" = ''
+endfunc
+
+" Test for saving and restoring large number of lines in a register
+func Test_viminfo_large_register()
+ let save_viminfo = &viminfo
+ set viminfo&vim
+ set viminfo-=<50
+ set viminfo+=<200
+ let lines = ['"r CHAR 0']
+ call extend(lines, repeat(["\tsun is rising"], 200))
+ call writefile(lines, 'Xviminfo', 'D')
+ let @r = ''
+ rviminfo! Xviminfo
+ call assert_equal(join(repeat(["sun is rising"], 200), "\n"), @r)
+
+ let @r = ''
+ let &viminfo = save_viminfo
+endfunc
+
+" Test for setting 'viminfofile' to NONE
+func Test_viminfofile_none()
+ let save_vif = &viminfofile
+ set viminfofile=NONE
+ wviminfo Xviminfo
+ call assert_false(filereadable('Xviminfo'))
+ call writefile([''], 'Xviminfo', 'D')
+ call assert_fails('rviminfo Xviminfo', 'E195:')
+
+ let &viminfofile = save_vif
+endfunc
+
+" Test for an unwritable and unreadable 'viminfo' file
+func Test_viminfo_perm()
+ CheckUnix
+ CheckNotRoot
+ call writefile([''], 'Xviminfo', 'D')
+ call setfperm('Xviminfo', 'r-x------')
+ call assert_fails('wviminfo Xviminfo', 'E137:')
+ call setfperm('Xviminfo', '--x------')
+ call assert_fails('rviminfo Xviminfo', 'E195:')
+
+ " Try to write the viminfo to a directory
+ call mkdir('Xvifdir', 'R')
+ call assert_fails('wviminfo Xvifdir', 'E137:')
+ call assert_fails('rviminfo Xvifdir', 'E195:')
+endfunc
+
+" Test for writing to an existing viminfo file merges the file marks
+func XTest_viminfo_marks_merge()
+ let save_viminfo = &viminfo
+ set viminfo&vim
+ set viminfo^=%
+ enew
+ %argdelete
+ %bwipe
+
+ call writefile(repeat(['editor'], 10), 'Xbufa', 'D')
+ call writefile(repeat(['Vim'], 10), 'Xbufb', 'D')
+
+ " set marks in buffers
+ call test_settime(10)
+ edit Xbufa
+ 4mark a
+ wviminfo Xviminfo
+ edit Xbufb
+ 4mark b
+ wviminfo Xviminfo
+ %bwipe
+
+ " set marks in buffers again
+ call test_settime(20)
+ edit Xbufb
+ 6mark b
+ wviminfo Xviminfo
+ edit Xbufa
+ 6mark a
+ wviminfo Xviminfo
+ %bwipe
+
+ " Load the buffer and check the marks
+ edit Xbufa
+ rviminfo! Xviminfo
+ call assert_equal(6, line("'a"))
+ edit Xbufb
+ rviminfo! Xviminfo
+ call assert_equal(6, line("'b"))
+
+ " cleanup
+ %bwipe
+ call delete('Xviminfo')
+ call test_settime(0)
+ let &viminfo=save_viminfo
+endfunc
+
+" Test for errors in setting 'viminfo'
+func Test_viminfo_option_error()
+ " Missing number
+ call assert_fails('set viminfo=\"', 'E526:')
+ for c in split("'/:<@s", '\zs')
+ call assert_fails('set viminfo=' .. c, 'E526:')
+ endfor
+
+ " Missing comma
+ call assert_fails('set viminfo=%10!', 'E527:')
+ call assert_fails('set viminfo=!%10', 'E527:')
+ call assert_fails('set viminfo=h%10', 'E527:')
+ call assert_fails('set viminfo=c%10', 'E527:')
+ call assert_fails('set viminfo=:10%10', 'E527:')
+
+ " Missing ' setting
+ call assert_fails('set viminfo=%10', 'E528:')
+endfunc
+
+func Test_viminfo_oldfiles_newfile()
+ CheckRunVimInTerminal
+
+ let save_viminfo = &viminfo
+ let save_viminfofile = &viminfofile
+ set viminfo&vim
+ let v:oldfiles = []
+ let commands =<< trim [CODE]
+ set viminfofile=Xviminfofile
+ set viminfo&vim
+ w! Xnew-file.txt
+ qall
+ [CODE]
+ call writefile(commands, 'Xviminfotest', 'D')
+ let buf = RunVimInTerminal('-S Xviminfotest', #{wait_for_ruler: 0})
+ call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))})
+
+ let &viminfofile = 'Xviminfofile'
+ rviminfo! Xviminfofile
+ call assert_match('Xnew-file.txt$', v:oldfiles[0])
+ call assert_equal(1, len(v:oldfiles))
+
+ call delete('Xviminfofile')
+ call delete('Xnew-file.txt')
+
+ let v:oldfiles = test_null_list()
+ call assert_equal("\nNo old files", execute('oldfiles'))
+
+ let &viminfo = save_viminfo
+ let &viminfofile = save_viminfofile
+endfunc
+
+" When writing CTRL-V or "\n" to a viminfo file, it is converted to CTRL-V
+" CTRL-V and CTRL-V n respectively.
+func Test_viminfo_with_Ctrl_V()
+ silent! exe "normal! /\<C-V>\<C-V>\n"
+ wviminfo Xviminfo
+ call assert_notequal(-1, readfile('Xviminfo')->index("?/\<C-V>\<C-V>"))
+ let @/ = 'abc'
+ rviminfo! Xviminfo
+ call assert_equal("\<C-V>", @/)
+ silent! exe "normal! /\<C-V>\<C-J>\n"
+ wviminfo Xviminfo
+ call assert_notequal(-1, readfile('Xviminfo')->index("?/\<C-V>n"))
+ let @/ = 'abc'
+ rviminfo! Xviminfo
+ call assert_equal("\n", @/)
+ call delete('Xviminfo')
+endfunc
+
+" Test for the 'r' field in 'viminfo' (removal media)
+func Test_viminfo_removable_media()
+ CheckUnix
+ if !isdirectory('/tmp') || getftype('/tmp') != 'dir'
+ return
+ endif
+ let save_viminfo = &viminfo
+ set viminfo+=r/tmp
+ edit /tmp/Xvima1b2c3
+ wviminfo Xviminfo
+ let matches = readfile('Xviminfo')->filter("v:val =~ 'Xvima1b2c3'")
+ call assert_equal(0, matches->len())
+ let &viminfo = save_viminfo
+ call delete('Xviminfo')
+endfunc
+
+" Test for the 'h' flag in 'viminfo'. If 'h' is not present, then the last
+" search pattern read from 'viminfo' should be highlighted with 'hlsearch'.
+" If 'h' is present, then the last search pattern should not be highlighted.
+func Test_viminfo_hlsearch()
+ set viminfo&vim
+
+ new
+ call setline(1, ['one two three'])
+ " save the screen attribute for the Search highlighted text and the normal
+ " text for later comparison
+ set hlsearch
+ let @/ = 'three'
+ redraw!
+ let hiSearch = screenattr(1, 9)
+ let hiNormal = screenattr(1, 1)
+
+ set viminfo-=h
+ let @/='two'
+ wviminfo! Xviminfo
+ let @/='one'
+ rviminfo! Xviminfo
+ redraw!
+ call assert_equal(hiSearch, screenattr(1, 5))
+ call assert_equal(hiSearch, screenattr(1, 6))
+ call assert_equal(hiSearch, screenattr(1, 7))
+
+ set viminfo+=h
+ let @/='two'
+ wviminfo! Xviminfo
+ let @/='one'
+ rviminfo! Xviminfo
+ redraw!
+ call assert_equal(hiNormal, screenattr(1, 5))
+ call assert_equal(hiNormal, screenattr(1, 6))
+ call assert_equal(hiNormal, screenattr(1, 7))
+
+ call delete('Xviminfo')
+ set hlsearch& viminfo&vim
+ bw!
+endfunc
+
+" Test for restoring the magicness of the last search pattern from the viminfo
+" file.
+func Test_viminfo_last_spat_magic()
+ set viminfo&vim
+ new
+ call setline(1, ' one abc a.c')
+
+ " restore 'nomagic'
+ set nomagic
+ exe "normal gg/a.c\<CR>"
+ wviminfo! Xviminfo
+ set magic
+ exe "normal gg/one\<CR>"
+ rviminfo! Xviminfo
+ exe "normal! gg/\<CR>"
+ call assert_equal(10, col('.'))
+
+ " restore 'magic'
+ set magic
+ exe "normal gg/a.c\<CR>"
+ wviminfo! Xviminfo
+ set nomagic
+ exe "normal gg/one\<CR>"
+ rviminfo! Xviminfo
+ exe "normal! gg/\<CR>"
+ call assert_equal(6, col('.'))
+
+ call delete('Xviminfo')
+ set viminfo&vim magic&
+ bw!
+endfunc
+
+" Test for restoring the smartcase of the last search pattern from the viminfo
+" file.
+func Test_viminfo_last_spat_smartcase()
+ new
+ call setline(1, ' one abc Abc')
+ set ignorecase smartcase
+
+ " Searching with * should disable smartcase
+ exe "normal! gg$b*"
+ wviminfo! Xviminfo
+ exe "normal gg/one\<CR>"
+ rviminfo! Xviminfo
+ exe "normal! gg/\<CR>"
+ call assert_equal(6, col('.'))
+
+ call delete('Xviminfo')
+ set ignorecase& smartcase& viminfo&
+ bw!
+endfunc
+
+" Test for restoring the last search pattern with a line or character offset
+" from the viminfo file.
+func Test_viminfo_last_spat_offset()
+ new
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ " line offset
+ exe "normal! /two/+2\<CR>"
+ wviminfo! Xviminfo
+ exe "normal gg/five\<CR>"
+ rviminfo! Xviminfo
+ exe "normal! gg/\<CR>"
+ call assert_equal(4, line('.'))
+ " character offset
+ exe "normal! gg/^th/e+2\<CR>"
+ wviminfo! Xviminfo
+ exe "normal gg/two\<CR>"
+ rviminfo! Xviminfo
+ exe "normal! gg/\<CR>"
+ call assert_equal([3, 4], [line('.'), col('.')])
+ call delete('Xviminfo')
+ bw!
+endfunc
+
+" Test for saving and restoring the last executed register (@ command)
+" from the viminfo file
+func Test_viminfo_last_exec_reg()
+ let g:val = 1
+ let @a = ":let g:val += 1\n"
+ normal! @a
+ wviminfo! Xviminfo
+ let @b = ''
+ normal! @b
+ rviminfo! Xviminfo
+ normal @@
+ call assert_equal(3, g:val)
+ call delete('Xviminfo')
+endfunc
+
+" Test for merging file marks in a viminfo file
+func Test_viminfo_merge_file_marks()
+ for [f, l, t] in [['a.txt', 5, 10], ['b.txt', 10, 20]]
+ call test_settime(t)
+ exe 'edit ' .. f
+ call setline(1, range(1, 20))
+ exe l . 'mark a'
+ wviminfo Xviminfo
+ bw!
+ endfor
+ call test_settime(30)
+ for [f, l] in [['a.txt', 5], ['b.txt', 10]]
+ exe 'edit ' .. f
+ rviminfo! Xviminfo
+ call assert_equal(l, line("'a"))
+ bw!
+ endfor
+ call delete('Xviminfo')
+ call test_settime(0)
+endfunc
+
+" Test for merging file marks from a old viminfo file
+func Test_viminfo_merge_old_filemarks()
+ let lines = []
+ call add(lines, '|1,4')
+ call add(lines, '> ' .. fnamemodify('a.txt', ':p:~'))
+ call add(lines, "\tb\t7\t0\n")
+ call writefile(lines, 'Xviminfo', 'D')
+ edit b.txt
+ call setline(1, range(1, 20))
+ 12mark b
+ wviminfo Xviminfo
+ bw!
+ edit a.txt
+ rviminfo! Xviminfo
+ call assert_equal(7, line("'b"))
+ edit b.txt
+ rviminfo! Xviminfo
+ call assert_equal(12, line("'b"))
+endfunc
+
+" Test for merging the jump list from a old viminfo file
+func Test_viminfo_merge_old_jumplist()
+ let lines = []
+ call add(lines, "-' 10 1 " .. fnamemodify('a.txt', ':p:~'))
+ call add(lines, "-' 20 1 " .. fnamemodify('a.txt', ':p:~'))
+ call add(lines, "-' 30 1 " .. fnamemodify('b.txt', ':p:~'))
+ call add(lines, "-' 40 1 " .. fnamemodify('b.txt', ':p:~'))
+ call writefile(lines, 'Xviminfo', 'D')
+ clearjumps
+ rviminfo! Xviminfo
+ let l = getjumplist()[0]
+ call assert_equal([40, 30, 20, 10], [l[0].lnum, l[1].lnum, l[2].lnum,
+ \ l[3].lnum])
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_vimscript.vim b/src/testdir/test_vimscript.vim
new file mode 100644
index 0000000..295a03b
--- /dev/null
+++ b/src/testdir/test_vimscript.vim
@@ -0,0 +1,7534 @@
+" Test various aspects of the Vim script language.
+" Most of this was formerly in test49.vim (developed by Servatius Brandt
+" <Servatius.Brandt@fujitsu-siemens.com>)
+
+source check.vim
+source shared.vim
+source script_util.vim
+
+"-------------------------------------------------------------------------------
+" Test environment {{{1
+"-------------------------------------------------------------------------------
+
+" Append a message to the "messages" file
+func Xout(text)
+ split messages
+ $put =a:text
+ wq
+endfunc
+
+com! -nargs=1 Xout call Xout(<args>)
+
+" Create a new instance of Vim and run the commands in 'test' and then 'verify'
+" The commands in 'test' are expected to store the test results in the Xtest.out
+" file. If the test passes successfully, then Xtest.out should be empty.
+func RunInNewVim(test, verify)
+ let init =<< trim END
+ set cpo-=C " support line-continuation in sourced script
+ source script_util.vim
+ XpathINIT
+ XloopINIT
+ END
+ let cleanup =<< trim END
+ call writefile(v:errors, 'Xtest.out')
+ qall
+ END
+ call writefile(init, 'Xtest.vim', 'D')
+ call writefile(a:test, 'Xtest.vim', 'a')
+ call writefile(a:verify, 'Xverify.vim', 'D')
+ call writefile(cleanup, 'Xverify.vim', 'a')
+ call RunVim([], [], "-S Xtest.vim -S Xverify.vim")
+ call assert_equal([], readfile('Xtest.out'))
+ call delete('Xtest.out')
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 1: :endwhile in function {{{1
+"
+" Detect if a broken loop is (incorrectly) reactivated by the
+" :endwhile. Use a :return to prevent an endless loop, and make
+" this test first to get a meaningful result on an error before other
+" tests will hang.
+"-------------------------------------------------------------------------------
+
+func T1_F()
+ Xpath 'a'
+ let first = 1
+ while 1
+ Xpath 'b'
+ if first
+ Xpath 'c'
+ let first = 0
+ break
+ else
+ Xpath 'd'
+ return
+ endif
+ endwhile
+endfunc
+
+func T1_G()
+ Xpath 'h'
+ let first = 1
+ while 1
+ Xpath 'i'
+ if first
+ Xpath 'j'
+ let first = 0
+ break
+ else
+ Xpath 'k'
+ return
+ endif
+ if 1 " unmatched :if
+ endwhile
+endfunc
+
+func Test_endwhile_function()
+ XpathINIT
+ call T1_F()
+ Xpath 'F'
+
+ try
+ call T1_G()
+ catch
+ " Catch missing :endif
+ call assert_true(v:exception =~ 'E171:')
+ Xpath 'x'
+ endtry
+ Xpath 'G'
+
+ call assert_equal('abcFhijxG', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 2: :endwhile in script {{{1
+"
+" Detect if a broken loop is (incorrectly) reactivated by the
+" :endwhile. Use a :finish to prevent an endless loop, and place
+" this test before others that might hang to get a meaningful result
+" on an error.
+"
+" This test executes the bodies of the functions T1_F and T1_G from
+" the previous test as script files (:return replaced by :finish).
+"-------------------------------------------------------------------------------
+
+func Test_endwhile_script()
+ XpathINIT
+ ExecAsScript T1_F
+ Xpath 'F'
+ call DeleteTheScript()
+
+ try
+ ExecAsScript T1_G
+ catch
+ " Catch missing :endif
+ call assert_true(v:exception =~ 'E171:')
+ Xpath 'x'
+ endtry
+ Xpath 'G'
+ call DeleteTheScript()
+
+ call assert_equal('abcFhijxG', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 3: :if, :elseif, :while, :continue, :break {{{1
+"-------------------------------------------------------------------------------
+
+func Test_if_while()
+ XpathINIT
+ if 1
+ Xpath 'a'
+ let loops = 3
+ while loops > -1 " main loop: loops == 3, 2, 1 (which breaks)
+ if loops <= 0
+ let break_err = 1
+ let loops = -1
+ else
+ Xpath 'b' . loops
+ endif
+ if (loops == 2)
+ while loops == 2 " dummy loop
+ Xpath 'c' . loops
+ let loops = loops - 1
+ continue " stop dummy loop
+ Xpath 'd' . loops
+ endwhile
+ continue " continue main loop
+ Xpath 'e' . loops
+ elseif (loops == 1)
+ let p = 1
+ while p " dummy loop
+ Xpath 'f' . loops
+ let p = 0
+ break " break dummy loop
+ Xpath 'g' . loops
+ endwhile
+ Xpath 'h' . loops
+ unlet p
+ break " break main loop
+ Xpath 'i' . loops
+ endif
+ if (loops > 0)
+ Xpath 'j' . loops
+ endif
+ while loops == 3 " dummy loop
+ let loops = loops - 1
+ endwhile " end dummy loop
+ endwhile " end main loop
+ Xpath 'k'
+ else
+ Xpath 'l'
+ endif
+ Xpath 'm'
+ if exists("break_err")
+ Xpath 'm'
+ unlet break_err
+ endif
+
+ unlet loops
+
+ call assert_equal('ab3j3b2c2b1f1h1km', g:Xpath)
+endfunc
+
+" Check double quote after skipped "elseif" does not give error E15
+func Test_skipped_elseif()
+ if "foo" ==? "foo"
+ let result = "first"
+ elseif "foo" ==? "foo"
+ let result = "second"
+ endif
+ call assert_equal('first', result)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 4: :return {{{1
+"-------------------------------------------------------------------------------
+
+func T4_F()
+ if 1
+ Xpath 'a'
+ let loops = 3
+ while loops > 0 " 3: 2: 1:
+ Xpath 'b' . loops
+ if (loops == 2)
+ Xpath 'c' . loops
+ return
+ Xpath 'd' . loops
+ endif
+ Xpath 'e' . loops
+ let loops = loops - 1
+ endwhile
+ Xpath 'f'
+ else
+ Xpath 'g'
+ endif
+endfunc
+
+func Test_return()
+ XpathINIT
+ call T4_F()
+ Xpath '4'
+
+ call assert_equal('ab3e3b2c24', g:Xpath)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 5: :finish {{{1
+"
+" This test executes the body of the function T4_F from the previous
+" test as a script file (:return replaced by :finish).
+"-------------------------------------------------------------------------------
+
+func Test_finish()
+ XpathINIT
+ ExecAsScript T4_F
+ Xpath '5'
+ call DeleteTheScript()
+
+ call assert_equal('ab3e3b2c25', g:Xpath)
+endfunc
+
+
+
+"-------------------------------------------------------------------------------
+" Test 6: Defining functions in :while loops {{{1
+"
+" Functions can be defined inside other functions. An inner function
+" gets defined when the outer function is executed. Functions may
+" also be defined inside while loops. Expressions in braces for
+" defining the function name are allowed.
+"
+" The functions are defined when sourcing the script, only the
+" resulting path is checked in the test function.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+" The command CALL collects the argument of all its invocations in "calls"
+" when used from a function (that is, when the global variable "calls" needs
+" the "g:" prefix). This is to check that the function code is skipped when
+" the function is defined. For inner functions, do so only if the outer
+" function is not being executed.
+"
+let calls = ""
+com! -nargs=1 CALL
+ \ if !exists("calls") && !exists("outer") |
+ \ let g:calls = g:calls . <args> |
+ \ endif
+
+let i = 0
+while i < 3
+ let i = i + 1
+ if i == 1
+ Xpath 'a'
+ function! F1(arg)
+ CALL a:arg
+ let outer = 1
+
+ let j = 0
+ while j < 1
+ Xpath 'b'
+ let j = j + 1
+ function! G1(arg)
+ CALL a:arg
+ endfunction
+ Xpath 'c'
+ endwhile
+ endfunction
+ Xpath 'd'
+
+ continue
+ endif
+
+ Xpath 'e' . i
+ function! F{i}(i, arg)
+ CALL a:arg
+ let outer = 1
+
+ if a:i == 3
+ Xpath 'f'
+ endif
+ let k = 0
+ while k < 3
+ Xpath 'g' . k
+ let k = k + 1
+ function! G{a:i}{k}(arg)
+ CALL a:arg
+ endfunction
+ Xpath 'h' . k
+ endwhile
+ endfunction
+ Xpath 'i'
+
+endwhile
+
+if exists("*G1")
+ Xpath 'j'
+endif
+if exists("*F1")
+ call F1("F1")
+ if exists("*G1")
+ call G1("G1")
+ endif
+endif
+
+if exists("G21") || exists("G22") || exists("G23")
+ Xpath 'k'
+endif
+if exists("*F2")
+ call F2(2, "F2")
+ if exists("*G21")
+ call G21("G21")
+ endif
+ if exists("*G22")
+ call G22("G22")
+ endif
+ if exists("*G23")
+ call G23("G23")
+ endif
+endif
+
+if exists("G31") || exists("G32") || exists("G33")
+ Xpath 'l'
+endif
+if exists("*F3")
+ call F3(3, "F3")
+ if exists("*G31")
+ call G31("G31")
+ endif
+ if exists("*G32")
+ call G32("G32")
+ endif
+ if exists("*G33")
+ call G33("G33")
+ endif
+endif
+
+Xpath 'm'
+
+let g:test6_result = g:Xpath
+let g:test6_calls = calls
+
+unlet calls
+delfunction F1
+delfunction G1
+delfunction F2
+delfunction G21
+delfunction G22
+delfunction G23
+delfunction G31
+delfunction G32
+delfunction G33
+
+func Test_defining_functions()
+ call assert_equal('ade2ie3ibcg0h1g1h2g2h3fg0h1g1h2g2h3m', g:test6_result)
+ call assert_equal('F1G1F2G21G22G23F3G31G32G33', g:test6_calls)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 7: Continuing on errors outside functions {{{1
+"
+" On an error outside a function, the script processing continues
+" at the line following the outermost :endif or :endwhile. When not
+" inside an :if or :while, the script processing continues at the next
+" line.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+if 1
+ Xpath 'a'
+ while 1
+ Xpath 'b'
+ asdf
+ Xpath 'c'
+ break
+ endwhile | Xpath 'd'
+ Xpath 'e'
+endif | Xpath 'f'
+Xpath 'g'
+
+while 1
+ Xpath 'h'
+ if 1
+ Xpath 'i'
+ asdf
+ Xpath 'j'
+ endif | Xpath 'k'
+ Xpath 'l'
+ break
+endwhile | Xpath 'm'
+Xpath 'n'
+
+asdf
+Xpath 'o'
+
+asdf | Xpath 'p'
+Xpath 'q'
+
+let g:test7_result = g:Xpath
+
+func Test_error_in_script()
+ call assert_equal('abghinoq', g:test7_result)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 8: Aborting and continuing on errors inside functions {{{1
+"
+" On an error inside a function without the "abort" attribute, the
+" script processing continues at the next line (unless the error was
+" in a :return command). On an error inside a function with the
+" "abort" attribute, the function is aborted and the script processing
+" continues after the function call; the value -1 is returned then.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+func T8_F()
+ if 1
+ Xpath 'a'
+ while 1
+ Xpath 'b'
+ asdf
+ Xpath 'c'
+ asdf | Xpath 'd'
+ Xpath 'e'
+ break
+ endwhile
+ Xpath 'f'
+ endif | Xpath 'g'
+ Xpath 'h'
+
+ while 1
+ Xpath 'i'
+ if 1
+ Xpath 'j'
+ asdf
+ Xpath 'k'
+ asdf | Xpath 'l'
+ Xpath 'm'
+ endif
+ Xpath 'n'
+ break
+ endwhile | Xpath 'o'
+ Xpath 'p'
+
+ return novar " returns (default return value 0)
+ Xpath 'q'
+ return 1 " not reached
+endfunc
+
+func T8_G() abort
+ if 1
+ Xpath 'r'
+ while 1
+ Xpath 's'
+ asdf " returns -1
+ Xpath 't'
+ break
+ endwhile
+ Xpath 'v'
+ endif | Xpath 'w'
+ Xpath 'x'
+
+ return -4 " not reached
+endfunc
+
+func T8_H() abort
+ while 1
+ Xpath 'A'
+ if 1
+ Xpath 'B'
+ asdf " returns -1
+ Xpath 'C'
+ endif
+ Xpath 'D'
+ break
+ endwhile | Xpath 'E'
+ Xpath 'F'
+
+ return -4 " not reached
+endfunc
+
+" Aborted functions (T8_G and T8_H) return -1.
+let g:test8_sum = (T8_F() + 1) - 4 * T8_G() - 8 * T8_H()
+Xpath 'X'
+let g:test8_result = g:Xpath
+
+func Test_error_in_function()
+ call assert_equal(13, g:test8_sum)
+ call assert_equal('abcefghijkmnoprsABX', g:test8_result)
+
+ delfunction T8_F
+ delfunction T8_G
+ delfunction T8_H
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 9: Continuing after aborted functions {{{1
+"
+" When a function with the "abort" attribute is aborted due to an
+" error, the next function back in the call hierarchy without an
+" "abort" attribute continues; the value -1 is returned then.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+func F() abort
+ Xpath 'a'
+ let result = G() " not aborted
+ Xpath 'b'
+ if result != 2
+ Xpath 'c'
+ endif
+ return 1
+endfunc
+
+func G() " no abort attribute
+ Xpath 'd'
+ if H() != -1 " aborted
+ Xpath 'e'
+ endif
+ Xpath 'f'
+ return 2
+endfunc
+
+func H() abort
+ Xpath 'g'
+ call I() " aborted
+ Xpath 'h'
+ return 4
+endfunc
+
+func I() abort
+ Xpath 'i'
+ asdf " error
+ Xpath 'j'
+ return 8
+endfunc
+
+if F() != 1
+ Xpath 'k'
+endif
+
+let g:test9_result = g:Xpath
+
+delfunction F
+delfunction G
+delfunction H
+delfunction I
+
+func Test_func_abort()
+ call assert_equal('adgifb', g:test9_result)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 10: :if, :elseif, :while argument parsing {{{1
+"
+" A '"' or '|' in an argument expression must not be mixed up with
+" a comment or a next command after a bar. Parsing errors should
+" be recognized.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+func MSG(enr, emsg)
+ let english = v:lang == "C" || v:lang =~ '^[Ee]n'
+ if a:enr == ""
+ Xout "TODO: Add message number for:" a:emsg
+ let v:errmsg = ":" . v:errmsg
+ endif
+ let match = 1
+ if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg)
+ let match = 0
+ if v:errmsg == ""
+ Xout "Message missing."
+ else
+ let v:errmsg = v:errmsg->escape('"')
+ Xout "Unexpected message:" v:errmsg
+ endif
+ endif
+ return match
+endfunc
+
+if 1 || strlen("\"") | Xpath 'a'
+ Xpath 'b'
+endif
+Xpath 'c'
+
+if 0
+elseif 1 || strlen("\"") | Xpath 'd'
+ Xpath 'e'
+endif
+Xpath 'f'
+
+while 1 || strlen("\"") | Xpath 'g'
+ Xpath 'h'
+ break
+endwhile
+Xpath 'i'
+
+let v:errmsg = ""
+if 1 ||| strlen("\"") | Xpath 'j'
+ Xpath 'k'
+endif
+Xpath 'l'
+if !MSG('E15', "Invalid expression")
+ Xpath 'm'
+endif
+
+let v:errmsg = ""
+if 0
+elseif 1 ||| strlen("\"") | Xpath 'n'
+ Xpath 'o'
+endif
+Xpath 'p'
+if !MSG('E15', "Invalid expression")
+ Xpath 'q'
+endif
+
+let v:errmsg = ""
+while 1 ||| strlen("\"") | Xpath 'r'
+ Xpath 's'
+ break
+endwhile
+Xpath 't'
+if !MSG('E15', "Invalid expression")
+ Xpath 'u'
+endif
+
+let g:test10_result = g:Xpath
+delfunction MSG
+
+func Test_expr_parsing()
+ call assert_equal('abcdefghilpt', g:test10_result)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 11: :if, :elseif, :while argument evaluation after abort {{{1
+"
+" When code is skipped over due to an error, the boolean argument to
+" an :if, :elseif, or :while must not be evaluated.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+let calls = 0
+
+func P(num)
+ let g:calls = g:calls + a:num " side effect on call
+ return 0
+endfunc
+
+if 1
+ Xpath 'a'
+ asdf " error
+ Xpath 'b'
+ if P(1) " should not be called
+ Xpath 'c'
+ elseif !P(2) " should not be called
+ Xpath 'd'
+ else
+ Xpath 'e'
+ endif
+ Xpath 'f'
+ while P(4) " should not be called
+ Xpath 'g'
+ endwhile
+ Xpath 'h'
+endif
+Xpath 'x'
+
+let g:test11_calls = calls
+let g:test11_result = g:Xpath
+
+unlet calls
+delfunction P
+
+func Test_arg_abort()
+ call assert_equal(0, g:test11_calls)
+ call assert_equal('ax', g:test11_result)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 12: Expressions in braces in skipped code {{{1
+"
+" In code skipped over due to an error or inactive conditional,
+" an expression in braces as part of a variable or function name
+" should not be evaluated.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+func NULL()
+ Xpath 'a'
+ return 0
+endfunc
+
+func ZERO()
+ Xpath 'b'
+ return 0
+endfunc
+
+func! F0()
+ Xpath 'c'
+endfunc
+
+func! F1(arg)
+ Xpath 'e'
+endfunc
+
+let V0 = 1
+
+Xpath 'f'
+echo 0 ? F{NULL() + V{ZERO()}}() : 1
+
+Xpath 'g'
+if 0
+ Xpath 'h'
+ call F{NULL() + V{ZERO()}}()
+endif
+
+Xpath 'i'
+if 1
+ asdf " error
+ Xpath 'j'
+ call F1(F{NULL() + V{ZERO()}}())
+endif
+
+Xpath 'k'
+if 1
+ asdf " error
+ Xpath 'l'
+ call F{NULL() + V{ZERO()}}()
+endif
+
+let g:test12_result = g:Xpath
+
+func Test_braces_skipped()
+ call assert_equal('fgik', g:test12_result)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 13: Failure in argument evaluation for :while {{{1
+"
+" A failure in the expression evaluation for the condition of a :while
+" causes the whole :while loop until the matching :endwhile being
+" ignored. Continuation is at the next following line.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+Xpath 'a'
+while asdf
+ Xpath 'b'
+ while 1
+ Xpath 'c'
+ break
+ endwhile
+ Xpath 'd'
+ break
+endwhile
+Xpath 'e'
+
+while asdf | Xpath 'f' | endwhile | Xpath 'g'
+Xpath 'h'
+let g:test13_result = g:Xpath
+
+func Test_while_fail()
+ call assert_equal('aeh', g:test13_result)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 14: Failure in argument evaluation for :if {{{1
+"
+" A failure in the expression evaluation for the condition of an :if
+" does not cause the corresponding :else or :endif being matched to
+" a previous :if/:elseif. Neither of both branches of the failed :if
+" are executed.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+function! F()
+ Xpath 'a'
+ let x = 0
+ if x " false
+ Xpath 'b'
+ elseif !x " always true
+ Xpath 'c'
+ let x = 1
+ if g:boolvar " possibly undefined
+ Xpath 'd'
+ else
+ Xpath 'e'
+ endif
+ Xpath 'f'
+ elseif x " never executed
+ Xpath 'g'
+ endif
+ Xpath 'h'
+endfunction
+
+let boolvar = 1
+call F()
+Xpath '-'
+
+unlet boolvar
+call F()
+let g:test14_result = g:Xpath
+
+delfunction F
+
+func Test_if_fail()
+ call assert_equal('acdfh-acfh', g:test14_result)
+endfunc
+
+
+"-------------------------------------------------------------------------------
+" Test 15: Failure in argument evaluation for :if (bar) {{{1
+"
+" Like previous test, except that the failing :if ... | ... | :endif
+" is in a single line.
+"-------------------------------------------------------------------------------
+
+XpathINIT
+
+function! F()
+ Xpath 'a'
+ let x = 0
+ if x " false
+ Xpath 'b'
+ elseif !x " always true
+ Xpath 'c'
+ let x = 1
+ if g:boolvar | Xpath 'd' | else | Xpath 'e' | endif
+ Xpath 'f'
+ elseif x " never executed
+ Xpath 'g'
+ endif
+ Xpath 'h'
+endfunction
+
+let boolvar = 1
+call F()
+Xpath '-'
+
+unlet boolvar
+call F()
+let g:test15_result = g:Xpath
+
+delfunction F
+
+func Test_if_bar_fail()
+ call assert_equal('acdfh-acfh', g:test15_result)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 16: Double :else or :elseif after :else {{{1
+"
+" Multiple :elses or an :elseif after an :else are forbidden.
+"-------------------------------------------------------------------------------
+
+func T16_F() abort
+ if 0
+ Xpath 'a'
+ else
+ Xpath 'b'
+ else " aborts function
+ Xpath 'c'
+ endif
+ Xpath 'd'
+endfunc
+
+func T16_G() abort
+ if 0
+ Xpath 'a'
+ else
+ Xpath 'b'
+ elseif 1 " aborts function
+ Xpath 'c'
+ else
+ Xpath 'd'
+ endif
+ Xpath 'e'
+endfunc
+
+func T16_H() abort
+ if 0
+ Xpath 'a'
+ elseif 0
+ Xpath 'b'
+ else
+ Xpath 'c'
+ else " aborts function
+ Xpath 'd'
+ endif
+ Xpath 'e'
+endfunc
+
+func T16_I() abort
+ if 0
+ Xpath 'a'
+ elseif 0
+ Xpath 'b'
+ else
+ Xpath 'c'
+ elseif 1 " aborts function
+ Xpath 'd'
+ else
+ Xpath 'e'
+ endif
+ Xpath 'f'
+endfunc
+
+func Test_Multi_Else()
+ XpathINIT
+ try
+ call T16_F()
+ catch /E583:/
+ Xpath 'e'
+ endtry
+ call assert_equal('be', g:Xpath)
+
+ XpathINIT
+ try
+ call T16_G()
+ catch /E584:/
+ Xpath 'f'
+ endtry
+ call assert_equal('bf', g:Xpath)
+
+ XpathINIT
+ try
+ call T16_H()
+ catch /E583:/
+ Xpath 'f'
+ endtry
+ call assert_equal('cf', g:Xpath)
+
+ XpathINIT
+ try
+ call T16_I()
+ catch /E584:/
+ Xpath 'g'
+ endtry
+ call assert_equal('cg', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 17: Nesting of unmatched :if or :endif inside a :while {{{1
+"
+" The :while/:endwhile takes precedence in nesting over an unclosed
+" :if or an unopened :endif.
+"-------------------------------------------------------------------------------
+
+" While loops inside a function are continued on error.
+func T17_F()
+ let loops = 3
+ while loops > 0
+ let loops -= 1
+ Xpath 'a' . loops
+ if (loops == 1)
+ Xpath 'b' . loops
+ continue
+ elseif (loops == 0)
+ Xpath 'c' . loops
+ break
+ elseif 1
+ Xpath 'd' . loops
+ " endif missing!
+ endwhile " :endwhile after :if 1
+ Xpath 'e'
+endfunc
+
+func T17_G()
+ let loops = 2
+ while loops > 0
+ let loops -= 1
+ Xpath 'a' . loops
+ if 0
+ Xpath 'b' . loops
+ " endif missing
+ endwhile " :endwhile after :if 0
+endfunc
+
+func T17_H()
+ let loops = 2
+ while loops > 0
+ let loops -= 1
+ Xpath 'a' . loops
+ " if missing!
+ endif " :endif without :if in while
+ Xpath 'b' . loops
+ endwhile
+endfunc
+
+" Error continuation outside a function is at the outermost :endwhile or :endif.
+XpathINIT
+let v:errmsg = ''
+let loops = 2
+while loops > 0
+ let loops -= 1
+ Xpath 'a' . loops
+ if 0
+ Xpath 'b' . loops
+ " endif missing! Following :endwhile fails.
+endwhile | Xpath 'c'
+Xpath 'd'
+call assert_match('E171:', v:errmsg)
+call assert_equal('a1d', g:Xpath)
+
+func Test_unmatched_if_in_while()
+ XpathINIT
+ call assert_fails('call T17_F()', 'E171:')
+ call assert_equal('a2d2a1b1a0c0e', g:Xpath)
+
+ XpathINIT
+ call assert_fails('call T17_G()', 'E171:')
+ call assert_equal('a1a0', g:Xpath)
+
+ XpathINIT
+ call assert_fails('call T17_H()', 'E580:')
+ call assert_equal('a1b1a0b0', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 18: Interrupt (Ctrl-C pressed) {{{1
+"
+" On an interrupt, the script processing is terminated immediately.
+"-------------------------------------------------------------------------------
+
+func Test_interrupt_while_if()
+ let test =<< trim [CODE]
+ try
+ if 1
+ Xpath 'a'
+ while 1
+ Xpath 'b'
+ if 1
+ Xpath 'c'
+ call interrupt()
+ call assert_report('should not get here')
+ break
+ finish
+ endif | call assert_report('should not get here')
+ call assert_report('should not get here')
+ endwhile | call assert_report('should not get here')
+ call assert_report('should not get here')
+ endif | call assert_report('should not get here')
+ call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'd'
+ endtry | Xpath 'e'
+ Xpath 'f'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdef', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_interrupt_try()
+ let test =<< trim [CODE]
+ try
+ try
+ Xpath 'a'
+ call interrupt()
+ call assert_report('should not get here')
+ endtry | call assert_report('should not get here')
+ call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'b'
+ endtry | Xpath 'c'
+ Xpath 'd'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcd', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_interrupt_func_while_if()
+ let test =<< trim [CODE]
+ func F()
+ if 1
+ Xpath 'a'
+ while 1
+ Xpath 'b'
+ if 1
+ Xpath 'c'
+ call interrupt()
+ call assert_report('should not get here')
+ break
+ return
+ endif | call assert_report('should not get here')
+ call assert_report('should not get here')
+ endwhile | call assert_report('should not get here')
+ call assert_report('should not get here')
+ endif | call assert_report('should not get here')
+ call assert_report('should not get here')
+ endfunc
+
+ Xpath 'd'
+ try
+ call F() | call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'e'
+ endtry | Xpath 'f'
+ Xpath 'g'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('dabcefg', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_interrupt_func_try()
+ let test =<< trim [CODE]
+ func G()
+ try
+ Xpath 'a'
+ call interrupt()
+ call assert_report('should not get here')
+ endtry | call assert_report('should not get here')
+ call assert_report('should not get here')
+ endfunc
+
+ Xpath 'b'
+ try
+ call G() | call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'c'
+ endtry | Xpath 'd'
+ Xpath 'e'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('bacde', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 19: Aborting on errors inside :try/:endtry {{{1
+"
+" An error in a command dynamically enclosed in a :try/:endtry region
+" aborts script processing immediately. It does not matter whether
+" the failing command is outside or inside a function and whether a
+" function has an "abort" attribute.
+"-------------------------------------------------------------------------------
+
+func Test_try_error_abort_1()
+ let test =<< trim [CODE]
+ func F() abort
+ Xpath 'a'
+ asdf
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ Xpath 'b'
+ call F()
+ call assert_report('should not get here')
+ endtry | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('ba', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_try_error_abort_2()
+ let test =<< trim [CODE]
+ func G()
+ Xpath 'a'
+ asdf
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ Xpath 'b'
+ call G()
+ call assert_report('should not get here')
+ endtry | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('ba', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_try_error_abort_3()
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ asdf
+ call assert_report('should not get here')
+ endtry | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_try_error_abort_4()
+ let test =<< trim [CODE]
+ if 1
+ try
+ Xpath 'a'
+ asdf
+ call assert_report('should not get here')
+ endtry | call assert_report('should not get here')
+ endif | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_try_error_abort_5()
+ let test =<< trim [CODE]
+ let p = 1
+ while p
+ let p = 0
+ try
+ Xpath 'a'
+ asdf
+ call assert_report('should not get here')
+ endtry | call assert_report('should not get here')
+ endwhile | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_try_error_abort_6()
+ let test =<< trim [CODE]
+ let p = 1
+ Xpath 'a'
+ while p
+ Xpath 'b'
+ let p = 0
+ try
+ Xpath 'c'
+ endwhile | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 20: Aborting on errors after :try/:endtry {{{1
+"
+" When an error occurs after the last active :try/:endtry region has
+" been left, termination behavior is as if no :try/:endtry has been
+" seen.
+"-------------------------------------------------------------------------------
+
+func Test_error_after_try_1()
+ let test =<< trim [CODE]
+ let p = 1
+ while p
+ let p = 0
+ Xpath 'a'
+ try
+ Xpath 'b'
+ endtry
+ asdf
+ call assert_report('should not get here')
+ endwhile | call assert_report('should not get here')
+ Xpath 'c'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_error_after_try_2()
+ let test =<< trim [CODE]
+ while 1
+ try
+ Xpath 'a'
+ break
+ call assert_report('should not get here')
+ endtry
+ endwhile
+ Xpath 'b'
+ asdf
+ Xpath 'c'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_error_after_try_3()
+ let test =<< trim [CODE]
+ while 1
+ try
+ Xpath 'a'
+ break
+ call assert_report('should not get here')
+ finally
+ Xpath 'b'
+ endtry
+ endwhile
+ Xpath 'c'
+ asdf
+ Xpath 'd'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcd', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_error_after_try_4()
+ let test =<< trim [CODE]
+ while 1
+ try
+ Xpath 'a'
+ finally
+ Xpath 'b'
+ break
+ call assert_report('should not get here')
+ endtry
+ endwhile
+ Xpath 'c'
+ asdf
+ Xpath 'd'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcd', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_error_after_try_5()
+ let test =<< trim [CODE]
+ let p = 1
+ while p
+ let p = 0
+ try
+ Xpath 'a'
+ continue
+ call assert_report('should not get here')
+ endtry
+ endwhile
+ Xpath 'b'
+ asdf
+ Xpath 'c'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_error_after_try_6()
+ let test =<< trim [CODE]
+ let p = 1
+ while p
+ let p = 0
+ try
+ Xpath 'a'
+ continue
+ call assert_report('should not get here')
+ finally
+ Xpath 'b'
+ endtry
+ endwhile
+ Xpath 'c'
+ asdf
+ Xpath 'd'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcd', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_error_after_try_7()
+ let test =<< trim [CODE]
+ let p = 1
+ while p
+ let p = 0
+ try
+ Xpath 'a'
+ finally
+ Xpath 'b'
+ continue
+ call assert_report('should not get here')
+ endtry
+ endwhile
+ Xpath 'c'
+ asdf
+ Xpath 'd'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcd', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1
+"
+" If a :try conditional stays inactive due to a preceding :continue,
+" :break, :return, or :finish, its :finally clause should not be
+" executed.
+"-------------------------------------------------------------------------------
+
+func Test_finally_after_loop_ctrl_statement()
+ let test =<< trim [CODE]
+ func F()
+ let loops = 2
+ while loops > 0
+ XloopNEXT
+ let loops = loops - 1
+ try
+ if loops == 1
+ Xloop 'a'
+ continue
+ call assert_report('should not get here')
+ elseif loops == 0
+ Xloop 'b'
+ break
+ call assert_report('should not get here')
+ endif
+
+ try " inactive
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ finally
+ Xloop 'c'
+ endtry
+ call assert_report('should not get here')
+ endwhile
+
+ try
+ Xpath 'd'
+ return
+ call assert_report('should not get here')
+ try " inactive
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ finally
+ Xpath 'e'
+ endtry
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ Xpath 'f'
+ call F()
+ Xpath 'g'
+ finish
+ call assert_report('should not get here')
+ try " inactive
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ finally
+ Xpath 'h'
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('fa2c2b3c3degh', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 22: :finally for a :try after an error/interrupt/:throw {{{1
+"
+" If a :try conditional stays inactive due to a preceding error or
+" interrupt or :throw, its :finally clause should not be executed.
+"-------------------------------------------------------------------------------
+
+func Test_finally_after_error_in_func()
+ let test =<< trim [CODE]
+ func Error()
+ try
+ Xpath 'b'
+ asdf " aborting error, triggering error exception
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endfunc
+
+ Xpath 'a'
+ call Error()
+ call assert_report('should not get here')
+
+ if 1 " not active due to error
+ try " not active since :if inactive
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ endif
+
+ try " not active due to error
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('ab', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_finally_after_interrupt()
+ let test =<< trim [CODE]
+ func Interrupt()
+ try
+ Xpath 'a'
+ call interrupt() " triggering interrupt exception
+ call assert_report('should not get here')
+ endtry
+ endfunc
+
+ Xpath 'b'
+ try
+ call Interrupt()
+ catch /^Vim:Interrupt$/
+ Xpath 'c'
+ finish
+ endtry
+ call assert_report('should not get here')
+
+ if 1 " not active due to interrupt
+ try " not active since :if inactive
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ endif
+
+ try " not active due to interrupt
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('bac', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_finally_after_throw()
+ let test =<< trim [CODE]
+ func Throw()
+ Xpath 'a'
+ throw 'xyz'
+ endfunc
+
+ Xpath 'b'
+ call Throw()
+ call assert_report('should not get here')
+
+ if 1 " not active due to :throw
+ try " not active since :if inactive
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ endif
+
+ try " not active due to :throw
+ call assert_report('should not get here')
+ finally
+ call assert_report('should not get here')
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('ba', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 23: :catch clauses for a :try after a :throw {{{1
+"
+" If a :try conditional stays inactive due to a preceding :throw,
+" none of its :catch clauses should be executed.
+"-------------------------------------------------------------------------------
+
+func Test_catch_after_throw()
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ throw "xyz"
+ call assert_report('should not get here')
+
+ if 1 " not active due to :throw
+ try " not active since :if inactive
+ call assert_report('should not get here')
+ catch /xyz/
+ call assert_report('should not get here')
+ endtry
+ endif
+ catch /xyz/
+ Xpath 'b'
+ endtry
+
+ Xpath 'c'
+ throw "abc"
+ call assert_report('should not get here')
+
+ try " not active due to :throw
+ call assert_report('should not get here')
+ catch /abc/
+ call assert_report('should not get here')
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 24: :endtry for a :try after a :throw {{{1
+"
+" If a :try conditional stays inactive due to a preceding :throw,
+" its :endtry should not rethrow the exception to the next surrounding
+" active :try conditional.
+"-------------------------------------------------------------------------------
+
+func Test_endtry_after_throw()
+ let test =<< trim [CODE]
+ try " try 1
+ try " try 2
+ Xpath 'a'
+ throw "xyz" " makes try 2 inactive
+ call assert_report('should not get here')
+
+ try " try 3
+ call assert_report('should not get here')
+ endtry " no rethrow to try 1
+ catch /xyz/ " should catch although try 2 inactive
+ Xpath 'b'
+ endtry
+ catch /xyz/ " try 1 active, but exception already caught
+ call assert_report('should not get here')
+ endtry
+ Xpath 'c'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 27: Executing :finally clauses after :return {{{1
+"
+" For a :return command dynamically enclosed in a :try/:endtry region,
+" :finally clauses are executed and the called function is ended.
+"-------------------------------------------------------------------------------
+
+func T27_F()
+ try
+ Xpath 'a'
+ try
+ Xpath 'b'
+ return
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ endtry
+ Xpath 'd'
+ finally
+ Xpath 'e'
+ endtry
+ call assert_report('should not get here')
+endfunc
+
+func T27_G()
+ try
+ Xpath 'f'
+ return
+ call assert_report('should not get here')
+ finally
+ Xpath 'g'
+ call T27_F()
+ Xpath 'h'
+ endtry
+ call assert_report('should not get here')
+endfunc
+
+func T27_H()
+ try
+ Xpath 'i'
+ call T27_G()
+ Xpath 'j'
+ finally
+ Xpath 'k'
+ return
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+endfunction
+
+func Test_finally_after_return()
+ XpathINIT
+ try
+ Xpath 'l'
+ call T27_H()
+ Xpath 'm'
+ finally
+ Xpath 'n'
+ endtry
+ call assert_equal('lifgabcehjkmn', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 28: Executing :finally clauses after :finish {{{1
+"
+" For a :finish command dynamically enclosed in a :try/:endtry region,
+" :finally clauses are executed and the sourced file is finished.
+"
+" This test executes the bodies of the functions F, G, and H from the
+" previous test as script files (:return replaced by :finish).
+"-------------------------------------------------------------------------------
+
+func Test_finally_after_finish()
+ XpathINIT
+
+ let scriptF = MakeScript("T27_F")
+ let scriptG = MakeScript("T27_G", scriptF)
+ let scriptH = MakeScript("T27_H", scriptG)
+
+ try
+ Xpath 'A'
+ exec "source" scriptH
+ Xpath 'B'
+ finally
+ Xpath 'C'
+ endtry
+ Xpath 'D'
+ call assert_equal('AifgabcehjkBCD', g:Xpath)
+ call delete(scriptF)
+ call delete(scriptG)
+ call delete(scriptH)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 29: Executing :finally clauses on errors {{{1
+"
+" After an error in a command dynamically enclosed in a :try/:endtry
+" region, :finally clauses are executed and the script processing is
+" terminated.
+"-------------------------------------------------------------------------------
+
+func Test_finally_after_error_1()
+ let test =<< trim [CODE]
+ func F()
+ while 1
+ try
+ Xpath 'a'
+ while 1
+ try
+ Xpath 'b'
+ asdf " error
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ endtry | call assert_report('should not get here')
+ call assert_report('should not get here')
+ break
+ endwhile
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ endtry | call assert_report('should not get here')
+ call assert_report('should not get here')
+ break
+ endwhile
+ call assert_report('should not get here')
+ endfunc
+
+ while 1
+ try
+ Xpath 'e'
+ while 1
+ call F()
+ call assert_report('should not get here')
+ break
+ endwhile | call assert_report('should not get here')
+ call assert_report('should not get here')
+ finally
+ Xpath 'f'
+ endtry | call assert_report('should not get here')
+ endwhile | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('eabcdf', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_finally_after_error_2()
+ let test =<< trim [CODE]
+ func G() abort
+ if 1
+ try
+ Xpath 'a'
+ asdf " error
+ call assert_report('should not get here')
+ finally
+ Xpath 'b'
+ endtry | Xpath 'c'
+ endif | Xpath 'd'
+ call assert_report('should not get here')
+ endfunc
+
+ if 1
+ try
+ Xpath 'e'
+ call G()
+ call assert_report('should not get here')
+ finally
+ Xpath 'f'
+ endtry | call assert_report('should not get here')
+ endif | call assert_report('should not get here')
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('eabf', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 30: Executing :finally clauses on interrupt {{{1
+"
+" After an interrupt in a command dynamically enclosed in
+" a :try/:endtry region, :finally clauses are executed and the
+" script processing is terminated.
+"-------------------------------------------------------------------------------
+
+func Test_finally_on_interrupt()
+ let test =<< trim [CODE]
+ func F()
+ try
+ Xloop 'a'
+ call interrupt()
+ call assert_report('should not get here')
+ finally
+ Xloop 'b'
+ endtry
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ try
+ Xpath 'c'
+ try
+ Xpath 'd'
+ call interrupt()
+ call assert_report('should not get here')
+ finally
+ Xpath 'e'
+ try
+ Xpath 'f'
+ try
+ Xpath 'g'
+ finally
+ Xpath 'h'
+ try
+ Xpath 'i'
+ call interrupt()
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ finally
+ Xpath 'j'
+ try
+ Xpath 'k'
+ call F()
+ call assert_report('should not get here')
+ finally
+ Xpath 'l'
+ try
+ Xpath 'm'
+ XloopNEXT
+ ExecAsScript F
+ call assert_report('should not get here')
+ finally
+ Xpath 'n'
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'o'
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('cdefghijka1b1lma2b2no', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 31: Executing :finally clauses after :throw {{{1
+"
+" After a :throw dynamically enclosed in a :try/:endtry region,
+" :finally clauses are executed and the script processing is
+" terminated.
+"-------------------------------------------------------------------------------
+
+func Test_finally_after_throw_2()
+ let test =<< trim [CODE]
+ func F()
+ try
+ Xloop 'a'
+ throw "exception"
+ call assert_report('should not get here')
+ finally
+ Xloop 'b'
+ endtry
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ Xpath 'c'
+ try
+ Xpath 'd'
+ throw "exception"
+ call assert_report('should not get here')
+ finally
+ Xpath 'e'
+ try
+ Xpath 'f'
+ try
+ Xpath 'g'
+ finally
+ Xpath 'h'
+ try
+ Xpath 'i'
+ throw "exception"
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ finally
+ Xpath 'j'
+ try
+ Xpath 'k'
+ call F()
+ call assert_report('should not get here')
+ finally
+ Xpath 'l'
+ try
+ Xpath 'm'
+ XloopNEXT
+ ExecAsScript F
+ call assert_report('should not get here')
+ finally
+ Xpath 'n'
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('cdefghijka1b1lma2b2n', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 34: :finally reason discarded by :continue {{{1
+"
+" When a :finally clause is executed due to a :continue, :break,
+" :return, :finish, error, interrupt or :throw, the jump reason is
+" discarded by a :continue in the finally clause.
+"-------------------------------------------------------------------------------
+
+func Test_finally_after_continue()
+ let test =<< trim [CODE]
+ func C(jump)
+ XloopNEXT
+ let loop = 0
+ while loop < 2
+ let loop = loop + 1
+ if loop == 1
+ try
+ if a:jump == "continue"
+ continue
+ elseif a:jump == "break"
+ break
+ elseif a:jump == "return" || a:jump == "finish"
+ return
+ elseif a:jump == "error"
+ asdf
+ elseif a:jump == "interrupt"
+ call interrupt()
+ let dummy = 0
+ elseif a:jump == "throw"
+ throw "abc"
+ endif
+ finally
+ continue " discards jump that caused the :finally
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ elseif loop == 2
+ Xloop 'a'
+ endif
+ endwhile
+ endfunc
+
+ call C("continue")
+ Xpath 'b'
+ call C("break")
+ Xpath 'c'
+ call C("return")
+ Xpath 'd'
+ let g:jump = "finish"
+ ExecAsScript C
+ unlet g:jump
+ Xpath 'e'
+ try
+ call C("error")
+ Xpath 'f'
+ finally
+ Xpath 'g'
+ try
+ call C("interrupt")
+ Xpath 'h'
+ finally
+ Xpath 'i'
+ call C("throw")
+ Xpath 'j'
+ endtry
+ endtry
+ Xpath 'k'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 35: :finally reason discarded by :break {{{1
+"
+" When a :finally clause is executed due to a :continue, :break,
+" :return, :finish, error, interrupt or :throw, the jump reason is
+" discarded by a :break in the finally clause.
+"-------------------------------------------------------------------------------
+
+func Test_finally_discard_by_break()
+ let test =<< trim [CODE]
+ func B(jump)
+ XloopNEXT
+ let loop = 0
+ while loop < 2
+ let loop = loop + 1
+ if loop == 1
+ try
+ if a:jump == "continue"
+ continue
+ elseif a:jump == "break"
+ break
+ elseif a:jump == "return" || a:jump == "finish"
+ return
+ elseif a:jump == "error"
+ asdf
+ elseif a:jump == "interrupt"
+ call interrupt()
+ let dummy = 0
+ elseif a:jump == "throw"
+ throw "abc"
+ endif
+ finally
+ break " discards jump that caused the :finally
+ call assert_report('should not get here')
+ endtry
+ elseif loop == 2
+ call assert_report('should not get here')
+ endif
+ endwhile
+ Xloop 'a'
+ endfunc
+
+ call B("continue")
+ Xpath 'b'
+ call B("break")
+ Xpath 'c'
+ call B("return")
+ Xpath 'd'
+ let g:jump = "finish"
+ ExecAsScript B
+ unlet g:jump
+ Xpath 'e'
+ try
+ call B("error")
+ Xpath 'f'
+ finally
+ Xpath 'g'
+ try
+ call B("interrupt")
+ Xpath 'h'
+ finally
+ Xpath 'i'
+ call B("throw")
+ Xpath 'j'
+ endtry
+ endtry
+ Xpath 'k'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 36: :finally reason discarded by :return {{{1
+"
+" When a :finally clause is executed due to a :continue, :break,
+" :return, :finish, error, interrupt or :throw, the jump reason is
+" discarded by a :return in the finally clause.
+"-------------------------------------------------------------------------------
+
+func Test_finally_discard_by_return()
+ let test =<< trim [CODE]
+ func R(jump, retval) abort
+ let loop = 0
+ while loop < 2
+ let loop = loop + 1
+ if loop == 1
+ try
+ if a:jump == "continue"
+ continue
+ elseif a:jump == "break"
+ break
+ elseif a:jump == "return"
+ return
+ elseif a:jump == "error"
+ asdf
+ elseif a:jump == "interrupt"
+ call interrupt()
+ let dummy = 0
+ elseif a:jump == "throw"
+ throw "abc"
+ endif
+ finally
+ return a:retval " discards jump that caused the :finally
+ call assert_report('should not get here')
+ endtry
+ elseif loop == 2
+ call assert_report('should not get here')
+ endif
+ endwhile
+ call assert_report('should not get here')
+ endfunc
+
+ let sum = -R("continue", -8)
+ Xpath 'a'
+ let sum = sum - R("break", -16)
+ Xpath 'b'
+ let sum = sum - R("return", -32)
+ Xpath 'c'
+ try
+ let sum = sum - R("error", -64)
+ Xpath 'd'
+ finally
+ Xpath 'e'
+ try
+ let sum = sum - R("interrupt", -128)
+ Xpath 'f'
+ finally
+ Xpath 'g'
+ let sum = sum - R("throw", -256)
+ Xpath 'h'
+ endtry
+ endtry
+ Xpath 'i'
+
+ let expected = 8 + 16 + 32 + 64 + 128 + 256
+ call assert_equal(sum, expected)
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefghi', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 37: :finally reason discarded by :finish {{{1
+"
+" When a :finally clause is executed due to a :continue, :break,
+" :return, :finish, error, interrupt or :throw, the jump reason is
+" discarded by a :finish in the finally clause.
+"-------------------------------------------------------------------------------
+
+func Test_finally_discard_by_finish()
+ let test =<< trim [CODE]
+ func F(jump) " not executed as function, transformed to a script
+ let loop = 0
+ while loop < 2
+ let loop = loop + 1
+ if loop == 1
+ try
+ if a:jump == "continue"
+ continue
+ elseif a:jump == "break"
+ break
+ elseif a:jump == "finish"
+ finish
+ elseif a:jump == "error"
+ asdf
+ elseif a:jump == "interrupt"
+ call interrupt()
+ let dummy = 0
+ elseif a:jump == "throw"
+ throw "abc"
+ endif
+ finally
+ finish " discards jump that caused the :finally
+ call assert_report('should not get here')
+ endtry
+ elseif loop == 2
+ call assert_report('should not get here')
+ endif
+ endwhile
+ call assert_report('should not get here')
+ endfunc
+
+ let scriptF = MakeScript("F")
+ delfunction F
+
+ let g:jump = "continue"
+ exec "source" scriptF
+ Xpath 'a'
+ let g:jump = "break"
+ exec "source" scriptF
+ Xpath 'b'
+ let g:jump = "finish"
+ exec "source" scriptF
+ Xpath 'c'
+ try
+ let g:jump = "error"
+ exec "source" scriptF
+ Xpath 'd'
+ finally
+ Xpath 'e'
+ try
+ let g:jump = "interrupt"
+ exec "source" scriptF
+ Xpath 'f'
+ finally
+ Xpath 'g'
+ try
+ let g:jump = "throw"
+ exec "source" scriptF
+ Xpath 'h'
+ finally
+ Xpath 'i'
+ endtry
+ endtry
+ endtry
+ unlet g:jump
+ call delete(scriptF)
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefghi', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 38: :finally reason discarded by an error {{{1
+"
+" When a :finally clause is executed due to a :continue, :break,
+" :return, :finish, error, interrupt or :throw, the jump reason is
+" discarded by an error in the finally clause.
+"-------------------------------------------------------------------------------
+
+func Test_finally_discard_by_error()
+ let test =<< trim [CODE]
+ func E(jump)
+ let loop = 0
+ while loop < 2
+ let loop = loop + 1
+ if loop == 1
+ try
+ if a:jump == "continue"
+ continue
+ elseif a:jump == "break"
+ break
+ elseif a:jump == "return" || a:jump == "finish"
+ return
+ elseif a:jump == "error"
+ asdf
+ elseif a:jump == "interrupt"
+ call interrupt()
+ let dummy = 0
+ elseif a:jump == "throw"
+ throw "abc"
+ endif
+ finally
+ asdf " error; discards jump that caused the :finally
+ endtry
+ elseif loop == 2
+ call assert_report('should not get here')
+ endif
+ endwhile
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ Xpath 'a'
+ call E("continue")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'b'
+ call E("break")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'c'
+ call E("return")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'd'
+ let g:jump = "finish"
+ ExecAsScript E
+ call assert_report('should not get here')
+ finally
+ unlet g:jump
+ try
+ Xpath 'e'
+ call E("error")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'f'
+ call E("interrupt")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'g'
+ call E("throw")
+ call assert_report('should not get here')
+ finally
+ Xpath 'h'
+ delfunction E
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefgh', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 39: :finally reason discarded by an interrupt {{{1
+"
+" When a :finally clause is executed due to a :continue, :break,
+" :return, :finish, error, interrupt or :throw, the jump reason is
+" discarded by an interrupt in the finally clause.
+"-------------------------------------------------------------------------------
+
+func Test_finally_discarded_by_interrupt()
+ let test =<< trim [CODE]
+ func I(jump)
+ let loop = 0
+ while loop < 2
+ let loop = loop + 1
+ if loop == 1
+ try
+ if a:jump == "continue"
+ continue
+ elseif a:jump == "break"
+ break
+ elseif a:jump == "return" || a:jump == "finish"
+ return
+ elseif a:jump == "error"
+ asdf
+ elseif a:jump == "interrupt"
+ call interrupt()
+ let dummy = 0
+ elseif a:jump == "throw"
+ throw "abc"
+ endif
+ finally
+ call interrupt()
+ let dummy = 0
+ endtry
+ elseif loop == 2
+ call assert_report('should not get here')
+ endif
+ endwhile
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ try
+ Xpath 'a'
+ call I("continue")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'b'
+ call I("break")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'c'
+ call I("return")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'd'
+ let g:jump = "finish"
+ ExecAsScript I
+ call assert_report('should not get here')
+ finally
+ unlet g:jump
+ try
+ Xpath 'e'
+ call I("error")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'f'
+ call I("interrupt")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'g'
+ call I("throw")
+ call assert_report('should not get here')
+ finally
+ Xpath 'h'
+ delfunction I
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'A'
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefghA', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 40: :finally reason discarded by :throw {{{1
+"
+" When a :finally clause is executed due to a :continue, :break,
+" :return, :finish, error, interrupt or :throw, the jump reason is
+" discarded by a :throw in the finally clause.
+"-------------------------------------------------------------------------------
+
+func Test_finally_discard_by_throw()
+ let test =<< trim [CODE]
+ func T(jump)
+ let loop = 0
+ while loop < 2
+ let loop = loop + 1
+ if loop == 1
+ try
+ if a:jump == "continue"
+ continue
+ elseif a:jump == "break"
+ break
+ elseif a:jump == "return" || a:jump == "finish"
+ return
+ elseif a:jump == "error"
+ asdf
+ elseif a:jump == "interrupt"
+ call interrupt()
+ let dummy = 0
+ elseif a:jump == "throw"
+ throw "abc"
+ endif
+ finally
+ throw "xyz" " discards jump that caused the :finally
+ endtry
+ elseif loop == 2
+ call assert_report('should not get here')
+ endif
+ endwhile
+ call assert_report('should not get here')
+ endfunc
+
+ try
+ Xpath 'a'
+ call T("continue")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'b'
+ call T("break")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'c'
+ call T("return")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'd'
+ let g:jump = "finish"
+ ExecAsScript T
+ call assert_report('should not get here')
+ finally
+ unlet g:jump
+ try
+ Xpath 'e'
+ call T("error")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'f'
+ call T("interrupt")
+ call assert_report('should not get here')
+ finally
+ try
+ Xpath 'g'
+ call T("throw")
+ call assert_report('should not get here')
+ finally
+ Xpath 'h'
+ delfunction T
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefgh', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 49: Throwing exceptions across functions {{{1
+"
+" When an exception is thrown but not caught inside a function, the
+" caller is checked for a matching :catch clause.
+"-------------------------------------------------------------------------------
+
+func T49_C()
+ try
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')
+ catch /arrgh/
+ Xpath 'b'
+ endtry
+ Xpath 'c'
+endfunc
+
+func T49_T1()
+ XloopNEXT
+ try
+ Xloop 'd'
+ throw "arrgh"
+ call assert_report('should not get here')
+ finally
+ Xloop 'e'
+ endtry
+ Xloop 'f'
+endfunc
+
+func T49_T2()
+ try
+ Xpath 'g'
+ call T49_T1()
+ call assert_report('should not get here')
+ finally
+ Xpath 'h'
+ endtry
+ call assert_report('should not get here')
+endfunc
+
+func Test_throw_exception_across_funcs()
+ XpathINIT
+ XloopINIT
+ try
+ Xpath 'i'
+ call T49_C() " throw and catch
+ Xpath 'j'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+
+ try
+ Xpath 'k'
+ call T49_T1() " throw, one level
+ call assert_report('should not get here')
+ catch /arrgh/
+ Xpath 'l'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+
+ try
+ Xpath 'm'
+ call T49_T2() " throw, two levels
+ call assert_report('should not get here')
+ catch /arrgh/
+ Xpath 'n'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ Xpath 'o'
+
+ call assert_equal('iabcjkd2e2lmgd3e3hno', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 50: Throwing exceptions across script files {{{1
+"
+" When an exception is thrown but not caught inside a script file,
+" the sourcing script or function is checked for a matching :catch
+" clause.
+"
+" This test executes the bodies of the functions C, T1, and T2 from
+" the previous test as script files (:return replaced by :finish).
+"-------------------------------------------------------------------------------
+
+func T50_F()
+ try
+ Xpath 'A'
+ exec "source" g:scriptC
+ Xpath 'B'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+
+ try
+ Xpath 'C'
+ exec "source" g:scriptT1
+ call assert_report('should not get here')
+ catch /arrgh/
+ Xpath 'D'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+endfunc
+
+func Test_throw_across_script()
+ XpathINIT
+ XloopINIT
+ let g:scriptC = MakeScript("T49_C")
+ let g:scriptT1 = MakeScript("T49_T1")
+ let scriptT2 = MakeScript("T49_T2", g:scriptT1)
+
+ try
+ Xpath 'E'
+ call T50_F()
+ Xpath 'F'
+ exec "source" scriptT2
+ call assert_report('should not get here')
+ catch /arrgh/
+ Xpath 'G'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ Xpath 'H'
+ call assert_equal('EAabcBCd2e2DFgd3e3hGH', g:Xpath)
+
+ call delete(g:scriptC)
+ call delete(g:scriptT1)
+ call delete(scriptT2)
+ unlet g:scriptC g:scriptT1 scriptT2
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 52: Uncaught exceptions {{{1
+"
+" When an exception is thrown but not caught, an error message is
+" displayed when the script is terminated. In case of an interrupt
+" or error exception, the normal interrupt or error message(s) are
+" displayed.
+"-------------------------------------------------------------------------------
+
+func Test_uncaught_exception_1()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')`
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('E605: Exception not caught: arrgh', v:errmsg)
+ call assert_equal('a', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_uncaught_exception_2()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ throw "oops"
+ call assert_report('should not get here')`
+ catch /arrgh/
+ call assert_report('should not get here')`
+ endtry
+ call assert_report('should not get here')`
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('E605: Exception not caught: oops', v:errmsg)
+ call assert_equal('a', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_uncaught_exception_3()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ func T()
+ Xpath 'c'
+ throw "brrr"
+ call assert_report('should not get here')`
+ endfunc
+
+ try
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')`
+ catch /.*/
+ Xpath 'b'
+ call T()
+ call assert_report('should not get here')`
+ endtry
+ call assert_report('should not get here')`
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('E605: Exception not caught: brrr', v:errmsg)
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_uncaught_exception_4()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')`
+ finally
+ Xpath 'b'
+ throw "brrr"
+ call assert_report('should not get here')`
+ endtry
+ call assert_report('should not get here')`
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('E605: Exception not caught: brrr', v:errmsg)
+ call assert_equal('ab', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_uncaught_exception_5()
+ CheckEnglish
+
+ " Need to catch and handle interrupt, otherwise the test will wait for the
+ " user to press <Enter> to continue
+ let test =<< trim [CODE]
+ try
+ try
+ Xpath 'a'
+ call interrupt()
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'b'
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('ab', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_uncaught_exception_6()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ let x = novar " error E121; exception: E121
+ catch /E15:/ " should not catch
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a', g:Xpath)
+ call assert_equal('E121: Undefined variable: novar', v:errmsg)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_uncaught_exception_7()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ " error E108/E488; exception: E488
+ unlet novar #
+ catch /E108:/ " should not catch
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a', g:Xpath)
+ call assert_equal('E488: Trailing characters: #', v:errmsg)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 53: Nesting errors: :endif/:else/:elseif {{{1
+"
+" For nesting errors of :if conditionals the correct error messages
+" should be given.
+"-------------------------------------------------------------------------------
+
+func Test_nested_if_else_errors()
+ CheckEnglish
+
+ " :endif without :if
+ let code =<< trim END
+ endif
+ END
+ call writefile(code, 'Xtest', 'D')
+ call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
+
+ " :endif without :if
+ let code =<< trim END
+ while 1
+ endif
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
+
+ " :endif without :if
+ let code =<< trim END
+ try
+ finally
+ endif
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
+
+ " :endif without :if
+ let code =<< trim END
+ try
+ endif
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
+
+ " :endif without :if
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ endif
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if')
+
+ " :else without :if
+ let code =<< trim END
+ else
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
+
+ " :else without :if
+ let code =<< trim END
+ while 1
+ else
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
+
+ " :else without :if
+ let code =<< trim END
+ try
+ finally
+ else
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
+
+ " :else without :if
+ let code =<< trim END
+ try
+ else
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
+
+ " :else without :if
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ else
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if')
+
+ " :elseif without :if
+ let code =<< trim END
+ elseif 1
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
+
+ " :elseif without :if
+ let code =<< trim END
+ while 1
+ elseif 1
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
+
+ " :elseif without :if
+ let code =<< trim END
+ try
+ finally
+ elseif 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
+
+ " :elseif without :if
+ let code =<< trim END
+ try
+ elseif 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
+
+ " :elseif without :if
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ elseif 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if')
+
+ " multiple :else
+ let code =<< trim END
+ if 1
+ else
+ else
+ endif
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(else):E583: Multiple :else')
+
+ " :elseif after :else
+ let code =<< trim END
+ if 1
+ else
+ elseif 1
+ endif
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(elseif):E584: :elseif after :else')
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 54: Nesting errors: :while/:endwhile {{{1
+"
+" For nesting errors of :while conditionals the correct error messages
+" should be given.
+"
+" This test reuses the function MESSAGES() from the previous test.
+" This functions checks the messages in g:msgfile.
+"-------------------------------------------------------------------------------
+
+func Test_nested_while_error()
+ CheckEnglish
+
+ " :endwhile without :while
+ let code =<< trim END
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
+
+ " :endwhile without :while
+ let code =<< trim END
+ if 1
+ endwhile
+ endif
+ END
+ call writefile(code, 'Xtest', 'D')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
+
+ " Missing :endif
+ let code =<< trim END
+ while 1
+ if 1
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif')
+
+ " :endwhile without :while
+ let code =<< trim END
+ try
+ finally
+ endwhile
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
+
+ " Missing :endtry
+ let code =<< trim END
+ while 1
+ try
+ finally
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry')
+
+ " Missing :endtry
+ let code =<< trim END
+ while 1
+ if 1
+ try
+ finally
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry')
+
+ " Missing :endif
+ let code =<< trim END
+ while 1
+ try
+ finally
+ if 1
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif')
+
+ " :endwhile without :while
+ let code =<< trim END
+ try
+ endwhile
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
+
+ " :endwhile without :while
+ let code =<< trim END
+ while 1
+ try
+ endwhile
+ endtry
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
+
+ " :endwhile without :while
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ endwhile
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
+
+ " :endwhile without :while
+ let code =<< trim END
+ while 1
+ try
+ throw "a"
+ catch /a/
+ endwhile
+ endtry
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while')
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 55: Nesting errors: :continue/:break {{{1
+"
+" For nesting errors of :continue and :break commands the correct
+" error messages should be given.
+"
+" This test reuses the function MESSAGES() from the previous test.
+" This functions checks the messages in g:msgfile.
+"-------------------------------------------------------------------------------
+
+func Test_nested_cont_break_error()
+ CheckEnglish
+
+ " :continue without :while
+ let code =<< trim END
+ continue
+ END
+ call writefile(code, 'Xtest', 'D')
+ call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
+
+ " :continue without :while
+ let code =<< trim END
+ if 1
+ continue
+ endif
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
+
+ " :continue without :while
+ let code =<< trim END
+ try
+ finally
+ continue
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
+
+ " :continue without :while
+ let code =<< trim END
+ try
+ continue
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
+
+ " :continue without :while
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ continue
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for')
+
+ " :break without :while
+ let code =<< trim END
+ break
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
+
+ " :break without :while
+ let code =<< trim END
+ if 1
+ break
+ endif
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
+
+ " :break without :while
+ let code =<< trim END
+ try
+ finally
+ break
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
+
+ " :break without :while
+ let code =<< trim END
+ try
+ break
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
+
+ " :break without :while
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ break
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for')
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 56: Nesting errors: :endtry {{{1
+"
+" For nesting errors of :try conditionals the correct error messages
+" should be given.
+"
+" This test reuses the function MESSAGES() from the previous test.
+" This functions checks the messages in g:msgfile.
+"-------------------------------------------------------------------------------
+
+func Test_nested_endtry_error()
+ CheckEnglish
+
+ " :endtry without :try
+ let code =<< trim END
+ endtry
+ END
+ call writefile(code, 'Xtest', 'D')
+ call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try')
+
+ " :endtry without :try
+ let code =<< trim END
+ if 1
+ endtry
+ endif
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try')
+
+ " :endtry without :try
+ let code =<< trim END
+ while 1
+ endtry
+ endwhile
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try')
+
+ " Missing :endif
+ let code =<< trim END
+ try
+ if 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif')
+
+ " Missing :endwhile
+ let code =<< trim END
+ try
+ while 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile')
+
+ " Missing :endif
+ let code =<< trim END
+ try
+ finally
+ if 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif')
+
+ " Missing :endwhile
+ let code =<< trim END
+ try
+ finally
+ while 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile')
+
+ " Missing :endif
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ if 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif')
+
+ " Missing :endwhile
+ let code =<< trim END
+ try
+ throw "a"
+ catch /a/
+ while 1
+ endtry
+ END
+ call writefile(code, 'Xtest')
+ call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile')
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 57: v:exception and v:throwpoint for user exceptions {{{1
+"
+" v:exception evaluates to the value of the exception that was caught
+" most recently and is not finished. (A caught exception is finished
+" when the next ":catch", ":finally", or ":endtry" is reached.)
+" v:throwpoint evaluates to the script/function name and line number
+" where that exception has been thrown.
+"-------------------------------------------------------------------------------
+
+func Test_user_exception_info()
+ CheckEnglish
+
+ XpathINIT
+ XloopINIT
+
+ func FuncException()
+ let g:exception = v:exception
+ endfunc
+
+ func FuncThrowpoint()
+ let g:throwpoint = v:throwpoint
+ endfunc
+
+ let scriptException = MakeScript("FuncException")
+ let scriptThrowPoint = MakeScript("FuncThrowpoint")
+
+ command! CmdException let g:exception = v:exception
+ command! CmdThrowpoint let g:throwpoint = v:throwpoint
+
+ func T(arg, line)
+ if a:line == 2
+ throw a:arg " in line 2
+ elseif a:line == 4
+ throw a:arg " in line 4
+ elseif a:line == 6
+ throw a:arg " in line 6
+ elseif a:line == 8
+ throw a:arg " in line 8
+ endif
+ endfunc
+
+ func G(arg, line)
+ call T(a:arg, a:line)
+ endfunc
+
+ func F(arg, line)
+ call G(a:arg, a:line)
+ endfunc
+
+ let scriptT = MakeScript("T")
+ let scriptG = MakeScript("G", scriptT)
+ let scriptF = MakeScript("F", scriptG)
+
+ try
+ Xpath 'a'
+ call F("oops", 2)
+ catch /.*/
+ Xpath 'b'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("oops", v:exception)
+ call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<2\>', v:throwpoint)
+
+ exec "let exception = v:exception"
+ exec "let throwpoint = v:throwpoint"
+ call assert_equal("oops", v:exception)
+ call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<2\>', v:throwpoint)
+
+ CmdException
+ CmdThrowpoint
+ call assert_equal("oops", v:exception)
+ call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<2\>', v:throwpoint)
+
+ call FuncException()
+ call FuncThrowpoint()
+ call assert_equal("oops", v:exception)
+ call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<2\>', v:throwpoint)
+
+ exec "source" scriptException
+ exec "source" scriptThrowPoint
+ call assert_equal("oops", v:exception)
+ call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<2\>', v:throwpoint)
+
+ try
+ Xpath 'c'
+ call G("arrgh", 4)
+ catch /.*/
+ Xpath 'd'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("arrgh", v:exception)
+ call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<4\>', v:throwpoint)
+
+ try
+ Xpath 'e'
+ let g:arg = "autsch"
+ let g:line = 6
+ exec "source" scriptF
+ catch /.*/
+ Xpath 'f'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("autsch", v:exception)
+ call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint)
+ call assert_match('\<6\>', v:throwpoint)
+ finally
+ Xpath 'g'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("arrgh", v:exception)
+ call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<4\>', v:throwpoint)
+ try
+ Xpath 'h'
+ let g:arg = "brrrr"
+ let g:line = 8
+ exec "source" scriptG
+ catch /.*/
+ Xpath 'i'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ " Resolve scriptT for matching it against v:throwpoint.
+ call assert_equal("brrrr", v:exception)
+ call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint)
+ call assert_match('\<8\>', v:throwpoint)
+ finally
+ Xpath 'j'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("arrgh", v:exception)
+ call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<4\>', v:throwpoint)
+ endtry
+ Xpath 'k'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("arrgh", v:exception)
+ call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<4\>', v:throwpoint)
+ endtry
+ Xpath 'l'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("arrgh", v:exception)
+ call assert_match('\<G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<4\>', v:throwpoint)
+ finally
+ Xpath 'm'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("oops", v:exception)
+ call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<2\>', v:throwpoint)
+ endtry
+ Xpath 'n'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("oops", v:exception)
+ call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint)
+ call assert_match('\<2\>', v:throwpoint)
+ finally
+ Xpath 'o'
+ let exception = v:exception
+ let throwpoint = v:throwpoint
+ call assert_equal("", v:exception)
+ call assert_match('^$', v:throwpoint)
+ call assert_match('^$', v:throwpoint)
+ endtry
+
+ call assert_equal('abcdefghijklmno', g:Xpath)
+
+ unlet exception throwpoint
+ delfunction FuncException
+ delfunction FuncThrowpoint
+ call delete(scriptException)
+ call delete(scriptThrowPoint)
+ unlet scriptException scriptThrowPoint
+ delcommand CmdException
+ delcommand CmdThrowpoint
+ delfunction T
+ delfunction G
+ delfunction F
+ call delete(scriptT)
+ call delete(scriptG)
+ call delete(scriptF)
+ unlet scriptT scriptG scriptF
+endfunc
+
+"-------------------------------------------------------------------------------
+"
+" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1
+"
+" v:exception and v:throwpoint work also for error and interrupt
+" exceptions.
+"-------------------------------------------------------------------------------
+
+func Test_exception_info_for_error()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ func T(line)
+ if a:line == 2
+ delfunction T " error (function in use) in line 2
+ elseif a:line == 4
+ call interrupt()
+ endif
+ endfunc
+
+ while 1
+ try
+ Xpath 'a'
+ call T(2)
+ call assert_report('should not get here')
+ catch /.*/
+ Xpath 'b'
+ if v:exception !~ 'Vim(delfunction):'
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint !~ '\<T\>'
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint !~ '\<2\>'
+ call assert_report('should not get here')
+ endif
+ finally
+ Xpath 'c'
+ if v:exception != ""
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint != ""
+ call assert_report('should not get here')
+ endif
+ break
+ endtry
+ endwhile
+
+ Xpath 'd'
+ if v:exception != ""
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint != ""
+ call assert_report('should not get here')
+ endif
+
+ while 1
+ try
+ Xpath 'e'
+ call T(4)
+ call assert_report('should not get here')
+ catch /.*/
+ Xpath 'f'
+ if v:exception != 'Vim:Interrupt'
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint !~ 'function T'
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint !~ '\<4\>'
+ call assert_report('should not get here')
+ endif
+ finally
+ Xpath 'g'
+ if v:exception != ""
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint != ""
+ call assert_report('should not get here')
+ endif
+ break
+ endtry
+ endwhile
+
+ Xpath 'h'
+ if v:exception != ""
+ call assert_report('should not get here')
+ endif
+ if v:throwpoint != ""
+ call assert_report('should not get here')
+ endif
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefgh', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+"
+" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1
+"
+" When a :catch clause is left by a ":break" etc or an error or
+" interrupt exception, v:exception and v:throwpoint are reset. They
+" are not affected by an exception that is discarded before being
+" caught.
+"-------------------------------------------------------------------------------
+func Test_exception_info_on_discard()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ let sfile = expand("<sfile>")
+
+ while 1
+ try
+ throw "x1"
+ catch /.*/
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ while 1
+ try
+ throw "x2"
+ catch /.*/
+ break
+ finally
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+ endtry
+ break
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ while 1
+ try
+ let errcaught = 0
+ try
+ try
+ throw "x3"
+ catch /.*/
+ let lnum = expand("<sflnum>")
+ asdf
+ endtry
+ catch /.*/
+ let errcaught = 1
+ call assert_match('Vim:E492: Not an editor command:', v:exception)
+ call assert_match('line ' .. (lnum + 1), v:throwpoint)
+ endtry
+ finally
+ call assert_equal(1, errcaught)
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ Xpath 'a'
+
+ while 1
+ try
+ let intcaught = 0
+ try
+ try
+ throw "x4"
+ catch /.*/
+ let lnum = expand("<sflnum>")
+ call interrupt()
+ endtry
+ catch /.*/
+ let intcaught = 1
+ call assert_match('Vim:Interrupt', v:exception)
+ call assert_match('line ' .. (lnum + 1), v:throwpoint)
+ endtry
+ finally
+ call assert_equal(1, intcaught)
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ Xpath 'b'
+
+ while 1
+ try
+ let errcaught = 0
+ try
+ try
+ if 1
+ let lnum = expand("<sflnum>")
+ throw "x5"
+ " missing endif
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ catch /.*/
+ let errcaught = 1
+ call assert_match('Vim(catch):E171: Missing :endif:', v:exception)
+ call assert_match('line ' .. (lnum + 3), v:throwpoint)
+ endtry
+ finally
+ call assert_equal(1, errcaught)
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ Xpath 'c'
+
+ try
+ while 1
+ try
+ throw "x6"
+ finally
+ break
+ endtry
+ break
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ try
+ while 1
+ try
+ throw "x7"
+ finally
+ break
+ endtry
+ break
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+ endtry
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ while 1
+ try
+ let errcaught = 0
+ try
+ try
+ throw "x8"
+ finally
+ let lnum = expand("<sflnum>")
+ asdf
+ endtry
+ catch /.*/
+ let errcaught = 1
+ call assert_match('Vim:E492: Not an editor command:', v:exception)
+ call assert_match('line ' .. (lnum + 1), v:throwpoint)
+ endtry
+ finally
+ call assert_equal(1, errcaught)
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ Xpath 'd'
+
+ while 1
+ try
+ let intcaught = 0
+ try
+ try
+ throw "x9"
+ finally
+ let lnum = expand("<sflnum>")
+ call interrupt()
+ endtry
+ catch /.*/
+ let intcaught = 1
+ call assert_match('Vim:Interrupt', v:exception)
+ call assert_match('line ' .. (lnum + 1), v:throwpoint)
+ endtry
+ finally
+ call assert_equal(1, intcaught)
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ Xpath 'e'
+
+ while 1
+ try
+ let errcaught = 0
+ try
+ try
+ if 1
+ let lnum = expand("<sflnum>")
+ throw "x10"
+ " missing endif
+ finally
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+ endtry
+ catch /.*/
+ let errcaught = 1
+ call assert_match('Vim(finally):E171: Missing :endif:', v:exception)
+ call assert_match('line ' .. (lnum + 3), v:throwpoint)
+ endtry
+ finally
+ call assert_equal(1, errcaught)
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ Xpath 'f'
+
+ while 1
+ try
+ let errcaught = 0
+ try
+ try
+ if 1
+ let lnum = expand("<sflnum>")
+ throw "x11"
+ " missing endif
+ endtry
+ catch /.*/
+ let errcaught = 1
+ call assert_match('Vim(endtry):E171: Missing :endif:', v:exception)
+ call assert_match('line ' .. (lnum + 3), v:throwpoint)
+ endtry
+ finally
+ call assert_equal(1, errcaught)
+ break
+ endtry
+ endwhile
+ call assert_equal('', v:exception)
+ call assert_equal('', v:throwpoint)
+
+ Xpath 'g'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefg', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+"
+" Test 60: (Re)throwing v:exception; :echoerr. {{{1
+"
+" A user exception can be rethrown after catching by throwing
+" v:exception. An error or interrupt exception cannot be rethrown
+" because Vim exceptions cannot be faked. A Vim exception using the
+" value of v:exception can, however, be triggered by the :echoerr
+" command.
+"-------------------------------------------------------------------------------
+
+func Test_rethrow_exception_1()
+ XpathINIT
+ try
+ try
+ Xpath 'a'
+ throw "oops"
+ catch /oops/
+ Xpath 'b'
+ throw v:exception " rethrow user exception
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ catch /^oops$/ " catches rethrown user exception
+ Xpath 'c'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ call assert_equal('abc', g:Xpath)
+endfunc
+
+func Test_rethrow_exception_2()
+ XpathINIT
+ try
+ let caught = 0
+ try
+ Xpath 'a'
+ write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e
+ call assert_report('should not get here')
+ catch /^Vim(write):/
+ let caught = 1
+ throw v:exception " throw error: cannot fake Vim exception
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ endtry
+ catch /^Vim(throw):/ " catches throw error
+ let caught = caught + 1
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ call assert_equal(2, caught)
+ endtry
+ call assert_equal('abc', g:Xpath)
+endfunc
+
+func Test_rethrow_exception_3()
+ XpathINIT
+ try
+ let caught = 0
+ try
+ Xpath 'a'
+ asdf
+ catch /^Vim/ " catch error exception
+ let caught = 1
+ " Trigger Vim error exception with value specified after :echoerr
+ let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "")
+ echoerr value
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ endtry
+ catch /^Vim(echoerr):/
+ let caught = caught + 1
+ call assert_match(value, v:exception)
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ call assert_equal(2, caught)
+ endtry
+ call assert_equal('abc', g:Xpath)
+endfunc
+
+func Test_rethrow_exception_3()
+ XpathINIT
+ try
+ let errcaught = 0
+ try
+ Xpath 'a'
+ let intcaught = 0
+ call interrupt()
+ catch /^Vim:/ " catch interrupt exception
+ let intcaught = 1
+ " Trigger Vim error exception with value specified after :echoerr
+ echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "")
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'b'
+ call assert_equal(1, intcaught)
+ endtry
+ catch /^Vim(echoerr):/
+ let errcaught = 1
+ call assert_match('Interrupt', v:exception)
+ finally
+ Xpath 'c'
+ call assert_equal(1, errcaught)
+ endtry
+ call assert_equal('abc', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 61: Catching interrupt exceptions {{{1
+"
+" When an interrupt occurs inside a :try/:endtry region, an
+" interrupt exception is thrown and can be caught. Its value is
+" "Vim:Interrupt". If the interrupt occurs after an error or a :throw
+" but before a matching :catch is reached, all following :catches of
+" that try block are ignored, but the interrupt exception can be
+" caught by the next surrounding try conditional. An interrupt is
+" ignored when there is a previous interrupt that has not been caught
+" or causes a :finally clause to be executed.
+"-------------------------------------------------------------------------------
+
+func Test_catch_intr_exception()
+ let test =<< trim [CODE]
+ while 1
+ try
+ try
+ Xpath 'a'
+ call interrupt()
+ call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'b'
+ finally
+ Xpath 'c'
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ try
+ try
+ Xpath 'e'
+ asdf
+ call assert_report('should not get here')
+ catch /do_not_catch/
+ call assert_report('should not get here')
+ catch /.*/
+ Xpath 'f'
+ call interrupt()
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'g'
+ call interrupt()
+ call assert_report('should not get here')
+ endtry
+ catch /^Vim:Interrupt$/
+ Xpath 'h'
+ finally
+ Xpath 'i'
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'j'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ try
+ try
+ Xpath 'k'
+ throw "x"
+ call assert_report('should not get here')
+ catch /do_not_catch/
+ call assert_report('should not get here')
+ catch /x/
+ Xpath 'l'
+ call interrupt()
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ catch /^Vim:Interrupt$/
+ Xpath 'm'
+ finally
+ Xpath 'n'
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'o'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ try
+ Xpath 'p'
+ call interrupt()
+ call assert_report('should not get here')
+ catch /do_not_catch/
+ call interrupt()
+ call assert_report('should not get here')
+ catch /^Vim:Interrupt$/
+ Xpath 'q'
+ finally
+ Xpath 'r'
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 's'
+ break
+ endtry
+ endwhile
+
+ Xpath 't'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefghijklmnopqrst', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 62: Catching error exceptions {{{1
+"
+" An error inside a :try/:endtry region is converted to an exception
+" and can be caught. The error exception has a "Vim(cmdname):" prefix
+" where cmdname is the name of the failing command, or a "Vim:" prefix
+" if no command name is known. The "Vim" prefixes cannot be faked.
+"-------------------------------------------------------------------------------
+
+func Test_catch_err_exception_1()
+ XpathINIT
+ while 1
+ try
+ try
+ let caught = 0
+ unlet novar
+ catch /^Vim(unlet):/
+ Xpath 'a'
+ let caught = 1
+ let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "")
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ call assert_match('E108: No such variable: "novar"', v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abc', g:Xpath)
+endfunc
+
+func Test_catch_err_exception_2()
+ XpathINIT
+ while 1
+ try
+ try
+ let caught = 0
+ throw novar " error in :throw
+ catch /^Vim(throw):/
+ Xpath 'a'
+ let caught = 1
+ let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "")
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ call assert_match('E121: Undefined variable: novar', v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abc', g:Xpath)
+endfunc
+
+func Test_catch_err_exception_3()
+ XpathINIT
+ while 1
+ try
+ try
+ let caught = 0
+ throw "Vim:faked" " error: cannot fake Vim exception
+ catch /^Vim(throw):/
+ Xpath 'a'
+ let caught = 1
+ let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "")
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ call assert_match("E608: Cannot :throw exceptions with 'Vim' prefix",
+ \ v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abc', g:Xpath)
+endfunc
+
+func Test_catch_err_exception_4()
+ XpathINIT
+ func F()
+ while 1
+ " Missing :endwhile
+ endfunc
+
+ while 1
+ try
+ try
+ let caught = 0
+ call F()
+ catch /^Vim(endfunction):/
+ Xpath 'a'
+ let caught = 1
+ let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "")
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ call assert_match("E170: Missing :endwhile", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abc', g:Xpath)
+ delfunc F
+endfunc
+
+func Test_catch_err_exception_5()
+ XpathINIT
+ func F()
+ while 1
+ " Missing :endwhile
+ endfunc
+
+ while 1
+ try
+ try
+ let caught = 0
+ ExecAsScript F
+ catch /^Vim:/
+ Xpath 'a'
+ let caught = 1
+ let v:errmsg = substitute(v:exception, '^Vim:', '', "")
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ call assert_match("E170: Missing :endwhile", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abc', g:Xpath)
+ delfunc F
+endfunc
+
+func Test_catch_err_exception_6()
+ XpathINIT
+ func G()
+ call G()
+ endfunc
+
+ while 1
+ try
+ let mfd_save = &mfd
+ set mfd=3
+ try
+ let caught = 0
+ call G()
+ catch /^Vim(call):/
+ Xpath 'a'
+ let caught = 1
+ let v:errmsg = substitute(v:exception, '^Vim(call):', '', "")
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ let &mfd = mfd_save
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abc', g:Xpath)
+ delfunc G
+endfunc
+
+func Test_catch_err_exception_7()
+ XpathINIT
+ func H()
+ return H()
+ endfunc
+
+ while 1
+ try
+ let mfd_save = &mfd
+ set mfd=3
+ try
+ let caught = 0
+ call H()
+ catch /^Vim(return):/
+ Xpath 'a'
+ let caught = 1
+ let v:errmsg = substitute(v:exception, '^Vim(return):', '', "")
+ finally
+ Xpath 'b'
+ call assert_equal(1, caught)
+ call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ let &mfd = mfd_save
+ break " discard error for $VIMNOERRTHROW
+ endtry
+ call assert_report('should not get here')
+ endwhile
+
+ call assert_equal('abc', g:Xpath)
+ delfunc H
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 63: Suppressing error exceptions by :silent!. {{{1
+"
+" A :silent! command inside a :try/:endtry region suppresses the
+" conversion of errors to an exception and the immediate abortion on
+" error. When the commands executed by the :silent! themselves open
+" a new :try/:endtry region, conversion of errors to exception and
+" immediate abortion is switched on again - until the next :silent!
+" etc. The :silent! has the effect of setting v:errmsg to the error
+" message text (without displaying it) and continuing with the next
+" script line.
+"
+" When a command triggering autocommands is executed by :silent!
+" inside a :try/:endtry, the autocommand execution is not suppressed
+" on error.
+"
+" This test reuses the function MSG() from the previous test.
+"-------------------------------------------------------------------------------
+
+func Test_silent_exception()
+ XpathINIT
+ XloopINIT
+ let g:taken = ""
+
+ func S(n) abort
+ XloopNEXT
+ let g:taken = g:taken . "E" . a:n
+ let v:errmsg = ""
+ exec "asdf" . a:n
+
+ " Check that ":silent!" continues:
+ Xloop 'a'
+
+ " Check that ":silent!" sets "v:errmsg":
+ call assert_match("E492: Not an editor command", v:errmsg)
+ endfunc
+
+ func Foo()
+ while 1
+ try
+ try
+ let caught = 0
+ " This is not silent:
+ call S(3)
+ catch /^Vim:/
+ Xpath 'b'
+ let caught = 1
+ let errmsg3 = substitute(v:exception, '^Vim:', '', "")
+ silent! call S(4)
+ finally
+ call assert_equal(1, caught)
+ Xpath 'c'
+ call assert_match("E492: Not an editor command", errmsg3)
+ silent! call S(5)
+ " Break out of try conditionals that cover ":silent!". This also
+ " discards the aborting error when $VIMNOERRTHROW is non-zero.
+ break
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ endwhile
+ " This is a double ":silent!" (see caller).
+ silent! call S(6)
+ endfunc
+
+ func Bar()
+ try
+ silent! call S(2)
+ silent! execute "call Foo() | call S(7)"
+ silent! call S(8)
+ endtry " normal end of try cond that covers ":silent!"
+ " This has a ":silent!" from the caller:
+ call S(9)
+ endfunc
+
+ silent! call S(1)
+ silent! call Bar()
+ silent! call S(10)
+
+ call assert_equal("E1E2E3E4E5E6E7E8E9E10", g:taken)
+
+ augroup TMP
+ au!
+ autocmd BufWritePost * Xpath 'd'
+ augroup END
+
+ Xpath 'e'
+ silent! write /i/m/p/o/s/s/i/b/l/e
+ Xpath 'f'
+
+ call assert_equal('a2a3ba5ca6a7a8a9a10a11edf', g:Xpath)
+
+ augroup TMP
+ au!
+ augroup END
+ augroup! TMP
+ delfunction S
+ delfunction Foo
+ delfunction Bar
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 64: Error exceptions after error, interrupt or :throw {{{1
+"
+" When an error occurs after an interrupt or a :throw but before
+" a matching :catch is reached, all following :catches of that try
+" block are ignored, but the error exception can be caught by the next
+" surrounding try conditional. Any previous error exception is
+" discarded. An error is ignored when there is a previous error that
+" has not been caught.
+"-------------------------------------------------------------------------------
+
+func Test_exception_after_error_1()
+ XpathINIT
+ while 1
+ try
+ try
+ Xpath 'a'
+ let caught = 0
+ while 1
+ if 1
+ " Missing :endif
+ endwhile " throw error exception
+ catch /^Vim(/
+ Xpath 'b'
+ let caught = 1
+ finally
+ Xpath 'c'
+ call assert_equal(1, caught)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abcd', g:Xpath)
+endfunc
+
+func Test_exception_after_error_2()
+ XpathINIT
+ while 1
+ try
+ try
+ Xpath 'a'
+ let caught = 0
+ try
+ if 1
+ " Missing :endif
+ catch /.*/ " throw error exception
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ catch /^Vim(/
+ Xpath 'b'
+ let caught = 1
+ finally
+ Xpath 'c'
+ call assert_equal(1, caught)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abcd', g:Xpath)
+endfunc
+
+func Test_exception_after_error_3()
+ XpathINIT
+ while 1
+ try
+ try
+ let caught = 0
+ try
+ Xpath 'a'
+ call interrupt()
+ catch /do_not_catch/
+ call assert_report('should not get here')
+ if 1
+ " Missing :endif
+ catch /.*/ " throw error exception
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ catch /^Vim(/
+ Xpath 'b'
+ let caught = 1
+ finally
+ Xpath 'c'
+ call assert_equal(1, caught)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abcd', g:Xpath)
+endfunc
+
+func Test_exception_after_error_4()
+ XpathINIT
+ while 1
+ try
+ try
+ let caught = 0
+ try
+ Xpath 'a'
+ throw "x"
+ catch /do_not_catch/
+ call assert_report('should not get here')
+ if 1
+ " Missing :endif
+ catch /x/ " throw error exception
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ catch /^Vim(/
+ Xpath 'b'
+ let caught = 1
+ finally
+ Xpath 'c'
+ call assert_equal(1, caught)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abcd', g:Xpath)
+endfunc
+
+func Test_exception_after_error_5()
+ XpathINIT
+ while 1
+ try
+ try
+ let caught = 0
+ Xpath 'a'
+ endif " :endif without :if; throw error exception
+ if 1
+ " Missing :endif
+ catch /do_not_catch/ " ignore new error
+ call assert_report('should not get here')
+ catch /^Vim(endif):/
+ Xpath 'b'
+ let caught = 1
+ catch /^Vim(/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ call assert_equal(1, caught)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ break
+ endtry
+ call assert_report('should not get here')
+ endwhile
+ call assert_equal('abcd', g:Xpath)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 65: Errors in the /pattern/ argument of a :catch {{{1
+"
+" On an error in the /pattern/ argument of a :catch, the :catch does
+" not match. Any following :catches of the same :try/:endtry don't
+" match either. Finally clauses are executed.
+"-------------------------------------------------------------------------------
+
+func Test_catch_pattern_error()
+ CheckEnglish
+ XpathINIT
+
+ try
+ try
+ Xpath 'a'
+ throw "oops"
+ catch /^oops$/
+ Xpath 'b'
+ catch /\)/ " not checked; exception has already been caught
+ call assert_report('should not get here')
+ endtry
+ Xpath 'c'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ call assert_equal('abc', g:Xpath)
+
+ XpathINIT
+ func F()
+ try
+ try
+ try
+ Xpath 'a'
+ throw "ab"
+ catch /abc/ " does not catch
+ call assert_report('should not get here')
+ catch /\)/ " error; discards exception
+ call assert_report('should not get here')
+ catch /.*/ " not checked
+ call assert_report('should not get here')
+ finally
+ Xpath 'b'
+ endtry
+ call assert_report('should not get here')
+ catch /^ab$/ " checked, but original exception is discarded
+ call assert_report('should not get here')
+ catch /^Vim(catch):/
+ Xpath 'c'
+ call assert_match('Vim(catch):E475: Invalid argument:', v:exception)
+ finally
+ Xpath 'd'
+ endtry
+ Xpath 'e'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ Xpath 'f'
+ endfunc
+
+ call F()
+ call assert_equal('abcdef', g:Xpath)
+
+ delfunc F
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 66: Stop range :call on error, interrupt, or :throw {{{1
+"
+" When a function which is multiply called for a range since it
+" doesn't handle the range itself has an error in a command
+" dynamically enclosed by :try/:endtry or gets an interrupt or
+" executes a :throw, no more calls for the remaining lines in the
+" range are made. On an error in a command not dynamically enclosed
+" by :try/:endtry, the function is executed again for the remaining
+" lines in the range.
+"-------------------------------------------------------------------------------
+
+func Test_stop_range_on_error()
+ let test =<< trim [CODE]
+ let file = tempname()
+ exec "edit" file
+ call setline(1, ['line 1', 'line 2', 'line 3'])
+ let taken = ""
+ let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)"
+
+ func F(reason, n) abort
+ let g:taken = g:taken .. "F" .. a:n ..
+ \ substitute(a:reason, '\(\l\).*', '\u\1', "") ..
+ \ "(" .. line(".") .. ")"
+
+ if a:reason == "error"
+ asdf
+ elseif a:reason == "interrupt"
+ call interrupt()
+ elseif a:reason == "throw"
+ throw "xyz"
+ elseif a:reason == "aborting error"
+ XloopNEXT
+ call assert_equal(g:taken, g:expected)
+ try
+ bwipeout!
+ call delete(g:file)
+ asdf
+ endtry
+ endif
+ endfunc
+
+ func G(reason, n)
+ let g:taken = g:taken .. "G" .. a:n ..
+ \ substitute(a:reason, '\(\l\).*', '\u\1', "")
+ 1,3call F(a:reason, a:n)
+ endfunc
+
+ Xpath 'a'
+ call G("error", 1)
+ try
+ Xpath 'b'
+ try
+ call G("error", 2)
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ try
+ call G("interrupt", 3)
+ call assert_report('should not get here')
+ finally
+ Xpath 'd'
+ try
+ call G("throw", 4)
+ call assert_report('should not get here')
+ endtry
+ endtry
+ endtry
+ catch /xyz/
+ Xpath 'e'
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ Xpath 'f'
+ call G("aborting error", 5)
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdef', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 67: :throw across :call command {{{1
+"
+" On a call command, an exception might be thrown when evaluating the
+" function name, during evaluation of the arguments, or when the
+" function is being executed. The exception can be caught by the
+" caller.
+"-------------------------------------------------------------------------------
+
+func THROW(x, n)
+ if a:n == 1
+ Xpath 'A'
+ elseif a:n == 2
+ Xpath 'B'
+ elseif a:n == 3
+ Xpath 'C'
+ endif
+ throw a:x
+endfunc
+
+func NAME(x, n)
+ if a:n == 1
+ call assert_report('should not get here')
+ elseif a:n == 2
+ Xpath 'D'
+ elseif a:n == 3
+ Xpath 'E'
+ elseif a:n == 4
+ Xpath 'F'
+ endif
+ return a:x
+endfunc
+
+func ARG(x, n)
+ if a:n == 1
+ call assert_report('should not get here')
+ elseif a:n == 2
+ call assert_report('should not get here')
+ elseif a:n == 3
+ Xpath 'G'
+ elseif a:n == 4
+ Xpath 'I'
+ endif
+ return a:x
+endfunc
+
+func Test_throw_across_call_cmd()
+ XpathINIT
+
+ func F(x, n)
+ if a:n == 2
+ call assert_report('should not get here')
+ elseif a:n == 4
+ Xpath 'a'
+ endif
+ endfunc
+
+ while 1
+ try
+ let v:errmsg = ""
+
+ while 1
+ try
+ Xpath 'b'
+ call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1)
+ call assert_report('should not get here')
+ catch /^name$/
+ Xpath 'c'
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ Xpath 'd'
+ call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2)
+ call assert_report('should not get here')
+ catch /^arg$/
+ Xpath 'e'
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ Xpath 'f'
+ call {NAME("THROW", 3)}(ARG("call", 3), 3)
+ call assert_report('should not get here')
+ catch /^call$/
+ Xpath 'g'
+ catch /^0$/ " default return value
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ Xpath 'h'
+ call {NAME("F", 4)}(ARG(4711, 4), 4)
+ Xpath 'i'
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+
+ catch /^0$/ " default return value
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+
+ call assert_equal('bAcdDBefEGCghFIai', g:Xpath)
+ delfunction F
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 68: :throw across function calls in expressions {{{1
+"
+" On a function call within an expression, an exception might be
+" thrown when evaluating the function name, during evaluation of the
+" arguments, or when the function is being executed. The exception
+" can be caught by the caller.
+"
+" This test reuses the functions THROW(), NAME(), and ARG() from the
+" previous test.
+"-------------------------------------------------------------------------------
+
+func Test_throw_across_call_expr()
+ XpathINIT
+
+ func F(x, n)
+ if a:n == 2
+ call assert_report('should not get here')
+ elseif a:n == 4
+ Xpath 'a'
+ endif
+ return a:x
+ endfunction
+
+ while 1
+ try
+ let error = 0
+ let v:errmsg = ""
+
+ while 1
+ try
+ Xpath 'b'
+ let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1)
+ call assert_report('should not get here')
+ catch /^name$/
+ Xpath 'c'
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+ call assert_true(!exists('var1'))
+
+ while 1
+ try
+ Xpath 'd'
+ let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2)
+ call assert_report('should not get here')
+ catch /^arg$/
+ Xpath 'e'
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+ call assert_true(!exists('var2'))
+
+ while 1
+ try
+ Xpath 'f'
+ let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3)
+ call assert_report('should not get here')
+ catch /^call$/
+ Xpath 'g'
+ catch /^0$/ " default return value
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+ call assert_true(!exists('var3'))
+
+ while 1
+ try
+ Xpath 'h'
+ let var4 = {NAME("F", 4)}(ARG(4711, 4), 4)
+ Xpath 'i'
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ let v:errmsg = ""
+ break
+ endtry
+ endwhile
+ call assert_true(exists('var4') && var4 == 4711)
+
+ catch /^0$/ " default return value
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ call assert_equal("", v:errmsg)
+ break
+ endtry
+ endwhile
+
+ call assert_equal('bAcdDBefEGCghFIai', g:Xpath)
+ delfunc F
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 76: Errors, interrupts, :throw during expression evaluation {{{1
+"
+" When a function call made during expression evaluation is aborted
+" due to an error inside a :try/:endtry region or due to an interrupt
+" or a :throw, the expression evaluation is aborted as well. No
+" message is displayed for the cancelled expression evaluation. On an
+" error not inside :try/:endtry, the expression evaluation continues.
+"-------------------------------------------------------------------------------
+
+func Test_expr_eval_error()
+ let test =<< trim [CODE]
+ let taken = ""
+
+ func ERR(n)
+ let g:taken = g:taken .. "E" .. a:n
+ asdf
+ endfunc
+
+ func ERRabort(n) abort
+ let g:taken = g:taken .. "A" .. a:n
+ asdf
+ endfunc " returns -1; may cause follow-up msg for illegal var/func name
+
+ func WRAP(n, arg)
+ let g:taken = g:taken .. "W" .. a:n
+ let g:saved_errmsg = v:errmsg
+ return arg
+ endfunc
+
+ func INT(n)
+ let g:taken = g:taken .. "I" .. a:n
+ call interrupt()
+ endfunc
+
+ func THR(n)
+ let g:taken = g:taken .. "T" .. a:n
+ throw "should not be caught"
+ endfunc
+
+ func CONT(n)
+ let g:taken = g:taken .. "C" .. a:n
+ endfunc
+
+ func MSG(n)
+ let g:taken = g:taken .. "M" .. a:n
+ let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg
+ let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf"
+ call assert_match(msgptn, errmsg)
+ let v:errmsg = ""
+ let g:saved_errmsg = ""
+ endfunc
+
+ let v:errmsg = ""
+
+ try
+ let t = 1
+ while t <= 9
+ Xloop 'a'
+ try
+ if t == 1
+ let v{ERR(t) + CONT(t)} = 0
+ elseif t == 2
+ let v{ERR(t) + CONT(t)}
+ elseif t == 3
+ let var = exists('v{ERR(t) + CONT(t)}')
+ elseif t == 4
+ unlet v{ERR(t) + CONT(t)}
+ elseif t == 5
+ function F{ERR(t) + CONT(t)}()
+ endfunction
+ elseif t == 6
+ function F{ERR(t) + CONT(t)}
+ elseif t == 7
+ let var = exists('*F{ERR(t) + CONT(t)}')
+ elseif t == 8
+ delfunction F{ERR(t) + CONT(t)}
+ elseif t == 9
+ let var = ERR(t) + CONT(t)
+ endif
+ catch /asdf/
+ " v:errmsg is not set when the error message is converted to an
+ " exception. Set it to the original error message.
+ let v:errmsg = substitute(v:exception, '^Vim:', '', "")
+ catch /^Vim\((\a\+)\)\=:/
+ " An error exception has been thrown after the original error.
+ let v:errmsg = ""
+ finally
+ call MSG(t)
+ let t = t + 1
+ XloopNEXT
+ continue " discard an aborting error
+ endtry
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+
+ try
+ let t = 10
+ while t <= 18
+ Xloop 'b'
+ try
+ if t == 10
+ let v{INT(t) + CONT(t)} = 0
+ elseif t == 11
+ let v{INT(t) + CONT(t)}
+ elseif t == 12
+ let var = exists('v{INT(t) + CONT(t)}')
+ elseif t == 13
+ unlet v{INT(t) + CONT(t)}
+ elseif t == 14
+ function F{INT(t) + CONT(t)}()
+ endfunction
+ elseif t == 15
+ function F{INT(t) + CONT(t)}
+ elseif t == 16
+ let var = exists('*F{INT(t) + CONT(t)}')
+ elseif t == 17
+ delfunction F{INT(t) + CONT(t)}
+ elseif t == 18
+ let var = INT(t) + CONT(t)
+ endif
+ catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/
+ " An error exception has been triggered after the interrupt.
+ let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
+ finally
+ call MSG(t)
+ let t = t + 1
+ XloopNEXT
+ continue " discard interrupt
+ endtry
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+
+ try
+ let t = 19
+ while t <= 27
+ Xloop 'c'
+ try
+ if t == 19
+ let v{THR(t) + CONT(t)} = 0
+ elseif t == 20
+ let v{THR(t) + CONT(t)}
+ elseif t == 21
+ let var = exists('v{THR(t) + CONT(t)}')
+ elseif t == 22
+ unlet v{THR(t) + CONT(t)}
+ elseif t == 23
+ function F{THR(t) + CONT(t)}()
+ endfunction
+ elseif t == 24
+ function F{THR(t) + CONT(t)}
+ elseif t == 25
+ let var = exists('*F{THR(t) + CONT(t)}')
+ elseif t == 26
+ delfunction F{THR(t) + CONT(t)}
+ elseif t == 27
+ let var = THR(t) + CONT(t)
+ endif
+ catch /^Vim\((\a\+)\)\=:/
+ " An error exception has been triggered after the :throw.
+ let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
+ finally
+ call MSG(t)
+ let t = t + 1
+ XloopNEXT
+ continue " discard exception
+ endtry
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+
+ let v{ERR(28) + CONT(28)} = 0
+ call MSG(28)
+ let v{ERR(29) + CONT(29)}
+ call MSG(29)
+ let var = exists('v{ERR(30) + CONT(30)}')
+ call MSG(30)
+ unlet v{ERR(31) + CONT(31)}
+ call MSG(31)
+ function F{ERR(32) + CONT(32)}()
+ endfunction
+ call MSG(32)
+ function F{ERR(33) + CONT(33)}
+ call MSG(33)
+ let var = exists('*F{ERR(34) + CONT(34)}')
+ call MSG(34)
+ delfunction F{ERR(35) + CONT(35)}
+ call MSG(35)
+ let var = ERR(36) + CONT(36)
+ call MSG(36)
+
+ let saved_errmsg = ""
+
+ let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0
+ call MSG(37)
+ let v{WRAP(38, ERRabort(38)) + CONT(38)}
+ call MSG(38)
+ let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}')
+ call MSG(39)
+ unlet v{WRAP(40, ERRabort(40)) + CONT(40)}
+ call MSG(40)
+ function F{WRAP(41, ERRabort(41)) + CONT(41)}()
+ endfunction
+ call MSG(41)
+ function F{WRAP(42, ERRabort(42)) + CONT(42)}
+ call MSG(42)
+ let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}')
+ call MSG(43)
+ delfunction F{WRAP(44, ERRabort(44)) + CONT(44)}
+ call MSG(44)
+ let var = ERRabort(45) + CONT(45)
+ call MSG(45)
+ Xpath 'd'
+
+ let expected = ""
+ \ .. "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9"
+ \ .. "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18"
+ \ .. "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27"
+ \ .. "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33"
+ \ .. "E34C34M34E35C35M35E36C36M36"
+ \ .. "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41"
+ \ .. "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45"
+ call assert_equal(expected, taken)
+ [CODE]
+ let verify =<< trim [CODE]
+ let expected = "a1a2a3a4a5a6a7a8a9"
+ \ .. "b10b11b12b13b14b15b16b17b18"
+ \ .. "c19c20c21c22c23c24c25c26c27d"
+ call assert_equal(expected, g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1
+"
+" When a function call made during evaluation of an expression in
+" braces as part of a function name after ":function" is aborted due
+" to an error inside a :try/:endtry region or due to an interrupt or
+" a :throw, the expression evaluation is aborted as well, and the
+" function definition is ignored, skipping all commands to the
+" ":endfunction". On an error not inside :try/:endtry, the expression
+" evaluation continues and the function gets defined, and can be
+" called and deleted.
+"-------------------------------------------------------------------------------
+func Test_brace_expr_error()
+ let test =<< trim [CODE]
+ func ERR() abort
+ Xloop 'a'
+ asdf
+ endfunc " returns -1
+
+ func OK()
+ Xloop 'b'
+ let v:errmsg = ""
+ return 0
+ endfunc
+
+ let v:errmsg = ""
+
+ Xpath 'c'
+ func F{1 + ERR() + OK()}(arg)
+ " F0 should be defined.
+ if exists("a:arg") && a:arg == "calling"
+ Xpath 'd'
+ else
+ call assert_report('should not get here')
+ endif
+ endfunction
+ call assert_equal("", v:errmsg)
+ XloopNEXT
+
+ Xpath 'e'
+ call F{1 + ERR() + OK()}("calling")
+ call assert_equal("", v:errmsg)
+ XloopNEXT
+
+ Xpath 'f'
+ delfunction F{1 + ERR() + OK()}
+ call assert_equal("", v:errmsg)
+ XloopNEXT
+
+ try
+ while 1
+ try
+ Xpath 'g'
+ func G{1 + ERR() + OK()}(arg)
+ " G0 should not be defined, and the function body should be
+ " skipped.
+ call assert_report('should not get here')
+ " Use an unmatched ":finally" to check whether the body is
+ " skipped when an error occurs in ERR(). This works whether or
+ " not the exception is converted to an exception.
+ finally
+ call assert_report('should not get here')
+ endtry
+ try
+ call assert_report('should not get here')
+ endfunction
+
+ call assert_report('should not get here')
+ catch /asdf/
+ " Jumped to when the function is not defined and the body is
+ " skipped.
+ Xpath 'h'
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'i'
+ break
+ endtry " jumped to when the body is not skipped
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('ca1b1ea2b2dfa3b3ga4hi', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 78: Messages on parsing errors in expression evaluation {{{1
+"
+" When an expression evaluation detects a parsing error, an error
+" message is given and converted to an exception, and the expression
+" evaluation is aborted.
+"-------------------------------------------------------------------------------
+func Test_expr_eval_error_msg()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ let taken = ""
+
+ func F(n)
+ let g:taken = g:taken . "F" . a:n
+ endfunc
+
+ func MSG(n, enr, emsg)
+ let g:taken = g:taken . "M" . a:n
+ call assert_match('^' .. a:enr .. ':', v:errmsg)
+ call assert_match(a:emsg, v:errmsg)
+ endfunc
+
+ func CONT(n)
+ let g:taken = g:taken . "C" . a:n
+ endfunc
+
+ let v:errmsg = ""
+ try
+ let t = 1
+ while t <= 14
+ let g:taken = g:taken . "T" . t
+ let v:errmsg = ""
+ try
+ if t == 1
+ let v{novar + CONT(t)} = 0
+ elseif t == 2
+ let v{novar + CONT(t)}
+ elseif t == 3
+ let var = exists('v{novar + CONT(t)}')
+ elseif t == 4
+ unlet v{novar + CONT(t)}
+ elseif t == 5
+ function F{novar + CONT(t)}()
+ endfunction
+ elseif t == 6
+ function F{novar + CONT(t)}
+ elseif t == 7
+ let var = exists('*F{novar + CONT(t)}')
+ elseif t == 8
+ delfunction F{novar + CONT(t)}
+ elseif t == 9
+ echo novar + CONT(t)
+ elseif t == 10
+ echo v{novar + CONT(t)}
+ elseif t == 11
+ echo F{novar + CONT(t)}
+ elseif t == 12
+ let var = novar + CONT(t)
+ elseif t == 13
+ let var = v{novar + CONT(t)}
+ elseif t == 14
+ let var = F{novar + CONT(t)}()
+ endif
+ catch /^Vim\((\a\+)\)\=:/
+ Xloop 'a'
+ " v:errmsg is not set when the error message is converted to an
+ " exception. Set it to the original error message.
+ let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
+ finally
+ Xloop 'b'
+ if t <= 8 && t != 3 && t != 7
+ call MSG(t, 'E475', 'Invalid argument\>')
+ else
+ call MSG(t, 'E121', "Undefined variable")
+ endif
+ let t = t + 1
+ XloopNEXT
+ continue " discard an aborting error
+ endtry
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+
+ func T(n, expr, enr, emsg)
+ try
+ let g:taken = g:taken . "T" . a:n
+ let v:errmsg = ""
+ try
+ execute "let var = " . a:expr
+ catch /^Vim\((\a\+)\)\=:/
+ Xloop 'c'
+ " v:errmsg is not set when the error message is converted to an
+ " exception. Set it to the original error message.
+ let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "")
+ finally
+ Xloop 'd'
+ call MSG(a:n, a:enr, a:emsg)
+ XloopNEXT
+ " Discard an aborting error:
+ return
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ endfunc
+
+ call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function")
+ call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments")
+ call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments")
+ call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments")
+ call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'")
+ call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'")
+ call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression")
+ call T(22, '1 2 + CONT(22)', 'E488', "Trailing characters: 2 +")
+ call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'")
+ call T(24, '("abc) + CONT(24)', 'E114', "Missing double quote")
+ call T(25, "('abc) + CONT(25)", 'E115', "Missing single quote")
+ call T(26, '& + CONT(26)', 'E112', "Option name missing")
+ call T(27, '&asdf + CONT(27)', 'E113', "Unknown option")
+
+ let expected = ""
+ \ .. "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14"
+ \ .. "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25"
+ \ .. "T26M26T27M27"
+
+ call assert_equal(expected, taken)
+ [CODE]
+ let verify =<< trim [CODE]
+ let expected = "a1b1a2b2a3b3a4b4a5b5a6b6a7b7a8b8a9b9a10b10a11b11a12b12"
+ \ .. "a13b13a14b14c15d15c16d16c17d17c18d18c19d19c20d20"
+ \ .. "c21d21c22d22c23d23c24d24c25d25c26d26c27d27"
+ call assert_equal(expected, g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 79: Throwing one of several errors for the same command {{{1
+"
+" When several errors appear in a row (for instance during expression
+" evaluation), the first as the most specific one is used when
+" throwing an error exception. If, however, a syntax error is
+" detected afterwards, this one is used for the error exception.
+" On a syntax error, the next command is not executed, on a normal
+" error, however, it is (relevant only in a function without the
+" "abort" flag). v:errmsg is not set.
+"
+" If throwing error exceptions is configured off, v:errmsg is always
+" set to the latest error message, that is, to the more general
+" message or the syntax error, respectively.
+"-------------------------------------------------------------------------------
+func Test_throw_multi_error()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ func NEXT(cmd)
+ exec a:cmd . " | Xloop 'a'"
+ endfun
+
+ call NEXT('echo novar') " (checks nextcmd)
+ XloopNEXT
+ call NEXT('let novar #') " (skips nextcmd)
+ XloopNEXT
+ call NEXT('unlet novar #') " (skips nextcmd)
+ XloopNEXT
+ call NEXT('let {novar}') " (skips nextcmd)
+ XloopNEXT
+ call NEXT('unlet{ novar}') " (skips nextcmd)
+
+ call assert_equal('a1', g:Xpath)
+ XpathINIT
+ XloopINIT
+
+ func EXEC(cmd)
+ exec a:cmd
+ endfunc
+
+ try
+ while 1 " dummy loop
+ try
+ let v:errmsg = ""
+ call EXEC('echo novar') " normal error
+ catch /^Vim\((\a\+)\)\=:/
+ Xpath 'b'
+ call assert_match('E121: Undefined variable: novar', v:exception)
+ finally
+ Xpath 'c'
+ call assert_equal("", v:errmsg)
+ break
+ endtry
+ endwhile
+
+ Xpath 'd'
+ let cmd = "let"
+ while cmd != ""
+ try
+ let v:errmsg = ""
+ call EXEC(cmd . ' novar #') " normal plus syntax error
+ catch /^Vim\((\a\+)\)\=:/
+ Xloop 'e'
+ if cmd =~ 'unlet'
+ " TODO: should get error for 'novar'
+ call assert_match('E488: Trailing characters', v:exception)
+ else
+ call assert_match('E121: Undefined variable: novar', v:exception)
+ endif
+ finally
+ Xloop 'f'
+ call assert_equal("", v:errmsg)
+ if cmd == "let"
+ let cmd = "unlet"
+ else
+ let cmd = ""
+ endif
+ XloopNEXT
+ continue
+ endtry
+ endwhile
+
+ Xpath 'g'
+ let cmd = "let"
+ while cmd != ""
+ try
+ let v:errmsg = ""
+ call EXEC(cmd . ' {novar}') " normal plus syntax error
+ catch /^Vim\((\a\+)\)\=:/
+ Xloop 'h'
+ call assert_match('E475: Invalid argument: {novar}', v:exception)
+ finally
+ Xloop 'i'
+ call assert_equal("", v:errmsg)
+ if cmd == "let"
+ let cmd = "unlet"
+ else
+ let cmd = ""
+ endif
+ XloopNEXT
+ continue
+ endtry
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ Xpath 'j'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('bcde1f1e2f2gh3i3h4i4j', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 80: Syntax error in expression for illegal :elseif {{{1
+"
+" If there is a syntax error in the expression after an illegal
+" :elseif, an error message is given (or an error exception thrown)
+" for the illegal :elseif rather than the expression error.
+"-------------------------------------------------------------------------------
+func Test_if_syntax_error()
+ CheckEnglish
+
+ let test =<< trim [CODE]
+ let v:errmsg = ""
+ if 0
+ else
+ elseif 1 ||| 2
+ endif
+ Xpath 'a'
+ call assert_match('E584: :elseif after :else', v:errmsg)
+
+ let v:errmsg = ""
+ if 1
+ else
+ elseif 1 ||| 2
+ endif
+ Xpath 'b'
+ call assert_match('E584: :elseif after :else', v:errmsg)
+
+ let v:errmsg = ""
+ elseif 1 ||| 2
+ Xpath 'c'
+ call assert_match('E582: :elseif without :if', v:errmsg)
+
+ let v:errmsg = ""
+ while 1
+ elseif 1 ||| 2
+ endwhile
+ Xpath 'd'
+ call assert_match('E582: :elseif without :if', v:errmsg)
+
+ while 1
+ try
+ try
+ let v:errmsg = ""
+ if 0
+ else
+ elseif 1 ||| 2
+ endif
+ catch /^Vim\((\a\+)\)\=:/
+ Xpath 'e'
+ call assert_match('E584: :elseif after :else', v:exception)
+ finally
+ Xpath 'f'
+ call assert_equal("", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'g'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ try
+ let v:errmsg = ""
+ if 1
+ else
+ elseif 1 ||| 2
+ endif
+ catch /^Vim\((\a\+)\)\=:/
+ Xpath 'h'
+ call assert_match('E584: :elseif after :else', v:exception)
+ finally
+ Xpath 'i'
+ call assert_equal("", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'j'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ try
+ let v:errmsg = ""
+ elseif 1 ||| 2
+ catch /^Vim\((\a\+)\)\=:/
+ Xpath 'k'
+ call assert_match('E582: :elseif without :if', v:exception)
+ finally
+ Xpath 'l'
+ call assert_equal("", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'm'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ try
+ let v:errmsg = ""
+ while 1
+ elseif 1 ||| 2
+ endwhile
+ catch /^Vim\((\a\+)\)\=:/
+ Xpath 'n'
+ call assert_match('E582: :elseif without :if', v:exception)
+ finally
+ Xpath 'o'
+ call assert_equal("", v:errmsg)
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'p'
+ break
+ endtry
+ endwhile
+ Xpath 'q'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefghijklmnopq', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 81: Discarding exceptions after an error or interrupt {{{1
+"
+" When an exception is thrown from inside a :try conditional without
+" :catch and :finally clauses and an error or interrupt occurs before
+" the :endtry is reached, the exception is discarded.
+"-------------------------------------------------------------------------------
+
+func Test_discard_exception_after_error_1()
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ try
+ Xpath 'b'
+ throw "arrgh"
+ call assert_report('should not get here')
+ if 1
+ call assert_report('should not get here')
+ " error after :throw: missing :endif
+ endtry
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('ab', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+" interrupt the code before the endtry is invoked
+func Test_discard_exception_after_error_2()
+ XpathINIT
+ let lines =<< trim [CODE]
+ try
+ Xpath 'a'
+ try
+ Xpath 'b'
+ throw "arrgh"
+ call assert_report('should not get here')
+ endtry " interrupt here
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ call writefile(lines, 'Xscript', 'D')
+
+ breakadd file 7 Xscript
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":source Xscript\<CR>quit\<CR>", "xt")
+ catch /^Vim:Interrupt$/
+ call assert_match('Xscript, line 7', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('ab', g:Xpath)
+ breakdel *
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 82: Ignoring :catch clauses after an error or interrupt {{{1
+"
+" When an exception is thrown and an error or interrupt occurs before
+" the matching :catch clause is reached, the exception is discarded
+" and the :catch clause is ignored (also for the error or interrupt
+" exception being thrown then).
+"-------------------------------------------------------------------------------
+
+func Test_ignore_catch_after_error_1()
+ let test =<< trim [CODE]
+ try
+ try
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')
+ if 1
+ call assert_report('should not get here')
+ " error after :throw: missing :endif
+ catch /.*/
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+func Test_ignore_catch_after_error_2()
+ let test =<< trim [CODE]
+ func E()
+ try
+ try
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')
+ if 1
+ call assert_report('should not get here')
+ " error after :throw: missing :endif
+ catch /.*/
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ endfunc
+
+ call E()
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('a', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+" interrupt right before a catch is invoked in a script
+func Test_ignore_catch_after_intr_1()
+ " for unknown reasons this test sometimes fails on MS-Windows.
+ let g:test_is_flaky = 1
+
+ XpathINIT
+ let lines =<< trim [CODE]
+ try
+ try
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')
+ catch /.*/ " interrupt here
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ call writefile(lines, 'Xscript', 'D')
+
+ breakadd file 6 Xscript
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":source Xscript\<CR>quit\<CR>", "xt")
+ catch /^Vim:Interrupt$/
+ call assert_match('Xscript, line 6', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+endfunc
+
+" interrupt right before a catch is invoked inside a function.
+func Test_ignore_catch_after_intr_2()
+ " for unknown reasons this test sometimes fails on MS-Windows.
+ let g:test_is_flaky = 1
+
+ XpathINIT
+ func F()
+ try
+ try
+ Xpath 'a'
+ throw "arrgh"
+ call assert_report('should not get here')
+ catch /.*/ " interrupt here
+ call assert_report('should not get here')
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ endfunc
+
+ breakadd func 6 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 6', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 83: Executing :finally clauses after an error or interrupt {{{1
+"
+" When an exception is thrown and an error or interrupt occurs before
+" the :finally of the innermost :try is reached, the exception is
+" discarded and the :finally clause is executed.
+"-------------------------------------------------------------------------------
+
+func Test_finally_after_error()
+ let test =<< trim [CODE]
+ try
+ Xpath 'a'
+ try
+ Xpath 'b'
+ throw "arrgh"
+ call assert_report('should not get here')
+ if 1
+ call assert_report('should not get here')
+ " error after :throw: missing :endif
+ finally
+ Xpath 'c'
+ endtry
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abc', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+" interrupt the code right before the finally is invoked
+func Test_finally_after_intr()
+ XpathINIT
+ let lines =<< trim [CODE]
+ try
+ Xpath 'a'
+ try
+ Xpath 'b'
+ throw "arrgh"
+ call assert_report('should not get here')
+ finally " interrupt here
+ Xpath 'c'
+ endtry
+ call assert_report('should not get here')
+ catch /arrgh/
+ call assert_report('should not get here')
+ endtry
+ call assert_report('should not get here')
+ [CODE]
+ call writefile(lines, 'Xscript', 'D')
+
+ breakadd file 7 Xscript
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":source Xscript\<CR>quit\<CR>", "xt")
+ catch /^Vim:Interrupt$/
+ call assert_match('Xscript, line 7', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('abc', g:Xpath)
+ breakdel *
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 84: Exceptions in autocommand sequences. {{{1
+"
+" When an exception occurs in a sequence of autocommands for
+" a specific event, the rest of the sequence is not executed. The
+" command that triggered the autocommand execution aborts, and the
+" exception is propagated to the caller.
+"
+" For the FuncUndefined event under a function call expression or
+" :call command, the function is not executed, even when it has
+" been defined by the autocommands before the exception occurred.
+"-------------------------------------------------------------------------------
+
+func Test_autocmd_exception()
+ let test =<< trim [CODE]
+ func INT()
+ call interrupt()
+ endfunc
+
+ aug TMP
+ autocmd!
+
+ autocmd User x1 Xpath 'a'
+ autocmd User x1 throw "x1"
+ autocmd User x1 call assert_report('should not get here')
+
+ autocmd User x2 Xpath 'b'
+ autocmd User x2 asdf
+ autocmd User x2 call assert_report('should not get here')
+
+ autocmd User x3 Xpath 'c'
+ autocmd User x3 call INT()
+ autocmd User x3 call assert_report('should not get here')
+
+ autocmd FuncUndefined U1 func U1()
+ autocmd FuncUndefined U1 call assert_report('should not get here')
+ autocmd FuncUndefined U1 endfunc
+ autocmd FuncUndefined U1 Xpath 'd'
+ autocmd FuncUndefined U1 throw "U1"
+ autocmd FuncUndefined U1 call assert_report('should not get here')
+
+ autocmd FuncUndefined U2 func U2()
+ autocmd FuncUndefined U2 call assert_report('should not get here')
+ autocmd FuncUndefined U2 endfunc
+ autocmd FuncUndefined U2 Xpath 'e'
+ autocmd FuncUndefined U2 ASDF
+ autocmd FuncUndefined U2 call assert_report('should not get here')
+
+ autocmd FuncUndefined U3 func U3()
+ autocmd FuncUndefined U3 call assert_report('should not get here')
+ autocmd FuncUndefined U3 endfunc
+ autocmd FuncUndefined U3 Xpath 'f'
+ autocmd FuncUndefined U3 call INT()
+ autocmd FuncUndefined U3 call assert_report('should not get here')
+ aug END
+
+ try
+ try
+ Xpath 'g'
+ doautocmd User x1
+ catch /x1/
+ Xpath 'h'
+ endtry
+
+ while 1
+ try
+ Xpath 'i'
+ doautocmd User x2
+ catch /asdf/
+ Xpath 'j'
+ finally
+ Xpath 'k'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ Xpath 'l'
+ doautocmd User x3
+ catch /Vim:Interrupt/
+ Xpath 'm'
+ finally
+ Xpath 'n'
+ " ... but break loop for caught interrupt exception,
+ " or discard interrupt and break loop if $VIMNOINTTHROW
+ break
+ endtry
+ endwhile
+
+ if exists("*U1") | delfunction U1 | endif
+ if exists("*U2") | delfunction U2 | endif
+ if exists("*U3") | delfunction U3 | endif
+
+ try
+ Xpath 'o'
+ call U1()
+ catch /U1/
+ Xpath 'p'
+ endtry
+
+ while 1
+ try
+ Xpath 'q'
+ call U2()
+ catch /ASDF/
+ Xpath 'r'
+ finally
+ Xpath 's'
+ " ... but break loop for caught error exception,
+ " or discard error and break loop if $VIMNOERRTHROW
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ Xpath 't'
+ call U3()
+ catch /Vim:Interrupt/
+ Xpath 'u'
+ finally
+ Xpath 'v'
+ " ... but break loop for caught interrupt exception,
+ " or discard interrupt and break loop if $VIMNOINTTHROW
+ break
+ endtry
+ endwhile
+ catch /.*/
+ call assert_report('should not get here')
+ endtry
+ Xpath 'w'
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('gahibjklcmnodpqerstfuvw', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 85: Error exceptions in autocommands for I/O command events {{{1
+"
+" When an I/O command is inside :try/:endtry, autocommands to be
+" executed after it should be skipped on an error (exception) in the
+" command itself or in autocommands to be executed before the command.
+" In the latter case, the I/O command should not be executed either.
+" Example 1: BufWritePre, :write, BufWritePost
+" Example 2: FileReadPre, :read, FileReadPost.
+"-------------------------------------------------------------------------------
+
+func Test_autocmd_error_io_exception()
+ let test =<< trim [CODE]
+ " Remove the autocommands for the events specified as arguments in all used
+ " autogroups.
+ func Delete_autocommands(...)
+ let augfile = tempname()
+ while 1
+ try
+ exec "redir >" . augfile
+ aug
+ redir END
+ exec "edit" augfile
+ g/^$/d
+ norm G$
+ let wrap = "w"
+ while search('\%( \|^\)\@<=.\{-}\%( \)\@=', wrap) > 0
+ let wrap = "W"
+ exec "norm y/ \n"
+ let argno = 1
+ while argno <= a:0
+ exec "au!" escape(@", " ") a:{argno}
+ let argno = argno + 1
+ endwhile
+ endwhile
+ catch /.*/
+ finally
+ bwipeout!
+ call delete(augfile)
+ break
+ endtry
+ endwhile
+ endfunc
+
+ call Delete_autocommands("BufWritePre", "BufWritePost")
+
+ while 1
+ try
+ try
+ let post = 0
+ aug TMP
+ au! BufWritePost * let post = 1
+ aug END
+ write /n/o/n/e/x/i/s/t/e/n/t
+ catch /^Vim(write):/
+ Xpath 'a'
+ call assert_match("E212: Can't open file for writing", v:exception)
+ finally
+ Xpath 'b'
+ call assert_equal(0, post)
+ au! TMP
+ aug! TMP
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'c'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ try
+ let post = 0
+ aug TMP
+ au! BufWritePre * asdf
+ au! BufWritePost * let post = 1
+ aug END
+ let tmpfile = tempname()
+ exec "write" tmpfile
+ catch /^Vim\((write)\)\=:/
+ Xpath 'd'
+ call assert_match('E492: Not an editor command', v:exception)
+ finally
+ Xpath 'e'
+ if filereadable(tmpfile)
+ call assert_report('should not get here')
+ endif
+ call assert_equal(0, post)
+ au! TMP
+ aug! TMP
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'f'
+ break
+ endtry
+ endwhile
+
+ call delete(tmpfile)
+
+ call Delete_autocommands("BufWritePre", "BufWritePost",
+ \ "BufReadPre", "BufReadPost", "FileReadPre", "FileReadPost")
+
+ while 1
+ try
+ try
+ let post = 0
+ aug TMP
+ au! FileReadPost * let post = 1
+ aug END
+ let caught = 0
+ read /n/o/n/e/x/i/s/t/e/n/t
+ catch /^Vim(read):/
+ Xpath 'g'
+ call assert_match("E484: Can't open file", v:exception)
+ finally
+ Xpath 'h'
+ call assert_equal(0, post)
+ au! TMP
+ aug! TMP
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'i'
+ break
+ endtry
+ endwhile
+
+ while 1
+ try
+ let infile = tempname()
+ let tmpfile = tempname()
+ call writefile(["XYZ"], infile)
+ exec "edit" tmpfile
+ try
+ Xpath 'j'
+ try
+ let post = 0
+ aug TMP
+ au! FileReadPre * asdf
+ au! FileReadPost * let post = 1
+ aug END
+ exec "0read" infile
+ catch /^Vim\((read)\)\=:/
+ Xpath 'k'
+ call assert_match('E492: Not an editor command', v:exception)
+ finally
+ Xpath 'l'
+ if getline("1") == "XYZ"
+ call assert_report('should not get here')
+ endif
+ call assert_equal(0, post)
+ au! TMP
+ aug! TMP
+ endtry
+ finally
+ Xpath 'm'
+ bwipeout!
+ endtry
+ catch /.*/
+ call assert_report('should not get here')
+ finally
+ Xpath 'n'
+ break
+ endtry
+ endwhile
+
+ call delete(infile)
+ call delete(tmpfile)
+ [CODE]
+ let verify =<< trim [CODE]
+ call assert_equal('abcdefghijklmn', g:Xpath)
+ [CODE]
+ call RunInNewVim(test, verify)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 87 using (expr) ? funcref : funcref {{{1
+"
+" Vim needs to correctly parse the funcref and even when it does
+" not execute the funcref, it needs to consume the trailing ()
+"-------------------------------------------------------------------------------
+
+func Add2(x1, x2)
+ return a:x1 + a:x2
+endfu
+
+func GetStr()
+ return "abcdefghijklmnopqrstuvwxyp"
+endfu
+
+func Test_funcref_with_condexpr()
+ call assert_equal(5, function('Add2')(2,3))
+
+ call assert_equal(3, 1 ? function('Add2')(1,2) : function('Add2')(2,3))
+ call assert_equal(5, 0 ? function('Add2')(1,2) : function('Add2')(2,3))
+ " Make sure, GetStr() still works.
+ call assert_equal('abcdefghijk', GetStr()[0:10])
+endfunc
+
+" Test 90: Recognizing {} in variable name. {{{1
+"-------------------------------------------------------------------------------
+
+func Test_curlies()
+ let s:var = 66
+ let ns = 's'
+ call assert_equal(66, {ns}:var)
+
+ let g:a = {}
+ let g:b = 't'
+ let g:a[g:b] = 77
+ call assert_equal(77, g:a['t'])
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 91: using type(). {{{1
+"-------------------------------------------------------------------------------
+
+func Test_type()
+ call assert_equal(0, type(0))
+ call assert_equal(1, type(""))
+ call assert_equal(2, type(function("tr")))
+ call assert_equal(2, type(function("tr", [8])))
+ call assert_equal(3, type([]))
+ call assert_equal(4, type({}))
+ call assert_equal(5, type(0.0))
+ call assert_equal(6, type(v:false))
+ call assert_equal(6, type(v:true))
+ call assert_equal(7, type(v:none))
+ call assert_equal(7, type(v:null))
+ call assert_equal(8, v:t_job)
+ call assert_equal(9, v:t_channel)
+ call assert_equal(v:t_number, type(0))
+ call assert_equal(v:t_string, type(""))
+ call assert_equal(v:t_func, type(function("tr")))
+ call assert_equal(v:t_func, type(function("tr", [8])))
+ call assert_equal(v:t_list, type([]))
+ call assert_equal(v:t_dict, type({}))
+ call assert_equal(v:t_float, type(0.0))
+ call assert_equal(v:t_bool, type(v:false))
+ call assert_equal(v:t_bool, type(v:true))
+ call assert_equal(v:t_none, type(v:none))
+ call assert_equal(v:t_none, type(v:null))
+ call assert_equal(v:t_string, type(test_null_string()))
+ call assert_equal(v:t_func, type(test_null_function()))
+ call assert_equal(v:t_func, type(test_null_partial()))
+ call assert_equal(v:t_list, type(test_null_list()))
+ call assert_equal(v:t_dict, type(test_null_dict()))
+ if has('job')
+ call assert_equal(v:t_job, type(test_null_job()))
+ endif
+ if has('channel')
+ call assert_equal(v:t_channel, type(test_null_channel()))
+ endif
+ call assert_equal(v:t_blob, type(test_null_blob()))
+
+ call assert_fails("call type(test_void())", 'E685:')
+ call assert_fails("call type(test_unknown())", 'E685:')
+
+ call assert_equal(0, 0 + v:false)
+ call assert_equal(1, 0 + v:true)
+ call assert_equal(0, 0 + v:none)
+ call assert_equal(0, 0 + v:null)
+
+ call assert_equal('v:false', '' . v:false)
+ call assert_equal('v:true', '' . v:true)
+ call assert_equal('v:none', '' . v:none)
+ call assert_equal('v:null', '' . v:null)
+
+ call assert_true(v:false == 0)
+ call assert_false(v:false != 0)
+ call assert_true(v:true == 1)
+ call assert_false(v:true != 1)
+ call assert_false(v:true == v:false)
+ call assert_true(v:true != v:false)
+
+ call assert_true(v:null == 0)
+ call assert_false(v:null == 1)
+ call assert_false(v:null != 0)
+ call assert_true(v:none == 0)
+ call assert_false(v:none == 1)
+ call assert_false(v:none != 0)
+ call assert_true(v:null == 0.0)
+ call assert_false(v:null == 0.1)
+ call assert_false(v:null != 0.0)
+
+ call assert_true(v:false is v:false)
+ call assert_true(v:true is v:true)
+ call assert_true(v:none is v:none)
+ call assert_true(v:null is v:null)
+
+ call assert_false(v:false isnot v:false)
+ call assert_false(v:true isnot v:true)
+ call assert_false(v:none isnot v:none)
+ call assert_false(v:null isnot v:null)
+
+ call assert_false(v:false is 0)
+ call assert_false(v:true is 1)
+ call assert_false(v:true is v:false)
+ call assert_false(v:none is 0)
+ call assert_false(v:none is [])
+ call assert_false(v:none is {})
+ call assert_false(v:none is 'text')
+ call assert_false(v:null is 0)
+ call assert_false(v:null is v:none)
+
+ call assert_true(v:false isnot 0)
+ call assert_true(v:true isnot 1)
+ call assert_true(v:true isnot v:false)
+ call assert_true(v:none isnot 0)
+ call assert_true(v:null isnot 0)
+ call assert_true(v:null isnot v:none)
+
+ call assert_equal(v:false, eval(string(v:false)))
+ call assert_equal(v:true, eval(string(v:true)))
+ call assert_equal(v:none, eval(string(v:none)))
+ call assert_equal(v:null, eval(string(v:null)))
+
+ call assert_equal(v:false, copy(v:false))
+ call assert_equal(v:true, copy(v:true))
+ call assert_equal(v:none, copy(v:none))
+ call assert_equal(v:null, copy(v:null))
+
+ call assert_equal([v:false], deepcopy([v:false]))
+ call assert_equal([v:true], deepcopy([v:true]))
+ call assert_equal([v:none], deepcopy([v:none]))
+ call assert_equal([v:null], deepcopy([v:null]))
+
+ call assert_true(empty(v:false))
+ call assert_false(empty(v:true))
+ call assert_true(empty(v:null))
+ call assert_true(empty(v:none))
+
+ func ChangeYourMind()
+ try
+ return v:true
+ finally
+ return 'something else'
+ endtry
+ endfunc
+
+ call ChangeYourMind()
+endfunc
+
+func Test_typename()
+ call assert_equal('number', typename(123))
+ call assert_equal('string', typename('x'))
+ call assert_equal('list<number>', typename([123]))
+ call assert_equal('dict<number>', typename(#{key: 123}))
+ call assert_equal('list<dict<number>>', typename([#{key: 123}]))
+
+ let l = []
+ let d = #{a: 0}
+ let l = [d]
+ let l[0].e = #{b: l}
+ call assert_equal('list<dict<any>>', typename(l))
+ call assert_equal('dict<any>', typename(d))
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 92: skipping code {{{1
+"-------------------------------------------------------------------------------
+
+func Test_skip()
+ let Fn = function('Test_type')
+ call assert_false(0 && Fn[1])
+ call assert_false(0 && string(Fn))
+ call assert_false(0 && len(Fn))
+ let l = []
+ call assert_false(0 && l[1])
+ call assert_false(0 && string(l))
+ call assert_false(0 && len(l))
+ let f = 1.0
+ call assert_false(0 && f[1])
+ call assert_false(0 && string(f))
+ call assert_false(0 && len(f))
+ let sp = v:null
+ call assert_false(0 && sp[1])
+ call assert_false(0 && string(sp))
+ call assert_false(0 && len(sp))
+
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 93: :echo and string() {{{1
+"-------------------------------------------------------------------------------
+
+func Test_echo_and_string()
+ " String
+ let a = 'foo bar'
+ redir => result
+ echo a
+ echo string(a)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["foo bar",
+ \ "'foo bar'"], l)
+
+ " Float
+ let a = -1.2e0
+ redir => result
+ echo a
+ echo string(a)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["-1.2",
+ \ "-1.2"], l)
+
+ " Funcref
+ redir => result
+ echo function('string')
+ echo string(function('string'))
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["string",
+ \ "function('string')"], l)
+
+ " Recursive dictionary
+ let a = {}
+ let a["a"] = a
+ redir => result
+ echo a
+ echo string(a)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["{'a': {...}}",
+ \ "{'a': {...}}"], l)
+
+ " Recursive list
+ let a = [0]
+ let a[0] = a
+ redir => result
+ echo a
+ echo string(a)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["[[...]]",
+ \ "[[...]]"], l)
+
+ " Empty dictionaries in a list
+ let a = {}
+ redir => result
+ echo [a, a, a]
+ echo string([a, a, a])
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["[{}, {}, {}]",
+ \ "[{}, {}, {}]"], l)
+
+ " Empty dictionaries in a dictionary
+ let a = {}
+ let b = {"a": a, "b": a}
+ redir => result
+ echo b
+ echo string(b)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["{'a': {}, 'b': {}}",
+ \ "{'a': {}, 'b': {}}"], l)
+
+ " Empty lists in a list
+ let a = []
+ redir => result
+ echo [a, a, a]
+ echo string([a, a, a])
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["[[], [], []]",
+ \ "[[], [], []]"], l)
+
+ " Empty lists in a dictionary
+ let a = []
+ let b = {"a": a, "b": a}
+ redir => result
+ echo b
+ echo string(b)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["{'a': [], 'b': []}",
+ \ "{'a': [], 'b': []}"], l)
+
+ " Dictionaries in a list
+ let a = {"one": "yes", "two": "yes", "three": "yes"}
+ redir => result
+ echo [a, a, a]
+ echo string([a, a, a])
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["[{'one': 'yes', 'two': 'yes', 'three': 'yes'}, {...}, {...}]",
+ \ "[{'one': 'yes', 'two': 'yes', 'three': 'yes'}, {'one': 'yes', 'two': 'yes', 'three': 'yes'}, {'one': 'yes', 'two': 'yes', 'three': 'yes'}]"], l)
+
+ " Dictionaries in a dictionary
+ let a = {"one": "yes", "two": "yes", "three": "yes"}
+ let b = {"a": a, "b": a}
+ redir => result
+ echo b
+ echo string(b)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["{'a': {'one': 'yes', 'two': 'yes', 'three': 'yes'}, 'b': {...}}",
+ \ "{'a': {'one': 'yes', 'two': 'yes', 'three': 'yes'}, 'b': {'one': 'yes', 'two': 'yes', 'three': 'yes'}}"], l)
+
+ " Lists in a list
+ let a = [1, 2, 3]
+ redir => result
+ echo [a, a, a]
+ echo string([a, a, a])
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["[[1, 2, 3], [...], [...]]",
+ \ "[[1, 2, 3], [1, 2, 3], [1, 2, 3]]"], l)
+
+ " Lists in a dictionary
+ let a = [1, 2, 3]
+ let b = {"a": a, "b": a}
+ redir => result
+ echo b
+ echo string(b)
+ redir END
+ let l = split(result, "\n")
+ call assert_equal(["{'a': [1, 2, 3], 'b': [...]}",
+ \ "{'a': [1, 2, 3], 'b': [1, 2, 3]}"], l)
+
+ call assert_fails('echo &:', 'E112:')
+ call assert_fails('echo &g:', 'E112:')
+ call assert_fails('echo &l:', 'E112:')
+
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 94: 64-bit Numbers {{{1
+"-------------------------------------------------------------------------------
+
+func Test_num64()
+ call assert_notequal( 4294967296, 0)
+ call assert_notequal(-4294967296, 0)
+ call assert_equal( 4294967296, 0xFFFFffff + 1)
+ call assert_equal(-4294967296, -0xFFFFffff - 1)
+
+ call assert_equal( 9223372036854775807, 1 / 0)
+ call assert_equal(-9223372036854775807, -1 / 0)
+ call assert_equal(-9223372036854775807 - 1, 0 / 0)
+
+ call assert_equal( 0x7FFFffffFFFFffff, float2nr( 1.0e150))
+ call assert_equal(-0x7FFFffffFFFFffff, float2nr(-1.0e150))
+
+ let rng = range(0xFFFFffff, 0x100000001)
+ call assert_equal([0xFFFFffff, 0x100000000, 0x100000001], rng)
+ call assert_equal(0x100000001, max(rng))
+ call assert_equal(0xFFFFffff, min(rng))
+ call assert_equal(rng, sort(range(0x100000001, 0xFFFFffff, -1), 'N'))
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 95: lines of :append, :change, :insert {{{1
+"-------------------------------------------------------------------------------
+
+func DefineFunction(name, body)
+ let func = join(['function! ' . a:name . '()'] + a:body + ['endfunction'], "\n")
+ exec func
+endfunc
+
+func Test_script_lines()
+ " :append
+ try
+ call DefineFunction('T_Append', [
+ \ 'append',
+ \ 'py <<EOS',
+ \ '.',
+ \ ])
+ catch
+ call assert_report("Can't define function")
+ endtry
+ try
+ call DefineFunction('T_Append', [
+ \ 'append',
+ \ 'abc',
+ \ ])
+ call assert_report("Shouldn't be able to define function")
+ catch
+ call assert_exception('Vim(function):E1145: Missing heredoc end marker: .')
+ endtry
+
+ " :change
+ try
+ call DefineFunction('T_Change', [
+ \ 'change',
+ \ 'py <<EOS',
+ \ '.',
+ \ ])
+ catch
+ call assert_report("Can't define function")
+ endtry
+ try
+ call DefineFunction('T_Change', [
+ \ 'change',
+ \ 'abc',
+ \ ])
+ call assert_report("Shouldn't be able to define function")
+ catch
+ call assert_exception('Vim(function):E1145: Missing heredoc end marker: .')
+ endtry
+
+ " :insert
+ try
+ call DefineFunction('T_Insert', [
+ \ 'insert',
+ \ 'py <<EOS',
+ \ '.',
+ \ ])
+ catch
+ call assert_report("Can't define function")
+ endtry
+ try
+ call DefineFunction('T_Insert', [
+ \ 'insert',
+ \ 'abc',
+ \ ])
+ call assert_report("Shouldn't be able to define function")
+ catch
+ call assert_exception('Vim(function):E1145: Missing heredoc end marker: .')
+ endtry
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 96: line continuation {{{1
+"
+" Undefined behavior was detected by ubsan with line continuation
+" after an empty line.
+"-------------------------------------------------------------------------------
+func Test_script_empty_line_continuation()
+
+ \
+endfunc
+
+"-------------------------------------------------------------------------------
+" Test 97: bitwise functions {{{1
+"-------------------------------------------------------------------------------
+func Test_bitwise_functions()
+ " and
+ call assert_equal(127, and(127, 127))
+ call assert_equal(16, and(127, 16))
+ eval 127->and(16)->assert_equal(16)
+ call assert_equal(0, and(127, 128))
+ call assert_fails("call and([], 1)", 'E745:')
+ call assert_fails("call and({}, 1)", 'E728:')
+ call assert_fails("call and(1.0, 1)", 'E805:')
+ call assert_fails("call and(1, 1.0)", 'E805:')
+ call assert_fails("call and(1, [])", 'E745:')
+ call assert_fails("call and(1, {})", 'E728:')
+ " or
+ call assert_equal(23, or(16, 7))
+ call assert_equal(15, or(8, 7))
+ eval 8->or(7)->assert_equal(15)
+ call assert_equal(123, or(0, 123))
+ call assert_fails("call or([], 1)", 'E745:')
+ call assert_fails("call or({}, 1)", 'E728:')
+ call assert_fails("call or(1.0, 1)", 'E805:')
+ call assert_fails("call or(1, 1.0)", 'E805:')
+ call assert_fails("call or(1, [])", 'E745:')
+ call assert_fails("call or(1, {})", 'E728:')
+ " xor
+ call assert_equal(0, xor(127, 127))
+ call assert_equal(111, xor(127, 16))
+ eval 127->xor(16)->assert_equal(111)
+ call assert_equal(255, xor(127, 128))
+ call assert_fails("call xor(1.0, 1)", 'E805:')
+ call assert_fails("call xor(1, 1.0)", 'E805:')
+ call assert_fails("call xor([], 1)", 'E745:')
+ call assert_fails("call xor({}, 1)", 'E728:')
+ call assert_fails("call xor(1, [])", 'E745:')
+ call assert_fails("call xor(1, {})", 'E728:')
+ " invert
+ call assert_equal(65408, and(invert(127), 65535))
+ eval 127->invert()->and(65535)->assert_equal(65408)
+ call assert_equal(65519, and(invert(16), 65535))
+ call assert_equal(65407, and(invert(128), 65535))
+ call assert_fails("call invert(1.0)", 'E805:')
+ call assert_fails("call invert([])", 'E745:')
+ call assert_fails("call invert({})", 'E728:')
+endfunc
+
+" Test using bang after user command {{{1
+func Test_user_command_with_bang()
+ command -bang Nieuw let nieuw = 1
+ Ni!
+ call assert_equal(1, nieuw)
+ unlet nieuw
+ delcommand Nieuw
+endfunc
+
+func Test_script_expand_sfile()
+ let lines =<< trim END
+ func s:snr()
+ return expand('<sfile>')
+ endfunc
+ let g:result = s:snr()
+ END
+ call writefile(lines, 'Xexpand', 'D')
+ source Xexpand
+ call assert_match('<SNR>\d\+_snr', g:result)
+ source Xexpand
+ call assert_match('<SNR>\d\+_snr', g:result)
+
+ unlet g:result
+endfunc
+
+func Test_compound_assignment_operators()
+ " Test for number
+ let x = 1
+ let x += 10
+ call assert_equal(11, x)
+ let x -= 5
+ call assert_equal(6, x)
+ let x *= 4
+ call assert_equal(24, x)
+ let x /= 3
+ call assert_equal(8, x)
+ let x %= 3
+ call assert_equal(2, x)
+ let x .= 'n'
+ call assert_equal('2n', x)
+
+ " Test special cases: division or modulus with 0.
+ let x = 1
+ let x /= 0
+ call assert_equal(0x7FFFFFFFFFFFFFFF, x)
+
+ let x = -1
+ let x /= 0
+ call assert_equal(-0x7FFFFFFFFFFFFFFF, x)
+
+ let x = 0
+ let x /= 0
+ call assert_equal(-0x7FFFFFFFFFFFFFFF - 1, x)
+
+ let x = 1
+ let x %= 0
+ call assert_equal(0, x)
+
+ let x = -1
+ let x %= 0
+ call assert_equal(0, x)
+
+ let x = 0
+ let x %= 0
+ call assert_equal(0, x)
+
+ " Test for string
+ let x = 'str'
+ let x .= 'ing'
+ call assert_equal('string', x)
+ let x += 1
+ call assert_equal(1, x)
+
+ " Test for float
+ let x -= 1.5
+ call assert_equal(-0.5, x)
+ let x = 0.5
+ let x += 4.5
+ call assert_equal(5.0, x)
+ let x -= 1.5
+ call assert_equal(3.5, x)
+ let x *= 3.0
+ call assert_equal(10.5, x)
+ let x /= 2.5
+ call assert_equal(4.2, x)
+ call assert_fails('let x %= 0.5', 'E734:')
+ call assert_fails('let x .= "f"', 'E734:')
+ let x = !3.14
+ call assert_equal(0.0, x)
+
+ " integer and float operations
+ let x = 1
+ let x *= 2.1
+ call assert_equal(2.1, x)
+ let x = 1
+ let x /= 0.25
+ call assert_equal(4.0, x)
+ let x = 1
+ call assert_fails('let x %= 0.25', 'E734:')
+ let x = 1
+ call assert_fails('let x .= 0.25', 'E734:')
+ let x = 1.0
+ call assert_fails('let x += [1.1]', 'E734:')
+
+ " Test for environment variable
+ let $FOO = 1
+ call assert_fails('let $FOO += 1', 'E734:')
+ call assert_fails('let $FOO -= 1', 'E734:')
+ call assert_fails('let $FOO *= 1', 'E734:')
+ call assert_fails('let $FOO /= 1', 'E734:')
+ call assert_fails('let $FOO %= 1', 'E734:')
+ let $FOO .= 's'
+ call assert_equal('1s', $FOO)
+ unlet $FOO
+
+ " Test for option variable (type: number)
+ let &scrolljump = 1
+ let &scrolljump += 5
+ call assert_equal(6, &scrolljump)
+ let &scrolljump -= 2
+ call assert_equal(4, &scrolljump)
+ let &scrolljump *= 3
+ call assert_equal(12, &scrolljump)
+ let &scrolljump /= 2
+ call assert_equal(6, &scrolljump)
+ let &scrolljump %= 5
+ call assert_equal(1, &scrolljump)
+ call assert_fails('let &scrolljump .= "j"', 'E734:')
+ set scrolljump&vim
+
+ let &foldlevelstart = 2
+ let &foldlevelstart -= 1
+ call assert_equal(1, &foldlevelstart)
+ let &foldlevelstart -= 1
+ call assert_equal(0, &foldlevelstart)
+ let &foldlevelstart = 2
+ let &foldlevelstart -= 2
+ call assert_equal(0, &foldlevelstart)
+
+ " Test for register
+ let @/ = 1
+ call assert_fails('let @/ += 1', 'E734:')
+ call assert_fails('let @/ -= 1', 'E734:')
+ call assert_fails('let @/ *= 1', 'E734:')
+ call assert_fails('let @/ /= 1', 'E734:')
+ call assert_fails('let @/ %= 1', 'E734:')
+ let @/ .= 's'
+ call assert_equal('1s', @/)
+ let @/ = ''
+endfunc
+
+func Test_unlet_env()
+ let $TESTVAR = 'yes'
+ call assert_equal('yes', $TESTVAR)
+ call assert_fails('lockvar $TESTVAR', 'E940:')
+ call assert_fails('unlockvar $TESTVAR', 'E940:')
+ call assert_equal('yes', $TESTVAR)
+ if 0
+ unlet $TESTVAR
+ endif
+ call assert_equal('yes', $TESTVAR)
+ unlet $TESTVAR
+ call assert_equal('', $TESTVAR)
+endfunc
+
+func Test_refcount()
+ " Immediate values
+ call assert_equal(-1, test_refcount(1))
+ call assert_equal(-1, test_refcount('s'))
+ call assert_equal(-1, test_refcount(v:true))
+ call assert_equal(0, test_refcount([]))
+ call assert_equal(0, test_refcount({}))
+ call assert_equal(0, test_refcount(0zff))
+ call assert_equal(0, test_refcount({-> line('.')}))
+ call assert_equal(-1, test_refcount(0.1))
+ if has('job')
+ call assert_equal(0, test_refcount(job_start([&shell, &shellcmdflag, 'echo .'])))
+ endif
+
+ " No refcount types
+ let x = 1
+ call assert_equal(-1, test_refcount(x))
+ let x = 's'
+ call assert_equal(-1, test_refcount(x))
+ let x = v:true
+ call assert_equal(-1, test_refcount(x))
+ let x = 0.1
+ call assert_equal(-1, test_refcount(x))
+
+ " Check refcount
+ let x = []
+ call assert_equal(1, test_refcount(x))
+
+ let x = {}
+ call assert_equal(1, x->test_refcount())
+
+ let x = 0zff
+ call assert_equal(1, test_refcount(x))
+
+ let X = {-> line('.')}
+ call assert_equal(1, test_refcount(X))
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+
+ if has('job')
+ let job = job_start([&shell, &shellcmdflag, 'echo .'])
+ call assert_equal(1, test_refcount(job))
+ call assert_equal(1, test_refcount(job_getchannel(job)))
+ call assert_equal(1, test_refcount(job))
+ endif
+
+ " Function arguments, copying and unassigning
+ func ExprCheck(x, i)
+ let i = a:i + 1
+ call assert_equal(i, test_refcount(a:x))
+ let Y = a:x
+ call assert_equal(i + 1, test_refcount(a:x))
+ call assert_equal(test_refcount(a:x), test_refcount(Y))
+ let Y = 0
+ call assert_equal(i, test_refcount(a:x))
+ endfunc
+ call ExprCheck([], 0)
+ call ExprCheck({}, 0)
+ call ExprCheck(0zff, 0)
+ call ExprCheck({-> line('.')}, 0)
+ if has('job')
+ call ExprCheck(job, 1)
+ call ExprCheck(job_getchannel(job), 1)
+ call job_stop(job)
+ endif
+ delfunc ExprCheck
+
+ " Regarding function
+ func Func(x) abort
+ call assert_equal(2, test_refcount(function('Func')))
+ call assert_equal(0, test_refcount(funcref('Func')))
+ endfunc
+ call assert_equal(1, test_refcount(function('Func')))
+ call assert_equal(0, test_refcount(function('Func', [1])))
+ call assert_equal(0, test_refcount(funcref('Func')))
+ call assert_equal(0, test_refcount(funcref('Func', [1])))
+ let X = function('Func')
+ let Y = X
+ call assert_equal(1, test_refcount(X))
+ let X = function('Func', [1])
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+ let X = funcref('Func')
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+ let X = funcref('Func', [1])
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+ unlet X
+ unlet Y
+ call Func(1)
+ delfunc Func
+
+ " Function with dict
+ func DictFunc() dict
+ call assert_equal(3, test_refcount(self))
+ endfunc
+ let d = {'Func': function('DictFunc')}
+ call assert_equal(1, test_refcount(d))
+ call assert_equal(0, test_refcount(d.Func))
+ call d.Func()
+ unlet d
+ delfunc DictFunc
+
+ if has('channel')
+ call assert_equal(-1, test_refcount(test_null_job()))
+ call assert_equal(-1, test_refcount(test_null_channel()))
+ endif
+ call assert_equal(-1, test_refcount(test_null_function()))
+ call assert_equal(-1, test_refcount(test_null_partial()))
+ call assert_equal(-1, test_refcount(test_null_blob()))
+ call assert_equal(-1, test_refcount(test_null_list()))
+ call assert_equal(-1, test_refcount(test_null_dict()))
+endfunc
+
+" Test for missing :endif, :endfor, :endwhile and :endtry {{{1
+func Test_missing_end()
+ call writefile(['if 2 > 1', 'echo ">"'], 'Xscript', 'D')
+ call assert_fails('source Xscript', 'E171:')
+ call writefile(['for i in range(5)', 'echo i'], 'Xscript')
+ call assert_fails('source Xscript', 'E170:')
+ call writefile(['while v:true', 'echo "."'], 'Xscript')
+ call assert_fails('source Xscript', 'E170:')
+ call writefile(['try', 'echo "."'], 'Xscript')
+ call assert_fails('source Xscript', 'E600:')
+
+ " Using endfor with :while
+ let caught_e732 = 0
+ try
+ while v:true
+ endfor
+ catch /E732:/
+ let caught_e732 = 1
+ endtry
+ call assert_equal(1, caught_e732)
+
+ " Using endwhile with :for
+ let caught_e733 = 0
+ try
+ for i in range(1)
+ endwhile
+ catch /E733:/
+ let caught_e733 = 1
+ endtry
+ call assert_equal(1, caught_e733)
+
+ " Using endfunc with :if
+ call assert_fails('exe "if 1 | endfunc | endif"', 'E193:')
+
+ " Missing 'in' in a :for statement
+ call assert_fails('for i range(1) | endfor', 'E690:')
+
+ " Incorrect number of variables in for
+ call assert_fails('for [i,] in range(3) | endfor', 'E475:')
+endfunc
+
+" Test for deep nesting of if/for/while/try statements {{{1
+func Test_deep_nest()
+ CheckRunVimInTerminal
+
+ let lines =<< trim [SCRIPT]
+ " Deep nesting of if ... endif
+ func Test1()
+ let @a = join(repeat(['if v:true'], 51), "\n")
+ let @a ..= "\n"
+ let @a ..= join(repeat(['endif'], 51), "\n")
+ @a
+ let @a = ''
+ endfunc
+
+ " Deep nesting of for ... endfor
+ func Test2()
+ let @a = join(repeat(['for i in [1]'], 51), "\n")
+ let @a ..= "\n"
+ let @a ..= join(repeat(['endfor'], 51), "\n")
+ @a
+ let @a = ''
+ endfunc
+
+ " Deep nesting of while ... endwhile
+ func Test3()
+ let @a = join(repeat(['while v:true'], 51), "\n")
+ let @a ..= "\n"
+ let @a ..= join(repeat(['endwhile'], 51), "\n")
+ @a
+ let @a = ''
+ endfunc
+
+ " Deep nesting of try ... endtry
+ func Test4()
+ let @a = join(repeat(['try'], 51), "\n")
+ let @a ..= "\necho v:true\n"
+ let @a ..= join(repeat(['endtry'], 51), "\n")
+ @a
+ let @a = ''
+ endfunc
+
+ " Deep nesting of function ... endfunction
+ func Test5()
+ let @a = join(repeat(['function X()'], 51), "\n")
+ let @a ..= "\necho v:true\n"
+ let @a ..= join(repeat(['endfunction'], 51), "\n")
+ @a
+ let @a = ''
+ endfunc
+ [SCRIPT]
+ call writefile(lines, 'Xscript', 'D')
+
+ let buf = RunVimInTerminal('-S Xscript', {'rows': 6})
+
+ " Deep nesting of if ... endif
+ call term_sendkeys(buf, ":call Test1()\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))})
+
+ " Deep nesting of for ... endfor
+ call term_sendkeys(buf, ":call Test2()\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))})
+
+ " Deep nesting of while ... endwhile
+ call term_sendkeys(buf, ":call Test3()\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))})
+
+ " Deep nesting of try ... endtry
+ call term_sendkeys(buf, ":call Test4()\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))})
+
+ " Deep nesting of function ... endfunction
+ call term_sendkeys(buf, ":call Test5()\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^E1058:', term_getline(buf, 4))})
+ call term_sendkeys(buf, "\<C-C>\n")
+ call TermWait(buf)
+
+ "let l = ''
+ "for i in range(1, 6)
+ " let l ..= term_getline(buf, i) . "\n"
+ "endfor
+ "call assert_report(l)
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for errors in converting to float from various types {{{1
+func Test_float_conversion_errors()
+ call assert_fails('let x = 4.0 % 2.0', 'E804:')
+ call assert_fails('echo 1.1[0]', 'E806:')
+ call assert_fails('echo sort([function("min"), 1], "f")', 'E891:')
+ call assert_fails('echo 3.2 == "vim"', 'E892:')
+ call assert_fails('echo sort([[], 1], "f")', 'E893:')
+ call assert_fails('echo sort([{}, 1], "f")', 'E894:')
+ call assert_fails('echo 3.2 == v:true', 'E362:')
+ call assert_fails('echo 3.2 == v:none', 'E907:')
+endfunc
+
+" invalid function names {{{1
+func Test_invalid_function_names()
+ " function name not starting with capital
+ let caught_e128 = 0
+ try
+ func! g:test()
+ echo "test"
+ endfunc
+ catch /E128:/
+ let caught_e128 = 1
+ endtry
+ call assert_equal(1, caught_e128)
+
+ " function name includes a colon
+ let caught_e884 = 0
+ try
+ func! b:test()
+ echo "test"
+ endfunc
+ catch /E884:/
+ let caught_e884 = 1
+ endtry
+ call assert_equal(1, caught_e884)
+
+ " function name followed by #
+ let caught_e128 = 0
+ try
+ func! test2() "#
+ echo "test2"
+ endfunc
+ catch /E128:/
+ let caught_e128 = 1
+ endtry
+ call assert_equal(1, caught_e128)
+
+ " function name starting with/without "g:", buffer-local funcref.
+ function! g:Foo(n)
+ return 'called Foo(' . a:n . ')'
+ endfunction
+ let b:my_func = function('Foo')
+ call assert_equal('called Foo(1)', b:my_func(1))
+ call assert_equal('called Foo(2)', g:Foo(2))
+ call assert_equal('called Foo(3)', Foo(3))
+ delfunc g:Foo
+
+ " script-local function used in Funcref must exist.
+ let lines =<< trim END
+ func s:Testje()
+ return "foo"
+ endfunc
+ let Bar = function('s:Testje')
+ call assert_equal(0, exists('s:Testje'))
+ call assert_equal(1, exists('*s:Testje'))
+ call assert_equal(1, exists('Bar'))
+ call assert_equal(1, exists('*Bar'))
+ END
+ call writefile(lines, 'Xscript', 'D')
+ source Xscript
+endfunc
+
+" substring and variable name {{{1
+func Test_substring_var()
+ let str = 'abcdef'
+ let n = 3
+ call assert_equal('def', str[n:])
+ call assert_equal('abcd', str[:n])
+ call assert_equal('d', str[n:n])
+ unlet n
+ let nn = 3
+ call assert_equal('def', str[nn:])
+ call assert_equal('abcd', str[:nn])
+ call assert_equal('d', str[nn:nn])
+ unlet nn
+ let b:nn = 4
+ call assert_equal('ef', str[b:nn:])
+ call assert_equal('abcde', str[:b:nn])
+ call assert_equal('e', str[b:nn:b:nn])
+ unlet b:nn
+endfunc
+
+" Test using s: with a typed command {{{1
+func Test_typed_script_var()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', {'rows': 6})
+
+ " Deep nesting of if ... endif
+ call term_sendkeys(buf, ":echo get(s:, 'foo', 'x')\n")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_match('^E116:', term_getline(buf, 5))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for issue6776 {{{1
+func Test_ternary_expression()
+ try
+ call eval('0 ? 0')
+ catch
+ endtry
+ " previous failure should not cause next expression to fail
+ call assert_equal(v:false, eval(string(v:false)))
+
+ try
+ call eval('0 ? "burp')
+ catch
+ endtry
+ " previous failure should not cause next expression to fail
+ call assert_equal(v:false, eval(string(v:false)))
+
+ try
+ call eval('1 ? 0 : "burp')
+ catch
+ endtry
+ " previous failure should not cause next expression to fail
+ call assert_equal(v:false, eval(string(v:false)))
+endfunction
+
+func Test_for_over_string()
+ let res = ''
+ for c in 'aéc̀d'
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('a-é-c̀-d-', res)
+
+ let res = ''
+ for c in ''
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+
+ let res = ''
+ for c in test_null_string()
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+endfunc
+
+" Test for deeply nested :source command {{{1
+func Test_deeply_nested_source()
+ let lines =<< trim END
+
+ so
+ sil 0scr
+ delete
+ so
+ 0
+ END
+ call writefile(["vim9 silent! @0 \n/"] + lines, 'Xnested.vim', 'D')
+
+ " this must not crash
+ let cmd = GetVimCommand() .. " -e -s -S Xnested.vim -c qa!"
+ call system(cmd)
+endfunc
+
+"-------------------------------------------------------------------------------
+" Modelines {{{1
+" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
+"-------------------------------------------------------------------------------
diff --git a/src/testdir/test_virtualedit.vim b/src/testdir/test_virtualedit.vim
new file mode 100644
index 0000000..edaae67
--- /dev/null
+++ b/src/testdir/test_virtualedit.vim
@@ -0,0 +1,612 @@
+" Tests for 'virtualedit'.
+
+func Test_yank_move_change()
+ new
+ call setline(1, [
+ \ "func foo() error {",
+ \ "\tif n, err := bar();",
+ \ "\terr != nil {",
+ \ "\t\treturn err",
+ \ "\t}",
+ \ "\tn = n * n",
+ \ ])
+ set virtualedit=all
+ set ts=4
+ function! MoveSelectionDown(count) abort
+ normal! m`
+ silent! exe "'<,'>move'>+".a:count
+ norm! ``
+ endfunction
+
+ xmap ]e :<C-U>call MoveSelectionDown(v:count1)<CR>
+ 2
+ normal 2gg
+ normal J
+ normal jVj
+ normal ]e
+ normal ce
+ bwipe!
+ set virtualedit=
+ set ts=8
+endfunc
+
+func Test_paste_end_of_line()
+ new
+ set virtualedit=all
+ call setline(1, ['456', '123'])
+ normal! gg0"ay$
+ exe "normal! 2G$lllA\<C-O>:normal! \"agP\r"
+ call assert_equal('123456', getline(2))
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+func Test_replace_end_of_line()
+ new
+ set virtualedit=all
+ call setline(1, range(20))
+ exe "normal! gg2jv10lr-"
+ call assert_equal(["1", "-----------", "3"], getline(2,4))
+ call setline(1, range(20))
+ exe "normal! gg2jv10lr\<c-k>hh"
+ call assert_equal(["1", "───────────", "3"], getline(2,4))
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+func Test_edit_CTRL_G()
+ new
+ set virtualedit=insert
+ call setline(1, ['123', '1', '12'])
+ exe "normal! ggA\<c-g>jx\<c-g>jx"
+ call assert_equal(['123', '1 x', '12 x'], getline(1,'$'))
+
+ set virtualedit=all
+ %d_
+ call setline(1, ['1', '12'])
+ exe "normal! ggllix\<c-g>jx"
+ call assert_equal(['1 x', '12x'], getline(1,'$'))
+
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+func Test_edit_change()
+ new
+ set virtualedit=all
+ call setline(1, "\t⒌")
+ normal Cx
+ call assert_equal('x', getline(1))
+ " Do a visual block change
+ call setline(1, ['a', 'b', 'c'])
+ exe "normal gg3l\<C-V>2jcx"
+ call assert_equal(['a x', 'b x', 'c x'], getline(1, '$'))
+ bwipe!
+ set virtualedit=
+endfunc
+
+func Test_edit_special_char()
+ new
+ se ve=all
+ norm a0
+ sil! exe "norm o00000\<Nul>k<a0s"
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Tests for pasting at the beginning, end and middle of a tab character
+" in virtual edit mode.
+func Test_paste_in_tab()
+ new
+ call append(0, '')
+ set virtualedit=all
+
+ " Tests for pasting a register with characterwise mode type
+ call setreg('"', 'xyz', 'c')
+
+ " paste (p) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal p
+ call assert_equal('a xyz b', getline(1))
+
+ " paste (P) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal P
+ call assert_equal("axyz\tb", getline(1))
+
+ " paste (p) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal p
+ call assert_equal("a\txyzb", getline(1))
+
+ " paste (P) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal P
+ call assert_equal('a xyz b', getline(1))
+
+ " Tests for pasting a register with blockwise mode type
+ call setreg('"', 'xyz', 'b')
+
+ " paste (p) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal p
+ call assert_equal('a xyz b', getline(1))
+
+ " paste (P) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal P
+ call assert_equal("axyz\tb", getline(1))
+
+ " paste (p) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal p
+ call assert_equal("a\txyzb", getline(1))
+
+ " paste (P) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal P
+ call assert_equal('a xyz b', getline(1))
+
+ " Tests for pasting with gp and gP in virtual edit mode
+
+ " paste (gp) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal gp
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 12, 0, 12], getcurpos())
+
+ " paste (gP) unnamed register at the beginning of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 0)
+ normal gP
+ call assert_equal("axyz\tb", getline(1))
+ call assert_equal([0, 1, 5, 0, 5], getcurpos())
+
+ " paste (gp) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal gp
+ call assert_equal("a\txyzb", getline(1))
+ call assert_equal([0, 1, 6, 0, 12], getcurpos())
+
+ " paste (gP) unnamed register at the end of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 6)
+ normal gP
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 12, 0, 12], getcurpos())
+
+ " Tests for pasting a named register
+ let @r = 'xyz'
+
+ " paste (gp) named register in the middle of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 2)
+ normal "rgp
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 8, 0, 8], getcurpos())
+
+ " paste (gP) named register in the middle of a tab
+ call setline(1, "a\tb")
+ call cursor(1, 2, 2)
+ normal "rgP
+ call assert_equal('a xyz b', getline(1))
+ call assert_equal([0, 1, 7, 0, 7], getcurpos())
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yanking a few spaces within a tab to a register
+func Test_yank_in_tab()
+ new
+ let @r = ''
+ call setline(1, "a\tb")
+ set virtualedit=all
+ call cursor(1, 2, 2)
+ normal "ry5l
+ call assert_equal(' ', @r)
+
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
+" Repeating CTRL-N fixes it. (Mary Ellen Foster)
+func Test_ve_completion()
+ new
+ set completeopt&vim
+ set virtualedit=all
+ exe "normal ikeyword keyw\<Esc>C\<C-N>"
+ call assert_equal('keyword keyword', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Using "C" then then <CR> moves the last remaining character to the next
+" line. (Mary Ellen Foster)
+func Test_ve_del_to_eol()
+ new
+ set virtualedit=all
+ call append(0, 'all your base are belong to us')
+ call search('are', 'w')
+ exe "normal C\<CR>are belong to vim"
+ call assert_equal(['all your base ', 'are belong to vim'], getline(1, 2))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" When past the end of a line that ends in a single character "b" skips
+" that word.
+func Test_ve_b_past_eol()
+ new
+ set virtualedit=all
+ call append(0, '1 2 3 4 5 6')
+ normal gg^$15lbC7
+ call assert_equal('1 2 3 4 5 7', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Make sure 'i', 'C', 'a', 'A' and 'D' works
+func Test_ve_ins_del()
+ new
+ set virtualedit=all
+ call append(0, ["'i'", "'C'", "'a'", "'A'", "'D'"])
+ call cursor(1, 1)
+ normal $4lix
+ call assert_equal("'i' x", getline(1))
+ call cursor(2, 1)
+ normal $4lCx
+ call assert_equal("'C' x", getline(2))
+ call cursor(3, 1)
+ normal $4lax
+ call assert_equal("'a' x", getline(3))
+ call cursor(4, 1)
+ normal $4lAx
+ call assert_equal("'A'x", getline(4))
+ call cursor(5, 1)
+ normal $4lDix
+ call assert_equal("'D' x", getline(5))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yank bug reported by Mark Waggoner.
+func Test_yank_block()
+ new
+ set virtualedit=block
+ call append(0, repeat(['this is a test'], 3))
+ exe "normal gg^2w\<C-V>3jy"
+ call assert_equal("a\na\na\n ", @")
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" beyond the end of the line
+func Test_replace_after_eol()
+ new
+ set virtualedit=all
+ call append(0, '"r"')
+ normal gg$5lrxa
+ call assert_equal('"r" x', getline(1))
+ " visual block replace
+ %d _
+ call setline(1, ['a', '', 'b'])
+ exe "normal 2l\<C-V>2jrx"
+ call assert_equal(['a x', ' x', 'b x'], getline(1, '$'))
+ " visual characterwise selection replace after eol
+ %d _
+ call setline(1, 'a')
+ normal 4lv2lrx
+ call assert_equal('a xxx', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" on a tab
+" Note that for this test, 'ts' must be 8 (the default).
+func Test_replace_on_tab()
+ new
+ set virtualedit=all
+ call append(0, "'r'\t")
+ normal gg^5lrxAy
+ call assert_equal("'r' x y", getline(1))
+ call setline(1, 'aaaaaaaaaaaa')
+ exe "normal! gg2lgR\<Tab>"
+ call assert_equal("aa\taaaa", getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test to make sure 'x' can delete control characters
+func Test_ve_del_ctrl_chars()
+ new
+ set virtualedit=all
+ call append(0, "a\<C-V>b\<CR>sd")
+ set display=uhex
+ normal gg^xxxxxxi[text]
+ set display=
+ call assert_equal('[text]', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for ^Y/^E due to bad w_virtcol value, reported by
+" Roy <royl@netropolis.net>.
+func Test_ins_copy_char()
+ new
+ set virtualedit=all
+ call append(0, 'abcv8efi.him2kl')
+ exe "normal gg^O\<Esc>3li\<C-E>\<Esc>4li\<C-E>\<Esc>4li\<C-E> <--"
+ exe "normal j^o\<Esc>4li\<C-Y>\<Esc>4li\<C-Y>\<Esc>4li\<C-Y> <--"
+ call assert_equal(' v i m <--', getline(1))
+ call assert_equal(' 8 . 2 <--', getline(3))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yanking and pasting using the small delete register
+func Test_yank_paste_small_del_reg()
+ new
+ set virtualedit=all
+ call append(0, "foo, bar")
+ normal ggdewve"-p
+ call assert_equal(', foo', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for delete that breaks a tab into spaces
+func Test_delete_break_tab()
+ new
+ call setline(1, "one\ttwo")
+ set virtualedit=all
+ normal v3ld
+ call assert_equal(' two', getline(1))
+ set virtualedit&
+ close!
+endfunc
+
+" Test for using <BS>, <C-W> and <C-U> in virtual edit mode
+" to erase character, word and line.
+func Test_ve_backspace()
+ new
+ call setline(1, 'sample')
+ set virtualedit=all
+ set backspace=indent,eol,start
+ exe "normal 15|i\<BS>\<BS>"
+ call assert_equal([0, 1, 7, 5], getpos('.'))
+ exe "normal 15|i\<C-W>"
+ call assert_equal([0, 1, 6, 0], getpos('.'))
+ exe "normal 15|i\<C-U>"
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ set backspace&
+ set virtualedit&
+ close!
+endfunc
+
+" Test for delete (x) on EOL character and after EOL
+func Test_delete_past_eol()
+ new
+ call setline(1, "ab")
+ set virtualedit=all
+ exe "normal 2lx"
+ call assert_equal('ab', getline(1))
+ exe "normal 10lx"
+ call assert_equal('ab', getline(1))
+ set virtualedit&
+ bw!
+endfunc
+
+" After calling s:TryVirtualeditReplace(), line 1 will contain one of these
+" two strings, depending on whether virtual editing is on or off.
+let s:result_ve_on = 'a x'
+let s:result_ve_off = 'x'
+
+" Utility function for Test_global_local_virtualedit()
+func s:TryVirtualeditReplace()
+ call setline(1, 'a')
+ normal gg7l
+ normal rx
+endfunc
+
+" Test for :set and :setlocal
+func Test_global_local_virtualedit()
+ new
+
+ " Verify that 'virtualedit' is initialized to empty, can be set globally to
+ " all and to empty, and can be set locally to all and to empty.
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ set ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ set ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ " Verify that :set affects multiple windows.
+ split
+ set ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ set ve=
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+
+ " Verify that :setlocal affects only the current window.
+ new
+ split
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ wincmd p
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ " Verify that the buffer 'virtualedit' state follows the global value only
+ " when empty and that "none" works as expected.
+ "
+ " 'virtualedit' State
+ " +--------+--------------------------+
+ " | Local | Global |
+ " | | |
+ " +--------+--------+--------+--------+
+ " | | "" | "all" | "none" |
+ " +--------+--------+--------+--------+
+ " | "" | off | on | off |
+ " | "all" | on | on | on |
+ " | "none" | off | off | off |
+ " +--------+--------+--------+--------+
+ new
+
+ setglobal ve=
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ setglobal ve=all
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=NONE
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ setglobal ve=none
+ setlocal ve=
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=none
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+
+ bwipe!
+
+ " Verify that the 'virtualedit' state is copied to new windows.
+ new
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ split
+ setlocal ve=all
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ split
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_on, getline(1))
+ setlocal ve=
+ split
+ call s:TryVirtualeditReplace()
+ call assert_equal(s:result_ve_off, getline(1))
+ bwipe!
+
+ setlocal virtualedit&
+ set virtualedit&
+endfunc
+
+func Test_virtualedit_setlocal()
+ enew
+ setglobal virtualedit=all
+ setlocal virtualedit=all
+ normal! l
+ redraw
+ setlocal virtualedit=none
+ call assert_equal(1, wincol())
+
+ setlocal virtualedit&
+ set virtualedit&
+endfunc
+
+func Test_virtualedit_mouse()
+ let save_mouse = &mouse
+ set mouse=a
+ set virtualedit=all
+ new
+
+ call setline(1, ["text\tword"])
+ redraw
+ call test_setmouse(1, 4)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 4, 0, 4], getcurpos())
+ call test_setmouse(1, 5)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 5, 0, 5], getcurpos())
+ call test_setmouse(1, 6)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 5, 1, 6], getcurpos())
+ call test_setmouse(1, 7)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 5, 2, 7], getcurpos())
+ call test_setmouse(1, 8)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 5, 3, 8], getcurpos())
+ call test_setmouse(1, 9)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 6, 0, 9], getcurpos())
+ call test_setmouse(1, 15)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 2, 15], getcurpos())
+
+ bwipe!
+ let &mouse = save_mouse
+ set virtualedit&
+endfunc
+
+" this was replacing the NUL at the end of the line
+func Test_virtualedit_replace_after_tab()
+ new
+ s/\v/ 0
+ set ve=all
+ let @" = ''
+ sil! norm vPvr0
+
+ call assert_equal("\t0", getline(1))
+ set ve&
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_visual.vim b/src/testdir/test_visual.vim
new file mode 100644
index 0000000..f152e7b
--- /dev/null
+++ b/src/testdir/test_visual.vim
@@ -0,0 +1,1558 @@
+" Tests for various Visual modes.
+
+source shared.vim
+source check.vim
+source screendump.vim
+
+func Test_block_shift_multibyte()
+ " Uses double-wide character.
+ split
+ call setline(1, ['xヹxxx', 'ヹxxx'])
+ exe "normal 1G0l\<C-V>jl>"
+ call assert_equal('x ヹxxx', getline(1))
+ call assert_equal(' ヹxxx', getline(2))
+ q!
+endfunc
+
+func Test_block_shift_overflow()
+ " This used to cause a multiplication overflow followed by a crash.
+ new
+ normal ii
+ exe "normal \<C-V>876543210>"
+ q!
+endfunc
+
+func Test_dotregister_paste()
+ new
+ exe "norm! ihello world\<esc>"
+ norm! 0ve".p
+ call assert_equal('hello world world', getline(1))
+ q!
+endfunc
+
+func Test_Visual_ctrl_o()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ call cursor(1,2)
+ set noshowmode
+ set tw=0
+ call feedkeys("\<c-v>jjlIa\<c-\>\<c-o>:set tw=88\<cr>\<esc>", 'tx')
+ call assert_equal(['oane', 'tawo', 'tahree'], getline(1, 3))
+ call assert_equal(88, &tw)
+ set tw&
+ bw!
+endfu
+
+func Test_Visual_vapo()
+ new
+ normal oxx
+ normal vapo
+ bwipe!
+endfunc
+
+func Test_Visual_inner_quote()
+ new
+ normal oxX
+ normal vki'
+ bwipe!
+endfunc
+
+" Test for Visual mode not being reset causing E315 error.
+func TriggerTheProblem()
+ " At this point there is no visual selection because :call reset it.
+ " Let's restore the selection:
+ normal gv
+ '<,'>del _
+ try
+ exe "normal \<Esc>"
+ catch /^Vim\%((\a\+)\)\=:E315/
+ echom 'Snap! E315 error!'
+ let g:msg = 'Snap! E315 error!'
+ endtry
+endfunc
+
+func Test_visual_mode_reset()
+ enew
+ let g:msg = "Everything's fine."
+ enew
+ setl buftype=nofile
+ call append(line('$'), 'Delete this line.')
+
+ " NOTE: this has to be done by a call to a function because executing :del
+ " the ex-way will require the colon operator which resets the visual mode
+ " thus preventing the problem:
+ exe "normal! GV:call TriggerTheProblem()\<CR>"
+ call assert_equal("Everything's fine.", g:msg)
+endfunc
+
+" Test for visual block shift and tab characters.
+func Test_block_shift_tab()
+ new
+ call append(0, repeat(['one two three'], 5))
+ call cursor(1,1)
+ exe "normal i\<C-G>u"
+ exe "normal fe\<C-V>4jR\<Esc>ugvr1"
+ call assert_equal('on1 two three', getline(1))
+ call assert_equal('on1 two three', getline(2))
+ call assert_equal('on1 two three', getline(5))
+
+ %d _
+ call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5))
+ call cursor(1,1)
+ exe "normal \<C-V>4jI \<Esc>j<<11|D"
+ exe "normal j7|a\<Tab>\<Tab>"
+ exe "normal j7|a\<Tab>\<Tab> "
+ exe "normal j7|a\<Tab> \<Tab>\<Esc>4k13|\<C-V>4j<"
+ call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1))
+ call assert_equal('abcdefghij', getline(2))
+ call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3))
+ call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(4))
+ call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5))
+
+ %s/\s\+//g
+ call cursor(1,1)
+ exe "normal \<C-V>4jI \<Esc>j<<"
+ exe "normal j7|a\<Tab>\<Tab>"
+ exe "normal j7|a\<Tab>\<Tab>\<Tab>\<Tab>\<Tab>"
+ exe "normal j7|a\<Tab> \<Tab>\<Tab>\<Esc>4k13|\<C-V>4j3<"
+ call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1))
+ call assert_equal('abcdefghij', getline(2))
+ call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3))
+ call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4))
+ call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5))
+
+ " Test for block shift with space characters at the beginning and with
+ " 'noexpandtab' and 'expandtab'
+ %d _
+ call setline(1, [" 1", " 2", " 3"])
+ setlocal shiftwidth=2 noexpandtab
+ exe "normal gg\<C-V>3j>"
+ call assert_equal(["\t1", "\t2", "\t3"], getline(1, '$'))
+ %d _
+ call setline(1, [" 1", " 2", " 3"])
+ setlocal shiftwidth=2 expandtab
+ exe "normal gg\<C-V>3j>"
+ call assert_equal([" 1", " 2", " 3"], getline(1, '$'))
+ setlocal shiftwidth&
+
+ bw!
+endfunc
+
+" Tests Blockwise Visual when there are TABs before the text.
+func Test_blockwise_visual()
+ new
+ call append(0, ['123456',
+ \ '234567',
+ \ '345678',
+ \ '',
+ \ 'test text test tex start here',
+ \ "\t\tsome text",
+ \ "\t\ttest text",
+ \ 'test text'])
+ call cursor(1,1)
+ exe "normal /start here$\<CR>"
+ exe 'normal "by$' . "\<C-V>jjlld"
+ exe "normal /456$\<CR>"
+ exe "normal \<C-V>jj" . '"bP'
+ call assert_equal(['123start here56',
+ \ '234start here67',
+ \ '345start here78',
+ \ '',
+ \ 'test text test tex rt here',
+ \ "\t\tsomext",
+ \ "\t\ttesext"], getline(1, 7))
+
+ bw!
+endfunc
+
+" Test swapping corners in blockwise visual mode with o and O
+func Test_blockwise_visual_o_O()
+ new
+
+ exe "norm! 10i.\<Esc>Y4P3lj\<C-V>4l2jr "
+ exe "norm! gvO\<Esc>ra"
+ exe "norm! gvO\<Esc>rb"
+ exe "norm! gvo\<C-c>rc"
+ exe "norm! gvO\<C-c>rd"
+ set selection=exclusive
+ exe "norm! gvOo\<C-c>re"
+ call assert_equal('...a be.', getline(4))
+ exe "norm! gvOO\<C-c>rf"
+ set selection&
+
+ call assert_equal(['..........',
+ \ '...c d..',
+ \ '... ..',
+ \ '...a bf.',
+ \ '..........'], getline(1, '$'))
+
+ bw!
+endfun
+
+" Test Virtual replace mode.
+func Test_virtual_replace()
+ if exists('&t_kD')
+ let save_t_kD = &t_kD
+ endif
+ if exists('&t_kb')
+ let save_t_kb = &t_kb
+ endif
+ exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08"
+ enew!
+ exe "normal a\nabcdefghi\njk\tlmn\n opq rst\n\<C-D>uvwxyz"
+ call cursor(1,1)
+ set ai bs=2
+ exe "normal gR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR"
+ call assert_equal([' 1',
+ \ ' A',
+ \ ' BCDEFGHIJ',
+ \ ' KL',
+ \ ' MNO',
+ \ ' PQR',
+ \ ], getline(1, 6))
+ normal G
+ mark a
+ exe "normal o0\<C-D>\nabcdefghi\njk\tlmn\n opq\trst\n\<C-D>uvwxyz\n"
+ exe "normal 'ajgR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" . repeat("\<BS>", 29)
+ call assert_equal([' 1',
+ \ 'abcdefghi',
+ \ 'jk lmn',
+ \ ' opq rst',
+ \ 'uvwxyz'], getline(7, 11))
+ normal G
+ exe "normal iab\tcdefghi\tjkl"
+ exe "normal 0gRAB......CDEFGHI.J\<Esc>o"
+ exe "normal iabcdefghijklmnopqrst\<Esc>0gRAB\tIJKLMNO\tQR"
+ call assert_equal(['AB......CDEFGHI.Jkl',
+ \ 'AB IJKLMNO QRst'], getline(12, 13))
+
+ " Test inserting Tab with 'noexpandtab' and 'softabstop' set to 4
+ %d
+ call setline(1, 'aaaaaaaaaaaaa')
+ set softtabstop=4
+ exe "normal gggR\<Tab>\<Tab>x"
+ call assert_equal("\txaaaa", getline(1))
+ set softtabstop&
+
+ enew!
+ set noai bs&vim
+ if exists('save_t_kD')
+ let &t_kD = save_t_kD
+ endif
+ if exists('save_t_kb')
+ let &t_kb = save_t_kb
+ endif
+endfunc
+
+" Test Virtual replace mode.
+func Test_virtual_replace2()
+ enew!
+ set bs=2
+ exe "normal a\nabcdefghi\njk\tlmn\n opq rst\n\<C-D>uvwxyz"
+ call cursor(1,1)
+ " Test 1: Test that del deletes the newline
+ exe "normal gR0\<del> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR"
+ call assert_equal(['0 1',
+ \ 'A',
+ \ 'BCDEFGHIJ',
+ \ ' KL',
+ \ 'MNO',
+ \ 'PQR',
+ \ ], getline(1, 6))
+ " Test 2:
+ " a newline is not deleted, if no newline has been added in virtual replace mode
+ %d_
+ call setline(1, ['abcd', 'efgh', 'ijkl'])
+ call cursor(2,1)
+ exe "norm! gR1234\<cr>5\<bs>\<bs>\<bs>"
+ call assert_equal(['abcd',
+ \ '123h',
+ \ 'ijkl'], getline(1, '$'))
+ " Test 3:
+ " a newline is deleted, if a newline has been inserted before in virtual replace mode
+ %d_
+ call setline(1, ['abcd', 'efgh', 'ijkl'])
+ call cursor(2,1)
+ exe "norm! gR1234\<cr>\<cr>56\<bs>\<bs>\<bs>"
+ call assert_equal(['abcd',
+ \ '1234',
+ \ 'ijkl'], getline(1, '$'))
+ " Test 4:
+ " delete add a newline, delete it, add it again and check undo
+ %d_
+ call setline(1, ['abcd', 'efgh', 'ijkl'])
+ call cursor(2,1)
+ " break undo sequence explicitly
+ let &ul = &ul
+ exe "norm! gR1234\<cr>\<bs>\<del>56\<cr>"
+ let &ul = &ul
+ call assert_equal(['abcd',
+ \ '123456',
+ \ ''], getline(1, '$'))
+ norm! u
+ call assert_equal(['abcd',
+ \ 'efgh',
+ \ 'ijkl'], getline(1, '$'))
+
+ " Test for truncating spaces in a newly added line using 'autoindent' if
+ " characters are not added to that line.
+ %d_
+ call setline(1, [' app', ' bee', ' cat'])
+ setlocal autoindent
+ exe "normal gg$gRt\n\nr"
+ call assert_equal([' apt', '', ' rat'], getline(1, '$'))
+
+ " clean up
+ %d_
+ set bs&vim
+endfunc
+
+func Test_Visual_word_textobject()
+ new
+ call setline(1, ['First sentence. Second sentence.'])
+
+ " When start and end of visual area are identical, 'aw' or 'iw' select
+ " the whole word.
+ norm! 1go2fcvawy
+ call assert_equal('Second ', @")
+ norm! 1go2fcviwy
+ call assert_equal('Second', @")
+
+ " When start and end of visual area are not identical, 'aw' or 'iw'
+ " extend the word in direction of the end of the visual area.
+ norm! 1go2fcvlawy
+ call assert_equal('cond ', @")
+ norm! gv2awy
+ call assert_equal('cond sentence.', @")
+
+ norm! 1go2fcvliwy
+ call assert_equal('cond', @")
+ norm! gv2iwy
+ call assert_equal('cond sentence', @")
+
+ " Extend visual area in opposite direction.
+ norm! 1go2fcvhawy
+ call assert_equal(' Sec', @")
+ norm! gv2awy
+ call assert_equal(' sentence. Sec', @")
+
+ norm! 1go2fcvhiwy
+ call assert_equal('Sec', @")
+ norm! gv2iwy
+ call assert_equal('. Sec', @")
+
+ bwipe!
+endfunc
+
+func Test_Visual_sentence_textobject()
+ new
+ call setline(1, ['First sentence. Second sentence. Third', 'sentence. Fourth sentence'])
+
+ " When start and end of visual area are identical, 'as' or 'is' select
+ " the whole sentence.
+ norm! 1gofdvasy
+ call assert_equal('Second sentence. ', @")
+ norm! 1gofdvisy
+ call assert_equal('Second sentence.', @")
+
+ " When start and end of visual area are not identical, 'as' or 'is'
+ " extend the sentence in direction of the end of the visual area.
+ norm! 1gofdvlasy
+ call assert_equal('d sentence. ', @")
+ norm! gvasy
+ call assert_equal("d sentence. Third\nsentence. ", @")
+
+ norm! 1gofdvlisy
+ call assert_equal('d sentence.', @")
+ norm! gvisy
+ call assert_equal('d sentence. ', @")
+ norm! gvisy
+ call assert_equal("d sentence. Third\nsentence.", @")
+
+ " Extend visual area in opposite direction.
+ norm! 1gofdvhasy
+ call assert_equal(' Second', @")
+ norm! gvasy
+ call assert_equal("First sentence. Second", @")
+
+ norm! 1gofdvhisy
+ call assert_equal('Second', @")
+ norm! gvisy
+ call assert_equal(' Second', @")
+ norm! gvisy
+ call assert_equal('First sentence. Second', @")
+
+ bwipe!
+endfunc
+
+func Test_Visual_paragraph_textobject()
+ new
+ let lines =<< trim [END]
+ First line.
+
+ Second line.
+ Third line.
+ Fourth line.
+ Fifth line.
+
+ Sixth line.
+ [END]
+ call setline(1, lines)
+
+ " When start and end of visual area are identical, 'ap' or 'ip' select
+ " the whole paragraph.
+ norm! 4ggvapy
+ call assert_equal("Second line.\nThird line.\nFourth line.\nFifth line.\n\n", @")
+ norm! 4ggvipy
+ call assert_equal("Second line.\nThird line.\nFourth line.\nFifth line.\n", @")
+
+ " When start and end of visual area are not identical, 'ap' or 'ip'
+ " extend the sentence in direction of the end of the visual area.
+ " FIXME: actually, it is not sufficient to have different start and
+ " end of visual selection, the start line and end line have to differ,
+ " which is not consistent with the documentation.
+ norm! 4ggVjapy
+ call assert_equal("Third line.\nFourth line.\nFifth line.\n\n", @")
+ norm! gvapy
+ call assert_equal("Third line.\nFourth line.\nFifth line.\n\nSixth line.\n", @")
+ norm! 4ggVjipy
+ call assert_equal("Third line.\nFourth line.\nFifth line.\n", @")
+ norm! gvipy
+ call assert_equal("Third line.\nFourth line.\nFifth line.\n\n", @")
+ norm! gvipy
+ call assert_equal("Third line.\nFourth line.\nFifth line.\n\nSixth line.\n", @")
+
+ " Extend visual area in opposite direction.
+ norm! 5ggVkapy
+ call assert_equal("\nSecond line.\nThird line.\nFourth line.\n", @")
+ norm! gvapy
+ call assert_equal("First line.\n\nSecond line.\nThird line.\nFourth line.\n", @")
+ norm! 5ggVkipy
+ call assert_equal("Second line.\nThird line.\nFourth line.\n", @")
+ norma gvipy
+ call assert_equal("\nSecond line.\nThird line.\nFourth line.\n", @")
+ norm! gvipy
+ call assert_equal("First line.\n\nSecond line.\nThird line.\nFourth line.\n", @")
+
+ bwipe!
+endfunc
+
+func Test_curswant_not_changed()
+ new
+ call setline(1, ['one', 'two'])
+ au InsertLeave * call getcurpos()
+ call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt')
+ call assert_equal([0, 2, 1, 0, 1], getcurpos())
+
+ bwipe!
+ au! InsertLeave
+endfunc
+
+" Tests for "vaBiB", end could be wrong.
+func Test_Visual_Block()
+ new
+ a
+- Bug in "vPPPP" on this text:
+ {
+ cmd;
+ {
+ cmd;\t/* <-- Start cursor here */
+ {
+ }
+ }
+ }
+.
+ normal gg
+ call search('Start cursor here')
+ normal vaBiBD
+ call assert_equal(['- Bug in "vPPPP" on this text:',
+ \ "\t{",
+ \ "\t}"], getline(1, '$'))
+
+ close!
+endfunc
+
+" Test for 'p'ut in visual block mode
+func Test_visual_block_put()
+ new
+ call append(0, ['One', 'Two', 'Three'])
+ normal gg
+ yank
+ call feedkeys("jl\<C-V>ljp", 'xt')
+ call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$'))
+ bw!
+endfunc
+
+func Test_visual_block_put_invalid()
+ enew!
+ behave mswin
+ norm yy
+ norm v)Ps/^/
+ " this was causing the column to become negative
+ silent norm ggv)P
+
+ bwipe!
+ behave xterm
+endfunc
+
+" Visual modes (v V CTRL-V) followed by an operator; count; repeating
+func Test_visual_mode_op()
+ new
+ call append(0, '')
+
+ call setline(1, 'apple banana cherry')
+ call cursor(1, 1)
+ normal lvld.l3vd.
+ call assert_equal('a y', getline(1))
+
+ call setline(1, ['line 1 line 1', 'line 2 line 2', 'line 3 line 3',
+ \ 'line 4 line 4', 'line 5 line 5', 'line 6 line 6'])
+ call cursor(1, 1)
+ exe "normal Vcnewline\<Esc>j.j2Vd."
+ call assert_equal(['newline', 'newline'], getline(1, '$'))
+
+ call deletebufline('', 1, '$')
+ call setline(1, ['xxxxxxxxxxxxx', 'xxxxxxxxxxxxx', 'xxxxxxxxxxxxx',
+ \ 'xxxxxxxxxxxxx'])
+ exe "normal \<C-V>jlc \<Esc>l.l2\<C-V>c----\<Esc>l."
+ call assert_equal([' --------x',
+ \ ' --------x',
+ \ 'xxxx--------x',
+ \ 'xxxx--------x'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Visual mode maps (movement and text object)
+" Visual mode maps; count; repeating
+" - Simple
+" - With an Ex command (custom text object)
+func Test_visual_mode_maps()
+ new
+ call append(0, '')
+
+ func SelectInCaps()
+ let [line1, col1] = searchpos('\u', 'bcnW')
+ let [line2, col2] = searchpos('.\u', 'nW')
+ call setpos("'<", [0, line1, col1, 0])
+ call setpos("'>", [0, line2, col2, 0])
+ normal! gv
+ endfunction
+
+ vnoremap W /\u/s-1<CR>
+ vnoremap iW :<C-U>call SelectInCaps()<CR>
+
+ call setline(1, 'KiwiRaspberryDateWatermelonPeach')
+ call cursor(1, 1)
+ exe "normal vWcNo\<Esc>l.fD2vd."
+ call assert_equal('NoNoberryach', getline(1))
+
+ call setline(1, 'JambuRambutanBananaTangerineMango')
+ call cursor(1, 1)
+ exe "normal llviWc-\<Esc>l.l2vdl."
+ call assert_equal('--ago', getline(1))
+
+ vunmap W
+ vunmap iW
+ bwipe!
+ delfunc SelectInCaps
+endfunc
+
+" Operator-pending mode maps (movement and text object)
+" - Simple
+" - With Ex command moving the cursor
+" - With Ex command and Visual selection (custom text object)
+func Test_visual_oper_pending_mode_maps()
+ new
+ call append(0, '')
+
+ func MoveToCap()
+ call search('\u', 'W')
+ endfunction
+
+ func SelectInCaps()
+ let [line1, col1] = searchpos('\u', 'bcnW')
+ let [line2, col2] = searchpos('.\u', 'nW')
+ call setpos("'<", [0, line1, col1, 0])
+ call setpos("'>", [0, line2, col2, 0])
+ normal! gv
+ endfunction
+
+ onoremap W /\u/<CR>
+ onoremap <Leader>W :<C-U>call MoveToCap()<CR>
+ onoremap iW :<C-U>call SelectInCaps()<CR>
+
+ call setline(1, 'PineappleQuinceLoganberryOrangeGrapefruitKiwiZ')
+ call cursor(1, 1)
+ exe "normal cW-\<Esc>l.l2.l."
+ call assert_equal('----Z', getline(1))
+
+ call setline(1, 'JuniperDurianZ')
+ call cursor(1, 1)
+ exe "normal g?\WfD."
+ call assert_equal('WhavcreQhevnaZ', getline(1))
+
+ call setline(1, 'LemonNectarineZ')
+ call cursor(1, 1)
+ exe "normal yiWPlciWNew\<Esc>fr."
+ call assert_equal('LemonNewNewZ', getline(1))
+
+ ounmap W
+ ounmap <Leader>W
+ ounmap iW
+ bwipe!
+ delfunc MoveToCap
+ delfunc SelectInCaps
+endfunc
+
+" Patch 7.3.879: Properly abort Operator-pending mode for "dv:<Esc>" etc.
+func Test_op_pend_mode_abort()
+ new
+ call append(0, '')
+
+ call setline(1, ['zzzz', 'zzzz'])
+ call cursor(1, 1)
+
+ exe "normal dV:\<CR>dv:\<CR>"
+ call assert_equal(['zzz'], getline(1, 2))
+ set nomodifiable
+ call assert_fails('exe "normal d:\<CR>"', 'E21:')
+ set modifiable
+ call feedkeys("dv:\<Esc>dV:\<Esc>", 'xt')
+ call assert_equal(['zzz'], getline(1, 2))
+ set nomodifiable
+ let v:errmsg = ''
+ call feedkeys("d:\<Esc>", 'xt')
+ call assert_true(v:errmsg !~# '^E21:')
+ set modifiable
+
+ bwipe!
+endfunc
+
+func Test_characterwise_visual_mode()
+ new
+
+ " characterwise visual mode: replace last line
+ $put ='a'
+ let @" = 'x'
+ normal v$p
+ call assert_equal('x', getline('$'))
+
+ " characterwise visual mode: delete middle line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal G
+ normal kkv$d
+ call assert_equal(['', 'b', 'c'], getline(1, '$'))
+
+ " characterwise visual mode: delete middle two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal Gkkvj$d
+ call assert_equal(['', 'c'], getline(1, '$'))
+
+ " characterwise visual mode: delete last line
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal Gv$d
+ call assert_equal(['', 'a', 'b', ''], getline(1, '$'))
+
+ " characterwise visual mode: delete last two lines
+ call deletebufline('', 1, '$')
+ call append('$', ['a', 'b', 'c'])
+ normal Gkvj$d
+ call assert_equal(['', 'a', ''], getline(1, '$'))
+
+ " characterwise visual mode: use a count with the visual mode from the last
+ " line in the buffer
+ %d _
+ call setline(1, ['one', 'two', 'three', 'four'])
+ norm! vj$y
+ norm! G1vy
+ call assert_equal('four', @")
+
+ " characterwise visual mode: replace a single character line and the eol
+ %d _
+ call setline(1, "a")
+ normal v$rx
+ call assert_equal(['x'], getline(1, '$'))
+
+ " replace a character with composing characters
+ call setline(1, "xã̳x")
+ normal gg0lvrb
+ call assert_equal("xbx", getline(1))
+
+ bwipe!
+endfunc
+
+func Test_visual_mode_put()
+ new
+
+ " v_p: replace last character with line register at middle line
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal k$vp
+ call assert_equal(['', 'aaa', 'bb', 'aaa', '', 'ccc'], getline(1, '$'))
+
+ " v_p: replace last character with line register at middle line selecting
+ " newline
+ call deletebufline('', 1, '$')
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal k$v$p
+ call assert_equal(['', 'aaa', 'bb', 'aaa', 'ccc'], getline(1, '$'))
+
+ " v_p: replace last character with line register at last line
+ call deletebufline('', 1, '$')
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal $vp
+ call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$'))
+
+ " v_p: replace last character with line register at last line selecting
+ " newline
+ call deletebufline('', 1, '$')
+ call append('$', ['aaa', 'bbb', 'ccc'])
+ normal G
+ -2yank
+ normal $v$p
+ call assert_equal(['', 'aaa', 'bbb', 'cc', 'aaa', ''], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+func Test_gv_with_exclusive_selection()
+ new
+
+ " gv with exclusive selection after an operation
+ call append('$', ['zzz ', 'äà '])
+ set selection=exclusive
+ normal Gkv3lyjv3lpgvcxxx
+ call assert_equal(['', 'zzz ', 'xxx '], getline(1, '$'))
+
+ " gv with exclusive selection without an operation
+ call deletebufline('', 1, '$')
+ call append('$', 'zzz ')
+ set selection=exclusive
+ exe "normal G0v3l\<Esc>gvcxxx"
+ call assert_equal(['', 'xxx '], getline(1, '$'))
+
+ set selection&vim
+ bwipe!
+endfunc
+
+" Tests for the visual block mode commands
+func Test_visual_block_mode()
+ new
+ call append(0, '')
+ call setline(1, repeat(['abcdefghijklm'], 5))
+ call cursor(1, 1)
+
+ " Test shift-right of a block
+ exe "normal jllll\<C-V>jj>wll\<C-V>jlll>"
+ " Test shift-left of a block
+ exe "normal G$hhhh\<C-V>kk<"
+ " Test block-insert
+ exe "normal Gkl\<C-V>kkkIxyz"
+ " Test block-replace
+ exe "normal Gllll\<C-V>kkklllrq"
+ " Test block-change
+ exe "normal G$khhh\<C-V>hhkkcmno"
+ call assert_equal(['axyzbcdefghijklm',
+ \ 'axyzqqqq mno ghijklm',
+ \ 'axyzqqqqef mno ghijklm',
+ \ 'axyzqqqqefgmnoklm',
+ \ 'abcdqqqqijklm'], getline(1, 5))
+
+ " Test 'C' to change till the end of the line
+ call cursor(3, 4)
+ exe "normal! \<C-V>j3lCooo"
+ call assert_equal(['axyooo', 'axyooo'], getline(3, 4))
+
+ " Test 'D' to delete till the end of the line
+ call cursor(3, 3)
+ exe "normal! \<C-V>j2lD"
+ call assert_equal(['ax', 'ax'], getline(3, 4))
+
+ " Test block insert with a short line that ends before the block
+ %d _
+ call setline(1, [" one", "a", " two"])
+ exe "normal gg\<C-V>2jIx"
+ call assert_equal([" xone", "a", " xtwo"], getline(1, '$'))
+
+ " Test block append at EOL with '$' and without '$'
+ %d _
+ call setline(1, ["one", "a", "two"])
+ exe "normal gg$\<C-V>2jAx"
+ call assert_equal(["onex", "ax", "twox"], getline(1, '$'))
+ %d _
+ call setline(1, ["one", "a", "two"])
+ exe "normal gg3l\<C-V>2jAx"
+ call assert_equal(["onex", "a x", "twox"], getline(1, '$'))
+
+ " Test block replace with an empty line in the middle and use $ to jump to
+ " the end of the line.
+ %d _
+ call setline(1, ['one', '', 'two'])
+ exe "normal gg$\<C-V>2jrx"
+ call assert_equal(["onx", "", "twx"], getline(1, '$'))
+
+ " Test block replace with an empty line in the middle and move cursor to the
+ " end of the line
+ %d _
+ call setline(1, ['one', '', 'two'])
+ exe "normal gg2l\<C-V>2jrx"
+ call assert_equal(["onx", "", "twx"], getline(1, '$'))
+
+ " Replace odd number of characters with a multibyte character
+ %d _
+ call setline(1, ['abcd', 'efgh'])
+ exe "normal ggl\<C-V>2ljr\u1100"
+ call assert_equal(["a\u1100 ", "e\u1100 "], getline(1, '$'))
+
+ " During visual block append, if the cursor moved outside of the selected
+ " range, then the edit should not be applied to the block.
+ %d _
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal 2G\<C-V>jAx\<Up>"
+ call assert_equal(['aaa', 'bxbb', 'ccc'], getline(1, '$'))
+
+ " During visual block append, if the cursor is moved before the start of the
+ " block, then the new text should be appended there.
+ %d _
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal $\<C-V>2jA\<Left>x"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+ " Repeat the previous test but use 'l' to move the cursor instead of '$'
+ call setline(1, ['aaa', 'bbb', 'ccc'])
+ exe "normal! gg2l\<C-V>2jA\<Left>x"
+ call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$'))
+
+ " Change a characterwise motion to a blockwise motion using CTRL-V
+ %d _
+ call setline(1, ['123', '456', '789'])
+ exe "normal ld\<C-V>j"
+ call assert_equal(['13', '46', '789'], getline(1, '$'))
+
+ " Test from ':help v_b_I_example'
+ %d _
+ setlocal tabstop=8 shiftwidth=4
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3jISTRING"
+ let expected =<< trim END
+ abcdefghijklmnSTRINGopqrstuvwxyz
+ abc STRING defghijklmnopqrstuvwxyz
+ abcdef ghi STRING jklmnopqrstuvwxyz
+ abcdefghijklmnSTRINGopqrstuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_A_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3j$ASTRING"
+ let expected =<< trim END
+ abcdefghijklmnopqrstuvwxyzSTRING
+ abc defghijklmnopqrstuvwxyzSTRING
+ abcdef ghi jklmnopqrstuvwxyzSTRING
+ abcdefghijklmnopqrstuvwxyzSTRING
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_<_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3j3l<.."
+ let expected =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_>_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3j>.."
+ let expected =<< trim END
+ abcdefghijklmn opqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmn opqrstuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_r_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>5l3jrX"
+ let expected =<< trim END
+ abcdefghijklmnXXXXXXuvwxyz
+ abc XXXXXXhijklmnopqrstuvwxyz
+ abcdef ghi XXXXXX jklmnopqrstuvwxyz
+ abcdefghijklmnXXXXXXuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+ set tabstop& shiftwidth&
+endfunc
+
+func Test_visual_force_motion_feedkeys()
+ onoremap <expr> i- execute('let g:mode = mode(1)')->slice(0, 0)
+ call feedkeys('dvi-', 'x')
+ call assert_equal('nov', g:mode)
+ call feedkeys('di-', 'x')
+ call assert_equal('no', g:mode)
+ ounmap i-
+endfunc
+
+" Test block-insert using cursor keys for movement
+func Test_visual_block_insert_cursor_keys()
+ new
+ call append(0, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd'])
+ call cursor(1, 1)
+
+ exe "norm! l\<C-V>jjjlllI\<Right>\<Right> \<Esc>"
+ call assert_equal(['aaa aaa', 'bbb bbb', 'ccc ccc', 'ddd ddd'],
+ \ getline(1, 4))
+
+ call deletebufline('', 1, '$')
+ call setline(1, ['xaaa', 'bbbb', 'cccc', 'dddd'])
+ call cursor(1, 1)
+ exe "norm! \<C-V>jjjI<>\<Left>p\<Esc>"
+ call assert_equal(['<p>xaaa', '<p>bbbb', '<p>cccc', '<p>dddd'],
+ \ getline(1, 4))
+ bwipe!
+endfunc
+
+func Test_visual_block_create()
+ new
+ call append(0, '')
+ " Test for Visual block was created with the last <C-v>$
+ call setline(1, ['A23', '4567'])
+ call cursor(1, 1)
+ exe "norm! l\<C-V>j$Aab\<Esc>"
+ call assert_equal(['A23ab', '4567ab'], getline(1, 2))
+
+ " Test for Visual block was created with the middle <C-v>$ (1)
+ call deletebufline('', 1, '$')
+ call setline(1, ['B23', '4567'])
+ call cursor(1, 1)
+ exe "norm! l\<C-V>j$hAab\<Esc>"
+ call assert_equal(['B23 ab', '4567ab'], getline(1, 2))
+
+ " Test for Visual block was created with the middle <C-v>$ (2)
+ call deletebufline('', 1, '$')
+ call setline(1, ['C23', '4567'])
+ call cursor(1, 1)
+ exe "norm! l\<C-V>j$hhAab\<Esc>"
+ call assert_equal(['C23ab', '456ab7'], getline(1, 2))
+ bwipe!
+endfunc
+
+" Test for Visual block insert when virtualedit=all
+func Test_virtualedit_visual_block()
+ set ve=all
+ new
+ call append(0, ["\t\tline1", "\t\tline2", "\t\tline3"])
+ call cursor(1, 1)
+ exe "norm! 07l\<C-V>jjIx\<Esc>"
+ call assert_equal([" x \tline1",
+ \ " x \tline2",
+ \ " x \tline3"], getline(1, 3))
+
+ " Test for Visual block append when virtualedit=all
+ exe "norm! 012l\<C-v>jjAx\<Esc>"
+ call assert_equal([' x x line1',
+ \ ' x x line2',
+ \ ' x x line3'], getline(1, 3))
+ set ve=
+ bwipe!
+endfunc
+
+" Test for changing case
+func Test_visual_change_case()
+ new
+ " gUe must uppercase a whole word, also when ß changes to SS
+ exe "normal Gothe youtußeuu end\<Esc>Ypk0wgUe\r"
+ " gUfx must uppercase until x, inclusive.
+ exe "normal O- youßtußexu -\<Esc>0fogUfx\r"
+ " VU must uppercase a whole line
+ exe "normal YpkVU\r"
+ " same, when it's the last line in the buffer
+ exe "normal YPGi111\<Esc>VUddP\r"
+ " Uppercase two lines
+ exe "normal Oblah di\rdoh dut\<Esc>VkUj\r"
+ " Uppercase part of two lines
+ exe "normal ddppi333\<Esc>k0i222\<Esc>fyllvjfuUk"
+ call assert_equal(['the YOUTUSSEUU end', '- yOUSSTUSSEXu -',
+ \ 'THE YOUTUSSEUU END', '111THE YOUTUSSEUU END', 'BLAH DI', 'DOH DUT',
+ \ '222the yoUTUSSEUU END', '333THE YOUTUßeuu end'], getline(2, '$'))
+ bwipe!
+endfunc
+
+" Test for Visual replace using Enter or NL
+func Test_visual_replace_crnl()
+ new
+ exe "normal G3o123456789\e2k05l\<C-V>2jr\r"
+ exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\r\n"
+ exe "normal G3o123456789\e2k05l\<C-V>2jr\n"
+ exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\n"
+ call assert_equal(['12345', '789', '12345', '789', '12345', '789', "98\r65",
+ \ "98\r65", "98\r65", '12345', '789', '12345', '789', '12345', '789',
+ \ "98\n65", "98\n65", "98\n65"], getline(2, '$'))
+ bwipe!
+endfunc
+
+func Test_ve_block_curpos()
+ new
+ " Test cursor position. When ve=block and Visual block mode and $gj
+ call append(0, ['12345', '789'])
+ call cursor(1, 3)
+ set virtualedit=block
+ exe "norm! \<C-V>$gj\<Esc>"
+ call assert_equal([0, 2, 4, 0], getpos("'>"))
+ set virtualedit=
+ bwipe!
+endfunc
+
+" Test for block_insert when replacing spaces in front of the a with tabs
+func Test_block_insert_replace_tabs()
+ new
+ set ts=8 sts=4 sw=4
+ call append(0, ["#define BO_ALL\t 0x0001",
+ \ "#define BO_BS\t 0x0002",
+ \ "#define BO_CRSR\t 0x0004"])
+ call cursor(1, 1)
+ exe "norm! f0\<C-V>2jI\<tab>\<esc>"
+ call assert_equal([
+ \ "#define BO_ALL\t\t0x0001",
+ \ "#define BO_BS\t \t0x0002",
+ \ "#define BO_CRSR\t \t0x0004", ''], getline(1, '$'))
+ set ts& sts& sw&
+ bwipe!
+endfunc
+
+" Test for * register in :
+func Test_star_register()
+ call assert_fails('*bfirst', 'E16:')
+ new
+ call setline(1, ['foo', 'bar', 'baz', 'qux'])
+ exe "normal jVj\<ESC>"
+ *yank r
+ call assert_equal("bar\nbaz\n", @r)
+
+ delmarks < >
+ call assert_fails('*yank', 'E20:')
+ close!
+endfunc
+
+" Test for changing text in visual mode with 'exclusive' selection
+func Test_exclusive_selection()
+ new
+ call setline(1, ['one', 'two'])
+ set selection=exclusive
+ call feedkeys("vwcabc", 'xt')
+ call assert_equal('abctwo', getline(1))
+ call setline(1, ["\tone"])
+ set virtualedit=all
+ call feedkeys('0v2lcl', 'xt')
+ call assert_equal('l one', getline(1))
+ set virtualedit&
+ set selection&
+ close!
+endfunc
+
+" Test for starting linewise visual with a count.
+" This test needs to be run without any previous visual mode. Otherwise the
+" count will use the count from the previous visual mode.
+func Test_linewise_visual_with_count()
+ let after =<< trim [CODE]
+ call setline(1, ['one', 'two', 'three', 'four'])
+ norm! 3Vy
+ call assert_equal("one\ntwo\nthree\n", @")
+ call writefile(v:errors, 'Xtestout')
+ qall!
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for starting characterwise visual with a count.
+" This test needs to be run without any previous visual mode. Otherwise the
+" count will use the count from the previous visual mode.
+func Test_characterwise_visual_with_count()
+ let after =<< trim [CODE]
+ call setline(1, ['one two', 'three'])
+ norm! l5vy
+ call assert_equal("ne tw", @")
+ call writefile(v:errors, 'Xtestout')
+ qall!
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for visually selecting an inner block (iB)
+func Test_visual_inner_block()
+ new
+ call setline(1, ['one', '{', 'two', '{', 'three', '}', 'four', '}', 'five'])
+ call cursor(5, 1)
+ " visually select all the lines in the block and then execute iB
+ call feedkeys("ViB\<C-C>", 'xt')
+ call assert_equal([0, 5, 1, 0], getpos("'<"))
+ call assert_equal([0, 5, 6, 0], getpos("'>"))
+ " visually select two inner blocks
+ call feedkeys("ViBiB\<C-C>", 'xt')
+ call assert_equal([0, 3, 1, 0], getpos("'<"))
+ call assert_equal([0, 7, 5, 0], getpos("'>"))
+ " try to select non-existing inner block
+ call cursor(5, 1)
+ call assert_beeps('normal ViBiBiB')
+ " try to select a unclosed inner block
+ 8,9d
+ call cursor(5, 1)
+ call assert_beeps('normal ViBiB')
+ close!
+endfunc
+
+func Test_visual_put_in_block()
+ new
+ call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
+ normal 1G2yl
+ exe "normal 1G2l\<C-V>jjlp"
+ call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
+ bwipe!
+endfunc
+
+func Test_visual_put_in_block_using_zp()
+ new
+ " paste using zP
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ '/subdir',
+ \ '/longsubdir',
+ \ '/longlongsubdir'])
+ exe "normal! 5G\<c-v>2j$y"
+ norm! 1Gf;zP
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+ %d
+ " paste using zP
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ '/subdir',
+ \ '/longsubdir',
+ \ '/longlongsubdir'])
+ exe "normal! 5G\<c-v>2j$y"
+ norm! 1Gf;hzp
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+ bwipe!
+endfunc
+
+func Test_visual_put_in_block_using_zy_and_zp()
+ new
+
+ " Test 1) Paste using zp - after the cursor without trailing spaces
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;hzp
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 2) Paste using zP - in front of the cursor without trailing spaces
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;zP
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 3) Paste using p - with trailing spaces
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;hp
+ call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 4) Paste using P - with trailing spaces
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /subdir columntext',
+ \ 'texttext /longsubdir columntext',
+ \ 'texttext /longlongsubdir columntext'])
+ exe "normal! 5G0f/\<c-v>2jezy"
+ norm! 1G0f;P
+ call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3))
+
+ " Test 5) Yank with spaces inside the block
+ %d
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ 'texttext /sub dir/ columntext',
+ \ 'texttext /lon gsubdir/ columntext',
+ \ 'texttext /lon glongsubdir/ columntext'])
+ exe "normal! 5G0f/\<c-v>2jf/zy"
+ norm! 1G0f;zP
+ call assert_equal(['/path/sub dir/;text', '/path/lon gsubdir/;text', '/path/lon glongsubdir/;text'], getline(1, 3))
+ bwipe!
+endfunc
+
+func Test_visual_put_blockedit_zy_and_zp()
+ new
+
+ call setline(1, ['aa', 'bbbbb', 'ccc', '', 'XX', 'GGHHJ', 'RTZU'])
+ exe "normal! gg0\<c-v>2j$zy"
+ norm! 5gg0zP
+ call assert_equal(['aa', 'bbbbb', 'ccc', '', 'aaXX', 'bbbbbGGHHJ', 'cccRTZU'], getline(1, 7))
+ "
+ " now with blockmode editing
+ sil %d
+ :set ve=block
+ call setline(1, ['aa', 'bbbbb', 'ccc', '', 'XX', 'GGHHJ', 'RTZU'])
+ exe "normal! gg0\<c-v>2j$zy"
+ norm! 5gg0zP
+ call assert_equal(['aa', 'bbbbb', 'ccc', '', 'aaXX', 'bbbbbGGHHJ', 'cccRTZU'], getline(1, 7))
+ set ve&vim
+ bw!
+endfunc
+
+func Test_visual_block_yank_zy()
+ new
+ " this was reading before the start of the line
+ exe "norm o\<C-T>\<Esc>\<C-V>zy"
+ bwipe!
+endfunc
+
+func Test_visual_block_with_virtualedit()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['aaaaaa', 'bbbb', 'cc'])
+ set virtualedit=block
+ normal G
+ END
+ call writefile(lines, 'XTest_block', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_block', {'rows': 8, 'cols': 50})
+ call term_sendkeys(buf, "\<C-V>gg$")
+ call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit', {})
+
+ call term_sendkeys(buf, "\<Esc>gg\<C-V>G$")
+ call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit2', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_visual_block_ctrl_w_f()
+ " Empty block selected in new buffer should not result in an error.
+ au! BufNew foo sil norm f
+ edit foo
+
+ au! BufNew
+endfunc
+
+func Test_visual_block_append_invalid_char()
+ " this was going over the end of the line
+ set isprint=@,161-255
+ new
+ call setline(1, [' let xxx', 'xxxxxˆ', 'xxxxxxxxxxx'])
+ exe "normal 0\<C-V>jjA-\<Esc>"
+ call assert_equal([' - let xxx', 'xxxxx -ˆ', 'xxxxxxxx-xxx'], getline(1, 3))
+ bwipe!
+ set isprint&
+endfunc
+
+func Test_visual_block_with_substitute()
+ " this was reading beyond the end of the line
+ new
+ norm a0)
+ sil! norm  O
+ s/)
+ sil! norm 
+ bwipe!
+endfunc
+
+func Test_visual_reselect_with_count()
+ enew
+ call setline(1, ['aaaaaa', '✗ bbbb', '✗ bbbb'])
+ exe "normal! 2Gw\<C-V>jed"
+ exe "normal! gg0lP"
+ call assert_equal(['abbbbaaaaa', '✗bbbb ', '✗ '], getline(1, '$'))
+
+ exe "normal! 1vr."
+ call assert_equal(['a....aaaaa', '✗.... ', '✗ '], getline(1, '$'))
+
+ bwipe!
+
+ " this was causing an illegal memory access
+ let lines =<< trim END
+
+
+
+ :
+ r<sfile>
+ exe "%norm e3\<c-v>kr\t"
+ :
+
+ :
+ END
+ call writefile(lines, 'XvisualReselect', 'D')
+ source XvisualReselect
+
+ bwipe!
+endfunc
+
+func Test_visual_reselect_exclusive()
+ new
+ call setline(1, ['abcde', 'abcde'])
+ set selection=exclusive
+ normal 1G0viwd
+ normal 2G01vd
+ call assert_equal(['', ''], getline(1, 2))
+
+ set selection&
+ bwipe!
+endfunc
+
+func Test_visual_block_insert_round_off()
+ new
+ " The number of characters are tuned to fill a 4096 byte allocated block,
+ " so that valgrind reports going over the end.
+ call setline(1, ['xxxxx', repeat('0', 1350), "\t", repeat('x', 60)])
+ exe "normal gg0\<C-V>GI" .. repeat('0', 1320) .. "\<Esc>"
+ bwipe!
+endfunc
+
+" this was causing an ml_get error
+func Test_visual_exchange_windows()
+ enew!
+ new
+ call setline(1, ['foo', 'bar'])
+ exe "normal G\<C-V>gg\<C-W>\<C-X>OO\<Esc>"
+ bwipe!
+ bwipe!
+endfunc
+
+" this was leaving the end of the Visual area beyond the end of a line
+func Test_visual_ex_copy_line()
+ new
+ call setline(1, ["aaa", "bbbbbbbbbxbb"])
+ /x
+ exe "normal ggvjfxO"
+ t0
+ normal gNU
+ bwipe!
+endfunc
+
+" This was leaving the end of the Visual area beyond the end of a line.
+" Set 'undolevels' to start a new undo block.
+func Test_visual_undo_deletes_last_line()
+ new
+ call setline(1, ["aaa", "ccc", "dyd"])
+ set undolevels=100
+ exe "normal obbbbbbbbbxbb\<Esc>"
+ set undolevels=100
+ /y
+ exe "normal ggvjfxO"
+ undo
+ normal gNU
+
+ bwipe!
+endfunc
+
+func Test_visual_paste()
+ new
+
+ " v_p overwrites unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vp
+ call assert_equal('f', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('bazooxxf', getline(1))
+
+ bwipe!
+endfunc
+
+func Test_visual_paste_clipboard()
+ CheckFeature clipboard_working
+
+ if has('gui')
+ " auto select feature breaks tests
+ set guioptions-=a
+ endif
+
+ " v_P does not overwrite unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vP
+ call assert_equal('foo', @")
+ call assert_equal('bar', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vP
+ call assert_equal('foo', @")
+ call assert_equal('bar', @-)
+ call assert_equal('fooxxfoo', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vP
+ call assert_equal('baz', @")
+ call assert_equal('bar', @-)
+ call assert_equal('bazooxxfoo', getline(1))
+ normal $vP
+ call assert_equal('baz', @")
+ call assert_equal('bar', @-)
+ call assert_equal('bazooxxfobaz', getline(1))
+
+ " Test for unnamed clipboard
+ set clipboard=unnamed
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ call setreg('*', 'baz')
+ normal gg0vP
+ call assert_equal('foo', @")
+ call assert_equal('bar', @-)
+ call assert_equal('baz', @*)
+ call assert_equal('bazxxx', getline(1))
+
+ " Test for unnamedplus clipboard
+ if has('unnamedplus')
+ set clipboard=unnamedplus
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ call setreg('+', 'baz')
+ normal gg0vP
+ call assert_equal('foo', @")
+ call assert_equal('bar', @-)
+ call assert_equal('baz', @+)
+ call assert_equal('bazxxx', getline(1))
+ endif
+
+ set clipboard&
+ if has('gui')
+ set guioptions&
+ endif
+ bwipe!
+endfunc
+
+func Test_visual_area_adjusted_when_hiding()
+ " The Visual area ended after the end of the line after :hide
+ call setline(1, 'xxx')
+ vsplit Xvaafile
+ call setline(1, 'xxxxxxxx')
+ norm! $o
+ hid
+ norm! zW
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_switch_buffer_ends_visual_mode()
+ enew
+ call setline(1, 'foo')
+ set hidden
+ set virtualedit=all
+ let buf1 = bufnr()
+ enew
+ let buf2 = bufnr()
+ call setline(1, ['', '', '', ''])
+ call cursor(4, 5)
+ call feedkeys("\<C-V>3k4h", 'xt')
+ exe 'buffer' buf1
+ call assert_equal('n', mode())
+
+ set nohidden
+ set virtualedit=
+ bwipe!
+ exe 'bwipe!' buf2
+endfunc
+
+" Check fix for the heap-based buffer overflow bug found in the function
+" utfc_ptr2len and reported at
+" https://huntr.dev/bounties/ae933869-a1ec-402a-bbea-d51764c6618e
+func Test_heap_buffer_overflow()
+ enew
+ set updatecount=0
+
+ norm R0
+ split other
+ norm R000
+ exe "norm \<C-V>l"
+ ball
+ call assert_equal(getpos("."), getpos("v"))
+ call assert_equal('n', mode())
+ norm zW
+
+ %bwipe!
+ set updatecount&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_winbar.vim b/src/testdir/test_winbar.vim
new file mode 100644
index 0000000..954c3e0
--- /dev/null
+++ b/src/testdir/test_winbar.vim
@@ -0,0 +1,190 @@
+" Test WinBar
+
+source check.vim
+CheckFeature menu
+
+source shared.vim
+source screendump.vim
+
+func Test_add_remove_menu()
+ new
+ amenu 1.10 WinBar.Next :let g:did_next = 11<CR>
+ amenu 1.20 WinBar.Cont :let g:did_cont = 12<CR>
+ redraw
+ call assert_match('Next Cont', Screenline(1))
+
+ emenu WinBar.Next
+ call assert_equal(11, g:did_next)
+ emenu WinBar.Cont
+ call assert_equal(12, g:did_cont)
+
+ wincmd w
+ call assert_fails('emenu WinBar.Next', 'E334:')
+ wincmd p
+
+ aunmenu WinBar.Next
+ aunmenu WinBar.Cont
+ close
+endfunc
+
+" Create a WinBar with three buttons.
+" Columns of the button edges:
+" _Next_ _Cont_ _Close_
+" 2 7 10 15 18 24
+func SetupWinbar()
+ amenu 1.10 WinBar.Next :let g:did_next = 11<CR>
+ amenu 1.20 WinBar.Cont :let g:did_cont = 12<CR>
+ amenu 1.30 WinBar.Close :close<CR>
+ redraw
+ call assert_match('Next Cont Close', Screenline(1))
+endfunc
+
+func Test_click_in_winbar()
+ new
+ call SetupWinbar()
+ let save_mouse = &mouse
+ set mouse=a
+
+ let g:did_next = 0
+ let g:did_cont = 0
+ for col in [1, 8, 9, 16, 17, 25, 26]
+ call test_setmouse(1, col)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal(0, g:did_next, 'col ' .. col)
+ call assert_equal(0, g:did_cont, 'col ' .. col)
+ endfor
+
+ for col in range(2, 7)
+ let g:did_next = 0
+ call test_setmouse(1, col)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal(11, g:did_next, 'col ' .. col)
+ endfor
+
+ for col in range(10, 15)
+ let g:did_cont = 0
+ call test_setmouse(1, col)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal(12, g:did_cont, 'col ' .. col)
+ endfor
+
+ let wincount = winnr('$')
+ call test_setmouse(1, 20)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal(wincount - 1, winnr('$'))
+
+ let &mouse = save_mouse
+endfunc
+
+func Test_click_in_other_winbar()
+ new
+ call SetupWinbar()
+ let save_mouse = &mouse
+ set mouse=a
+ let winid = win_getid()
+
+ split
+ let [row, col] = win_screenpos(winid)
+
+ " Click on Next button in other window
+ let g:did_next = 0
+ call test_setmouse(row, 5)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal(11, g:did_next)
+
+ " Click on Cont button in other window from Visual mode
+ let g:did_cont = 0
+ call setline(1, 'select XYZ here')
+ call test_setmouse(row, 12)
+ call feedkeys("0fXvfZ\<LeftMouse>x", "xt")
+ call assert_equal(12, g:did_cont)
+ call assert_equal('select here', getline(1))
+
+ " Click on Close button in other window
+ let wincount = winnr('$')
+ let winid = win_getid()
+ call test_setmouse(row, 20)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal(wincount - 1, winnr('$'))
+ call assert_equal(winid, win_getid())
+
+ bwipe!
+endfunc
+
+func Test_redraw_after_scroll()
+ new
+ amenu 1.10 WinBar.Next :let g:did_next = 11<CR>
+ redraw
+ call assert_equal(" Next", Screenline(1))
+ echo "some\nmore"
+ redraw
+ call assert_equal(" Next", Screenline(1))
+ bwipe!
+endfunc
+
+func Test_winbar_not_visible()
+ CheckScreendump
+
+ let lines =<< trim END
+ split
+ nnoremenu WinBar.Test :test
+ set winminheight=0
+ wincmd j
+ wincmd _
+ END
+ call writefile(lines, 'XtestWinbarNotVisible', 'D')
+ let buf = RunVimInTerminal('-S XtestWinbarNotVisible', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_winbar_not_visible', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunction
+
+func Test_winbar_not_visible_custom_statusline()
+ CheckScreendump
+
+ let lines =<< trim END
+ split
+ nnoremenu WinBar.Test :test
+ set winminheight=0
+ set statusline=abcde
+ wincmd j
+ wincmd _
+ END
+ call writefile(lines, 'XtestWinbarNotVisible', 'D')
+ let buf = RunVimInTerminal('-S XtestWinbarNotVisible', #{rows: 10})
+ call VerifyScreenDump(buf, 'Test_winbar_not_visible_custom_statusline', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunction
+
+func Test_drag_statusline_with_winbar()
+ call SetupWinbar()
+ let save_mouse = &mouse
+ set mouse=a
+ set laststatus=2
+
+ call test_setmouse(&lines - 1, 1)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call test_setmouse(&lines - 2, 1)
+ call feedkeys("\<LeftDrag>", 'xt')
+ call assert_equal(2, &cmdheight)
+
+ call test_setmouse(&lines - 2, 1)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call test_setmouse(&lines - 3, 1)
+ call feedkeys("\<LeftDrag>", 'xt')
+ call assert_equal(3, &cmdheight)
+
+ call test_setmouse(&lines - 3, 1)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call test_setmouse(&lines - 1, 1)
+ call feedkeys("\<LeftDrag>", 'xt')
+ call assert_equal(1, &cmdheight)
+
+ let &mouse = save_mouse
+ set laststatus&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_winbuf_close.vim b/src/testdir/test_winbuf_close.vim
new file mode 100644
index 0000000..5175c2c
--- /dev/null
+++ b/src/testdir/test_winbuf_close.vim
@@ -0,0 +1,228 @@
+" Test for commands that close windows and/or buffers:
+" :quit
+" :close
+" :hide
+" :only
+" :sall
+" :all
+" :ball
+" :buf
+" :edit
+"
+func Test_winbuf_close()
+ enew | only
+
+ call writefile(['testtext 1'], 'Xtest1', 'D')
+ call writefile(['testtext 2'], 'Xtest2', 'D')
+ call writefile(['testtext 3'], 'Xtest3', 'D')
+
+ next! Xtest1 Xtest2
+ call setline(1, 'testtext 1 1')
+
+ " test for working :n when hidden set
+ set hidden
+ next
+ call assert_equal('Xtest2', bufname('%'))
+
+ " test for failing :rew when hidden not set
+ set nohidden
+ call setline(1, 'testtext 2 2')
+ call assert_fails('rewind', 'E37:')
+ call assert_equal('Xtest2', bufname('%'))
+ call assert_equal('testtext 2 2', getline(1))
+
+ " test for working :rew when hidden set
+ set hidden
+ rewind
+ call assert_equal('Xtest1', bufname('%'))
+ call assert_equal('testtext 1 1', getline(1))
+
+ " test for :all keeping a buffer when it's modified
+ set nohidden
+ call setline(1, 'testtext 1 1 1')
+ split
+ next Xtest2 Xtest3
+ all
+ 1wincmd w
+ call assert_equal('Xtest1', bufname('%'))
+ call assert_equal('testtext 1 1 1', getline(1))
+
+ " test abandoning changed buffer, should be unloaded even when 'hidden' set
+ set hidden
+ call setline(1, 'testtext 1 1 1 1')
+ quit!
+ call assert_equal('Xtest2', bufname('%'))
+ call assert_equal('testtext 2 2', getline(1))
+ unhide
+ call assert_equal('Xtest2', bufname('%'))
+ call assert_equal('testtext 2 2', getline(1))
+
+ " test ":hide" hides anyway when 'hidden' not set
+ set nohidden
+ call setline(1, 'testtext 2 2 2')
+ hide
+ call assert_equal('Xtest3', bufname('%'))
+ call assert_equal('testtext 3', getline(1))
+
+ " test ":edit" failing in modified buffer when 'hidden' not set
+ call setline(1, 'testtext 3 3')
+ call assert_fails('edit Xtest1', 'E37:')
+ call assert_equal('Xtest3', bufname('%'))
+ call assert_equal('testtext 3 3', getline(1))
+
+ " test ":edit" working in modified buffer when 'hidden' set
+ set hidden
+ edit Xtest1
+ call assert_equal('Xtest1', bufname('%'))
+ call assert_equal('testtext 1', getline(1))
+
+ " test ":close" not hiding when 'hidden' not set in modified buffer
+ split Xtest3
+ set nohidden
+ call setline(1, 'testtext 3 3 3')
+ call assert_fails('close', 'E37:')
+ call assert_equal('Xtest3', bufname('%'))
+ call assert_equal('testtext 3 3 3', getline(1))
+
+ " test ":close!" does hide when 'hidden' not set in modified buffer;
+ call setline(1, 'testtext 3 3 3 3')
+ close!
+ call assert_equal('Xtest1', bufname('%'))
+ call assert_equal('testtext 1', getline(1))
+
+ set nohidden
+
+ " test ":all!" hides changed buffer
+ split Xtest4
+ call setline(1, 'testtext 4')
+ all!
+ 1wincmd w
+ call assert_equal('Xtest2', bufname('%'))
+ call assert_equal('testtext 2 2 2', getline(1))
+
+ " test ":q!" and hidden buffer.
+ bwipe! Xtest1 Xtest2 Xtest3 Xtest4
+ split Xtest1
+ wincmd w
+ bwipe!
+ set modified
+ bot split Xtest2
+ set modified
+ bot split Xtest3
+ set modified
+ wincmd t
+ hide
+ call assert_equal('Xtest2', bufname('%'))
+ quit!
+ call assert_equal('Xtest3', bufname('%'))
+ call assert_fails('silent! quit!', 'E37:')
+ call assert_equal('Xtest1', bufname('%'))
+endfunc
+
+" Test that ":close" will respect 'winfixheight' when possible.
+func Test_winfixheight_on_close()
+ set nosplitbelow nosplitright
+
+ split | split | vsplit
+
+ $wincmd w
+ setlocal winfixheight
+ let l:height = winheight(0)
+
+ 3close
+
+ call assert_equal(l:height, winheight(0))
+
+ %bwipeout!
+ setlocal nowinfixheight splitbelow& splitright&
+endfunc
+
+" Test that ":close" will respect 'winfixwidth' when possible.
+func Test_winfixwidth_on_close()
+ set nosplitbelow nosplitright
+
+ vsplit | vsplit | split
+
+ $wincmd w
+ setlocal winfixwidth
+ let l:width = winwidth(0)
+
+ 3close
+
+ call assert_equal(l:width, winwidth(0))
+
+ %bwipeout!
+ setlocal nowinfixwidth splitbelow& splitright&
+endfunction
+
+" Test that 'winfixheight' will be respected even there is non-leaf frame
+func Test_winfixheight_non_leaf_frame()
+ vsplit
+ botright 11new
+ let l:wid = win_getid()
+ setlocal winfixheight
+ call assert_equal(11, winheight(l:wid))
+ botright new
+ bwipe!
+ call assert_equal(11, winheight(l:wid))
+ %bwipe!
+endf
+
+" Test that 'winfixwidth' will be respected even there is non-leaf frame
+func Test_winfixwidth_non_leaf_frame()
+ split
+ topleft 11vnew
+ let l:wid = win_getid()
+ setlocal winfixwidth
+ call assert_equal(11, winwidth(l:wid))
+ topleft new
+ bwipe!
+ call assert_equal(11, winwidth(l:wid))
+ %bwipe!
+endf
+
+func Test_tabwin_close()
+ enew
+ let l:wid = win_getid()
+ tabedit
+ call win_execute(l:wid, 'close')
+ " Should not crash.
+ call assert_true(v:true)
+
+ " This tests closing a window in another tab, while leaving the tab open
+ " i.e. two windows in another tab.
+ tabedit
+ let w:this_win = 42
+ new
+ let othertab_wid = win_getid()
+ tabprevious
+ call win_execute(othertab_wid, 'q')
+ " drawing the tabline helps check that the other tab's windows and buffers
+ " are still valid
+ redrawtabline
+ " but to be certain, ensure we can focus the other tab too
+ tabnext
+ call assert_equal(42, w:this_win)
+
+ bwipe!
+endfunc
+
+" Test when closing a split window (above/below) restores space to the window
+" below when 'noequalalways' and 'splitright' are set.
+func Test_window_close_splitright_noequalalways()
+ set noequalalways
+ set splitright
+ new
+ let w1 = win_getid()
+ new
+ let w2 = win_getid()
+ execute "normal \<c-w>b"
+ let h = winheight(0)
+ let w = win_getid()
+ new
+ q
+ call assert_equal(h, winheight(0), "Window height does not match eight before opening and closing another window")
+ call assert_equal(w, win_getid(), "Did not return to original window after opening and closing a window")
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_window_cmd.vim b/src/testdir/test_window_cmd.vim
new file mode 100644
index 0000000..3d29266
--- /dev/null
+++ b/src/testdir/test_window_cmd.vim
@@ -0,0 +1,1952 @@
+" Tests for window cmd (:wincmd, :split, :vsplit, :resize and etc...)
+
+source check.vim
+source screendump.vim
+
+func Test_window_cmd_ls0_with_split()
+ set ls=0
+ set splitbelow
+ split
+ quit
+ call assert_equal(0, &lines - &cmdheight - winheight(0))
+ new | only!
+ "
+ set splitbelow&vim
+ botright split
+ quit
+ call assert_equal(0, &lines - &cmdheight - winheight(0))
+ new | only!
+ set ls&vim
+endfunc
+
+func Test_window_cmd_cmdwin_with_vsp()
+ let efmt = 'Expected 0 but got %d (in ls=%d, %s window)'
+ for v in range(0, 2)
+ exec "set ls=" . v
+ vsplit
+ call feedkeys("q:\<CR>")
+ let ac = &lines - (&cmdheight + winheight(0) + !!v)
+ let emsg = printf(efmt, ac, v, 'left')
+ call assert_equal(0, ac, emsg)
+ wincmd w
+ let ac = &lines - (&cmdheight + winheight(0) + !!v)
+ let emsg = printf(efmt, ac, v, 'right')
+ call assert_equal(0, ac, emsg)
+ new | only!
+ endfor
+ set ls&vim
+endfunc
+
+func Test_cmdheight_not_changed()
+ set cmdheight=2
+ set winminheight=0
+ augroup Maximize
+ autocmd WinEnter * wincmd _
+ augroup END
+ split
+ tabnew
+ tabfirst
+ call assert_equal(2, &cmdheight)
+
+ tabonly!
+ only
+ set winminwidth& cmdheight&
+ augroup Maximize
+ au!
+ augroup END
+ augroup! Maximize
+endfunc
+
+" Test for jumping to windows
+func Test_window_jump()
+ new
+ " jumping to a window with a count greater than the max windows
+ exe "normal 4\<C-W>w"
+ call assert_equal(2, winnr())
+ only
+endfunc
+
+func Test_window_cmd_wincmd_gf()
+ let fname = 'test_gf.txt'
+ let swp_fname = '.' . fname . '.swp'
+ call writefile([], fname, 'D')
+ call writefile([], swp_fname, 'D')
+ function s:swap_exists()
+ let v:swapchoice = s:swap_choice
+ endfunc
+ " Remove the catch-all that runtest.vim adds
+ au! SwapExists
+ augroup test_window_cmd_wincmd_gf
+ autocmd!
+ exec "autocmd SwapExists " . fname . " call s:swap_exists()"
+ augroup END
+
+ call setline(1, fname)
+ " (E)dit anyway
+ let s:swap_choice = 'e'
+ wincmd gf
+ call assert_equal(2, tabpagenr())
+ call assert_equal(fname, bufname("%"))
+ quit!
+
+ " (Q)uit
+ let s:swap_choice = 'q'
+ wincmd gf
+ call assert_equal(1, tabpagenr())
+ call assert_notequal(fname, bufname("%"))
+ new | only!
+
+ augroup! test_window_cmd_wincmd_gf
+endfunc
+
+func Test_window_quit()
+ e Xa
+ split Xb
+ call assert_equal(2, '$'->winnr())
+ call assert_equal('Xb', bufname(winbufnr(1)))
+ call assert_equal('Xa', bufname(winbufnr(2)))
+
+ wincmd q
+ call assert_equal(1, winnr('$'))
+ call assert_equal('Xa', bufname(winbufnr(1)))
+
+ bw Xa Xb
+endfunc
+
+func Test_window_horizontal_split()
+ call assert_equal(1, winnr('$'))
+ 3wincmd s
+ call assert_equal(2, winnr('$'))
+ call assert_equal(3, winheight(0))
+ call assert_equal(winwidth(1), 2->winwidth())
+
+ call assert_fails('botright topleft wincmd s', 'E442:')
+ bw
+endfunc
+
+func Test_window_vertical_split()
+ call assert_equal(1, winnr('$'))
+ 3wincmd v
+ call assert_equal(2, winnr('$'))
+ call assert_equal(3, winwidth(0))
+ call assert_equal(winheight(1), winheight(2))
+
+ call assert_fails('botright topleft wincmd v', 'E442:')
+ bw
+endfunc
+
+" Test the ":wincmd ^" and "<C-W>^" commands.
+func Test_window_split_edit_alternate()
+ " Test for failure when the alternate buffer/file no longer exists.
+ edit Xfoo | %bw
+ call assert_fails(':wincmd ^', 'E23:')
+
+ " Test for the expected behavior when we have two named buffers.
+ edit Xfoo | edit Xbar
+ wincmd ^
+ call assert_equal('Xfoo', bufname(winbufnr(1)))
+ call assert_equal('Xbar', bufname(winbufnr(2)))
+ only
+
+ " Test for the expected behavior when the alternate buffer is not named.
+ enew | let l:nr1 = bufnr('%')
+ edit Xfoo | let l:nr2 = bufnr('%')
+ wincmd ^
+ call assert_equal(l:nr1, winbufnr(1))
+ call assert_equal(l:nr2, winbufnr(2))
+ only
+
+ " FIXME: this currently fails on AppVeyor, but passes locally
+ if !has('win32')
+ " Test the Normal mode command.
+ call feedkeys("\<C-W>\<C-^>", 'tx')
+ call assert_equal(l:nr2, winbufnr(1))
+ call assert_equal(l:nr1, winbufnr(2))
+ endif
+
+ %bw!
+endfunc
+
+" Test the ":[count]wincmd ^" and "[count]<C-W>^" commands.
+func Test_window_split_edit_bufnr()
+ %bwipeout
+ let l:nr = bufnr('%') + 1
+ call assert_fails(':execute "normal! ' . l:nr . '\<C-W>\<C-^>"', 'E92:')
+ call assert_fails(':' . l:nr . 'wincmd ^', 'E16:')
+ call assert_fails(':0wincmd ^', 'E16:')
+
+ edit Xfoo | edit Xbar | edit Xbaz
+ let l:foo_nr = bufnr('Xfoo')
+ let l:bar_nr = bufnr('Xbar')
+ let l:baz_nr = bufnr('Xbaz')
+
+ " FIXME: this currently fails on AppVeyor, but passes locally
+ if !has('win32')
+ call feedkeys(l:foo_nr . "\<C-W>\<C-^>", 'tx')
+ call assert_equal('Xfoo', bufname(winbufnr(1)))
+ call assert_equal('Xbaz', bufname(winbufnr(2)))
+ only
+
+ call feedkeys(l:bar_nr . "\<C-W>\<C-^>", 'tx')
+ call assert_equal('Xbar', bufname(winbufnr(1)))
+ call assert_equal('Xfoo', bufname(winbufnr(2)))
+ only
+
+ execute l:baz_nr . 'wincmd ^'
+ call assert_equal('Xbaz', bufname(winbufnr(1)))
+ call assert_equal('Xbar', bufname(winbufnr(2)))
+ endif
+
+ %bw!
+endfunc
+
+func Test_window_split_no_room()
+ " N horizontal windows need >= 2*N + 1 lines:
+ " - 1 line + 1 status line in each window
+ " - 1 Ex command line
+ "
+ " 2*N + 1 <= &lines
+ " N <= (lines - 1)/2
+ "
+ " Beyond that number of windows, E36: Not enough room is expected.
+ let hor_win_count = (&lines - 1)/2
+ let hor_split_count = hor_win_count - 1
+ for s in range(1, hor_split_count) | split | endfor
+ call assert_fails('split', 'E36:')
+
+ " N vertical windows need >= 2*(N - 1) + 1 columns:
+ " - 1 column + 1 separator for each window (except last window)
+ " - 1 column for the last window which does not have separator
+ "
+ " 2*(N - 1) + 1 <= &columns
+ " 2*N - 1 <= &columns
+ " N <= (&columns + 1)/2
+ let ver_win_count = (&columns + 1)/2
+ let ver_split_count = ver_win_count - 1
+ for s in range(1, ver_split_count) | vsplit | endfor
+ call assert_fails('vsplit', 'E36:')
+
+ %bw!
+endfunc
+
+func Test_window_exchange()
+ e Xa
+
+ " Nothing happens with window exchange when there is 1 window
+ wincmd x
+ call assert_equal(1, winnr('$'))
+
+ split Xb
+ split Xc
+
+ call assert_equal('Xc', bufname(winbufnr(1)))
+ call assert_equal('Xb', bufname(winbufnr(2)))
+ call assert_equal('Xa', bufname(winbufnr(3)))
+
+ " Exchange current window 1 with window 3
+ 3wincmd x
+ call assert_equal('Xa', bufname(winbufnr(1)))
+ call assert_equal('Xb', bufname(winbufnr(2)))
+ call assert_equal('Xc', bufname(winbufnr(3)))
+
+ " Exchange window with next when at the top window
+ wincmd x
+ call assert_equal('Xb', bufname(winbufnr(1)))
+ call assert_equal('Xa', bufname(winbufnr(2)))
+ call assert_equal('Xc', bufname(winbufnr(3)))
+
+ " Exchange window with next when at the middle window
+ wincmd j
+ wincmd x
+ call assert_equal('Xb', bufname(winbufnr(1)))
+ call assert_equal('Xc', bufname(winbufnr(2)))
+ call assert_equal('Xa', bufname(winbufnr(3)))
+
+ " Exchange window with next when at the bottom window.
+ " When there is no next window, it exchanges with the previous window.
+ wincmd j
+ wincmd x
+ call assert_equal('Xb', bufname(winbufnr(1)))
+ call assert_equal('Xa', bufname(winbufnr(2)))
+ call assert_equal('Xc', bufname(winbufnr(3)))
+
+ bw Xa Xb Xc
+endfunc
+
+func Test_window_rotate()
+ e Xa
+ split Xb
+ split Xc
+ call assert_equal('Xc', bufname(winbufnr(1)))
+ call assert_equal('Xb', bufname(winbufnr(2)))
+ call assert_equal('Xa', bufname(winbufnr(3)))
+
+ " Rotate downwards
+ wincmd r
+ call assert_equal('Xa', bufname(winbufnr(1)))
+ call assert_equal('Xc', bufname(winbufnr(2)))
+ call assert_equal('Xb', bufname(winbufnr(3)))
+
+ 2wincmd r
+ call assert_equal('Xc', bufname(winbufnr(1)))
+ call assert_equal('Xb', bufname(winbufnr(2)))
+ call assert_equal('Xa', bufname(winbufnr(3)))
+
+ " Rotate upwards
+ wincmd R
+ call assert_equal('Xb', bufname(winbufnr(1)))
+ call assert_equal('Xa', bufname(winbufnr(2)))
+ call assert_equal('Xc', bufname(winbufnr(3)))
+
+ 2wincmd R
+ call assert_equal('Xc', bufname(winbufnr(1)))
+ call assert_equal('Xb', bufname(winbufnr(2)))
+ call assert_equal('Xa', bufname(winbufnr(3)))
+
+ bot vsplit
+ call assert_fails('wincmd R', 'E443:')
+
+ bw Xa Xb Xc
+endfunc
+
+func Test_window_height()
+ e Xa
+ split Xb
+
+ let [wh1, wh2] = [winheight(1), winheight(2)]
+ " Active window (1) should have the same height or 1 more
+ " than the other window.
+ call assert_inrange(wh2, wh2 + 1, wh1)
+
+ wincmd -
+ call assert_equal(wh1 - 1, winheight(1))
+ call assert_equal(wh2 + 1, winheight(2))
+
+ wincmd +
+ call assert_equal(wh1, winheight(1))
+ call assert_equal(wh2, 2->winheight())
+
+ 2wincmd _
+ call assert_equal(2, winheight(1))
+ call assert_equal(wh1 + wh2 - 2, winheight(2))
+
+ wincmd =
+ call assert_equal(wh1, winheight(1))
+ call assert_equal(wh2, winheight(2))
+
+ 2wincmd _
+ set winfixheight
+ split Xc
+ let [wh1, wh2, wh3] = [winheight(1), winheight(2), winheight(3)]
+ call assert_equal(2, winheight(2))
+ call assert_inrange(wh3, wh3 + 1, wh1)
+ 3wincmd +
+ call assert_equal(2, winheight(2))
+ call assert_equal(wh1 + 3, winheight(1))
+ call assert_equal(wh3 - 3, winheight(3))
+ wincmd =
+ call assert_equal(2, winheight(2))
+ call assert_equal(wh1, winheight(1))
+ call assert_equal(wh3, winheight(3))
+
+ wincmd j
+ set winfixheight&
+
+ wincmd =
+ let [wh1, wh2, wh3] = [winheight(1), winheight(2), winheight(3)]
+ " Current window (2) should have the same height or 1 more
+ " than the other windows.
+ call assert_inrange(wh1, wh1 + 1, wh2)
+ call assert_inrange(wh3, wh3 + 1, wh2)
+
+ bw Xa Xb Xc
+endfunc
+
+func Test_wincmd_equal()
+ edit Xone
+ below split Xtwo
+ rightbelow vsplit Xthree
+ call assert_equal('Xone', bufname(winbufnr(1)))
+ call assert_equal('Xtwo', bufname(winbufnr(2)))
+ call assert_equal('Xthree', bufname(winbufnr(3)))
+
+ " Xone and Xtwo should be about the same height
+ let [wh1, wh2] = [winheight(1), winheight(2)]
+ call assert_inrange(wh1 - 1, wh1 + 1, wh2)
+ " Xtwo and Xthree should be about the same width
+ let [ww2, ww3] = [winwidth(2), winwidth(3)]
+ call assert_inrange(ww2 - 1, ww2 + 1, ww3)
+
+ 1wincmd w
+ 10wincmd _
+ 2wincmd w
+ 20wincmd |
+ call assert_equal(10, winheight(1))
+ call assert_equal(20, winwidth(2))
+
+ " equalizing horizontally doesn't change the heights
+ hor wincmd =
+ call assert_equal(10, winheight(1))
+ let [ww2, ww3] = [winwidth(2), winwidth(3)]
+ call assert_inrange(ww2 - 1, ww2 + 1, ww3)
+
+ 2wincmd w
+ 20wincmd |
+ call assert_equal(20, winwidth(2))
+ " equalizing vertically doesn't change the widths
+ vert wincmd =
+ call assert_equal(20, winwidth(2))
+ let [wh1, wh2] = [winheight(1), winheight(2)]
+ call assert_inrange(wh1 - 1, wh1 + 1, wh2)
+
+ bwipe Xone Xtwo Xthree
+endfunc
+
+func Test_window_width()
+ e Xa
+ vsplit Xb
+
+ let [ww1, ww2] = [winwidth(1), winwidth(2)]
+ " Active window (1) should have the same width or 1 more
+ " than the other window.
+ call assert_inrange(ww2, ww2 + 1, ww1)
+
+ wincmd <
+ call assert_equal(ww1 - 1, winwidth(1))
+ call assert_equal(ww2 + 1, winwidth(2))
+
+ wincmd >
+ call assert_equal(ww1, winwidth(1))
+ call assert_equal(ww2, winwidth(2))
+
+ 2wincmd |
+ call assert_equal(2, winwidth(1))
+ call assert_equal(ww1 + ww2 - 2, winwidth(2))
+
+ wincmd =
+ call assert_equal(ww1, winwidth(1))
+ call assert_equal(ww2, winwidth(2))
+
+ 2wincmd |
+ set winfixwidth
+ vsplit Xc
+ let [ww1, ww2, ww3] = [winwidth(1), winwidth(2), winwidth(3)]
+ call assert_equal(2, winwidth(2))
+ call assert_inrange(ww3, ww3 + 1, ww1)
+ 3wincmd >
+ call assert_equal(2, winwidth(2))
+ call assert_equal(ww1 + 3, winwidth(1))
+ call assert_equal(ww3 - 3, winwidth(3))
+ wincmd =
+ call assert_equal(2, winwidth(2))
+ call assert_equal(ww1, winwidth(1))
+ call assert_equal(ww3, winwidth(3))
+
+ wincmd l
+ set winfixwidth&
+
+ wincmd =
+ let [ww1, ww2, ww3] = [winwidth(1), winwidth(2), winwidth(3)]
+ " Current window (2) should have the same width or 1 more
+ " than the other windows.
+ call assert_inrange(ww1, ww1 + 1, ww2)
+ call assert_inrange(ww3, ww3 + 1, ww2)
+
+ " when the current window width is less than the new 'winwidth', the current
+ " window width should be increased.
+ enew | only
+ split
+ 10vnew
+ set winwidth=15
+ call assert_equal(15, winwidth(0))
+
+ %bw!
+endfunc
+
+func Test_equalalways_on_close()
+ set equalalways
+ vsplit
+ windo split
+ split
+ wincmd J
+ " now we have a frame top-left with two windows, a frame top-right with two
+ " windows and a frame at the bottom, full-width.
+ let height_1 = winheight(1)
+ let height_2 = winheight(2)
+ let height_3 = winheight(3)
+ let height_4 = winheight(4)
+ " closing the bottom window causes all windows to be resized.
+ close
+ call assert_notequal(height_1, winheight(1))
+ call assert_notequal(height_2, winheight(2))
+ call assert_notequal(height_3, winheight(3))
+ call assert_notequal(height_4, winheight(4))
+ call assert_equal(winheight(1), winheight(3))
+ call assert_equal(winheight(2), winheight(4))
+
+ 1wincmd w
+ split
+ 4wincmd w
+ resize + 5
+ " left column has three windows, equalized heights.
+ " right column has two windows, top one a bit higher
+ let height_1 = winheight(1)
+ let height_2 = winheight(2)
+ let height_4 = winheight(4)
+ let height_5 = winheight(5)
+ 3wincmd w
+ " closing window in left column equalizes heights in left column but not in
+ " the right column
+ close
+ call assert_notequal(height_1, winheight(1))
+ call assert_notequal(height_2, winheight(2))
+ call assert_equal(height_4, winheight(3))
+ call assert_equal(height_5, winheight(4))
+
+ only
+ set equalalways&
+endfunc
+
+func Test_win_screenpos()
+ CheckFeature quickfix
+
+ call assert_equal(1, winnr('$'))
+ split
+ vsplit
+ 10wincmd _
+ 30wincmd |
+ call assert_equal([1, 1], win_screenpos(1))
+ call assert_equal([1, 32], win_screenpos(2))
+ call assert_equal([12, 1], win_screenpos(3))
+ call assert_equal([0, 0], win_screenpos(4))
+ call assert_fails('let l = win_screenpos([])', 'E745:')
+ only
+endfunc
+
+func Test_window_jump_tag()
+ CheckFeature quickfix
+
+ help
+ /iccf
+ call assert_match('^|iccf|', getline('.'))
+ call assert_equal(2, winnr('$'))
+ 2wincmd }
+ call assert_equal(3, winnr('$'))
+ call assert_match('^|iccf|', getline('.'))
+ wincmd k
+ call assert_match('\*iccf\*', getline('.'))
+ call assert_equal(2, winheight(0))
+
+ wincmd z
+ set previewheight=4
+ help
+ /bugs
+ wincmd }
+ wincmd k
+ call assert_match('\*bugs\*', getline('.'))
+ call assert_equal(4, winheight(0))
+ set previewheight&
+
+ %bw!
+endfunc
+
+func Test_window_newtab()
+ e Xa
+
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal("\nAlready only one window", execute('wincmd T'))
+
+ split Xb
+ split Xc
+
+ wincmd T
+ call assert_equal(2, tabpagenr('$'))
+ call assert_equal(['Xb', 'Xa'], map(tabpagebuflist(1), 'bufname(v:val)'))
+ call assert_equal(['Xc' ], map(2->tabpagebuflist(), 'bufname(v:val)'))
+ call assert_equal(['Xc' ], map(tabpagebuflist(), 'bufname(v:val)'))
+
+ %bw!
+endfunc
+
+func Test_next_split_all()
+ " This was causing an illegal memory access.
+ n x
+ norm axxx
+ split
+ split
+ s/x
+ s/x
+ all
+ bwipe!
+endfunc
+
+" Tests for adjusting window and contents
+func GetScreenStr(row)
+ let str = ""
+ for c in range(1,3)
+ let str .= nr2char(screenchar(a:row, c))
+ endfor
+ return str
+endfunc
+
+func Test_window_contents()
+ enew! | only | new
+ call setline(1, range(1,256))
+
+ exe "norm! \<C-W>t\<C-W>=1Gzt\<C-W>w\<C-W>+"
+ redraw
+ let s3 = GetScreenStr(1)
+ wincmd p
+ call assert_equal(1, line("w0"))
+ call assert_equal('1 ', s3)
+
+ exe "norm! \<C-W>t\<C-W>=50Gzt\<C-W>w\<C-W>+"
+ redraw
+ let s3 = GetScreenStr(1)
+ wincmd p
+ call assert_equal(50, line("w0"))
+ call assert_equal('50 ', s3)
+
+ exe "norm! \<C-W>t\<C-W>=59Gzt\<C-W>w\<C-W>+"
+ redraw
+ let s3 = GetScreenStr(1)
+ wincmd p
+ call assert_equal(59, line("w0"))
+ call assert_equal('59 ', s3)
+
+ %d
+ call setline(1, ['one', 'two', 'three'])
+ call assert_equal(1, line('w0'))
+ call assert_equal(3, line('w$'))
+
+ bwipeout!
+ call test_garbagecollect_now()
+endfunc
+
+func Test_window_colon_command()
+ " This was reading invalid memory.
+ exe "norm! v\<C-W>:\<C-U>echo v:version"
+endfunc
+
+func Test_access_freed_mem()
+ call assert_equal(&columns, winwidth(0))
+ " This was accessing freed memory (but with what events?)
+ au BufEnter,BufLeave,WinEnter,WinLeave 0 vs xxx
+ arg 0
+ argadd
+ call assert_fails("all", "E242:")
+ au!
+ bwipe xxx
+ call assert_equal(&columns, winwidth(0))
+endfunc
+
+func Test_insert_cleared_on_switch_to_term()
+ CheckFeature terminal
+
+ set showmode
+ terminal
+ wincmd p
+
+ call feedkeys("i\<C-O>", 'ntx')
+ redraw
+
+ " The "-- (insert) --" indicator should be visible.
+ let chars = map(range(1, &columns), 'nr2char(screenchar(&lines, v:val))')
+ let str = trim(join(chars, ''))
+ call assert_equal('-- (insert) --', str)
+
+ call feedkeys("\<C-W>p", 'ntx')
+ redraw
+
+ " The "-- (insert) --" indicator should have been cleared.
+ let chars = map(range(1, &columns), 'nr2char(screenchar(&lines, v:val))')
+ let str = trim(join(chars, ''))
+ call assert_equal('', str)
+
+ set showmode&
+ %bw!
+endfunc
+
+func Test_visual_cleared_after_window_split()
+ new | only!
+ let smd_save = &showmode
+ set showmode
+ let ls_save = &laststatus
+ set laststatus=1
+ call setline(1, ['a', 'b', 'c', 'd', ''])
+ norm! G
+ exe "norm! kkvk"
+ redraw
+ exe "norm! \<C-W>v"
+ redraw
+ " check if '-- VISUAL --' disappeared from command line
+ let columns = range(1, &columns)
+ let cmdlinechars = map(columns, 'nr2char(screenchar(&lines, v:val))')
+ let cmdline = join(cmdlinechars, '')
+ let cmdline_ltrim = substitute(cmdline, '^\s*', "", "")
+ let mode_shown = substitute(cmdline_ltrim, '\s*$', "", "")
+ call assert_equal('', mode_shown)
+ let &showmode = smd_save
+ let &laststatus = ls_save
+ bwipe!
+endfunc
+
+func Test_winrestcmd()
+ 2split
+ 3vsplit
+ let restcmd = winrestcmd()
+ call assert_equal(2, winheight(0))
+ call assert_equal(3, winwidth(0))
+ wincmd =
+ call assert_notequal(2, winheight(0))
+ call assert_notequal(3, winwidth(0))
+ exe restcmd
+ call assert_equal(2, winheight(0))
+ call assert_equal(3, winwidth(0))
+ only
+
+ wincmd v
+ wincmd s
+ wincmd v
+ redraw
+ let restcmd = winrestcmd()
+ wincmd _
+ wincmd |
+ exe restcmd
+ redraw
+ call assert_equal(restcmd, winrestcmd())
+
+ only
+endfunc
+
+func Fun_RenewFile()
+ " Need to wait a bit for the timestamp to be older.
+ let old_ftime = getftime("tmp.txt")
+ while getftime("tmp.txt") == old_ftime
+ sleep 100m
+ silent execute '!echo "1" > tmp.txt'
+ endwhile
+ sp
+ wincmd p
+ edit! tmp.txt
+endfunc
+
+func Test_window_prevwin()
+ " Can we make this work on MS-Windows?
+ CheckUnix
+
+ set hidden autoread
+ call writefile(['2'], 'tmp.txt', 'D')
+ new tmp.txt
+ q
+ call Fun_RenewFile()
+ call assert_equal(2, winnr())
+ wincmd p
+ call assert_equal(1, winnr())
+ wincmd p
+ q
+ call Fun_RenewFile()
+ call assert_equal(2, winnr())
+ wincmd p
+ call assert_equal(1, winnr())
+ wincmd p
+ " reset
+ q
+ set hidden&vim autoread&vim
+ delfunc Fun_RenewFile
+endfunc
+
+func Test_relative_cursor_position_in_one_line_window()
+ new
+ only
+ call setline(1, range(1, 10000))
+ normal 50%
+ let lnum = getcurpos()[1]
+ split
+ split
+ " make third window take as many lines as possible, other windows will
+ " become one line
+ 3wincmd w
+ for i in range(1, &lines - 6)
+ wincmd +
+ redraw!
+ endfor
+
+ " first and second window should show cursor line
+ let wininfo = getwininfo()
+ call assert_equal(lnum, wininfo[0].topline)
+ call assert_equal(lnum, wininfo[1].topline)
+
+ only!
+ bwipe!
+ call assert_fails('call winrestview(test_null_dict())', 'E1297:')
+endfunc
+
+func Test_relative_cursor_position_after_move_and_resize()
+ let so_save = &so
+ set so=0
+ enew
+ call setline(1, range(1, 10000))
+ normal 50%
+ split
+ 1wincmd w
+ " Move cursor to first line in window
+ normal H
+ redraw!
+ " Reduce window height to two lines
+ let height = winheight(0)
+ while winheight(0) > 2
+ wincmd -
+ redraw!
+ endwhile
+ " move cursor to second/last line in window
+ normal j
+ " restore previous height
+ while winheight(0) < height
+ wincmd +
+ redraw!
+ endwhile
+ " make window two lines again
+ while winheight(0) > 2
+ wincmd -
+ redraw!
+ endwhile
+
+ " cursor should be at bottom line
+ let info = getwininfo(win_getid())[0]
+ call assert_equal(info.topline + 1, getcurpos()[1])
+
+ only!
+ bwipe!
+ let &so = so_save
+endfunc
+
+func Test_relative_cursor_position_after_resize()
+ let so_save = &so
+ set so=0
+ enew
+ call setline(1, range(1, 10000))
+ normal 50%
+ split
+ 1wincmd w
+ let winid1 = win_getid()
+ let info = getwininfo(winid1)[0]
+ " Move cursor to second line in window
+ exe "normal " . (info.topline + 1) . "G"
+ redraw!
+ let lnum = getcurpos()[1]
+
+ " Make the window only two lines high, cursor should end up in top line
+ 2wincmd w
+ exe (info.height - 2) . "wincmd +"
+ redraw!
+ let info = getwininfo(winid1)[0]
+ call assert_equal(lnum, info.topline)
+
+ only!
+ bwipe!
+ let &so = so_save
+endfunc
+
+func Test_relative_cursor_second_line_after_resize()
+ let so_save = &so
+ set so=0
+ enew
+ call setline(1, range(1, 10000))
+ normal 50%
+ split
+ 1wincmd w
+ let winid1 = win_getid()
+ let info = getwininfo(winid1)[0]
+
+ " Make the window only two lines high
+ 2wincmd _
+
+ " Move cursor to second line in window
+ normal H
+ normal j
+
+ " Make window size bigger, then back to 2 lines
+ for i in range(1, 10)
+ wincmd +
+ redraw!
+ endfor
+ for i in range(1, 10)
+ wincmd -
+ redraw!
+ endfor
+
+ " cursor should end up in bottom line
+ let info = getwininfo(winid1)[0]
+ call assert_equal(info.topline + 1, getcurpos()[1])
+
+ only!
+ bwipe!
+ let &so = so_save
+endfunc
+
+func Test_split_noscroll()
+ let so_save = &so
+ enew
+ call setline(1, range(1, 8))
+ normal 100%
+ split
+
+ 1wincmd w
+ let winid1 = win_getid()
+ let info1 = getwininfo(winid1)[0]
+
+ 2wincmd w
+ let winid2 = win_getid()
+ let info2 = getwininfo(winid2)[0]
+
+ call assert_equal(1, info1.topline)
+ call assert_equal(1, info2.topline)
+
+ " window that fits all lines by itself, but not when split: closing other
+ " window should restore fraction.
+ only!
+ call setline(1, range(1, &lines - 10))
+ exe &lines / 4
+ let winid1 = win_getid()
+ let info1 = getwininfo(winid1)[0]
+ call assert_equal(1, info1.topline)
+ new
+ redraw
+ close
+ let info1 = getwininfo(winid1)[0]
+ call assert_equal(1, info1.topline)
+
+ bwipe!
+ let &so = so_save
+endfunc
+
+" Tests for the winnr() function
+func Test_winnr()
+ only | tabonly
+ call assert_equal(1, winnr('j'))
+ call assert_equal(1, winnr('k'))
+ call assert_equal(1, winnr('h'))
+ call assert_equal(1, winnr('l'))
+
+ " create a set of horizontally and vertically split windows
+ leftabove new | wincmd p
+ leftabove new | wincmd p
+ rightbelow new | wincmd p
+ rightbelow new | wincmd p
+ leftabove vnew | wincmd p
+ leftabove vnew | wincmd p
+ rightbelow vnew | wincmd p
+ rightbelow vnew | wincmd p
+
+ call assert_equal(8, winnr('j'))
+ call assert_equal(2, winnr('k'))
+ call assert_equal(4, winnr('h'))
+ call assert_equal(6, winnr('l'))
+ call assert_equal(9, winnr('2j'))
+ call assert_equal(1, winnr('2k'))
+ call assert_equal(3, winnr('2h'))
+ call assert_equal(7, winnr('2l'))
+
+ " Error cases
+ call assert_fails("echo winnr('0.2k')", 'E15:')
+ call assert_equal(2, winnr('-2k'))
+ call assert_fails("echo winnr('-2xj')", 'E15:')
+ call assert_fails("echo winnr('j2j')", 'E15:')
+ call assert_fails("echo winnr('ll')", 'E15:')
+ call assert_fails("echo winnr('5')", 'E15:')
+ call assert_equal(4, winnr('0h'))
+ call assert_fails("let w = winnr([])", 'E730:')
+ call assert_equal('unknown', win_gettype(-1))
+ call assert_equal(-1, winheight(-1))
+ call assert_equal(-1, winwidth(-1))
+
+ tabnew
+ call assert_equal(8, tabpagewinnr(1, 'j'))
+ call assert_equal(2, 1->tabpagewinnr('k'))
+ call assert_equal(4, tabpagewinnr(1, 'h'))
+ call assert_equal(6, tabpagewinnr(1, 'l'))
+
+ only | tabonly
+endfunc
+
+func Test_winrestview()
+ split runtest.vim
+ normal 50%
+ let view = winsaveview()
+ close
+ split runtest.vim
+ eval view->winrestview()
+ call assert_equal(view, winsaveview())
+
+ bwipe!
+ call assert_fails('call winrestview(test_null_dict())', 'E1297:')
+endfunc
+
+func Test_win_splitmove()
+ CheckFeature quickfix
+
+ edit a
+ leftabove split b
+ leftabove vsplit c
+ leftabove split d
+ call assert_equal(0, win_splitmove(winnr(), winnr('l')))
+ call assert_equal(bufname(winbufnr(1)), 'c')
+ call assert_equal(bufname(winbufnr(2)), 'd')
+ call assert_equal(bufname(winbufnr(3)), 'b')
+ call assert_equal(bufname(winbufnr(4)), 'a')
+ call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1}))
+ call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1}))
+ call assert_equal(bufname(winbufnr(1)), 'c')
+ call assert_equal(bufname(winbufnr(2)), 'b')
+ call assert_equal(bufname(winbufnr(3)), 'd')
+ call assert_equal(bufname(winbufnr(4)), 'a')
+ call assert_equal(0, win_splitmove(winnr(), winnr('k'), {'vertical': 1}))
+ call assert_equal(bufname(winbufnr(1)), 'd')
+ call assert_equal(bufname(winbufnr(2)), 'c')
+ call assert_equal(bufname(winbufnr(3)), 'b')
+ call assert_equal(bufname(winbufnr(4)), 'a')
+ call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'rightbelow': v:true}))
+ call assert_equal(bufname(winbufnr(1)), 'c')
+ call assert_equal(bufname(winbufnr(2)), 'b')
+ call assert_equal(bufname(winbufnr(3)), 'a')
+ call assert_equal(bufname(winbufnr(4)), 'd')
+ call assert_fails('call win_splitmove(winnr(), winnr("k"), test_null_dict())', 'E1297:')
+ only | bd
+
+ call assert_fails('call win_splitmove(winnr(), 123)', 'E957:')
+ call assert_fails('call win_splitmove(123, winnr())', 'E957:')
+ call assert_fails('call win_splitmove(winnr(), winnr())', 'E957:')
+
+ tabnew
+ call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:')
+ tabclose
+endfunc
+
+" Test for the :only command
+func Test_window_only()
+ new
+ set modified
+ new
+ call assert_fails('only', 'E445:')
+ only!
+ " Test for :only with a count
+ let wid = win_getid()
+ new
+ new
+ 3only
+ call assert_equal(1, winnr('$'))
+ call assert_equal(wid, win_getid())
+ call assert_fails('close', 'E444:')
+ call assert_fails('%close', 'E16:')
+endfunc
+
+" Test for errors with :wincmd
+func Test_wincmd_errors()
+ call assert_fails('wincmd g', 'E474:')
+ call assert_fails('wincmd ab', 'E474:')
+endfunc
+
+" Test for errors with :winpos
+func Test_winpos_errors()
+ if !has("gui_running") && !has('win32')
+ call assert_fails('winpos', 'E188:')
+ endif
+ call assert_fails('winpos 10', 'E466:')
+endfunc
+
+" Test for +cmd in a :split command
+func Test_split_cmd()
+ split +set\ readonly
+ call assert_equal(1, &readonly)
+ call assert_equal(2, winnr('$'))
+ close
+endfunc
+
+" Create maximum number of horizontally or vertically split windows and then
+" run commands that create a new horizontally/vertically split window
+func Run_noroom_for_newwindow_test(dir_arg)
+ let dir = (a:dir_arg == 'v') ? 'vert ' : ''
+
+ " Open as many windows as possible
+ while v:true
+ try
+ exe dir . 'new'
+ catch /E36:/
+ break
+ endtry
+ endwhile
+
+ call writefile(['first', 'second', 'third'], 'Xnorfile1', 'D')
+ call writefile([], 'Xnorfile2', 'D')
+ call writefile([], 'Xnorfile3', 'D')
+
+ " Argument list related commands
+ args Xnorfile1 Xnorfile2 Xnorfile3
+ next
+ for cmd in ['sargument 2', 'snext', 'sprevious', 'sNext', 'srewind',
+ \ 'sfirst', 'slast']
+ call assert_fails(dir .. cmd, 'E36:')
+ endfor
+ %argdelete
+
+ " Buffer related commands
+ set modified
+ hide enew
+ for cmd in ['sbuffer Xnorfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind',
+ \ 'sbfirst', 'sblast', 'sball', 'sbmodified', 'sunhide']
+ call assert_fails(dir .. cmd, 'E36:')
+ endfor
+
+ " Window related commands
+ for cmd in ['split', 'split Xnorfile2', 'new', 'new Xnorfile3', 'sview Xnorfile1',
+ \ 'sfind runtest.vim']
+ call assert_fails(dir .. cmd, 'E36:')
+ endfor
+
+ " Help
+ call assert_fails(dir .. 'help', 'E36:')
+ call assert_fails(dir .. 'helpgrep window', 'E36:')
+
+ " Command-line window
+ if a:dir_arg == 'h'
+ " Cmd-line window is always a horizontally split window
+ call assert_beeps('call feedkeys("q:\<CR>", "xt")')
+ endif
+
+ " Quickfix and location list window
+ if has('quickfix')
+ cexpr ''
+ call assert_fails(dir .. 'copen', 'E36:')
+ lexpr ''
+ call assert_fails(dir .. 'lopen', 'E36:')
+
+ " Preview window
+ call assert_fails(dir .. 'pedit Xnorfile2', 'E36:')
+ call setline(1, 'abc')
+ call assert_fails(dir .. 'psearch abc', 'E36:')
+ endif
+
+ " Window commands (CTRL-W ^ and CTRL-W f)
+ if a:dir_arg == 'h'
+ call assert_fails('call feedkeys("\<C-W>^", "xt")', 'E36:')
+ call setline(1, 'Xnorfile1')
+ call assert_fails('call feedkeys("gg\<C-W>f", "xt")', 'E36:')
+ endif
+ enew!
+
+ " Tag commands (:stag, :stselect and :stjump)
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "second\tXnorfile1\t2",
+ \ "third\tXnorfile1\t3",],
+ \ 'Xtags')
+ set tags=Xtags
+ call assert_fails(dir .. 'stag second', 'E36:')
+ call assert_fails('call feedkeys(":" .. dir .. "stselect second\n1\n", "xt")', 'E36:')
+ call assert_fails(dir .. 'stjump second', 'E36:')
+ call assert_fails(dir .. 'ptag second', 'E36:')
+ set tags&
+ call delete('Xtags')
+
+ " :isplit and :dsplit
+ call setline(1, ['#define FOO 1', 'FOO'])
+ normal 2G
+ call assert_fails(dir .. 'isplit FOO', 'E36:')
+ call assert_fails(dir .. 'dsplit FOO', 'E36:')
+
+ " terminal
+ if has('terminal')
+ call assert_fails(dir .. 'terminal', 'E36:')
+ endif
+
+ %bwipe!
+ only
+endfunc
+
+func Test_split_cmds_with_no_room()
+ call Run_noroom_for_newwindow_test('h')
+ call Run_noroom_for_newwindow_test('v')
+endfunc
+
+" Test for various wincmd failures
+func Test_wincmd_fails()
+ only!
+ call assert_beeps("normal \<C-W>w")
+ call assert_beeps("normal \<C-W>p")
+ call assert_beeps("normal \<C-W>gk")
+ call assert_beeps("normal \<C-W>r")
+ call assert_beeps("normal \<C-W>K")
+ call assert_beeps("normal \<C-W>H")
+ call assert_beeps("normal \<C-W>2gt")
+endfunc
+
+func Test_window_resize()
+ " Vertical :resize (absolute, relative, min and max size).
+ vsplit
+ vert resize 8
+ call assert_equal(8, winwidth(0))
+ vert resize +2
+ call assert_equal(10, winwidth(0))
+ vert resize -2
+ call assert_equal(8, winwidth(0))
+ vert resize
+ call assert_equal(&columns - 2, winwidth(0))
+ vert resize 0
+ call assert_equal(1, winwidth(0))
+ vert resize 99999
+ call assert_equal(&columns - 2, winwidth(0))
+
+ %bwipe!
+
+ " Horizontal :resize (with absolute, relative size, min and max size).
+ split
+ resize 8
+ call assert_equal(8, winheight(0))
+ resize +2
+ call assert_equal(10, winheight(0))
+ resize -2
+ call assert_equal(8, winheight(0))
+ resize
+ call assert_equal(&lines - 4, winheight(0))
+ resize 0
+ call assert_equal(1, winheight(0))
+ resize 99999
+ call assert_equal(&lines - 4, winheight(0))
+
+ " :resize with explicit window number.
+ let other_winnr = winnr('j')
+ exe other_winnr .. 'resize 10'
+ call assert_equal(10, winheight(other_winnr))
+ call assert_equal(&lines - 10 - 3, winheight(0))
+ exe other_winnr .. 'resize +1'
+ exe other_winnr .. 'resize +1'
+ call assert_equal(12, winheight(other_winnr))
+ call assert_equal(&lines - 10 - 3 -2, winheight(0))
+ close
+
+ vsplit
+ wincmd l
+ let other_winnr = winnr('h')
+ call assert_notequal(winnr(), other_winnr)
+ exe 'vert ' .. other_winnr .. 'resize -' .. &columns
+ call assert_equal(0, winwidth(other_winnr))
+
+ %bwipe!
+endfunc
+
+" Test for adjusting the window width when a window is closed with some
+" windows using 'winfixwidth'
+func Test_window_width_adjust()
+ only
+ " Three vertical windows. Windows 1 and 2 have 'winfixwidth' set and close
+ " window 2.
+ wincmd v
+ vert resize 10
+ set winfixwidth
+ wincmd v
+ set winfixwidth
+ wincmd c
+ call assert_inrange(10, 12, winwidth(1))
+ " Three vertical windows. Windows 2 and 3 have 'winfixwidth' set and close
+ " window 3.
+ only
+ set winfixwidth
+ wincmd v
+ vert resize 10
+ set winfixwidth
+ wincmd v
+ set nowinfixwidth
+ wincmd b
+ wincmd c
+ call assert_inrange(10, 12, winwidth(2))
+
+ new | only
+endfunc
+
+" Test for jumping to a vertical/horizontal neighbor window based on the
+" current cursor position
+func Test_window_goto_neighbor()
+ %bw!
+
+ " Vertical window movement
+
+ " create the following window layout:
+ " +--+--+
+ " |w1|w3|
+ " +--+ |
+ " |w2| |
+ " +--+--+
+ " |w4 |
+ " +-----+
+ new
+ vsplit
+ split
+ " vertically jump from w4
+ wincmd b
+ call setline(1, repeat(' ', &columns))
+ call cursor(1, 1)
+ wincmd k
+ call assert_equal(2, winnr())
+ wincmd b
+ call cursor(1, &columns)
+ redraw!
+ wincmd k
+ call assert_equal(3, winnr())
+ %bw!
+
+ " create the following window layout:
+ " +--+--+--+
+ " |w1|w2|w3|
+ " +--+--+--+
+ " |w4 |
+ " +--------+
+ new
+ vsplit
+ vsplit
+ wincmd b
+ call setline(1, repeat(' ', &columns))
+ call cursor(1, 1)
+ wincmd k
+ call assert_equal(1, winnr())
+ wincmd b
+ call cursor(1, &columns / 2)
+ redraw!
+ wincmd k
+ call assert_equal(2, winnr())
+ wincmd b
+ call cursor(1, &columns)
+ redraw!
+ wincmd k
+ call assert_equal(3, winnr())
+ %bw!
+
+ " Horizontal window movement
+
+ " create the following window layout:
+ " +--+--+--+
+ " |w1|w2|w4|
+ " +--+--+ |
+ " |w3 | |
+ " +-----+--+
+ vsplit
+ split
+ vsplit
+ 4wincmd l
+ call setline(1, repeat([' '], &lines))
+ call cursor(1, 1)
+ redraw!
+ wincmd h
+ call assert_equal(2, winnr())
+ 4wincmd l
+ call cursor(&lines, 1)
+ redraw!
+ wincmd h
+ call assert_equal(3, winnr())
+ %bw!
+
+ " create the following window layout:
+ " +--+--+
+ " |w1|w4|
+ " +--+ +
+ " |w2| |
+ " +--+ +
+ " |w3| |
+ " +--+--+
+ vsplit
+ split
+ split
+ wincmd l
+ call setline(1, repeat([' '], &lines))
+ call cursor(1, 1)
+ redraw!
+ wincmd h
+ call assert_equal(1, winnr())
+ wincmd l
+ call cursor(&lines / 2, 1)
+ redraw!
+ wincmd h
+ call assert_equal(2, winnr())
+ wincmd l
+ call cursor(&lines, 1)
+ redraw!
+ wincmd h
+ call assert_equal(3, winnr())
+ %bw!
+endfunc
+
+" Test for an autocmd closing the destination window when jumping from one
+" window to another.
+func Test_close_dest_window()
+ split
+ edit Xdstfile
+
+ " Test for BufLeave
+ augroup T1
+ au!
+ au BufLeave Xdstfile $wincmd c
+ augroup END
+ wincmd b
+ call assert_equal(1, winnr('$'))
+ call assert_equal('Xdstfile', @%)
+ augroup T1
+ au!
+ augroup END
+
+ " Test for WinLeave
+ new
+ wincmd p
+ augroup T1
+ au!
+ au WinLeave * 1wincmd c
+ augroup END
+ wincmd t
+ call assert_equal(1, winnr('$'))
+ call assert_equal('Xdstfile', @%)
+ augroup T1
+ au!
+ augroup END
+ augroup! T1
+ %bw!
+endfunc
+
+func Test_window_minimal_size()
+ set winminwidth=0 winminheight=0
+
+ " check size is fixed vertically
+ new
+ call win_execute(win_getid(2), 'wincmd _')
+ call assert_equal(0, winheight(0))
+ call feedkeys('0', 'tx')
+ call assert_equal(1, winheight(0))
+ bwipe!
+
+ " check size is fixed horizontally
+ vert new
+ call win_execute(win_getid(2), 'wincmd |')
+ call assert_equal(0, winwidth(0))
+ call feedkeys('0', 'tx')
+ call assert_equal(1, winwidth(0))
+ bwipe!
+
+ if has('timers')
+ " check size is fixed in Insert mode
+ func s:CheckSize(timer) abort
+ call win_execute(win_getid(2), 'wincmd _')
+ call assert_equal(0, winheight(0))
+ call feedkeys(" \<Esc>", 't!')
+ endfunc
+ new
+ call timer_start(100, function('s:CheckSize'))
+ call feedkeys('a', 'tx!')
+ call assert_equal(1, winheight(0))
+ bwipe!
+ delfunc s:CheckSize
+ endif
+
+ set winminwidth& winminheight&
+endfunc
+
+func Test_win_move_separator()
+ edit a
+ leftabove vsplit b
+ let w = winwidth(0)
+ " check win_move_separator from left window on left window
+ call assert_equal(1, winnr())
+ for offset in range(5)
+ call assert_true(win_move_separator(0, offset))
+ call assert_equal(w + offset, winwidth(0))
+ call assert_true(0->win_move_separator(-offset))
+ call assert_equal(w, winwidth(0))
+ endfor
+ " check win_move_separator from right window on left window number
+ wincmd l
+ call assert_notequal(1, winnr())
+ for offset in range(5)
+ call assert_true(1->win_move_separator(offset))
+ call assert_equal(w + offset, winwidth(1))
+ call assert_true(win_move_separator(1, -offset))
+ call assert_equal(w, winwidth(1))
+ endfor
+ " check win_move_separator from right window on left window ID
+ let id = win_getid(1)
+ for offset in range(5)
+ call assert_true(win_move_separator(id, offset))
+ call assert_equal(w + offset, winwidth(id))
+ call assert_true(id->win_move_separator(-offset))
+ call assert_equal(w, winwidth(id))
+ endfor
+ " check win_move_separator from right window on right window is no-op
+ let w0 = winwidth(0)
+ call assert_true(win_move_separator(0, 1))
+ call assert_equal(w0, winwidth(0))
+ call assert_true(win_move_separator(0, -1))
+ call assert_equal(w0, winwidth(0))
+
+ " check that win_move_separator doesn't error with offsets beyond moving
+ " possibility
+ call assert_true(win_move_separator(id, 5000))
+ call assert_true(winwidth(id) > w)
+ call assert_true(win_move_separator(id, -5000))
+ call assert_true(winwidth(id) < w)
+
+ " check that win_move_separator returns false for an invalid window
+ wincmd =
+ let w = winwidth(0)
+ call assert_false(win_move_separator(-1, 1))
+ call assert_equal(w, winwidth(0))
+
+ " check that win_move_separator returns false for a popup window
+ let id = popup_create(['hello', 'world'], {})
+ let w = winwidth(id)
+ call assert_false(win_move_separator(id, 1))
+ call assert_equal(w, winwidth(id))
+ call popup_close(id)
+
+ " check that using another tabpage fails without crash
+ let id = win_getid()
+ tabnew
+ call assert_fails('call win_move_separator(id, -1)', 'E1308:')
+ tabclose
+
+ %bwipe!
+endfunc
+
+func Test_win_move_statusline()
+ edit a
+ leftabove split b
+ let h = winheight(0)
+ " check win_move_statusline from top window on top window
+ call assert_equal(1, winnr())
+ for offset in range(5)
+ call assert_true(win_move_statusline(0, offset))
+ call assert_equal(h + offset, winheight(0))
+ call assert_true(0->win_move_statusline(-offset))
+ call assert_equal(h, winheight(0))
+ endfor
+ " check win_move_statusline from bottom window on top window number
+ wincmd j
+ call assert_notequal(1, winnr())
+ for offset in range(5)
+ call assert_true(1->win_move_statusline(offset))
+ call assert_equal(h + offset, winheight(1))
+ call assert_true(win_move_statusline(1, -offset))
+ call assert_equal(h, winheight(1))
+ endfor
+ " check win_move_statusline from bottom window on bottom window
+ let h0 = winheight(0)
+ for offset in range(5)
+ call assert_true(0->win_move_statusline(-offset))
+ call assert_equal(h0 - offset, winheight(0))
+ call assert_equal(1 + offset, &cmdheight)
+ call assert_true(win_move_statusline(0, offset))
+ call assert_equal(h0, winheight(0))
+ call assert_equal(1, &cmdheight)
+ endfor
+ call assert_true(win_move_statusline(0, 1))
+ call assert_equal(h0, winheight(0))
+ call assert_equal(1, &cmdheight)
+ " check win_move_statusline from bottom window on top window ID
+ let id = win_getid(1)
+ for offset in range(5)
+ call assert_true(win_move_statusline(id, offset))
+ call assert_equal(h + offset, winheight(id))
+ call assert_true(id->win_move_statusline(-offset))
+ call assert_equal(h, winheight(id))
+ endfor
+
+ " check that win_move_statusline doesn't error with offsets beyond moving
+ " possibility
+ call assert_true(win_move_statusline(id, 5000))
+ call assert_true(winheight(id) > h)
+ call assert_true(win_move_statusline(id, -5000))
+ call assert_true(winheight(id) < h)
+
+ " check that win_move_statusline returns false for an invalid window
+ wincmd =
+ let h = winheight(0)
+ call assert_false(win_move_statusline(-1, 1))
+ call assert_equal(h, winheight(0))
+
+ " check that win_move_statusline returns false for a popup window
+ let id = popup_create(['hello', 'world'], {})
+ let h = winheight(id)
+ call assert_false(win_move_statusline(id, 1))
+ call assert_equal(h, winheight(id))
+ call popup_close(id)
+
+ " check that using another tabpage fails without crash
+ let id = win_getid()
+ tabnew
+ call assert_fails('call win_move_statusline(id, -1)', 'E1308:')
+ tabclose
+
+ %bwipe!
+endfunc
+
+" Test for window allocation failure
+func Test_window_alloc_failure()
+ %bw!
+
+ " test for creating a new window above current window
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('above new', 'E342:')
+ call assert_equal(1, winnr('$'))
+
+ " test for creating a new window below current window
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('below new', 'E342:')
+ call assert_equal(1, winnr('$'))
+
+ " test for popup window creation failure
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('call popup_create("Hello", {})', 'E342:')
+ call assert_equal([], popup_list())
+
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('split', 'E342:')
+ call assert_equal(1, winnr('$'))
+
+ edit Xwaffile1
+ edit Xwaffile2
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('sb Xwaffile1', 'E342:')
+ call assert_equal(1, winnr('$'))
+ call assert_equal('Xwaffile2', @%)
+ %bw!
+
+ " FIXME: The following test crashes Vim
+ " test for new tabpage creation failure
+ " call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ " call assert_fails('tabnew', 'E342:')
+ " call assert_equal(1, tabpagenr('$'))
+ " call assert_equal(1, winnr('$'))
+
+ " This test messes up the internal Vim window/frame information. So the
+ " Test_window_cmd_cmdwin_with_vsp() test fails after running this test.
+ " Open a new tab and close everything else to fix this issue.
+ tabnew
+ tabonly
+endfunc
+
+func Test_win_equal_last_status()
+ let save_lines = &lines
+ set lines=20
+ set splitbelow
+ set laststatus=0
+
+ split | split | quit
+ call assert_equal(winheight(1), winheight(2))
+
+ let &lines = save_lines
+ set splitbelow&
+ set laststatus&
+endfunc
+
+" Test "screen" and "cursor" values for 'splitkeep' with a sequence of
+" split operations for various options: with and without a winbar,
+" tabline, for each possible value of 'laststatus', 'scrolloff',
+" 'equalalways', and with the cursor at the top, middle and bottom.
+func Test_splitkeep_options()
+ " disallow window resizing
+ let save_WS = &t_WS
+ set t_WS=
+
+ let gui = has("gui_running")
+ inoremap <expr> c "<cmd>copen<bar>wincmd k<CR>"
+ for run in range(0, 20)
+ let &splitkeep = run > 10 ? 'topline' : 'screen'
+ let &scrolloff = (!(run % 4) ? 0 : run)
+ let &laststatus = (run % 3)
+ let &splitbelow = (run % 3)
+ let &equalalways = (run % 2)
+ let wsb = (run % 2) && &splitbelow
+ let tl = (gui ? 0 : ((run % 5) ? 1 : 0))
+ let pos = !(run % 3) ? 'H' : ((run % 2) ? 'M' : 'L')
+ tabnew | tabonly! | redraw
+ execute (run % 5) ? 'tabnew' : ''
+ execute (run % 2) ? 'nnoremenu 1.10 WinBar.Test :echo' : ''
+ call setline(1, range(1, 256))
+ " No scroll for restore_snapshot
+ norm G
+ try
+ copen | close | colder
+ catch /E380/
+ endtry
+ call assert_equal(257 - winheight(0), line("w0"))
+
+ " No scroll for firstwin horizontal split
+ execute 'norm gg' . pos
+ split | redraw | wincmd k
+ call assert_equal(1, line("w0"))
+ call assert_equal(&scroll, winheight(0) / 2)
+ wincmd j
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+
+ " No scroll when resizing windows
+ wincmd k | resize +2 | redraw
+ call assert_equal(1, line("w0"))
+ wincmd j
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+
+ " No scroll when dragging statusline
+ call win_move_statusline(1, -3)
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+ wincmd k
+ call assert_equal(1, line("w0"))
+
+ " No scroll when changing shellsize
+ set lines+=2
+ call assert_equal(1, line("w0"))
+ wincmd j
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+ set lines-=2
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+ wincmd k
+ call assert_equal(1, line("w0"))
+
+ " No scroll when equalizing windows
+ wincmd =
+ call assert_equal(1, line("w0"))
+ wincmd j
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+ wincmd k
+ call assert_equal(1, line("w0"))
+
+ " No scroll in windows split multiple times
+ vsplit | split | 4wincmd w
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+ 1wincmd w | quit | wincmd l | split
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+ wincmd j
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+
+ " No scroll in small window
+ 2wincmd w | only | 5split | wincmd k
+ call assert_equal(1, line("w0"))
+ wincmd j
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+
+ " No scroll for vertical split
+ quit | vsplit | wincmd l
+ call assert_equal(1, line("w0"))
+ wincmd h
+ call assert_equal(1, line("w0"))
+
+ " No scroll in windows split and quit multiple times
+ quit | redraw | split | split | quit | redraw
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0"))
+
+ " No scroll for new buffer
+ 1wincmd w | only | copen | wincmd k
+ call assert_equal(1, line("w0"))
+ only
+ call assert_equal(1, line("w0"))
+ above copen | wincmd j
+ call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl, line("w0"))
+
+ " No scroll when opening cmdwin, and no cursor move when closing cmdwin.
+ only | norm ggL
+ let curpos = getcurpos()
+ norm q:
+ call assert_equal(1, line("w0"))
+ call assert_equal(curpos, getcurpos())
+
+ " Scroll when cursor becomes invalid in insert mode.
+ norm Lic
+ call assert_equal(curpos, getcurpos(), 'run ' .. run)
+
+ " No scroll when topline not equal to 1
+ only | execute "norm gg5\<C-e>" | split | wincmd k
+ call assert_equal(6, line("w0"))
+ wincmd j
+ call assert_equal(&spk == 'topline' ? 6 : 5 + win_screenpos(0)[0] - tl - wsb, line("w0"))
+ endfor
+
+ tabnew | tabonly! | %bwipeout!
+ iunmap c
+ set scrolloff&
+ set splitbelow&
+ set laststatus&
+ set equalalways&
+ set splitkeep&
+ let &t_WS = save_WS
+endfunc
+
+func Test_splitkeep_cmdwin_cursor_position()
+ set splitkeep=screen
+ call setline(1, range(&lines))
+
+ " No scroll when cursor is at near bottom of window and cusor position
+ " recompution (done by line('w0') in this test) happens while in cmdwin.
+ normal! G
+ let firstline = line('w0')
+ autocmd CmdwinEnter * ++once autocmd WinEnter * ++once call line('w0')
+ execute "normal! q:\<C-w>q"
+ redraw!
+ call assert_equal(firstline, line('w0'))
+
+ " User script can change cursor position successfully while in cmdwin and it
+ " shouldn't be changed when closing cmdwin.
+ execute "normal! Gq:\<Cmd>call win_execute(winnr('#')->win_getid(), 'call cursor(1, 1)')\<CR>\<C-w>q"
+ call assert_equal(1, line('.'))
+ call assert_equal(1, col('.'))
+
+ execute "normal! Gq:\<Cmd>autocmd WinEnter * ++once call cursor(1, 1)\<CR>\<C-w>q"
+ call assert_equal(1, line('.'))
+ call assert_equal(1, col('.'))
+
+ %bwipeout!
+ set splitkeep&
+endfunc
+
+func Test_splitkeep_misc()
+ set splitkeep=screen
+ set splitbelow
+
+ call setline(1, range(1, &lines))
+ norm Gzz
+ let top = line('w0')
+ " No scroll when aucmd_win is opened
+ call setbufvar(bufnr("test", 1) , '&buftype', 'nofile')
+ call assert_equal(top, line('w0'))
+ " No scroll when tab is changed/closed
+ tab help | close
+ call assert_equal(top, line('w0'))
+ " No scroll when help is closed and buffer line count < window height
+ norm ggdG
+ call setline(1, range(1, &lines - 10))
+ norm G
+ let top = line('w0')
+ help | quit
+ call assert_equal(top, line('w0'))
+ " No error when resizing window in autocmd and buffer length changed
+ autocmd FileType qf exe "resize" line('$')
+ cexpr getline(1, '$')
+ copen
+ wincmd p
+ norm dd
+ cexpr getline(1, '$')
+
+ %bwipeout!
+ set splitbelow&
+ set splitkeep&
+endfunc
+
+func Test_splitkeep_callback()
+ CheckScreendump
+ let lines =<< trim END
+ set splitkeep=screen
+ call setline(1, range(&lines))
+ function C1(a, b)
+ split | wincmd p
+ endfunction
+ function C2(a, b)
+ close | split
+ endfunction
+ nn j <cmd>call job_start([&sh, &shcf, "true"], { 'exit_cb': 'C1' })<CR>
+ nn t <cmd>call popup_create(term_start([&sh, &shcf, "true"],
+ \ { 'hidden': 1, 'exit_cb': 'C2' }), {})<CR>
+ END
+ call writefile(lines, 'XTestSplitkeepCallback', 'D')
+ let buf = RunVimInTerminal('-S XTestSplitkeepCallback', #{rows: 8})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_splitkeep_callback_1', {})
+
+ call term_sendkeys(buf, ":quit\<CR>Ht")
+ call VerifyScreenDump(buf, 'Test_splitkeep_callback_2', {})
+
+ call term_sendkeys(buf, ":set sb\<CR>:quit\<CR>Gj")
+ call VerifyScreenDump(buf, 'Test_splitkeep_callback_3', {})
+
+ call term_sendkeys(buf, ":quit\<CR>Gt")
+ call VerifyScreenDump(buf, 'Test_splitkeep_callback_4', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_splitkeep_fold()
+ CheckScreendump
+
+ let lines =<< trim END
+ set splitkeep=screen
+ set foldmethod=marker
+ set number
+ let line = 1
+ for n in range(1, &lines)
+ call setline(line, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/',
+ \ 'after fold'])
+ let line += 8
+ endfor
+ END
+ call writefile(lines, 'XTestSplitkeepFold', 'D')
+ let buf = RunVimInTerminal('-S XTestSplitkeepFold', #{rows: 10})
+
+ call term_sendkeys(buf, "L:wincmd s\<CR>")
+ call VerifyScreenDump(buf, 'Test_splitkeep_fold_1', {})
+
+ call term_sendkeys(buf, ":quit\<CR>")
+ call VerifyScreenDump(buf, 'Test_splitkeep_fold_2', {})
+
+ call term_sendkeys(buf, "H:below split\<CR>")
+ call VerifyScreenDump(buf, 'Test_splitkeep_fold_3', {})
+
+ call term_sendkeys(buf, ":wincmd k\<CR>:quit\<CR>")
+ call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_splitkeep_status()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['a', 'b', 'c'])
+ set nomodified
+ set splitkeep=screen
+ let win = winnr()
+ wincmd s
+ wincmd j
+ END
+ call writefile(lines, 'XTestSplitkeepStatus', 'D')
+ let buf = RunVimInTerminal('-S XTestSplitkeepStatus', #{rows: 10})
+
+ call term_sendkeys(buf, ":call win_move_statusline(win, 1)\<CR>")
+ call VerifyScreenDump(buf, 'Test_splitkeep_status_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_new_help_window_on_error()
+ help change.txt
+ execute "normal! /CTRL-@\<CR>"
+ silent! execute "normal! \<C-W>]"
+
+ let wincount = winnr('$')
+ help 'mod'
+
+ call assert_equal(wincount, winnr('$'))
+ call assert_equal(expand("<cword>"), "'mod'")
+endfunc
+
+func Test_smoothscroll_in_zero_width_window()
+ let save_lines = &lines
+ let save_columns = &columns
+
+ winsize 0 24
+ set cpo+=n
+ exe "noremap 0 \<C-W>n\<C-W>L"
+ norm 000000
+ set number smoothscroll
+ exe "norm \<C-Y>"
+
+ only!
+ let &lines = save_lines
+ let &columns = save_columns
+ set cpo-=n
+ unmap 0
+ set nonumber nosmoothscroll
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_window_id.vim b/src/testdir/test_window_id.vim
new file mode 100644
index 0000000..396a49b
--- /dev/null
+++ b/src/testdir/test_window_id.vim
@@ -0,0 +1,142 @@
+" Test using the window ID.
+
+source check.vim
+
+func Test_win_getid()
+ edit one
+ let id1 = win_getid()
+ let w:one = 'one'
+ split two
+ let id2 = win_getid()
+ let bufnr2 = bufnr('%')
+ let w:two = 'two'
+ split three
+ let id3 = win_getid()
+ let w:three = 'three'
+ tabnew
+ edit four
+ let id4 = win_getid()
+ let w:four = 'four'
+ split five
+ let id5 = win_getid()
+ let bufnr5 = bufnr('%')
+ let w:five = 'five'
+ tabnext
+
+ wincmd w
+ call assert_equal("two", expand("%"))
+ call assert_equal(id2, win_getid())
+ let nr2 = winnr()
+ wincmd w
+ call assert_equal("one", expand("%"))
+ call assert_equal(id1, win_getid())
+ let nr1 = winnr()
+ wincmd w
+ call assert_equal("three", expand("%"))
+ call assert_equal(id3, win_getid())
+ let nr3 = winnr()
+ call assert_equal('one', getwinvar(id1, 'one'))
+ call assert_equal('two', getwinvar(id2, 'two'))
+ call assert_equal('three', getwinvar(id3, 'three'))
+ tabnext
+ call assert_equal("five", expand("%"))
+ call assert_equal(id5, win_getid())
+ let nr5 = winnr()
+ wincmd w
+ call assert_equal("four", expand("%"))
+ call assert_equal(id4, win_getid())
+ let nr4 = winnr()
+ call assert_equal('four', getwinvar(id4, 'four'))
+ call assert_equal('five', getwinvar(id5, 'five'))
+ call settabwinvar(1, id2, 'two', '2')
+ call setwinvar(id4, 'four', '4')
+ tabnext
+ call assert_equal('4', gettabwinvar(2, id4, 'four'))
+ call assert_equal('five', gettabwinvar(2, id5, 'five'))
+ call assert_equal('2', getwinvar(id2, 'two'))
+
+ exe nr1 . "wincmd w"
+ call assert_equal(id1, win_getid())
+ exe nr2 . "wincmd w"
+ call assert_equal(id2, win_getid())
+ exe nr3 . "wincmd w"
+ call assert_equal(id3, win_getid())
+ tabnext
+ exe nr4 . "wincmd w"
+ call assert_equal(id4, win_getid())
+ exe nr5 . "wincmd w"
+ call assert_equal(id5, win_getid())
+
+ call win_gotoid(id2)
+ call assert_equal("two", expand("%"))
+ eval id4->win_gotoid()
+ call assert_equal("four", expand("%"))
+ call win_gotoid(id1)
+ call assert_equal("one", expand("%"))
+ call win_gotoid(id5)
+ call assert_equal("five", expand("%"))
+
+ call assert_equal(0, win_id2win(9999))
+ call assert_equal(nr5, id5->win_id2win())
+ call assert_equal(0, win_id2win(id1))
+ tabnext
+ call assert_equal(nr1, win_id2win(id1))
+
+ call assert_equal([0, 0], win_id2tabwin(9999))
+ call assert_equal([1, nr2], id2->win_id2tabwin())
+ call assert_equal([2, nr4], win_id2tabwin(id4))
+
+ call assert_equal([], win_findbuf(9999))
+ call assert_equal([id2], bufnr2->win_findbuf())
+ call win_gotoid(id5)
+ split
+ call assert_equal(sort([id5, win_getid()]), sort(win_findbuf(bufnr5)))
+
+ call assert_fails('let w = win_getid([])', 'E745:')
+ call assert_equal(0, win_getid(-1))
+ call assert_equal(-1, win_getid(1, -1))
+
+ only!
+endfunc
+
+func Test_win_getid_curtab()
+ CheckFeature quickfix
+
+ tabedit X
+ tabfirst
+ copen
+ only
+ call assert_equal(win_getid(1), 1->win_getid( 1))
+ tabclose!
+endfunc
+
+func Test_winlayout()
+ let w1 = win_getid()
+ call assert_equal(['leaf', w1], winlayout())
+
+ split
+ let w2 = win_getid()
+ call assert_equal(['col', [['leaf', w2], ['leaf', w1]]], winlayout())
+
+ split
+ let w3 = win_getid()
+ call assert_equal(['col', [['leaf', w3], ['leaf', w2], ['leaf', w1]]], winlayout())
+
+ 2wincmd w
+ vsplit
+ let w4 = win_getid()
+ call assert_equal(['col', [['leaf', w3], ['row', [['leaf', w4], ['leaf', w2]]], ['leaf', w1]]], winlayout())
+
+ only!
+
+ let w1 = win_getid()
+ call assert_equal(['leaf', w1], winlayout(1))
+ tabnew
+ let w2 = win_getid()
+ call assert_equal(['leaf', w2], 2->winlayout())
+ tabclose
+
+ call assert_equal([], winlayout(-1))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_windows_home.vim b/src/testdir/test_windows_home.vim
new file mode 100644
index 0000000..7db5395
--- /dev/null
+++ b/src/testdir/test_windows_home.vim
@@ -0,0 +1,122 @@
+" Test for $HOME on Windows.
+
+source check.vim
+CheckMSWindows
+
+let s:env = {}
+
+func s:restore_env()
+ for i in keys(s:env)
+ exe 'let ' . i . '=s:env["' . i . '"]'
+ endfor
+endfunc
+
+func s:save_env(...)
+ for i in a:000
+ exe 'let s:env["' . i . '"]=' . i
+ endfor
+endfunc
+
+func s:unlet_env(...)
+ for i in a:000
+ exe 'let ' . i . '=""'
+ endfor
+endfunc
+
+func CheckHomeIsMissingFromSubprocessEnvironment()
+ silent! let out = system('set')
+ let env = filter(split(out, "\n"), 'v:val=~"^HOME="')
+ call assert_equal(0, len(env))
+endfunc
+
+func CheckHomeIsInSubprocessEnvironment(exp)
+ silent! let out = system('set')
+ let env = filter(split(out, "\n"), 'v:val=~"^HOME="')
+ let home = len(env) == 0 ? "" : substitute(env[0], '[^=]\+=', '', '')
+ call assert_equal(a:exp, home)
+endfunc
+
+func CheckHome(exp, ...)
+ call assert_equal(a:exp, $HOME)
+ call assert_equal(a:exp, expand('~', ':p'))
+ if !a:0
+ call CheckHomeIsMissingFromSubprocessEnvironment()
+ else
+ call CheckHomeIsInSubprocessEnvironment(a:1)
+ endif
+endfunc
+
+func Test_WindowsHome()
+ command! -nargs=* SaveEnv call <SID>save_env(<f-args>)
+ command! -nargs=* RestoreEnv call <SID>restore_env()
+ command! -nargs=* UnletEnv call <SID>unlet_env(<f-args>)
+ set noshellslash
+
+ let save_home = $HOME
+ SaveEnv $USERPROFILE $HOMEDRIVE $HOMEPATH
+ try
+ " Normal behavior: use $HOMEDRIVE and $HOMEPATH, ignore $USERPROFILE
+ let $USERPROFILE = 'unused'
+ let $HOMEDRIVE = 'C:'
+ let $HOMEPATH = '\foobar'
+ let $HOME = '' " Force recomputing "homedir"
+ call CheckHome('C:\foobar')
+
+ " Same, but with $HOMEPATH not set
+ UnletEnv $HOMEPATH
+ let $HOME = '' " Force recomputing "homedir"
+ call CheckHome('C:\')
+
+ " Use $USERPROFILE if $HOMEPATH and $HOMEDRIVE are empty
+ UnletEnv $HOMEDRIVE $HOMEPATH
+ let $USERPROFILE = 'C:\foo'
+ let $HOME = '' " Force recomputing "homedir"
+ call CheckHome('C:\foo')
+
+ " If $HOME is set the others don't matter
+ let $HOME = 'C:\bar'
+ let $USERPROFILE = 'unused'
+ let $HOMEDRIVE = 'unused'
+ let $HOMEPATH = 'unused'
+ call CheckHome('C:\bar', 'C:\bar')
+
+ " If $HOME contains %USERPROFILE% it is expanded
+ let $USERPROFILE = 'C:\foo'
+ let $HOME = '%USERPROFILE%\bar'
+ let $HOMEDRIVE = 'unused'
+ let $HOMEPATH = 'unused'
+ call CheckHome('C:\foo\bar', '%USERPROFILE%\bar')
+
+ " Invalid $HOME is kept
+ let $USERPROFILE = 'C:\foo'
+ let $HOME = '%USERPROFILE'
+ let $HOMEDRIVE = 'unused'
+ let $HOMEPATH = 'unused'
+ call CheckHome('%USERPROFILE', '%USERPROFILE')
+
+ " %USERPROFILE% not at start of $HOME is not expanded
+ let $USERPROFILE = 'unused'
+ let $HOME = 'C:\%USERPROFILE%'
+ let $HOMEDRIVE = 'unused'
+ let $HOMEPATH = 'unused'
+ call CheckHome('C:\%USERPROFILE%', 'C:\%USERPROFILE%')
+
+ if has('channel')
+ RestoreEnv
+ let $HOME = save_home
+ let env = ''
+ let job = job_start('cmd /c set', {'out_cb': {ch,x->[env,execute('let env=x')]}})
+ sleep 1
+ let env = filter(split(env, "\n"), 'v:val=="HOME"')
+ let home = len(env) == 0 ? "" : env[0]
+ call assert_equal('', home)
+ endif
+ finally
+ RestoreEnv
+ delcommand SaveEnv
+ delcommand RestoreEnv
+ delcommand UnletEnv
+ endtry
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_wnext.vim b/src/testdir/test_wnext.vim
new file mode 100644
index 0000000..ad782e4
--- /dev/null
+++ b/src/testdir/test_wnext.vim
@@ -0,0 +1,103 @@
+" Test :wnext :wNext and :wprevious
+
+func Test_wnext()
+ args X1 X2
+
+ call setline(1, '1')
+ wnext
+ call assert_equal(['1'], readfile('X1'))
+ call assert_equal('X2', bufname('%'))
+
+ call setline(1, '2')
+ call assert_fails('wnext', 'E165:')
+ call assert_equal(['2'], readfile('X2'))
+ call assert_equal('X2', bufname('%'))
+
+ " Test :wnext with a single file.
+ args X1
+ call assert_equal('X1', bufname('%'))
+ call assert_fails('wnext', 'E163:')
+
+ " Test :wnext with a count.
+ args X1 X2 X3
+ call assert_equal('X1', bufname('%'))
+ 2wnext
+ call assert_equal('X3', bufname('%'))
+
+ " Test :wnext {file}.
+ args X1 X2 X3
+ wnext X4
+ call assert_equal(['1'], readfile('X4'))
+ call assert_equal('X2', bufname('%'))
+ call assert_fails('wnext X4', 'E13:')
+ call assert_equal(['1'], readfile('X4'))
+ wnext! X4
+ call assert_equal(['2'], readfile('X4'))
+ call assert_equal('X3', bufname('%'))
+
+ args X1 X2
+ " Commented out as, E13 occurs on Windows instead of E17
+ "call assert_fails('wnext .', 'E17:')
+ call assert_fails('wnext! .', 'E502:')
+
+ %bwipe!
+ call delete('X1')
+ call delete('X2')
+ call delete('X3')
+ call delete('X4')
+endfunc
+
+func Test_wprevious()
+ args X1 X2
+
+ next
+ call assert_equal('X2', bufname('%'))
+ call setline(1, '2')
+ wprevious
+ call assert_equal(['2'], readfile('X2'))
+ call assert_equal('X1', bufname('%'))
+
+ call setline(1, '1')
+ call assert_fails('wprevious', 'E164:')
+ call assert_fails('wNext', 'E164:')
+
+ " Test :wprevious with a single file.
+ args X1
+ call assert_fails('wprevious', 'E163:')
+ call assert_fails('wNext', 'E163:')
+
+ " Test :wprevious with a count.
+ args X1 X2 X3
+ 2next
+ call setline(1, '3')
+ call assert_equal('X3', bufname('%'))
+ 2wprevious
+ call assert_equal('X1', bufname('%'))
+ call assert_equal(['3'], readfile('X3'))
+
+ " Test :wprevious {file}
+ args X1 X2 X3
+ 2next
+ call assert_equal('X3', bufname('%'))
+ wprevious X4
+ call assert_equal(['3'], readfile('X4'))
+ call assert_equal('X2', bufname('%'))
+ call assert_fails('wprevious X4', 'E13:')
+ call assert_equal(['3'], readfile('X4'))
+ wprevious! X4
+ call assert_equal(['2'], readfile('X4'))
+ call assert_equal('X1', bufname('%'))
+
+ args X1 X2
+ " Commented out as, E13 occurs on Windows instead of E17
+ "call assert_fails('wprevious .', 'E17:')
+ call assert_fails('wprevious! .', 'E502:')
+
+ %bwipe!
+ call delete('X1')
+ call delete('X2')
+ call delete('X3')
+ call delete('X4')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_wordcount.vim b/src/testdir/test_wordcount.vim
new file mode 100644
index 0000000..9d47743
--- /dev/null
+++ b/src/testdir/test_wordcount.vim
@@ -0,0 +1,106 @@
+" Test for wordcount() function
+
+func Test_wordcount()
+ let save_enc = &enc
+ set encoding=utf-8
+ set selection=inclusive fileformat=unix fileformats=unix
+
+ new
+
+ " Test 1: empty window
+ call assert_equal({'chars': 0, 'cursor_chars': 0, 'words': 0, 'cursor_words': 0,
+ \ 'bytes': 0, 'cursor_bytes': 0}, wordcount())
+
+ " Test 2: some words, cursor at start
+ call append(1, 'one two three')
+ call cursor([1, 1, 0])
+ call assert_equal({'chars': 15, 'cursor_chars': 1, 'words': 3, 'cursor_words': 0,
+ \ 'bytes': 15, 'cursor_bytes': 1}, wordcount())
+
+ " Test 3: some words, cursor at end
+ %d _
+ call append(1, 'one two three')
+ call cursor([2, 99, 0])
+ call assert_equal({'chars': 15, 'cursor_chars': 14, 'words': 3, 'cursor_words': 3,
+ \ 'bytes': 15, 'cursor_bytes': 14}, wordcount())
+
+ " Test 4: some words, cursor at end, ve=all
+ set ve=all
+ %d _
+ call append(1, 'one two three')
+ call cursor([2, 99, 0])
+ call assert_equal({'chars': 15, 'cursor_chars': 15, 'words': 3, 'cursor_words': 3,
+ \ 'bytes': 15, 'cursor_bytes': 15}, wordcount())
+ set ve=
+
+ " Test 5: several lines with words
+ %d _
+ call append(1, ['one two three', 'one two three', 'one two three'])
+ call cursor([4, 99, 0])
+ call assert_equal({'chars': 43, 'cursor_chars': 42, 'words': 9, 'cursor_words': 9,
+ \ 'bytes': 43, 'cursor_bytes': 42}, wordcount())
+
+ " Test 6: one line with BOM set
+ %d _
+ call append(1, 'one two three')
+ set bomb
+ w! Xtest
+ call cursor([2, 99, 0])
+ call assert_equal({'chars': 15, 'cursor_chars': 14, 'words': 3, 'cursor_words': 3,
+ \ 'bytes': 18, 'cursor_bytes': 14}, wordcount())
+ set nobomb
+ w!
+ call delete('Xtest')
+
+ " Test 7: one line with multibyte words
+ %d _
+ call append(1, ['Äne M¤ne Müh'])
+ call cursor([2, 99, 0])
+ call assert_equal({'chars': 14, 'cursor_chars': 13, 'words': 3, 'cursor_words': 3,
+ \ 'bytes': 17, 'cursor_bytes': 16}, wordcount())
+
+ " Test 8: several lines with multibyte words
+ %d _
+ call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
+ call cursor([3, 99, 0])
+ call assert_equal({'chars': 32, 'cursor_chars': 31, 'words': 7, 'cursor_words': 7,
+ \ 'bytes': 36, 'cursor_bytes': 35}, wordcount())
+
+ " Visual map to capture wordcount() in visual mode
+ vnoremap <expr> <F2> execute("let g:visual_stat = wordcount()")
+
+ " Test 9: visual mode, complete buffer
+ let g:visual_stat = {}
+ %d _
+ call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
+ " start visual mode and select the complete buffer
+ 0
+ exe "normal V2j\<F2>y"
+ call assert_equal({'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 32,
+ \ 'visual_words': 7, 'visual_bytes': 36}, g:visual_stat)
+
+ " Test 10: visual mode (empty)
+ %d _
+ call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
+ " start visual mode and select the complete buffer
+ 0
+ exe "normal v$\<F2>y"
+ call assert_equal({'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 1,
+ \ 'visual_words': 0, 'visual_bytes': 1}, g:visual_stat)
+
+ " Test 11: visual mode, single line
+ %d _
+ call append(1, ['Äne M¤ne Müh', 'und raus bist dü!'])
+ " start visual mode and select the complete buffer
+ 2
+ exe "normal 0v$\<F2>y"
+ call assert_equal({'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 13,
+ \ 'visual_words': 3, 'visual_bytes': 16}, g:visual_stat)
+
+ set selection& fileformat& fileformats&
+ let &enc = save_enc
+ enew!
+ close
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_writefile.vim b/src/testdir/test_writefile.vim
new file mode 100644
index 0000000..140b2ee
--- /dev/null
+++ b/src/testdir/test_writefile.vim
@@ -0,0 +1,980 @@
+" Tests for the writefile() function and some :write commands.
+
+source check.vim
+source term_util.vim
+
+func Test_writefile()
+ let f = tempname()
+ call writefile(["over", "written"], f, "bD")
+ call writefile(["hello", "world"], f, "b")
+ call writefile(["!", "good"], f, "a")
+ call writefile(["morning"], f, "ab")
+ call writefile(["", "vimmers"], f, "ab")
+ let l = readfile(f)
+ call assert_equal("hello", l[0])
+ call assert_equal("world!", l[1])
+ call assert_equal("good", l[2])
+ call assert_equal("morning", l[3])
+ call assert_equal("vimmers", l[4])
+
+ call assert_fails('call writefile("text", "Xwffile")', 'E475: Invalid argument: writefile() first argument must be a List or a Blob')
+endfunc
+
+func Test_writefile_ignore_regexp_error()
+ write Xt[z-a]est.txt
+ call delete('Xt[z-a]est.txt')
+endfunc
+
+func Test_writefile_fails_gently()
+ call assert_fails('call writefile(["test"], "Xwffile", [])', 'E730:')
+ call assert_false(filereadable("Xwffile"))
+ call delete("Xwffile")
+
+ call assert_fails('call writefile(["test", [], [], [], "tset"], "Xwffile")', 'E730:')
+ call assert_false(filereadable("Xwffile"))
+ call delete("Xwffile")
+
+ call assert_fails('call writefile([], "Xwffile", [])', 'E730:')
+ call assert_false(filereadable("Xwffile"))
+ call delete("Xwffile")
+
+ call assert_fails('call writefile([], [])', 'E730:')
+endfunc
+
+func Test_writefile_fails_conversion()
+ CheckFeature iconv
+ if has('sun')
+ throw 'Skipped: does not work on SunOS'
+ endif
+ " Without a backup file the write won't happen if there is a conversion
+ " error.
+ set nobackup nowritebackup backupdir=. backupskip=
+ new
+ let contents = ["line one", "line two"]
+ call writefile(contents, 'Xwfcfile', 'D')
+ edit Xwfcfile
+ call setline(1, ["first line", "cannot convert \u010b", "third line"])
+ call assert_fails('write ++enc=cp932', 'E513:')
+ call assert_equal(contents, readfile('Xwfcfile'))
+
+ " With 'backupcopy' set, if there is a conversion error, the backup file is
+ " still created.
+ set backupcopy=yes writebackup& backup&
+ call delete('Xwfcfile' .. &backupext)
+ call assert_fails('write ++enc=cp932', 'E513:')
+ call assert_equal(contents, readfile('Xwfcfile'))
+ call assert_equal(contents, readfile('Xwfcfile' .. &backupext))
+ set backupcopy&
+ %bw!
+
+ " Conversion error during write
+ new
+ call setline(1, ["\U10000000"])
+ let output = execute('write! ++enc=utf-16 Xwfcfile')
+ call assert_match('CONVERSION ERROR', output)
+ let output = execute('write! ++enc=ucs-2 Xwfcfile')
+ call assert_match('CONVERSION ERROR', output)
+ call delete('Xwfcfilz~')
+ call delete('Xwfcfily~')
+ %bw!
+
+ call delete('Xwfcfile' .. &backupext)
+ bwipe!
+ set backup& writebackup& backupdir&vim backupskip&vim
+endfunc
+
+func Test_writefile_fails_conversion2()
+ CheckFeature iconv
+ if has('sun')
+ throw 'Skipped: does not work on SunOS'
+ endif
+ " With a backup file the write happens even if there is a conversion error,
+ " but then the backup file must remain
+ set nobackup writebackup backupdir=. backupskip=
+ let contents = ["line one", "line two"]
+ call writefile(contents, 'Xwf2file_conversion_err', 'D')
+ edit Xwf2file_conversion_err
+ call setline(1, ["first line", "cannot convert \u010b", "third line"])
+ set fileencoding=latin1
+ let output = execute('write')
+ call assert_match('CONVERSION ERROR', output)
+ call assert_equal(contents, readfile('Xwf2file_conversion_err~'))
+
+ call delete('Xwf2file_conversion_err~')
+ bwipe!
+ set backup& writebackup& backupdir&vim backupskip&vim
+endfunc
+
+func SetFlag(timer)
+ let g:flag = 1
+endfunc
+
+func Test_write_quit_split()
+ " Prevent exiting by splitting window on file write.
+ augroup testgroup
+ autocmd BufWritePre * split
+ augroup END
+ e! Xwqsfile
+ call setline(1, 'nothing')
+ wq
+
+ if has('timers')
+ " timer will not run if "exiting" is still set
+ let g:flag = 0
+ call timer_start(1, 'SetFlag')
+ sleep 50m
+ call assert_equal(1, g:flag)
+ unlet g:flag
+ endif
+ au! testgroup
+ bwipe Xwqsfile
+ call delete('Xwqsfile')
+endfunc
+
+func Test_nowrite_quit_split()
+ " Prevent exiting by opening a help window.
+ e! Xnqsfile
+ help
+ wincmd w
+ exe winnr() . 'q'
+
+ if has('timers')
+ " timer will not run if "exiting" is still set
+ let g:flag = 0
+ call timer_start(1, 'SetFlag')
+ sleep 50m
+ call assert_equal(1, g:flag)
+ unlet g:flag
+ endif
+ bwipe Xnqsfile
+endfunc
+
+func Test_writefile_sync_arg()
+ " This doesn't check if fsync() works, only that the argument is accepted.
+ call writefile(['one'], 'Xtest', 'sD')
+ call writefile(['two'], 'Xtest', 'S')
+endfunc
+
+func Test_writefile_sync_dev_stdout()
+ CheckUnix
+ if filewritable('/dev/stdout')
+ " Just check that this doesn't cause an error.
+ call writefile(['one'], '/dev/stdout')
+ else
+ throw 'Skipped: /dev/stdout is not writable'
+ endif
+endfunc
+
+func Test_writefile_autowrite()
+ set autowrite
+ new
+ next Xa Xb Xc
+ call setline(1, 'aaa')
+ next
+ call assert_equal(['aaa'], readfile('Xa'))
+ call setline(1, 'bbb')
+ call assert_fails('edit XX')
+ call assert_false(filereadable('Xb'))
+
+ set autowriteall
+ edit XX
+ call assert_equal(['bbb'], readfile('Xb'))
+
+ bwipe!
+ call delete('Xa')
+ call delete('Xb')
+ set noautowrite
+endfunc
+
+func Test_writefile_autowrite_nowrite()
+ set autowrite
+ new
+ next Xa Xb Xc
+ set buftype=nowrite
+ call setline(1, 'aaa')
+ let buf = bufnr('%')
+ " buffer contents silently lost
+ edit XX
+ call assert_false(filereadable('Xa'))
+ rewind
+ call assert_equal('', getline(1))
+
+ bwipe!
+ set noautowrite
+endfunc
+
+" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
+" command.
+func Test_write_pipe_to_cmd()
+ CheckUnix
+ new
+ call setline(1, ['L1', 'L2', 'L3', 'L4'])
+ 2,3w !cat > Xptfile
+ call assert_equal(['L2', 'L3'], readfile('Xptfile'))
+ close!
+ call delete('Xptfile')
+endfunc
+
+" Test for :saveas
+func Test_saveas()
+ call assert_fails('saveas', 'E471:')
+ call writefile(['L1'], 'Xsafile')
+ new Xsafile
+ new
+ call setline(1, ['L1'])
+ call assert_fails('saveas Xsafile', 'E139:')
+ close!
+ enew | only
+ call delete('Xsafile')
+
+ " :saveas should detect and set the file type.
+ syntax on
+ saveas! Xsaveas.pl
+ call assert_equal('perl', &filetype)
+ syntax off
+ %bw!
+ call delete('Xsaveas.pl')
+
+ " :saveas fails for "nofile" buffer
+ set buftype=nofile
+ call assert_fails('saveas Xsafile', 'E676: No matching autocommands for buftype=nofile buffer')
+
+ bwipe!
+endfunc
+
+func Test_write_errors()
+ " Test for writing partial buffer
+ call writefile(['L1', 'L2', 'L3'], 'Xwefile')
+ new Xwefile
+ call assert_fails('1,2write', 'E140:')
+ close!
+
+ call assert_fails('w > Xtest', 'E494:')
+
+ " Try to overwrite a directory
+ if has('unix')
+ call mkdir('Xwerdir1')
+ call assert_fails('write Xwerdir1', 'E17:')
+ call delete('Xwerdir1', 'd')
+ endif
+
+ " Test for :wall for a buffer with no name
+ enew | only
+ call setline(1, ['L1'])
+ call assert_fails('wall', 'E141:')
+ enew!
+
+ " Test for writing a 'readonly' file
+ new Xwefile
+ set readonly
+ call assert_fails('write', 'E45:')
+ close
+
+ " Test for writing to a read-only file
+ new Xwefile
+ call setfperm('Xwefile', 'r--r--r--')
+ call assert_fails('write', 'E505:')
+ call setfperm('Xwefile', 'rw-rw-rw-')
+ close
+
+ call delete('Xwefile')
+
+ call writefile(test_null_list(), 'Xwefile')
+ call assert_false(filereadable('Xwefile'))
+ call writefile(test_null_blob(), 'Xwefile')
+ call assert_false(filereadable('Xwefile'))
+ call assert_fails('call writefile([], "")', 'E482:')
+
+ " very long file name
+ let long_fname = repeat('n', 5000)
+ call assert_fails('exe "w " .. long_fname', 'E75:')
+ call assert_fails('call writefile([], long_fname)', 'E482:')
+
+ " Test for writing to a block device on Unix-like systems
+ if has('unix') && getfperm('/dev/loop0') != ''
+ \ && getftype('/dev/loop0') == 'bdev' && !IsRoot()
+ new
+ edit /dev/loop0
+ call assert_fails('write', 'E503: ')
+ call assert_fails('write!', 'E503: ')
+ close!
+ endif
+endfunc
+
+" Test for writing to a file which is modified after Vim read it
+func Test_write_file_mtime()
+ CheckEnglish
+ CheckRunVimInTerminal
+
+ " First read the file into a buffer
+ call writefile(["Line1", "Line2"], 'Xwfmfile', 'D')
+ let old_ftime = getftime('Xwfmfile')
+ let buf = RunVimInTerminal('Xwfmfile', #{rows : 10})
+ call TermWait(buf)
+ call term_sendkeys(buf, ":set noswapfile\<CR>")
+ call TermWait(buf)
+
+ " Modify the file directly. Make sure the file modification time is
+ " different. Note that on Linux/Unix, the file is considered modified
+ " outside, only if the difference is 2 seconds or more
+ sleep 1
+ call writefile(["Line3", "Line4"], 'Xwfmfile')
+ let new_ftime = getftime('Xwfmfile')
+ while new_ftime - old_ftime < 2
+ sleep 100m
+ call writefile(["Line3", "Line4"], 'Xwfmfile')
+ let new_ftime = getftime('Xwfmfile')
+ endwhile
+
+ " Try to overwrite the file and check for the prompt
+ call term_sendkeys(buf, ":w\<CR>")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_equal("WARNING: The file has been changed since reading it!!!", term_getline(buf, 9))})
+ call assert_equal("Do you really want to write to it (y/n)?",
+ \ term_getline(buf, 10))
+ call term_sendkeys(buf, "n\<CR>")
+ call TermWait(buf)
+ call assert_equal(new_ftime, getftime('Xwfmfile'))
+ call term_sendkeys(buf, ":w\<CR>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "y\<CR>")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_equal('Line2', readfile('Xwfmfile')[1])})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for an autocmd unloading a buffer during a write command
+func Test_write_autocmd_unloadbuf_lockmark()
+ augroup WriteTest
+ autocmd BufWritePre Xwaufile enew | write
+ augroup END
+ e Xwaufile
+ call assert_fails('lockmarks write', ['E32:', 'E203:'])
+ augroup WriteTest
+ au!
+ augroup END
+ augroup! WriteTest
+endfunc
+
+" Test for writing a buffer with 'acwrite' but without autocmds
+func Test_write_acwrite_error()
+ new Xwaefile
+ call setline(1, ['line1', 'line2', 'line3'])
+ set buftype=acwrite
+ call assert_fails('write', 'E676:')
+ call assert_fails('1,2write!', 'E676:')
+ call assert_fails('w >>', 'E676:')
+ close!
+endfunc
+
+" Test for adding and removing lines from an autocmd when writing a buffer
+func Test_write_autocmd_add_remove_lines()
+ new Xwaafile
+ call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
+
+ " Autocmd deleting lines from the file when writing a partial file
+ augroup WriteTest2
+ au!
+ autocmd FileWritePre Xwaafile 1,2d
+ augroup END
+ call assert_fails('2,3w!', 'E204:')
+
+ " Autocmd adding lines to a file when writing a partial file
+ augroup WriteTest2
+ au!
+ autocmd FileWritePre Xwaafile call append(0, ['xxx', 'yyy'])
+ augroup END
+ %d
+ call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
+ 1,2w!
+ call assert_equal(['xxx', 'yyy', 'aaa', 'bbb'], readfile('Xwaafile'))
+
+ " Autocmd deleting lines from the file when writing the whole file
+ augroup WriteTest2
+ au!
+ autocmd BufWritePre Xwaafile 1,2d
+ augroup END
+ %d
+ call setline(1, ['aaa', 'bbb', 'ccc', 'ddd'])
+ w
+ call assert_equal(['ccc', 'ddd'], readfile('Xwaafile'))
+
+ augroup WriteTest2
+ au!
+ augroup END
+ augroup! WriteTest2
+
+ close!
+ call delete('Xwaafile')
+endfunc
+
+" Test for writing to a readonly file
+func Test_write_readonly()
+ call writefile([], 'Xwrofile', 'D')
+ call setfperm('Xwrofile', "r--------")
+ edit Xwrofile
+ set noreadonly backupskip=
+ call assert_fails('write', 'E505:')
+ let save_cpo = &cpo
+ set cpo+=W
+ call assert_fails('write!', 'E504:')
+ let &cpo = save_cpo
+ call setline(1, ['line1'])
+ write!
+ call assert_equal(['line1'], readfile('Xwrofile'))
+
+ " Auto-saving a readonly file should fail with 'autowriteall'
+ %bw!
+ e Xwrofile
+ set noreadonly autowriteall
+ call setline(1, ['aaaa'])
+ call assert_fails('n', 'E505:')
+ set cpo+=W
+ call assert_fails('n', 'E504:')
+ set cpo-=W
+ set autowriteall&
+
+ set backupskip&
+ %bw!
+endfunc
+
+" Test for 'patchmode'
+func Test_patchmode()
+ call writefile(['one'], 'Xpafile', 'D')
+ set patchmode=.orig nobackup backupskip= writebackup
+ new Xpafile
+ call setline(1, 'two')
+ " first write should create the .orig file
+ write
+ call assert_equal(['one'], readfile('Xpafile.orig'))
+ call setline(1, 'three')
+ " subsequent writes should not create/modify the .orig file
+ write
+ call assert_equal(['one'], readfile('Xpafile.orig'))
+
+ " use 'patchmode' with 'nobackup' and 'nowritebackup' to create an empty
+ " original file
+ call delete('Xpafile')
+ call delete('Xpafile.orig')
+ %bw!
+ set patchmode=.orig nobackup nowritebackup
+ edit Xpafile
+ call setline(1, ['xxx'])
+ write
+ call assert_equal(['xxx'], readfile('Xpafile'))
+ call assert_equal([], readfile('Xpafile.orig'))
+
+ set patchmode& backup& backupskip& writebackup&
+ call delete('Xpafile.orig')
+endfunc
+
+" Test for writing to a file in a readonly directory
+" NOTE: if you run tests as root this will fail. Don't run tests as root!
+func Test_write_readonly_dir()
+ " On MS-Windows, modifying files in a read-only directory is allowed.
+ CheckUnix
+ " Root can do it too.
+ CheckNotRoot
+
+ call mkdir('Xrodir/', 'R')
+ call writefile(['one'], 'Xrodir/Xfile1')
+ call setfperm('Xrodir', 'r-xr--r--')
+ " try to create a new file in the directory
+ new Xrodir/Xfile2
+ call setline(1, 'two')
+ call assert_fails('write', 'E212:')
+ " try to create a backup file in the directory
+ edit! Xrodir/Xfile1
+ set backupdir=./Xrodir backupskip=
+ set patchmode=.orig
+ call assert_fails('write', 'E509:')
+ call setfperm('Xrodir', 'rwxr--r--')
+ set backupdir& backupskip& patchmode&
+endfunc
+
+" Test for writing a file using invalid file encoding
+func Test_write_invalid_encoding()
+ new
+ call setline(1, 'abc')
+ call assert_fails('write ++enc=axbyc Xiefile', 'E213:')
+ close!
+endfunc
+
+" Tests for reading and writing files with conversion for Win32.
+func Test_write_file_encoding()
+ CheckMSWindows
+ let save_encoding = &encoding
+ let save_fileencodings = &fileencodings
+ set encoding=latin1 fileencodings&
+ let text =<< trim END
+ 1 utf-8 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
+ 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call writefile(text, 'Xwfefile', 'D')
+ edit Xwfefile
+
+ " write tests:
+ " combine three values for 'encoding' with three values for 'fileencoding'
+ " also write files for read tests
+ call cursor(1, 1)
+ set encoding=utf-8
+ .w! ++enc=utf-8 Xwfetest
+ .w ++enc=cp1251 >> Xwfetest
+ .w ++enc=cp866 >> Xwfetest
+ .w! ++enc=utf-8 Xutf8
+ let expected =<< trim END
+ 1 utf-8 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
+ 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 1 utf-8 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xwfetest'))
+
+ call cursor(2, 1)
+ set encoding=cp1251
+ .w! ++enc=utf-8 Xwfetest
+ .w ++enc=cp1251 >> Xwfetest
+ .w ++enc=cp866 >> Xwfetest
+ .w! ++enc=cp1251 Xcp1251
+ let expected =<< trim END
+ 2 cp1251 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
+ 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 2 cp1251 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xwfetest'))
+
+ call cursor(3, 1)
+ set encoding=cp866
+ .w! ++enc=utf-8 Xwfetest
+ .w ++enc=cp1251 >> Xwfetest
+ .w ++enc=cp866 >> Xwfetest
+ .w! ++enc=cp866 Xcp866
+ let expected =<< trim END
+ 3 cp866 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
+ 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xwfetest'))
+
+ " read three 'fileencoding's with utf-8 'encoding'
+ set encoding=utf-8 fencs=utf-8,cp1251
+ e Xutf8
+ .w! ++enc=utf-8 Xwfetest
+ e Xcp1251
+ .w ++enc=utf-8 >> Xwfetest
+ set fencs=utf-8,cp866
+ e Xcp866
+ .w ++enc=utf-8 >> Xwfetest
+ let expected =<< trim END
+ 1 utf-8 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
+ 2 cp1251 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
+ 3 cp866 text: Ð”Ð»Ñ Vim version 6.2. ПоÑледнее изменение: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xwfetest'))
+
+ " read three 'fileencoding's with cp1251 'encoding'
+ set encoding=utf-8 fencs=utf-8,cp1251
+ e Xutf8
+ .w! ++enc=cp1251 Xwfetest
+ e Xcp1251
+ .w ++enc=cp1251 >> Xwfetest
+ set fencs=utf-8,cp866
+ e Xcp866
+ .w ++enc=cp1251 >> Xwfetest
+ let expected =<< trim END
+ 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xwfetest'))
+
+ " read three 'fileencoding's with cp866 'encoding'
+ set encoding=cp866 fencs=utf-8,cp1251
+ e Xutf8
+ .w! ++enc=cp866 Xwfetest
+ e Xcp1251
+ .w ++enc=cp866 >> Xwfetest
+ set fencs=utf-8,cp866
+ e Xcp866
+ .w ++enc=cp866 >> Xwfetest
+ let expected =<< trim END
+ 1 utf-8 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ 2 cp1251 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ 3 cp866 text: „«ï Vim version 6.2. ®á«¥¤­¥¥ ¨§¬¥­¥­¨¥: 1970 Jan 01
+ END
+ call assert_equal(expected, readfile('Xwfetest'))
+
+ call delete('Xwfetest')
+ call delete('Xutf8')
+ call delete('Xcp1251')
+ call delete('Xcp866')
+ let &encoding = save_encoding
+ let &fileencodings = save_fileencodings
+ %bw!
+endfunc
+
+" Test for writing and reading a file starting with a BOM.
+" Byte Order Mark (BOM) character for various encodings is below:
+" UTF-8 : EF BB BF
+" UTF-16 (BE): FE FF
+" UTF-16 (LE): FF FE
+" UTF-32 (BE): 00 00 FE FF
+" UTF-32 (LE): FF FE 00 00
+func Test_readwrite_file_with_bom()
+ let utf8_bom = "\xEF\xBB\xBF"
+ let utf16be_bom = "\xFE\xFF"
+ let utf16le_bom = "\xFF\xFE"
+ let utf32be_bom = "\n\n\xFE\xFF"
+ let utf32le_bom = "\xFF\xFE\n\n"
+ let save_fileencoding = &fileencoding
+ set cpoptions+=S
+
+ " Check that editing a latin1 file doesn't see a BOM
+ call writefile(["\xFE\xFElatin-1"], 'Xrwtest1', 'D')
+ edit Xrwtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ set fenc=latin1
+ write Xrwfile2
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xrwfile2', 'b'))
+ set bomb fenc=latin1
+ write Xrwtest3
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xrwtest3', 'b'))
+ set bomb&
+
+ " Check utf-8 BOM
+ %bw!
+ call writefile([utf8_bom .. "utf-8"], 'Xrwtest1')
+ edit! Xrwtest1
+ call assert_equal('utf-8', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('utf-8', getline(1))
+ set fenc=latin1
+ write! Xrwfile2
+ call assert_equal(['utf-8', ''], readfile('Xrwfile2', 'b'))
+ set fenc=utf-8
+ w! Xrwtest3
+ call assert_equal([utf8_bom .. "utf-8", ''], readfile('Xrwtest3', 'b'))
+
+ " Check utf-8 with an error (will fall back to latin-1)
+ %bw!
+ call writefile([utf8_bom .. "utf-8\x80err"], 'Xrwtest1')
+ edit! Xrwtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ call assert_equal("\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", getline(1))
+ set fenc=latin1
+ write! Xrwfile2
+ call assert_equal([utf8_bom .. "utf-8\x80err", ''], readfile('Xrwfile2', 'b'))
+ set fenc=utf-8
+ w! Xrwtest3
+ call assert_equal(["\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", ''],
+ \ readfile('Xrwtest3', 'b'))
+
+ " Check ucs-2 BOM
+ %bw!
+ call writefile([utf16be_bom .. "\nu\nc\ns\n-\n2\n"], 'Xrwtest1')
+ edit! Xrwtest1
+ call assert_equal('utf-16', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2', getline(1))
+ set fenc=latin1
+ write! Xrwfile2
+ call assert_equal(["ucs-2", ''], readfile('Xrwfile2', 'b'))
+ set fenc=ucs-2
+ w! Xrwtest3
+ call assert_equal([utf16be_bom .. "\nu\nc\ns\n-\n2\n", ''],
+ \ readfile('Xrwtest3', 'b'))
+
+ " Check ucs-2le BOM
+ %bw!
+ call writefile([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n"], 'Xrwtest1')
+ " Need to add a NUL byte after the NL byte
+ call writefile(0z00, 'Xrwtest1', 'a')
+ edit! Xrwtest1
+ call assert_equal('utf-16le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2le', getline(1))
+ set fenc=latin1
+ write! Xrwfile2
+ call assert_equal(["ucs-2le", ''], readfile('Xrwfile2', 'b'))
+ set fenc=ucs-2le
+ w! Xrwtest3
+ call assert_equal([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n", "\n"],
+ \ readfile('Xrwtest3', 'b'))
+
+ " Check ucs-4 BOM
+ %bw!
+ call writefile([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n"], 'Xrwtest1')
+ edit! Xrwtest1
+ call assert_equal('ucs-4', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4', getline(1))
+ set fenc=latin1
+ write! Xrwfile2
+ call assert_equal(["ucs-4", ''], readfile('Xrwfile2', 'b'))
+ set fenc=ucs-4
+ w! Xrwtest3
+ call assert_equal([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n", ''], readfile('Xrwtest3', 'b'))
+
+ " Check ucs-4le BOM
+ %bw!
+ call writefile([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n"], 'Xrwtest1')
+ " Need to add three NUL bytes after the NL byte
+ call writefile(0z000000, 'Xrwtest1', 'a')
+ edit! Xrwtest1
+ call assert_equal('ucs-4le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4le', getline(1))
+ set fenc=latin1
+ write! Xrwfile2
+ call assert_equal(["ucs-4le", ''], readfile('Xrwfile2', 'b'))
+ set fenc=ucs-4le
+ w! Xrwtest3
+ call assert_equal([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n", "\n\n\n"], readfile('Xrwtest3', 'b'))
+
+ set cpoptions-=S
+ let &fileencoding = save_fileencoding
+ call delete('Xrwfile2')
+ call delete('Xrwtest3')
+ %bw!
+endfunc
+
+func Test_read_write_bin()
+ " write file missing EOL
+ call writefile(['noeol'], "XNoEolSetEol", 'bSD')
+ call assert_equal(0z6E6F656F6C, readfile('XNoEolSetEol', 'B'))
+
+ " when file is read 'eol' is off
+ set nofixeol
+ e! ++ff=unix XNoEolSetEol
+ call assert_equal(0, &eol)
+
+ " writing with 'eol' set adds the newline
+ setlocal eol
+ w
+ call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B'))
+
+ set ff& fixeol&
+ bwipe! XNoEolSetEol
+endfunc
+
+" Test for the 'backupcopy' option when writing files
+func Test_backupcopy()
+ CheckUnix
+ set backupskip=
+ " With the default 'backupcopy' setting, saving a symbolic link file
+ " should not break the link.
+ set backupcopy&
+ call writefile(['1111'], 'Xbcfile1')
+ silent !ln -s Xbcfile1 Xbcfile2
+ new Xbcfile2
+ call setline(1, ['2222'])
+ write
+ close
+ call assert_equal(['2222'], readfile('Xbcfile1'))
+ call assert_equal('Xbcfile1', resolve('Xbcfile2'))
+ call assert_equal('link', getftype('Xbcfile2'))
+ call delete('Xbcfile1')
+ call delete('Xbcfile2')
+
+ " With the 'backupcopy' set to 'breaksymlink', saving a symbolic link file
+ " should break the link.
+ set backupcopy=yes,breaksymlink
+ call writefile(['1111'], 'Xbcfile1')
+ silent !ln -s Xbcfile1 Xbcfile2
+ new Xbcfile2
+ call setline(1, ['2222'])
+ write
+ close
+ call assert_equal(['1111'], readfile('Xbcfile1'))
+ call assert_equal(['2222'], readfile('Xbcfile2'))
+ call assert_equal('Xbcfile2', resolve('Xbcfile2'))
+ call assert_equal('file', getftype('Xbcfile2'))
+ call delete('Xbcfile1')
+ call delete('Xbcfile2')
+ set backupcopy&
+
+ " With the default 'backupcopy' setting, saving a hard link file
+ " should not break the link.
+ set backupcopy&
+ call writefile(['1111'], 'Xbcfile1')
+ silent !ln Xbcfile1 Xbcfile2
+ new Xbcfile2
+ call setline(1, ['2222'])
+ write
+ close
+ call assert_equal(['2222'], readfile('Xbcfile1'))
+ call delete('Xbcfile1')
+ call delete('Xbcfile2')
+
+ " With the 'backupcopy' set to 'breaksymlink', saving a hard link file
+ " should break the link.
+ set backupcopy=yes,breakhardlink
+ call writefile(['1111'], 'Xbcfile1')
+ silent !ln Xbcfile1 Xbcfile2
+ new Xbcfile2
+ call setline(1, ['2222'])
+ write
+ call assert_equal(['1111'], readfile('Xbcfile1'))
+ call assert_equal(['2222'], readfile('Xbcfile2'))
+ call delete('Xbcfile1')
+ call delete('Xbcfile2')
+
+ " If a backup file is already present, then a slightly modified filename
+ " should be used as the backup file. Try with 'backupcopy' set to 'yes' and
+ " 'no'.
+ %bw
+ call writefile(['aaaa'], 'Xbcfile')
+ call writefile(['bbbb'], 'Xbcfile.bak')
+ set backupcopy=yes backupext=.bak
+ new Xbcfile
+ call setline(1, ['cccc'])
+ write
+ close
+ call assert_equal(['cccc'], readfile('Xbcfile'))
+ call assert_equal(['bbbb'], readfile('Xbcfile.bak'))
+ set backupcopy=no backupext=.bak
+ new Xbcfile
+ call setline(1, ['dddd'])
+ write
+ close
+ call assert_equal(['dddd'], readfile('Xbcfile'))
+ call assert_equal(['bbbb'], readfile('Xbcfile.bak'))
+ call delete('Xbcfile')
+ call delete('Xbcfile.bak')
+
+ " Write to a device file (in Unix-like systems) which cannot be backed up.
+ if has('unix')
+ set writebackup backupcopy=yes nobackup
+ new
+ call setline(1, ['aaaa'])
+ let output = execute('write! /dev/null')
+ call assert_match('"/dev/null" \[Device]', output)
+ close
+ set writebackup backupcopy=no nobackup
+ new
+ call setline(1, ['aaaa'])
+ let output = execute('write! /dev/null')
+ call assert_match('"/dev/null" \[Device]', output)
+ close
+ set backup writebackup& backupcopy&
+ new
+ call setline(1, ['aaaa'])
+ let output = execute('write! /dev/null')
+ call assert_match('"/dev/null" \[Device]', output)
+ close
+ endif
+
+ set backupcopy& backupskip& backupext& backup&
+endfunc
+
+" Test for writing a file with 'encoding' set to 'utf-16'
+func Test_write_utf16()
+ new
+ call setline(1, ["\U00010001"])
+ write ++enc=utf-16 Xw16file
+ bw!
+ call assert_equal(0zD800DC01, readfile('Xw16file', 'B')[0:3])
+ call delete('Xw16file')
+endfunc
+
+" Test for trying to save a backup file when the backup file is a symbolic
+" link to the original file. The backup file should not be modified.
+func Test_write_backup_symlink()
+ CheckUnix
+ call mkdir('Xbackup')
+ let save_backupdir = &backupdir
+ set backupdir=.,./Xbackup
+ call writefile(['1111'], 'Xwbsfile', 'D')
+ silent !ln -s Xwbsfile Xwbsfile.bak
+
+ new Xwbsfile
+ set backup backupcopy=yes backupext=.bak
+ write
+ call assert_equal('link', getftype('Xwbsfile.bak'))
+ call assert_equal('Xwbsfile', resolve('Xwbsfile.bak'))
+ " backup file should be created in the 'backup' directory
+ if !has('bsd')
+ " This check fails on FreeBSD
+ call assert_true(filereadable('./Xbackup/Xwbsfile.bak'))
+ endif
+ set backup& backupcopy& backupext&
+ %bw
+
+ call delete('Xwbsfile.bak')
+ call delete('Xbackup', 'rf')
+ let &backupdir = save_backupdir
+endfunc
+
+" Test for ':write ++bin' and ':write ++nobin'
+func Test_write_binary_file()
+ " create a file without an eol/eof character
+ call writefile(0z616161, 'Xwbfile1', 'bD')
+ new Xwbfile1
+ write ++bin Xwbfile2
+ write ++nobin Xwbfile3
+ call assert_equal(0z616161, readblob('Xwbfile2'))
+ if has('win32')
+ call assert_equal(0z6161610D.0A, readblob('Xwbfile3'))
+ else
+ call assert_equal(0z6161610A, readblob('Xwbfile3'))
+ endif
+ call delete('Xwbfile2')
+ call delete('Xwbfile3')
+endfunc
+
+func DoWriteDefer()
+ call writefile(['some text'], 'XdeferDelete', 'D')
+ call assert_equal(['some text'], readfile('XdeferDelete'))
+endfunc
+
+def DefWriteDefer()
+ writefile(['some text'], 'XdefdeferDelete', 'D')
+ assert_equal(['some text'], readfile('XdefdeferDelete'))
+enddef
+
+func Test_write_with_deferred_delete()
+ call DoWriteDefer()
+ call assert_equal('', glob('XdeferDelete'))
+ call DefWriteDefer()
+ call assert_equal('', glob('XdefdeferDelete'))
+endfunc
+
+func DoWriteFile()
+ call writefile(['text'], 'Xthefile', 'D')
+ cd ..
+endfunc
+
+func Test_write_defer_delete_chdir()
+ let dir = getcwd()
+ call DoWriteFile()
+ call assert_notequal(dir, getcwd())
+ call chdir(dir)
+ call assert_equal('', glob('Xthefile'))
+endfunc
+
+" Check that buffer is written before triggering QuitPre
+func Test_wq_quitpre_autocommand()
+ edit Xsomefile
+ call setline(1, 'hello')
+ split
+ let g:seq = []
+ augroup Testing
+ au QuitPre * call add(g:seq, 'QuitPre - ' .. (&modified ? 'modified' : 'not modified'))
+ au BufWritePost * call add(g:seq, 'written')
+ augroup END
+ wq
+ call assert_equal(['written', 'QuitPre - not modified'], g:seq)
+
+ augroup Testing
+ au!
+ augroup END
+ bwipe!
+ unlet g:seq
+ call delete('Xsomefile')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_xxd.vim b/src/testdir/test_xxd.vim
new file mode 100644
index 0000000..92a4d05
--- /dev/null
+++ b/src/testdir/test_xxd.vim
@@ -0,0 +1,404 @@
+" Test for the xxd command
+
+if empty($XXD) && executable('..\xxd\xxd.exe')
+ let s:xxd_cmd = '..\xxd\xxd.exe'
+elseif empty($XXD) || !executable($XXD)
+ throw 'Skipped: xxd program missing'
+else
+ let s:xxd_cmd = $XXD
+endif
+
+func PrepareBuffer(lines)
+ new
+ call append(0, a:lines)
+ $d
+endfunc
+
+func s:Mess(counter)
+ return printf("Failed xxd test %d:", a:counter)
+endfunc
+
+func Test_xxd()
+ call PrepareBuffer(range(1,30))
+ set ff=unix
+ w! XXDfile
+
+ " Test 1: simple, filter the result through xxd
+ let s:test = 1
+ exe '%!' . s:xxd_cmd . ' %'
+ let expected = [
+ \ '00000000: 310a 320a 330a 340a 350a 360a 370a 380a 1.2.3.4.5.6.7.8.',
+ \ '00000010: 390a 3130 0a31 310a 3132 0a31 330a 3134 9.10.11.12.13.14',
+ \ '00000020: 0a31 350a 3136 0a31 370a 3138 0a31 390a .15.16.17.18.19.',
+ \ '00000030: 3230 0a32 310a 3232 0a32 330a 3234 0a32 20.21.22.23.24.2',
+ \ '00000040: 350a 3236 0a32 370a 3238 0a32 390a 3330 5.26.27.28.29.30',
+ \ '00000050: 0a .']
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+
+ " Test 2: reverse the result
+ let s:test += 1
+ exe '%!' . s:xxd_cmd . ' -r'
+ call assert_equal(map(range(1,30), {v,c -> string(c)}), getline(1,'$'), s:Mess(s:test))
+
+ " Test 3: Skip the first 0x30 bytes
+ let s:test += 1
+ for arg in ['-s 0x30', '-s0x30', '-s+0x30', '-skip 0x030', '-seek 0x30', '-seek +0x30 --']
+ exe '%!' . s:xxd_cmd . ' ' . arg . ' %'
+ call assert_equal(expected[3:], getline(1,'$'), s:Mess(s:test))
+ endfor
+
+ " Test 4: Skip the first 30 bytes
+ let s:test += 1
+ for arg in ['-s -0x31', '-s-0x31']
+ exe '%!' . s:xxd_cmd . ' ' . arg . ' %'
+ call assert_equal(expected[2:], getline(1,'$'), s:Mess(s:test))
+ endfor
+
+ " The following tests use the xxd man page.
+ " For these tests to pass, the fileformat must be "unix".
+ let man_copy = 'Xxd.1'
+ let man_page = '../../runtime/doc/xxd.1'
+ if has('win32') && !filereadable(man_page)
+ let man_page = '../../doc/xxd.1'
+ endif
+ %d
+ exe '0r ' man_page '| set ff=unix | $d | w' man_copy '| bwipe!' man_copy
+
+ " Test 5: Print 120 bytes as continuous hexdump with 20 octets per line
+ let s:test += 1
+ %d
+ exe '0r! ' . s:xxd_cmd . ' -l 120 -ps -c20 ' . man_copy
+ $d
+ let expected = [
+ \ '2e54482058584420312022417567757374203139',
+ \ '39362220224d616e75616c207061676520666f72',
+ \ '20787864220a2e5c220a2e5c222032317374204d',
+ \ '617920313939360a2e5c22204d616e2070616765',
+ \ '20617574686f723a0a2e5c2220202020546f6e79',
+ \ '204e7567656e74203c746f6e79407363746e7567']
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+
+ " Test 6: Print the date from xxd.1
+ let s:test += 1
+ for arg in ['-l 13', '-l13', '-len 13']
+ %d
+ exe '0r! ' . s:xxd_cmd . ' -s 0x36 ' . arg . ' -cols 13 ' . man_copy
+ $d
+ call assert_equal('00000036: 3231 7374 204d 6179 2031 3939 36 21st May 1996', getline(1), s:Mess(s:test))
+ endfor
+
+ " Cleanup after tests 5 and 6
+ call delete(man_copy)
+
+ " Test 7: Print C include
+ let s:test += 1
+ call writefile(['TESTabcd09'], 'XXDfile')
+ %d
+ exe '0r! ' . s:xxd_cmd . ' -i XXDfile'
+ $d
+ let expected =<< trim [CODE]
+ unsigned char XXDfile[] = {
+ 0x54, 0x45, 0x53, 0x54, 0x61, 0x62, 0x63, 0x64, 0x30, 0x39, 0x0a
+ };
+ unsigned int XXDfile_len = 11;
+ [CODE]
+
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+
+ " Test 8: Print C include capitalized
+ let s:test += 1
+ for arg in ['-C', '-capitalize']
+ call writefile(['TESTabcd09'], 'XXDfile')
+ %d
+ exe '0r! ' . s:xxd_cmd . ' -i ' . arg . ' XXDfile'
+ $d
+ let expected =<< trim [CODE]
+ unsigned char XXDFILE[] = {
+ 0x54, 0x45, 0x53, 0x54, 0x61, 0x62, 0x63, 0x64, 0x30, 0x39, 0x0a
+ };
+ unsigned int XXDFILE_LEN = 11;
+ [CODE]
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+ endfor
+
+ " Test 9: Create a file with containing a single 'A'
+ let s:test += 1
+ call delete('XXDfile')
+ bwipe! XXDfile
+ if has('unix')
+ call system('echo "010000: 41"|' . s:xxd_cmd . ' -r -s -0x10000 > XXDfile')
+ else
+ call writefile(['010000: 41'], 'Xinput')
+ silent exe '!' . s:xxd_cmd . ' -r -s -0x10000 < Xinput > XXDfile'
+ call delete('Xinput')
+ endif
+ call PrepareBuffer(readfile('XXDfile')[0])
+ call assert_equal('A', getline(1), s:Mess(s:test))
+ call delete('XXDfile')
+
+ " Test 10: group with 4 octets
+ let s:test += 1
+ for arg in ['-g 4', '-group 4', '-g4']
+ call writefile(['TESTabcd09'], 'XXDfile')
+ %d
+ exe '0r! ' . s:xxd_cmd . ' ' . arg . ' XXDfile'
+ $d
+ let expected = ['00000000: 54455354 61626364 30390a TESTabcd09.']
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+ call delete('XXDfile')
+ endfor
+
+ " Test 11: reverse with CR, hex upper, Postscript style with a TAB
+ let s:test += 1
+ call writefile([" 54455354\t610B6364 30390A TESTa\0x0bcd09.\r"], 'Xinput')
+ silent exe '!' . s:xxd_cmd . ' -r -p < Xinput > XXDfile'
+ let blob = readfile('XXDfile', 'B')
+ call assert_equal(0z54455354.610B6364.30390A, blob)
+ call delete('Xinput')
+ call delete('XXDfile')
+
+ " Test 12: reverse with seek
+ let s:test += 1
+ call writefile(["00000000: 54455354\t610B6364 30390A TESTa\0x0bcd09.\r"], 'Xinput')
+ silent exe '!' . s:xxd_cmd . ' -r -seek 5 < Xinput > XXDfile'
+ let blob = readfile('XXDfile', 'B')
+ call assert_equal(0z0000000000.54455354.610B6364.30390A, blob)
+ call delete('Xinput')
+ call delete('XXDfile')
+
+ " Test 13: simple, decimal offset
+ call PrepareBuffer(range(1,30))
+ set ff=unix
+ w! XXDfile
+ let s:test += 1
+ exe '%!' . s:xxd_cmd . ' -d %'
+ let expected = [
+ \ '00000000: 310a 320a 330a 340a 350a 360a 370a 380a 1.2.3.4.5.6.7.8.',
+ \ '00000016: 390a 3130 0a31 310a 3132 0a31 330a 3134 9.10.11.12.13.14',
+ \ '00000032: 0a31 350a 3136 0a31 370a 3138 0a31 390a .15.16.17.18.19.',
+ \ '00000048: 3230 0a32 310a 3232 0a32 330a 3234 0a32 20.21.22.23.24.2',
+ \ '00000064: 350a 3236 0a32 370a 3238 0a32 390a 3330 5.26.27.28.29.30',
+ \ '00000080: 0a .']
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+
+ " Test 14: grouping with -d
+ let s:test += 1
+ let expected = [
+ \ '00000000: 310a320a 330a340a 350a360a 370a380a 1.2.3.4.5.6.7.8.',
+ \ '00000016: 390a3130 0a31310a 31320a31 330a3134 9.10.11.12.13.14',
+ \ '00000032: 0a31350a 31360a31 370a3138 0a31390a .15.16.17.18.19.',
+ \ '00000048: 32300a32 310a3232 0a32330a 32340a32 20.21.22.23.24.2',
+ \ '00000064: 350a3236 0a32370a 32380a32 390a3330 5.26.27.28.29.30',
+ \ '00000080: 0a .']
+ for arg in ['-g 4', '-group 4', '-g4']
+ exe '%!' . s:xxd_cmd . ' ' . arg . ' -d %'
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+ endfor
+
+ " Test 15: cols with decimal offset: -c 21 -d
+ let s:test += 1
+ let expected = [
+ \ '00000000: 310a 320a 330a 340a 350a 360a 370a 380a 390a 3130 0a 1.2.3.4.5.6.7.8.9.10.',
+ \ '00000021: 3131 0a31 320a 3133 0a31 340a 3135 0a31 360a 3137 0a 11.12.13.14.15.16.17.',
+ \ '00000042: 3138 0a31 390a 3230 0a32 310a 3232 0a32 330a 3234 0a 18.19.20.21.22.23.24.',
+ \ '00000063: 3235 0a32 360a 3237 0a32 380a 3239 0a33 300a 25.26.27.28.29.30.']
+ exe '%!' . s:xxd_cmd . ' -c 21 -d %'
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+
+ " Test 16: -o -offset
+ let s:test += 1
+ let expected = [
+ \ '0000000f: 310a 320a 330a 340a 350a 360a 370a 380a 1.2.3.4.5.6.7.8.',
+ \ '0000001f: 390a 3130 0a31 310a 3132 0a31 330a 3134 9.10.11.12.13.14',
+ \ '0000002f: 0a31 350a 3136 0a31 370a 3138 0a31 390a .15.16.17.18.19.',
+ \ '0000003f: 3230 0a32 310a 3232 0a32 330a 3234 0a32 20.21.22.23.24.2',
+ \ '0000004f: 350a 3236 0a32 370a 3238 0a32 390a 3330 5.26.27.28.29.30',
+ \ '0000005f: 0a .']
+ for arg in ['-o 15', '-offset 15', '-o15']
+ exe '%!' . s:xxd_cmd . ' ' . arg . ' %'
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+ endfor
+
+ " Test 17: Print C include with custom variable name
+ let s:test += 1
+ call writefile(['TESTabcd09'], 'XXDfile')
+ for arg in ['-nvarName', '-n varName', '-name varName']
+ %d
+ exe '0r! ' . s:xxd_cmd . ' -i ' . arg . ' XXDfile'
+ $d
+ let expected =<< trim [CODE]
+ unsigned char varName[] = {
+ 0x54, 0x45, 0x53, 0x54, 0x61, 0x62, 0x63, 0x64, 0x30, 0x39, 0x0a
+ };
+ unsigned int varName_len = 11;
+ [CODE]
+
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+ endfor
+
+ " using "-n name" reading from stdin
+ %d
+ exe '0r! ' . s:xxd_cmd . ' -i < XXDfile -n StdIn'
+ $d
+ let expected =<< trim [CODE]
+ unsigned char StdIn[] = {
+ 0x54, 0x45, 0x53, 0x54, 0x61, 0x62, 0x63, 0x64, 0x30, 0x39, 0x0a
+ };
+ unsigned int StdIn_len = 11;
+ [CODE]
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+
+
+ " Test 18: Print C include: custom variable names can be capitalized
+ let s:test += 1
+ for arg in ['-C', '-capitalize']
+ call writefile(['TESTabcd09'], 'XXDfile')
+ %d
+ exe '0r! ' . s:xxd_cmd . ' -i ' . arg . ' -n varName XXDfile'
+ $d
+ let expected =<< trim [CODE]
+ unsigned char VARNAME[] = {
+ 0x54, 0x45, 0x53, 0x54, 0x61, 0x62, 0x63, 0x64, 0x30, 0x39, 0x0a
+ };
+ unsigned int VARNAME_LEN = 11;
+ [CODE]
+ call assert_equal(expected, getline(1,'$'), s:Mess(s:test))
+ endfor
+
+
+ %d
+ bwipe!
+ call delete('XXDfile')
+endfunc
+
+func Test_xxd_patch()
+ let cmd1 = 'silent !' .. s:xxd_cmd .. ' -r Xxxdin Xxxdfile'
+ let cmd2 = 'silent !' .. s:xxd_cmd .. ' -g1 Xxxdfile > Xxxdout'
+ call writefile(["2: 41 41", "8: 42 42"], 'Xxxdin', 'D')
+ call writefile(['::::::::'], 'Xxxdfile', 'D')
+ exe cmd1
+ exe cmd2
+ call assert_equal(['00000000: 3a 3a 41 41 3a 3a 3a 3a 42 42 ::AA::::BB'], readfile('Xxxdout'))
+
+ call writefile(["2: 43 43 ", "8: 44 44"], 'Xxxdin')
+ exe cmd1
+ exe cmd2
+ call assert_equal(['00000000: 3a 3a 43 43 3a 3a 3a 3a 44 44 ::CC::::DD'], readfile('Xxxdout'))
+
+ call writefile(["2: 45 45 ", "8: 46 46"], 'Xxxdin')
+ exe cmd1
+ exe cmd2
+ call assert_equal(['00000000: 3a 3a 45 45 3a 3a 3a 3a 46 46 ::EE::::FF'], readfile('Xxxdout'))
+
+ call writefile(["2: 41 41", "08: 42 42"], 'Xxxdin')
+ call writefile(['::::::::'], 'Xxxdfile')
+ exe cmd1
+ exe cmd2
+ call assert_equal(['00000000: 3a 3a 41 41 3a 3a 3a 3a 42 42 ::AA::::BB'], readfile('Xxxdout'))
+
+ call writefile(["2: 43 43 ", "09: 44 44"], 'Xxxdin')
+ exe cmd1
+ exe cmd2
+ call assert_equal(['00000000: 3a 3a 43 43 3a 3a 3a 3a 42 44 44 ::CC::::BDD'], readfile('Xxxdout'))
+
+ call writefile(["2: 45 45 ", "0a: 46 46"], 'Xxxdin')
+ exe cmd1
+ exe cmd2
+ call assert_equal(['00000000: 3a 3a 45 45 3a 3a 3a 3a 42 44 46 46 ::EE::::BDFF'], readfile('Xxxdout'))
+
+ call delete('Xxxdout')
+endfunc
+
+" Various ways with wrong arguments that trigger the usage output.
+func Test_xxd_usage()
+ for arg in ['-h', '-c', '-g', '-o', '-s', '-l', '-X', 'one two three']
+ new
+ exe 'r! ' . s:xxd_cmd . ' ' . arg
+ call assert_match("Usage:", join(getline(1, 3)))
+ bwipe!
+ endfor
+endfunc
+
+func Test_xxd_ignore_garbage()
+ new
+ exe 'r! printf "\n\r xxxx 0: 42 42" | ' . s:xxd_cmd . ' -r'
+ call assert_match('BB', join(getline(1, 3)))
+ bwipe!
+endfunc
+
+func Test_xxd_bit_dump()
+ new
+ exe 'r! printf "123456" | ' . s:xxd_cmd . ' -b1'
+ call assert_match('00000000: 00110001 00110010 00110011 00110100 00110101 00110110 123456', join(getline(1, 3)))
+ bwipe!
+endfunc
+
+func Test_xxd_version()
+ new
+ exe 'r! ' . s:xxd_cmd . ' -v'
+ call assert_match('xxd 20\d\d-\d\d-\d\d by Juergen Weigert et al\.', join(getline(1, 3)))
+ bwipe!
+endfunc
+
+" number of columns must be non-negative
+func Test_xxd_min_cols()
+ for cols in ['-c-1', '-c -1', '-cols -1']
+ for fmt in ['', '-b', '-e', '-i', '-p', ]
+ new
+ exe 'r! printf "ignored" | ' . s:xxd_cmd . ' ' . cols . ' ' . fmt
+ call assert_match("invalid number of columns", join(getline(1, '$')))
+ bwipe!
+ endfor
+ endfor
+endfunc
+
+" some hex formats limit columns to 256 (a #define in xxd.c)
+func Test_xxd_max_cols()
+ for cols in ['-c257', '-c 257', '-cols 257']
+ for fmt in ['', '-b', '-e' ]
+ new
+ exe 'r! printf "ignored" | ' . s:xxd_cmd . ' ' . cols . ' ' . fmt
+ call assert_match("invalid number of columns", join(getline(1, '$')))
+ bwipe!
+ endfor
+ endfor
+endfunc
+
+" -c0 selects the format specific default column value, as if no -c was given
+" except for -ps, where it disables extra newlines
+func Test_xxd_c0_is_def_cols()
+ call writefile(["abcdefghijklmnopqrstuvwxyz0123456789"], 'Xxdin', 'D')
+ for cols in ['-c0', '-c 0', '-cols 0']
+ for fmt in ['', '-b', '-e', '-i']
+ exe 'r! ' . s:xxd_cmd . ' ' . fmt ' Xxdin > Xxdout1'
+ exe 'r! ' . s:xxd_cmd . ' ' . cols . ' ' . fmt ' Xxdin > Xxdout2'
+ call assert_equalfile('Xxdout1', 'Xxdout2')
+ endfor
+ endfor
+ call delete('Xxdout1')
+ call delete('Xxdout2')
+endfunc
+
+" all output in a single line for -c0 -ps
+func Test_xxd_plain_one_line()
+ call writefile([
+ \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+ \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+ \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+ \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+ \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+ \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"],
+ \ 'Xxdin', 'D')
+ for cols in ['-c0', '-c 0', '-cols 0']
+ exe 'r! ' . s:xxd_cmd . ' -ps ' . cols ' Xxdin'
+ " output seems to start in line 2
+ let out = join(getline(2, '$'))
+ bwipe!
+ " newlines in xxd output result in spaces in the string variable out
+ call assert_notmatch(" ", out)
+ " xxd output must be non-empty and comprise only lower case hex digits
+ call assert_match("^[0-9a-f][0-9a-f]*$", out)
+ endfor
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/testluaplugin/lua/testluaplugin/hello.lua b/src/testdir/testluaplugin/lua/testluaplugin/hello.lua
new file mode 100644
index 0000000..7c90552
--- /dev/null
+++ b/src/testdir/testluaplugin/lua/testluaplugin/hello.lua
@@ -0,0 +1,7 @@
+local function hello()
+ return "hello from lua"
+end
+
+return {
+ hello = hello
+}
diff --git a/src/testdir/testluaplugin/lua/testluaplugin/init.lua b/src/testdir/testluaplugin/lua/testluaplugin/init.lua
new file mode 100644
index 0000000..1e6c474
--- /dev/null
+++ b/src/testdir/testluaplugin/lua/testluaplugin/init.lua
@@ -0,0 +1,5 @@
+local hello = require('testluaplugin/hello').hello
+
+return {
+ hello = hello
+}
diff --git a/src/testdir/thread_util.py b/src/testdir/thread_util.py
new file mode 100644
index 0000000..e82f23c
--- /dev/null
+++ b/src/testdir/thread_util.py
@@ -0,0 +1,90 @@
+import platform
+
+if platform.system() == 'Darwin':
+ from ctypes import (
+ CDLL,
+ POINTER,
+ Structure,
+ byref,
+ c_int,
+ c_uint,
+ c_uint32,
+ c_void_p,
+ sizeof
+ )
+ from ctypes.util import find_library
+
+ class ThreadTimeConstraintPolicy(Structure):
+ _fields_ = [
+ ("period", c_uint32),
+ ("computation", c_uint32),
+ ("constraint", c_uint32),
+ ("preemptible", c_uint)
+ ]
+
+ _libc = CDLL(find_library('c'))
+
+ THREAD_TIME_CONSTRAINT_POLICY = c_uint(2)
+
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT = c_uint(
+ int(sizeof(ThreadTimeConstraintPolicy) / sizeof(c_int)))
+
+ _libc.pthread_self.restype = c_void_p
+
+ _libc.pthread_mach_thread_np.restype = c_uint
+ _libc.pthread_mach_thread_np.argtypes = [c_void_p]
+
+ _libc.thread_policy_get.restype = c_int
+ _libc.thread_policy_get.argtypes = [
+ c_uint,
+ c_uint,
+ c_void_p,
+ POINTER(c_uint),
+ POINTER(c_uint)
+ ]
+
+ _libc.thread_policy_set.restype = c_int
+ _libc.thread_policy_set.argtypes = [
+ c_uint,
+ c_uint,
+ c_void_p,
+ c_uint
+ ]
+
+ def _mach_thread_self():
+ return _libc.pthread_mach_thread_np(_libc.pthread_self())
+
+ def _get_time_constraint_policy(default=False):
+ thread = _mach_thread_self()
+ policy_info = ThreadTimeConstraintPolicy()
+ policy_infoCnt = THREAD_TIME_CONSTRAINT_POLICY_COUNT
+ get_default = c_uint(default)
+
+ kret = _libc.thread_policy_get(
+ thread,
+ THREAD_TIME_CONSTRAINT_POLICY,
+ byref(policy_info),
+ byref(policy_infoCnt),
+ byref(get_default))
+ if kret != 0:
+ return None
+ return policy_info
+
+ def _set_time_constraint_policy(policy_info):
+ thread = _mach_thread_self()
+ policy_infoCnt = THREAD_TIME_CONSTRAINT_POLICY_COUNT
+
+ kret = _libc.thread_policy_set(
+ thread,
+ THREAD_TIME_CONSTRAINT_POLICY,
+ byref(policy_info),
+ policy_infoCnt)
+ if kret != 0:
+ raise OSError(kret)
+
+ def set_high_priority():
+ policy_info = _get_time_constraint_policy(default=True)
+ if not policy_info:
+ return
+ policy_info.preemptible = c_uint(False)
+ _set_time_constraint_policy(policy_info)
diff --git a/src/testdir/unix.vim b/src/testdir/unix.vim
new file mode 100644
index 0000000..366c9b1
--- /dev/null
+++ b/src/testdir/unix.vim
@@ -0,0 +1,13 @@
+" Settings for test script execution
+" Always use "sh", don't use the value of "$SHELL".
+set shell=sh
+
+" Only when the +eval feature is present.
+if 1
+ " While some tests overwrite $HOME to prevent them from polluting user files,
+ " we need to remember the original value so that we can tell external systems
+ " where to ask about their own user settings.
+ let g:tester_HOME = $HOME
+endif
+
+source setup.vim
diff --git a/src/testdir/view_util.vim b/src/testdir/view_util.vim
new file mode 100644
index 0000000..71cb071
--- /dev/null
+++ b/src/testdir/view_util.vim
@@ -0,0 +1,117 @@
+" Functions about view shared by several tests
+
+" Only load this script once.
+if exists('*Screenline')
+ finish
+endif
+
+" Get line "lnum" as displayed on the screen.
+" Trailing white space is trimmed.
+func Screenline(lnum)
+ let chars = []
+ for c in range(1, winwidth(0))
+ call add(chars, nr2char(screenchar(a:lnum, c)))
+ endfor
+ let line = join(chars, '')
+ return matchstr(line, '^.\{-}\ze\s*$')
+endfunc
+
+" Get text on the screen, including composing characters.
+" ScreenLines(lnum, width) or
+" ScreenLines([start, end], width)
+func ScreenLines(lnum, width) abort
+ redraw!
+ if type(a:lnum) == v:t_list
+ let start = a:lnum[0]
+ let end = a:lnum[1]
+ else
+ let start = a:lnum
+ let end = a:lnum
+ endif
+ let lines = []
+ for l in range(start, end)
+ let lines += [join(map(range(1, a:width), 'screenstring(l, v:val)'), '')]
+ endfor
+ return lines
+endfunc
+
+func ScreenAttrs(lnum, width) abort
+ redraw!
+ if type(a:lnum) == v:t_list
+ let start = a:lnum[0]
+ let end = a:lnum[1]
+ else
+ let start = a:lnum
+ let end = a:lnum
+ endif
+ let attrs = []
+ for l in range(start, end)
+ let attrs += [map(range(1, a:width), 'screenattr(l, v:val)')]
+ endfor
+ return attrs
+endfunc
+
+" Create a new window with the requested size and fix it.
+func NewWindow(height, width) abort
+ exe a:height . 'new'
+ exe a:width . 'vsp'
+ set winfixwidth winfixheight
+ redraw!
+endfunc
+
+func CloseWindow() abort
+ bw!
+ redraw!
+endfunc
+
+
+" When using RunVimInTerminal() we expect modifyOtherKeys level 2 to be enabled
+" automatically. The key + modifier Escape codes must then use the
+" modifyOtherKeys encoding. They are recognized anyway, thus it's safer to use
+" than the raw code.
+
+" Return the modifyOtherKeys level 2 encoding for "key" with "modifier"
+" (number value, e.g. CTRL is 5).
+func GetEscCodeCSI27(key, modifier)
+ let key = printf("%d", char2nr(a:key))
+ let mod = printf("%d", a:modifier)
+ return "\<Esc>[27;" .. mod .. ';' .. key .. '~'
+endfunc
+
+" Return the modifyOtherKeys level 2 encoding for "key" with "modifier"
+" (character value, e.g. CTRL is "C").
+func GetEscCodeWithModifier(modifier, key)
+ let modifier = get({'C': 5}, a:modifier, '')
+ if modifier == ''
+ echoerr 'Unknown modifier: ' .. a:modifier
+ endif
+ return GetEscCodeCSI27(a:key, modifier)
+endfunc
+
+" Return the kitty keyboard protocol encoding for "key" with "modifier"
+" (number value, e.g. CTRL is 5).
+func GetEscCodeCSIu(key, modifier)
+ let key = printf("%d", char2nr(a:key))
+ let mod = printf("%d", a:modifier)
+ return "\<Esc>[" .. key .. ';' .. mod .. 'u'
+endfunc
+
+" Return the kitty keyboard protocol encoding for a function key:
+" CSI {key}
+" CSS 1;{modifier} {key}
+func GetEscCodeFunckey(key, modifier)
+ if a:modifier == 0
+ return "\<Esc>[" .. a:key
+ endif
+
+ let mod = printf("%d", a:modifier)
+ return "\<Esc>[1;".. mod .. a:key
+endfunc
+
+" Return the kitty keyboard protocol encoding for "key" without a modifier.
+" Used for the Escape key.
+func GetEscCodeCSIuWithoutModifier(key)
+ let key = printf("%d", char2nr(a:key))
+ return "\<Esc>[" .. key .. 'u'
+endfunc
+
diff --git a/src/testdir/vim9.vim b/src/testdir/vim9.vim
new file mode 100644
index 0000000..5877a79
--- /dev/null
+++ b/src/testdir/vim9.vim
@@ -0,0 +1,275 @@
+vim9script
+
+# Utility functions for testing vim9 script
+
+# Use a different file name for each run.
+var sequence = 1
+
+# Check that "lines" inside a ":def" function has no error when called.
+export func CheckDefSuccess(lines)
+ let cwd = getcwd()
+ let fname = 'XdefSuccess' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()'] + a:lines + ['enddef', 'defcompile'], fname)
+ try
+ exe 'so ' .. fname
+ call Func()
+ finally
+ call chdir(cwd)
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+# Check that "lines" inside a ":def" function has no error when compiled.
+export func CheckDefCompileSuccess(lines)
+ let fname = 'XdefSuccess' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()'] + a:lines + ['enddef', 'defcompile'], fname)
+ try
+ exe 'so ' .. fname
+ finally
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+# Check that "lines" inside ":def" results in an "error" message.
+# If "lnum" is given check that the error is reported for this line.
+# Add a line before and after to make it less likely that the line number is
+# accidentally correct.
+export func CheckDefFailure(lines, error, lnum = -3)
+ let cwd = getcwd()
+ let fname = 'XdefFailure' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()', '# comment'] + a:lines + ['#comment', 'enddef', 'defcompile'], fname)
+ try
+ call assert_fails('so ' .. fname, a:error, a:lines, a:lnum + 1)
+ finally
+ call chdir(cwd)
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+# Check that "lines" inside ":def" results in an "error" message when executed.
+# If "lnum" is given check that the error is reported for this line.
+# Add a line before and after to make it less likely that the line number is
+# accidentally correct.
+export func CheckDefExecFailure(lines, error, lnum = -3)
+ let cwd = getcwd()
+ let fname = 'XdefExecFailure' .. s:sequence
+ let s:sequence += 1
+ call writefile(['def Func()', '# comment'] + a:lines + ['#comment', 'enddef'], fname)
+ try
+ exe 'so ' .. fname
+ call assert_fails('call Func()', a:error, a:lines, a:lnum + 1)
+ finally
+ call chdir(cwd)
+ call delete(fname)
+ delfunc! Func
+ endtry
+endfunc
+
+export def CheckScriptFailure(lines: list<string>, error: string, lnum = -3)
+ var cwd = getcwd()
+ var fname = 'XScriptFailure' .. sequence
+ sequence += 1
+ writefile(lines, fname)
+ try
+ assert_fails('so ' .. fname, error, lines, lnum)
+ finally
+ chdir(cwd)
+ delete(fname)
+ endtry
+enddef
+
+export def CheckScriptFailureList(lines: list<string>, errors: list<string>, lnum = -3)
+ var cwd = getcwd()
+ var fname = 'XScriptFailure' .. sequence
+ sequence += 1
+ writefile(lines, fname)
+ try
+ assert_fails('so ' .. fname, errors, lines, lnum)
+ finally
+ chdir(cwd)
+ delete(fname)
+ endtry
+enddef
+
+export def CheckScriptSuccess(lines: list<string>)
+ var cwd = getcwd()
+ var fname = 'XScriptSuccess' .. sequence
+ sequence += 1
+ writefile(lines, fname)
+ try
+ exe 'so ' .. fname
+ finally
+ chdir(cwd)
+ delete(fname)
+ endtry
+enddef
+
+export def CheckDefAndScriptSuccess(lines: list<string>)
+ CheckDefSuccess(lines)
+ CheckScriptSuccess(['vim9script'] + lines)
+enddef
+
+# Check that a command fails when used in a :def function and when used in
+# Vim9 script.
+# When "error" is a string, both with the same error.
+# When "error" is a list, the :def function fails with "error[0]" , the script
+# fails with "error[1]".
+export def CheckDefAndScriptFailure(lines: list<string>, error: any, lnum = -3)
+ var errorDef: string
+ var errorScript: string
+ if type(error) == v:t_string
+ errorDef = error
+ errorScript = error
+ elseif type(error) == v:t_list && len(error) == 2
+ errorDef = error[0]
+ errorScript = error[1]
+ else
+ echoerr 'error argument must be a string or a list with two items'
+ return
+ endif
+ CheckDefFailure(lines, errorDef, lnum)
+ CheckScriptFailure(['vim9script'] + lines, errorScript, lnum + 1)
+enddef
+
+# Check that a command fails when executed in a :def function and when used in
+# Vim9 script.
+# When "error" is a string, both with the same error.
+# When "error" is a list, the :def function fails with "error[0]" , the script
+# fails with "error[1]".
+export def CheckDefExecAndScriptFailure(lines: list<string>, error: any, lnum = -3)
+ var errorDef: string
+ var errorScript: string
+ if type(error) == v:t_string
+ errorDef = error
+ errorScript = error
+ elseif type(error) == v:t_list && len(error) == 2
+ errorDef = error[0]
+ errorScript = error[1]
+ else
+ echoerr 'error argument must be a string or a list with two items'
+ return
+ endif
+ CheckDefExecFailure(lines, errorDef, lnum)
+ CheckScriptFailure(['vim9script'] + lines, errorScript, lnum + 1)
+enddef
+
+
+# Check that "lines" inside a legacy function has no error.
+export func CheckLegacySuccess(lines)
+ let cwd = getcwd()
+ let fname = 'XlegacySuccess' .. s:sequence
+ let s:sequence += 1
+ call writefile(['func Func()'] + a:lines + ['endfunc'], fname)
+ try
+ exe 'so ' .. fname
+ call Func()
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ call delete(fname)
+ endtry
+endfunc
+
+# Check that "lines" inside a legacy function results in the expected error
+export func CheckLegacyFailure(lines, error)
+ let cwd = getcwd()
+ let fname = 'XlegacyFails' .. s:sequence
+ let s:sequence += 1
+ call writefile(['func Func()'] + a:lines + ['endfunc', 'call Func()'], fname)
+ try
+ call assert_fails('so ' .. fname, a:error)
+ finally
+ delfunc! Func
+ call chdir(cwd)
+ call delete(fname)
+ endtry
+endfunc
+
+# Execute "lines" in a legacy function, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckTransLegacySuccess(lines: list<string>)
+ var legacylines = lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'let', 'g')
+ ->substitute('\<LET\>', 'let', 'g')
+ ->substitute('\<LSTART\>', '{', 'g')
+ ->substitute('\<LMIDDLE\>', '->', 'g')
+ ->substitute('\<LEND\>', '}', 'g')
+ ->substitute('\<TRUE\>', '1', 'g')
+ ->substitute('\<FALSE\>', '0', 'g')
+ ->substitute('#"', ' "', 'g'))
+ CheckLegacySuccess(legacylines)
+enddef
+
+export def Vim9Trans(lines: list<string>): list<string>
+ return lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'var', 'g')
+ ->substitute('\<LET ', '', 'g')
+ ->substitute('\<LSTART\>', '(', 'g')
+ ->substitute('\<LMIDDLE\>', ') =>', 'g')
+ ->substitute(' *\<LEND\> *', '', 'g')
+ ->substitute('\<TRUE\>', 'true', 'g')
+ ->substitute('\<FALSE\>', 'false', 'g'))
+enddef
+
+# Execute "lines" in a :def function, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckTransDefSuccess(lines: list<string>)
+ CheckDefSuccess(Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a Vim9 script, translated as in
+# CheckLegacyAndVim9Success()
+export def CheckTransVim9Success(lines: list<string>)
+ CheckScriptSuccess(['vim9script'] + Vim9Trans(lines))
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+# Use LSTART arg LMIDDLE expr LEND for lambda
+# Use 'TRUE' for 1 in legacy, true in Vim9
+# Use 'FALSE' for 0 in legacy, false in Vim9
+export def CheckLegacyAndVim9Success(lines: list<string>)
+ CheckTransLegacySuccess(lines)
+ CheckTransDefSuccess(lines)
+ CheckTransVim9Success(lines)
+enddef
+
+# Execute "lines" in a legacy function, :def function and Vim9 script.
+# Use 'VAR' for a declaration.
+# Use 'LET' for an assignment
+# Use ' #"' for a comment
+export def CheckLegacyAndVim9Failure(lines: list<string>, error: any)
+ var legacyError: string
+ var defError: string
+ var scriptError: string
+
+ if type(error) == type('string')
+ legacyError = error
+ defError = error
+ scriptError = error
+ else
+ legacyError = error[0]
+ defError = error[1]
+ scriptError = error[2]
+ endif
+
+ var legacylines = lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'let', 'g')
+ ->substitute('\<LET\>', 'let', 'g')
+ ->substitute('#"', ' "', 'g'))
+ CheckLegacyFailure(legacylines, legacyError)
+
+ var vim9lines = lines->mapnew((_, v) =>
+ v->substitute('\<VAR\>', 'var', 'g')
+ ->substitute('\<LET ', '', 'g'))
+ CheckDefExecFailure(vim9lines, defError)
+ CheckScriptFailure(['vim9script'] + vim9lines, scriptError)
+enddef
diff --git a/src/testdir/vms.vim b/src/testdir/vms.vim
new file mode 100644
index 0000000..64b390e
--- /dev/null
+++ b/src/testdir/vms.vim
@@ -0,0 +1,6 @@
+" Settings for test script execution under OpenVMS
+
+" Do not use any swap files
+set noswapfile
+
+source setup.vim